rainforest-cli 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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