rainforest-cli 1.1.4 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 64c92d088ca6ebcc0b9b64ec565c28755a5c3179
4
- data.tar.gz: ce73f06786865a973aa5db835458d88b4bd4edc5
3
+ metadata.gz: 5a4d51888c0d3ce1b8845532734aa59145159b2f
4
+ data.tar.gz: 49d5541c943fc62aca7dbd5c173823cf6c64743c
5
5
  SHA512:
6
- metadata.gz: 86ee464ee5745117353fb673682355aed50361b31b76a01b97dce5f4e34817e2439f5bd0a7467f511f80956b93e2ef32c87e349d4dbd24970a804c0b82e09da1
7
- data.tar.gz: f91efb44badfae00246e3a9d2d991242af873eed47c800330fd9c8a1f94d131a9d22f76ac6fe0c3119e6308bfed73ecd297a611ac17027d37501c18bc3651bcb
6
+ metadata.gz: 8f8479a400d1d88eaf84e0eeb584a1e6a8e56590e89b628cfbd9cb8c0126cc4b6b0e64c4ff9c8427f31d6f79795ad2a43abd5b27c03a10232fdc3481dd77c1a4
7
+ data.tar.gz: 1e1337f1e45e4b5131e612d0bea3fef8d01686ea043af10d7deb2d5d76baf79a13d6a558773619ace488f8f7acbb01ae5b3090d5aeac968f5c9e54494e610888
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Rainforest CLI Changelog
2
2
 
3
+ ## 1.2.0 - 8th February 2016
4
+ - Add support for embedded tests.
5
+ - Add support for customizable RFML ids.
6
+
3
7
  ## 1.1.4 - 15th January 2016
4
8
  - Customizable folder location for rainforest tests (fa4418738311cee8ca25cbb22a8ca52aa9cbd873, @ukd1)
5
9
  - Update valid browser list, though this doesn't include custom browsers today (e6195c42f95cce72a17f49643bfe8c297baf8dd9, @ukd1)
data/README.md CHANGED
@@ -41,6 +41,17 @@ Run all tests with tag 'run-me' and abort previous in-progress runs.
41
41
  rainforest run --tag run-me --fg --conflict abort --token YOUR_TOKEN_HERE
42
42
  ```
43
43
 
44
+ Create new Rainforest test in RFML format (Rainforest Markup Language).
45
+
46
+ ```bash
47
+ rainforest new
48
+ ```
49
+
50
+ Upload rainforest
51
+
52
+ ```bash
53
+ rainforest upload --token YOUR_TOKEN_HERE
54
+ ```
44
55
 
45
56
  ## Options
46
57
 
@@ -4,8 +4,9 @@ require "rainforest/cli/runner"
4
4
  require "rainforest/cli/http_client"
5
5
  require "rainforest/cli/git_trigger"
6
6
  require "rainforest/cli/csv_importer"
7
- require "rainforest/cli/test_importer"
8
7
  require "rainforest/cli/test_parser"
8
+ require "rainforest/cli/test_files"
9
+ require "rainforest/cli/test_importer"
9
10
  require "erb"
10
11
  require "httparty"
11
12
  require "json"
@@ -12,7 +12,7 @@ module RainforestCli
12
12
  attr_writer :file_name, :tags
13
13
  attr_reader :command, :token, :tags, :conflict, :browsers, :site_id, :environment_id,
14
14
  :import_file_name, :import_name, :custom_url, :description, :folder,
15
- :debug, :file_name, :test_spec_folder
15
+ :debug, :file_name, :test_folder
16
16
 
17
17
  # Note, not all of these may be available to your account
18
18
  # also, we may remove this in the future.
@@ -24,7 +24,6 @@ module RainforestCli
24
24
  @browsers = nil
25
25
  @require_token = true
26
26
  @debug = false
27
- @test_spec_folder = RainforestCli::TestImporter::SPEC_FOLDER
28
27
 
29
28
  @parsed = ::OptionParser.new do |opts|
30
29
  opts.on("--debug") do
@@ -36,7 +35,7 @@ module RainforestCli
36
35
  end
37
36
 
38
37
  opts.on("--test-folder spec/rainforest", "Specify the test folder. Defaults to spec/rainforest if not set.") do |value|
39
- @test_spec_folder = value
38
+ @test_folder = value
40
39
  end
41
40
 
42
41
  opts.on("--import-variable-csv-file FILE", "Import step variables; CSV data") do |value|
@@ -134,7 +133,7 @@ module RainforestCli
134
133
  end
135
134
 
136
135
  def validate!
137
- if @require_token
136
+ if @require_token
138
137
  unless token
139
138
  raise ValidationError, "You must pass your API token using: --token TOKEN"
140
139
  end
@@ -0,0 +1,32 @@
1
+ class RainforestCli::TestFiles
2
+ DEFAULT_TEST_FOLDER = './spec/rainforest'.freeze
3
+ EXT = ".rfml".freeze
4
+
5
+ attr_reader :test_folder, :test_paths, :test_data
6
+
7
+ def initialize(test_folder = nil)
8
+ @test_folder = test_folder || DEFAULT_TEST_FOLDER
9
+
10
+ unless Dir.exists?(@test_folder)
11
+ Dir.mkdir(@test_folder)
12
+ end
13
+ @test_paths = "#{@test_folder}/**/*#{EXT}"
14
+ @test_data = [].tap do |all_tests|
15
+ Dir.glob(@test_paths) do |file_name|
16
+ all_tests << RainforestCli::TestParser::Parser.new(File.read(file_name)).process
17
+ end
18
+ end
19
+ end
20
+
21
+ def file_extension
22
+ EXT
23
+ end
24
+
25
+ def rfml_ids
26
+ @test_data.map(&:rfml_id)
27
+ end
28
+
29
+ def count
30
+ @test_data.count
31
+ end
32
+ end
@@ -4,31 +4,29 @@ require 'parallel'
4
4
  require 'ruby-progressbar'
5
5
 
6
6
  class RainforestCli::TestImporter
7
- attr_reader :options, :client
8
- SPEC_FOLDER = 'spec/rainforest'.freeze
9
- EXT = ".rfml".freeze
7
+ attr_reader :options, :client, :test_files
10
8
  THREADS = 32.freeze
11
9
 
12
10
  SAMPLE_FILE = <<EOF
13
- #! %s (this is the ID, don't edit it)
11
+ #! %s (Test ID - only edit if this test has not yet been uploaded)
14
12
  # title: New test
13
+ # start_uri: /
15
14
  #
16
- # 1. steps:
17
- # a) pairs of lines are steps (first line = action, second = response)
18
- # b) second line must have a ?
19
- # c) second line must not be blank
20
- # 2. comments:
21
- # a) lines starting # are comments
15
+ # Lines starting with # are test attributes or comments
16
+ # Possible attributes: #{RainforestCli::TestParser::Parser::TEXT_FIELDS.join(', ')}
17
+ #
18
+ # Steps are composed of two lines: an action and a question. Example:
19
+ #
20
+ # This is the step action.
21
+ # This is the step question?
22
22
  #
23
23
 
24
24
  EOF
25
25
 
26
26
  def initialize(options)
27
27
  @options = options
28
- unless File.exists?(@options.test_spec_folder)
29
- logger.fatal "Rainforest test folder not found (#{@options.test_spec_folder})"
30
- exit 2
31
- end
28
+ ::Rainforest.api_key = @options.token
29
+ @test_files = RainforestCli::TestFiles.new(@options.test_folder)
32
30
  end
33
31
 
34
32
  def logger
@@ -36,8 +34,6 @@ EOF
36
34
  end
37
35
 
38
36
  def export
39
- ::Rainforest.api_key = @options.token
40
-
41
37
  tests = Rainforest::Test.all(page_size: 1000)
42
38
  p = ProgressBar.create(title: 'Rows', total: tests.count, format: '%a %B %p%% %t')
43
39
  Parallel.each(tests, in_threads: THREADS, finish: lambda { |item, i, result| p.increment }) do |test|
@@ -115,69 +111,35 @@ EOF
115
111
  end
116
112
 
117
113
  def upload
118
- ::Rainforest.api_key = @options.token
119
-
120
- ids = {}
121
- logger.info "Syncing tests"
122
- Rainforest::Test.all(page_size: 1000).each do |test|
123
- id = _get_id(test)
124
-
125
- next if id.nil?
126
-
127
- # note, this test id is numeric
128
- ids[id] = test.id
129
- end
130
-
131
- logger.debug ids.inspect if @options.debug
132
-
133
- tests = validate.values
134
-
135
- logger.info "Uploading tests..."
136
- p = ProgressBar.create(title: 'Rows', total: tests.count, format: '%a %B %p%% %t')
137
-
138
- # Insert the data
139
- Parallel.each(tests, in_threads: THREADS, finish: lambda { |item, i, result| p.increment }) do |test|
140
- next unless test.steps.count > 0
141
-
142
- if @options.debug
143
- logger.debug "Starting: #{test.id}"
144
- logger.debug "\t#{test.start_uri || "/"}"
145
- end
146
-
147
- test_obj = {
148
- start_uri: test.start_uri || "/",
149
- title: test.title,
150
- description: test.description,
151
- tags: (["ro"] + test.tags).uniq,
152
- elements: test.steps.map do |step|
153
- {type: 'step', redirection: true, element: {
154
- action: step.action,
155
- response: step.response
156
- }}
114
+ # Prioritize embedded tests before other tests
115
+ upload_groups = []
116
+ unordered_tests = []
117
+ queued_tests = test_files.test_data.dup
118
+
119
+ until queued_tests.empty?
120
+ new_ordered_group = []
121
+ ordered_ids = upload_groups.flatten.map(&:rfml_id)
122
+
123
+ queued_tests.each do |rfml_test|
124
+ if (rfml_test.embedded_ids - ordered_ids).empty?
125
+ new_ordered_group << rfml_test
126
+ else
127
+ unordered_tests << rfml_test
157
128
  end
158
- }
159
-
160
- unless test.browsers.empty?
161
- test_obj[:browsers] = test.browsers.map {|b|
162
- {'state' => 'enabled', 'name' => b}
163
- }
164
129
  end
165
130
 
166
- # Create the test
167
- begin
168
- if ids[test.id]
169
- t = Rainforest::Test.update(ids[test.id], test_obj)
131
+ upload_groups << new_ordered_group
132
+ queued_tests = unordered_tests
133
+ unordered_tests = []
134
+ end
170
135
 
171
- logger.info "\tUpdated #{test.id} -- ##{t.id}" if @options.debug
172
- else
173
- t = Rainforest::Test.create(test_obj)
136
+ logger.info "Uploading tests..."
174
137
 
175
- logger.info "\tCreated #{test.id} -- ##{t.id}" if @options.debug
176
- end
177
- rescue => e
178
- logger.fatal "Error: #{test.id}: #{e}"
179
- exit 2
180
- end
138
+ # Upload in parallel if order doesn't matter
139
+ if upload_groups.count > 1
140
+ upload_groups_sequentially(upload_groups)
141
+ else
142
+ upload_group_in_parallel(upload_groups.first)
181
143
  end
182
144
  end
183
145
 
@@ -185,7 +147,7 @@ EOF
185
147
  tests = {}
186
148
  has_errors = []
187
149
 
188
- Dir.glob("#{@options.test_spec_folder}/**/*#{EXT}").each do |file_name|
150
+ Dir.glob(test_files.test_paths).each do |file_name|
189
151
  out = RainforestCli::TestParser::Parser.new(File.read(file_name)).process
190
152
 
191
153
  tests[file_name] = out
@@ -224,15 +186,117 @@ EOF
224
186
  def create_new file_name = nil
225
187
  name = @options.file_name if @options.file_name
226
188
  name = file_name if !file_name.nil?
189
+ ext = test_files.file_extension
227
190
 
228
191
  uuid = SecureRandom.uuid
229
- name = "#{uuid}#{EXT}" unless name
230
- name += EXT unless name[-EXT.length..-1] == EXT
231
- name = File.join([@options.test_spec_folder, name])
192
+ name = "#{uuid}#{ext}" unless name
193
+ name += ext unless name[-ext.length..-1] == ext
194
+ name = File.join([@test_files.test_folder, name])
232
195
 
233
196
  File.open(name, "w") { |file| file.write(sprintf(SAMPLE_FILE, uuid)) }
234
197
 
235
198
  logger.info "Created #{name}" if file_name.nil?
236
199
  name
237
200
  end
201
+
202
+ private
203
+
204
+ def upload_groups_sequentially(upload_groups)
205
+ progress_bar = ProgressBar.create(title: 'Rows', total: test_files.count, format: '%a %B %p%% %t')
206
+ upload_groups.each_with_index do |rfml_tests, idx|
207
+ if idx == (rfml_tests.length - 1)
208
+ upload_group_in_parallel(rfml_tests, progress_bar)
209
+ else
210
+ rfml_tests.each { |rfml_test| upload_test(rfml_test) }
211
+ progress_bar.increment
212
+ end
213
+ end
214
+ end
215
+
216
+ def upload_group_in_parallel(rfml_tests, progress_bar = nil)
217
+ progress_bar ||= ProgressBar.create(title: 'Rows', total: rfml_tests.count, format: '%a %B %p%% %t')
218
+ Parallel.each(rfml_tests, in_threads: THREADS, finish: lambda { |item, i, result| progress_bar.increment }) do |rfml_test|
219
+ upload_test(rfml_test)
220
+ end
221
+ end
222
+
223
+ def upload_test(rfml_test)
224
+ return unless rfml_test.steps.count > 0
225
+
226
+ if @options.debug
227
+ logger.debug "Starting: #{rfml_test.rfml_id}"
228
+ logger.debug "\t#{rfml_test.start_uri || "/"}"
229
+ end
230
+
231
+ test_obj = create_test_obj(rfml_test)
232
+ # Upload the test
233
+ begin
234
+ if rfml_id_mappings[rfml_test.rfml_id]
235
+ t = Rainforest::Test.update(rfml_id_mappings[rfml_test.rfml_id], test_obj)
236
+
237
+ logger.info "\tUpdated #{rfml_test.rfml_id} -- ##{t.id}" if @options.debug
238
+ else
239
+ t = Rainforest::Test.create(test_obj)
240
+
241
+ logger.info "\tCreated #{rfml_test.rfml_id} -- ##{t.id}" if @options.debug
242
+ rfml_id_mappings[rfml_test.rfml_id] = t.id
243
+ end
244
+ rescue => e
245
+ logger.fatal "Error: #{rfml_test.rfml_id}: #{e}"
246
+ exit 2
247
+ end
248
+ end
249
+
250
+ def rfml_id_mappings
251
+ if @_id_mappings.nil?
252
+ @_id_mappings = {}.tap do |id_mappings|
253
+ Rainforest::Test.all(page_size: 1000, rfml_ids: test_files.rfml_ids).each do |rf_test|
254
+ rfml_id = rf_test.rfml_id
255
+ next if rfml_id.nil?
256
+
257
+ id_mappings[rfml_id] = rf_test.id
258
+ end
259
+ end
260
+ end
261
+ @_id_mappings
262
+ end
263
+
264
+ def create_test_obj(rfml_test)
265
+ test_obj = {
266
+ start_uri: rfml_test.start_uri || "/",
267
+ title: rfml_test.title,
268
+ description: rfml_test.description,
269
+ tags: (["ro"] + rfml_test.tags).uniq,
270
+ rfml_id: rfml_test.rfml_id,
271
+ elements: rfml_test.steps.map do |step|
272
+ case step.type
273
+ when :step
274
+ {
275
+ type: 'step',
276
+ redirection: true,
277
+ element: {
278
+ action: step.action,
279
+ response: step.response
280
+ }
281
+ }
282
+ when :test
283
+ {
284
+ type: 'test',
285
+ redirection: true,
286
+ element: {
287
+ id: rfml_id_mappings[step.rfml_id]
288
+ }
289
+ }
290
+ end
291
+ end
292
+ }
293
+
294
+ unless rfml_test.browsers.empty?
295
+ test_obj[:browsers] = rfml_test.browsers.map {|b|
296
+ {'state' => 'enabled', 'name' => b}
297
+ }
298
+ end
299
+
300
+ test_obj
301
+ end
238
302
  end
@@ -1,11 +1,19 @@
1
1
  module RainforestCli::TestParser
2
- class EmbeddedTest < Struct.new(:test_name)
2
+ class EmbeddedTest < Struct.new(:rfml_id)
3
+ def type
4
+ :test
5
+ end
6
+
3
7
  def to_s
4
- "--> embed: #{test_name}"
8
+ "--> embed: #{rfml_id}"
5
9
  end
6
10
  end
7
11
 
8
12
  class Step < Struct.new(:action, :response)
13
+ def type
14
+ :step
15
+ end
16
+
9
17
  def to_s
10
18
  "#{action} --> #{response}"
11
19
  end
@@ -17,7 +25,10 @@ module RainforestCli::TestParser
17
25
  end
18
26
  end
19
27
 
20
- class Test < Struct.new(:id, :description, :title, :start_uri, :steps, :errors, :tags, :browsers)
28
+ class Test < Struct.new(:rfml_id, :description, :title, :start_uri, :steps, :errors, :tags, :browsers)
29
+ def embedded_ids
30
+ steps.inject([]) { |embeds, step| step.type == :test ? embeds + [step.rfml_id] : embeds }
31
+ end
21
32
  end
22
33
 
23
34
  class Parser
@@ -43,7 +54,7 @@ module RainforestCli::TestParser
43
54
  text.lines.map(&:chomp).each_with_index do |line, line_no|
44
55
  if line[0..1] == '#!'
45
56
  # special comment, don't ignore!
46
- @test.id = line[2..-1].strip.split(" ")[0]
57
+ @test.rfml_id = line[2..-1].strip.split(" ")[0]
47
58
  @test.description += line[1..-1] + "\n"
48
59
 
49
60
  elsif line[0] == '#'
@@ -90,8 +101,8 @@ module RainforestCli::TestParser
90
101
  end
91
102
  end
92
103
 
93
- if @test.id == nil
94
- @test.errors[0] = Error.new(0, "Missing test ID. Please start a line #! followed by a unique id.")
104
+ if @test.rfml_id == nil
105
+ @test.errors[0] = Error.new(0, "Missing RFML ID. Please start a line #! followed by a unique id.")
95
106
  end
96
107
 
97
108
  return @test
@@ -1,3 +1,3 @@
1
1
  module RainforestCli
2
- VERSION = "1.1.4"
2
+ VERSION = "1.2.0"
3
3
  end
data/spec/options_spec.rb CHANGED
@@ -9,12 +9,7 @@ describe RainforestCli::OptionParser do
9
9
 
10
10
  context "test folder (when passed)" do
11
11
  let(:args) { ["--test-folder", "/path/to/folder"] }
12
- its(:test_spec_folder) { should == "/path/to/folder" }
13
- end
14
-
15
- context "test folder (when not passed)" do
16
- let(:args) { [] }
17
- its(:test_spec_folder) { should == RainforestCli::TestImporter::SPEC_FOLDER }
12
+ its(:test_folder) { should == "/path/to/folder" }
18
13
  end
19
14
 
20
15
  context "importing name" do
@@ -0,0 +1,6 @@
1
+ #! example_test (Test ID - only edit if this test has not yet been uploaded)
2
+ # title: Example Test
3
+ # start_uri: /
4
+
5
+ This is a step action.
6
+ This is a question?
@@ -0,0 +1,36 @@
1
+ describe RainforestCli::TestFiles do
2
+ let(:test_folder) { File.dirname(__FILE__) + '/rainforest-example' }
3
+ subject { described_class.new(test_folder) }
4
+
5
+ let(:rfml_test) { subject.test_data.first }
6
+ let(:text_file) { File.read(test_folder + '/example_test.rfml') }
7
+
8
+ describe '#initialize' do
9
+ before do
10
+ allow(Dir).to receive(:mkdir)
11
+ end
12
+
13
+ context 'when test folder name is not supplied' do
14
+ it 'uses the default file folder' do
15
+ expect(described_class.new.test_folder).to eq(described_class::DEFAULT_TEST_FOLDER)
16
+ end
17
+ end
18
+
19
+ context 'when test folder name is supplied' do
20
+ let(:folder_name) { './foo' }
21
+
22
+ it 'creates the supplied folder if file does not exist' do
23
+ expect(Dir).to receive(:mkdir).with(folder_name).once
24
+ described_class.new(folder_name)
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ describe '#test_data' do
31
+ it 'parses all available tests on initialization' do
32
+ expect(rfml_test.title).to eq(text_file.match(/^# title: (.+)$/)[1])
33
+ expect(rfml_test.rfml_id).to eq(text_file.match(/^#! (.+?)($| .+?$)/)[1])
34
+ end
35
+ end
36
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rainforest-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Mathieu
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-01-15 00:00:00.000000000 Z
12
+ date: 2016-02-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty
@@ -121,6 +121,7 @@ files:
121
121
  - lib/rainforest/cli/http_client.rb
122
122
  - lib/rainforest/cli/options.rb
123
123
  - lib/rainforest/cli/runner.rb
124
+ - lib/rainforest/cli/test_files.rb
124
125
  - lib/rainforest/cli/test_importer.rb
125
126
  - lib/rainforest/cli/test_parser.rb
126
127
  - lib/rainforest/cli/version.rb
@@ -130,8 +131,10 @@ files:
130
131
  - spec/fixtures/variables.txt
131
132
  - spec/git_trigger_spec.rb
132
133
  - spec/options_spec.rb
134
+ - spec/rainforest-example/example_test.rfml
133
135
  - spec/runner_spec.rb
134
136
  - spec/spec_helper.rb
137
+ - spec/test_files_spec.rb
135
138
  homepage: https://www.rainforestqa.com/
136
139
  licenses:
137
140
  - MIT
@@ -162,5 +165,7 @@ test_files:
162
165
  - spec/fixtures/variables.txt
163
166
  - spec/git_trigger_spec.rb
164
167
  - spec/options_spec.rb
168
+ - spec/rainforest-example/example_test.rfml
165
169
  - spec/runner_spec.rb
166
170
  - spec/spec_helper.rb
171
+ - spec/test_files_spec.rb