rainforest-cli 1.6.5 → 1.7.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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +8 -0
- data/README.md +42 -7
- data/bin/rainforest +1 -1
- data/lib/{rainforest/cli.rb → rainforest_cli.rb} +23 -15
- data/lib/{rainforest/cli → rainforest_cli}/constants.rb +0 -0
- data/lib/{rainforest/cli → rainforest_cli}/csv_importer.rb +0 -0
- data/lib/{rainforest/cli → rainforest_cli}/deleter.rb +0 -0
- data/lib/{rainforest/cli → rainforest_cli}/exporter.rb +0 -0
- data/lib/{rainforest/cli → rainforest_cli}/git_trigger.rb +0 -0
- data/lib/{rainforest/cli → rainforest_cli}/http_client.rb +5 -4
- data/lib/rainforest_cli/junit_outputter.rb +69 -0
- data/lib/{rainforest/cli → rainforest_cli}/options.rb +40 -1
- data/lib/{rainforest/cli → rainforest_cli}/remote_tests.rb +0 -0
- data/lib/rainforest_cli/reporter.rb +64 -0
- data/lib/{rainforest/cli → rainforest_cli}/resources.rb +0 -0
- data/lib/{rainforest/cli → rainforest_cli}/runner.rb +20 -6
- data/lib/{rainforest/cli → rainforest_cli}/test_files.rb +0 -0
- data/lib/{rainforest/cli → rainforest_cli}/test_parser.rb +4 -65
- data/lib/rainforest_cli/test_parser/embedded_test.rb +14 -0
- data/lib/rainforest_cli/test_parser/step.rb +24 -0
- data/lib/rainforest_cli/test_parser/test.rb +40 -0
- data/lib/{rainforest/cli → rainforest_cli}/uploader.rb +32 -27
- data/lib/rainforest_cli/uploader/multi_form_post_request.rb +50 -0
- data/lib/rainforest_cli/uploader/uploadable_parser.rb +143 -0
- data/lib/{rainforest/cli → rainforest_cli}/validator.rb +0 -0
- data/lib/{rainforest/cli → rainforest_cli}/version.rb +1 -1
- data/rainforest-cli.gemspec +3 -1
- data/spec/fixtures/failed_test_response.json +29 -0
- data/spec/fixtures/runs_response.json +275 -0
- data/spec/fixtures/tests_response.json +130 -0
- data/spec/{csv_importer_spec.rb → rainforest_cli/csv_importer_spec.rb} +7 -14
- data/spec/{deleter_spec.rb → rainforest_cli/deleter_spec.rb} +0 -0
- data/spec/{exporter_spec.rb → rainforest_cli/exporter_spec.rb} +0 -0
- data/spec/{git_trigger_spec.rb → rainforest_cli/git_trigger_spec.rb} +0 -0
- data/spec/{http_client_spec.rb → rainforest_cli/http_client_spec.rb} +23 -0
- data/spec/rainforest_cli/junit_outputter_spec.rb +32 -0
- data/spec/{options_spec.rb → rainforest_cli/options_spec.rb} +33 -0
- data/spec/{remote_tests_spec.rb → rainforest_cli/remote_tests_spec.rb} +0 -0
- data/spec/rainforest_cli/reporter_spec.rb +51 -0
- data/spec/{resources_spec.rb → rainforest_cli/resources_spec.rb} +0 -0
- data/spec/{runner_spec.rb → rainforest_cli/runner_spec.rb} +0 -0
- data/spec/{test_files_spec.rb → rainforest_cli/test_files_spec.rb} +1 -1
- data/spec/rainforest_cli/test_parser/step_spec.rb +53 -0
- data/spec/rainforest_cli/test_parser_spec.rb +15 -0
- data/spec/rainforest_cli/uploader/uploadable_parser_spec.rb +84 -0
- data/spec/{uploader_spec.rb → rainforest_cli/uploader_spec.rb} +2 -2
- data/spec/{validator_spec.rb → rainforest_cli/validator_spec.rb} +6 -7
- data/spec/{cli_spec.rb → rainforest_cli_spec.rb} +5 -5
- data/spec/spec_helper.rb +4 -1
- metadata +95 -47
- data/.rvmrc +0 -1
- data/spec/test_parser_spec.rb +0 -95
File without changes
|
@@ -9,6 +9,11 @@ module RainforestCli
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def run
|
12
|
+
if options.wait?
|
13
|
+
wait_for_run_completion(options.run_id)
|
14
|
+
return true
|
15
|
+
end
|
16
|
+
|
12
17
|
if options.import_file_name && options.import_name
|
13
18
|
delete_generator(options.import_name)
|
14
19
|
CSVImporter.new(options.import_name, options.import_file_name, options.token).import
|
@@ -26,9 +31,20 @@ module RainforestCli
|
|
26
31
|
exit 1
|
27
32
|
end
|
28
33
|
|
34
|
+
run_id = response.fetch('id')
|
35
|
+
logger.info "Issued run #{run_id}"
|
36
|
+
|
29
37
|
if options.foreground?
|
30
|
-
|
31
|
-
|
38
|
+
response = wait_for_run_completion(run_id)
|
39
|
+
if options.junit_file?
|
40
|
+
reporter = Reporter.new(options)
|
41
|
+
reporter.run_id = run_id
|
42
|
+
reporter.report
|
43
|
+
end
|
44
|
+
|
45
|
+
if response['result'] != 'passed'
|
46
|
+
exit 1
|
47
|
+
end
|
32
48
|
else
|
33
49
|
true
|
34
50
|
end
|
@@ -37,7 +53,6 @@ module RainforestCli
|
|
37
53
|
def wait_for_run_completion(run_id)
|
38
54
|
running = true
|
39
55
|
while running
|
40
|
-
Kernel.sleep 5
|
41
56
|
response = client.get("/runs/#{run_id}", {}, retries_on_failures: true)
|
42
57
|
if response
|
43
58
|
state_details = response.fetch('state_details')
|
@@ -50,15 +65,14 @@ module RainforestCli
|
|
50
65
|
running = false
|
51
66
|
end
|
52
67
|
end
|
68
|
+
Kernel.sleep 5 if running
|
53
69
|
end
|
54
70
|
|
55
71
|
if response['frontend_url']
|
56
72
|
logger.info "The detailed results are available at #{response['frontend_url']}"
|
57
73
|
end
|
58
74
|
|
59
|
-
|
60
|
-
exit 1
|
61
|
-
end
|
75
|
+
return response
|
62
76
|
end
|
63
77
|
|
64
78
|
def make_create_run_options
|
File without changes
|
@@ -1,70 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
module RainforestCli::TestParser
|
3
|
-
class EmbeddedTest < Struct.new(:rfml_id, :redirect)
|
4
|
-
def type
|
5
|
-
:test
|
6
|
-
end
|
7
|
-
|
8
|
-
def to_s
|
9
|
-
"--> embed: #{rfml_id}"
|
10
|
-
end
|
11
|
-
|
12
|
-
def redirection
|
13
|
-
redirect || 'true'
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_element(primary_key_id)
|
17
|
-
{
|
18
|
-
type: 'test',
|
19
|
-
redirection: redirection,
|
20
|
-
element: {
|
21
|
-
id: primary_key_id
|
22
|
-
}
|
23
|
-
}
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
class Step < Struct.new(:action, :response, :redirect)
|
28
|
-
def type
|
29
|
-
:step
|
30
|
-
end
|
31
2
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
def to_s
|
37
|
-
"#{action} --> #{response}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def to_element
|
41
|
-
{
|
42
|
-
type: 'step',
|
43
|
-
redirection: redirection,
|
44
|
-
element: {
|
45
|
-
action: action,
|
46
|
-
response: response
|
47
|
-
}
|
48
|
-
}
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class Test < Struct.new(
|
53
|
-
:file_name,
|
54
|
-
:rfml_id,
|
55
|
-
:description,
|
56
|
-
:title,
|
57
|
-
:start_uri,
|
58
|
-
:site_id,
|
59
|
-
:steps,
|
60
|
-
:errors,
|
61
|
-
:tags,
|
62
|
-
:browsers
|
63
|
-
)
|
64
|
-
def embedded_ids
|
65
|
-
steps.inject([]) { |embeds, step| step.type == :test ? embeds + [step.rfml_id] : embeds }
|
66
|
-
end
|
67
|
-
end
|
3
|
+
module RainforestCli::TestParser
|
4
|
+
require 'rainforest_cli/test_parser/test'
|
5
|
+
require 'rainforest_cli/test_parser/step'
|
6
|
+
require 'rainforest_cli/test_parser/embedded_test'
|
68
7
|
|
69
8
|
class Error < Struct.new(:line, :reason)
|
70
9
|
def to_s
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class RainforestCli::TestParser::Step < Struct.new(:action, :response, :redirect)
|
3
|
+
UPLOADABLE_REGEX = /{{ *file\.(download|screenshot)\(([^\)]+)\) *}}/
|
4
|
+
|
5
|
+
def type
|
6
|
+
:step
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
"#{action} --> #{response}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_uploadable_files?
|
14
|
+
uploadable_in_action.any? || uploadable_in_response.any?
|
15
|
+
end
|
16
|
+
|
17
|
+
def uploadable_in_action
|
18
|
+
action.scan(UPLOADABLE_REGEX)
|
19
|
+
end
|
20
|
+
|
21
|
+
def uploadable_in_response
|
22
|
+
response.scan(UPLOADABLE_REGEX)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class RainforestCli::TestParser::Test < Struct.new(
|
3
|
+
:file_name,
|
4
|
+
:rfml_id,
|
5
|
+
:description,
|
6
|
+
:title,
|
7
|
+
:start_uri,
|
8
|
+
:site_id,
|
9
|
+
:steps,
|
10
|
+
:errors,
|
11
|
+
:tags,
|
12
|
+
:browsers
|
13
|
+
)
|
14
|
+
def embedded_ids
|
15
|
+
steps.inject([]) { |embeds, step| step.type == :test ? embeds + [step.rfml_id] : embeds }
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_json
|
19
|
+
{
|
20
|
+
start_uri: start_uri || '/',
|
21
|
+
title: title,
|
22
|
+
site_id: site_id,
|
23
|
+
description: description,
|
24
|
+
source: 'rainforest-cli',
|
25
|
+
tags: tags.uniq,
|
26
|
+
rfml_id: rfml_id,
|
27
|
+
browsers: browser_json,
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def browser_json
|
32
|
+
browsers.map do |b|
|
33
|
+
{'state' => 'enabled', 'name' => b}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_uploadable_files?
|
38
|
+
steps.any?(&:has_uploadable_files?)
|
39
|
+
end
|
40
|
+
end
|
@@ -4,6 +4,8 @@ require 'parallel'
|
|
4
4
|
require 'ruby-progressbar'
|
5
5
|
|
6
6
|
class RainforestCli::Uploader
|
7
|
+
require 'rainforest_cli/uploader/uploadable_parser'
|
8
|
+
|
7
9
|
attr_reader :test_files, :remote_tests, :validator
|
8
10
|
|
9
11
|
def initialize(options)
|
@@ -26,6 +28,33 @@ class RainforestCli::Uploader
|
|
26
28
|
each_in_parallel(rfml_tests) { |rfml_test| upload_test(rfml_test) }
|
27
29
|
end
|
28
30
|
|
31
|
+
def create_test_obj(rfml_test)
|
32
|
+
parse_uploadables!(rfml_test) if rfml_test.has_uploadable_files?
|
33
|
+
|
34
|
+
elements = rfml_test.steps.map do |step|
|
35
|
+
element = case step.type
|
36
|
+
when :test then { id: primary_key_dictionary[step.rfml_id] }
|
37
|
+
when :step then { action: step.action, response: step.response }
|
38
|
+
else
|
39
|
+
logger.fatal "Unable to parse step type: #{step.type} in #{rfml_test.file_name}"
|
40
|
+
exit 1
|
41
|
+
end
|
42
|
+
{
|
43
|
+
type: step.type,
|
44
|
+
redirection: step.redirect || true,
|
45
|
+
element: element,
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
rfml_test.to_json.merge(elements: elements)
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_uploadables!(rfml_test)
|
53
|
+
test_id = primary_key_dictionary[rfml_test.rfml_id]
|
54
|
+
uploaded_files = http_client.get("/tests/#{test_id}/files")
|
55
|
+
UploadableParser.new(rfml_test, test_id, uploaded_files).parse_files!
|
56
|
+
end
|
57
|
+
|
29
58
|
private
|
30
59
|
|
31
60
|
def each_in_parallel(tests, &blk)
|
@@ -52,7 +81,7 @@ class RainforestCli::Uploader
|
|
52
81
|
title: rfml_test.title,
|
53
82
|
start_uri: rfml_test.start_uri,
|
54
83
|
rfml_id: rfml_test.rfml_id,
|
55
|
-
source: 'rainforest-cli'
|
84
|
+
source: 'rainforest-cli',
|
56
85
|
}
|
57
86
|
rf_test = Rainforest::Test.create(test_obj)
|
58
87
|
|
@@ -79,31 +108,7 @@ class RainforestCli::Uploader
|
|
79
108
|
RainforestCli.logger
|
80
109
|
end
|
81
110
|
|
82
|
-
def
|
83
|
-
|
84
|
-
start_uri: rfml_test.start_uri || '/',
|
85
|
-
title: rfml_test.title,
|
86
|
-
site_id: rfml_test.site_id,
|
87
|
-
description: rfml_test.description,
|
88
|
-
source: 'rainforest-cli',
|
89
|
-
tags: rfml_test.tags.uniq,
|
90
|
-
rfml_id: rfml_test.rfml_id
|
91
|
-
}
|
92
|
-
|
93
|
-
test_obj[:elements] = rfml_test.steps.map do |step|
|
94
|
-
if step.respond_to?(:rfml_id)
|
95
|
-
step.to_element(primary_key_dictionary[step.rfml_id])
|
96
|
-
else
|
97
|
-
step.to_element
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
unless rfml_test.browsers.empty?
|
102
|
-
test_obj[:browsers] = rfml_test.browsers.map do|b|
|
103
|
-
{'state' => 'enabled', 'name' => b}
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
test_obj
|
111
|
+
def http_client
|
112
|
+
RainforestCli.http_client
|
108
113
|
end
|
109
114
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'httparty'
|
3
|
+
|
4
|
+
class RainforestCli::Uploader::MultiFormPostRequest
|
5
|
+
BOUNDARY = 'RainforestCli'
|
6
|
+
|
7
|
+
class Param < Struct.new(:param_name, :value)
|
8
|
+
def to_multipart
|
9
|
+
<<-EOS
|
10
|
+
Content-Disposition: form-data; name="#{param_name}"\r
|
11
|
+
\r
|
12
|
+
#{value}\r
|
13
|
+
EOS
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class FileParam < Struct.new(:param_name, :file)
|
18
|
+
def to_multipart
|
19
|
+
<<-EOS
|
20
|
+
Content-Disposition: form-data; name="#{param_name}"; filename="#{File.basename(file.path)}"\r
|
21
|
+
Content-Type: #{MimeMagic.by_path(file.path)}\r
|
22
|
+
Content-Transfer-Encoding: binary\r
|
23
|
+
\r
|
24
|
+
#{file.read.strip}\r
|
25
|
+
EOS
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def request(url, params)
|
31
|
+
HTTParty.post(url, body: make_body(params), headers: headers)
|
32
|
+
end
|
33
|
+
|
34
|
+
def make_body(params)
|
35
|
+
fp = []
|
36
|
+
params.each do |k, v|
|
37
|
+
if v.respond_to?(:read)
|
38
|
+
fp.push(FileParam.new(k, v))
|
39
|
+
else
|
40
|
+
fp.push(Param.new(k, v))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
fp.map { |p| "--#{BOUNDARY}\n#{p.to_multipart}" }.join + "--#{BOUNDARY}--"
|
44
|
+
end
|
45
|
+
|
46
|
+
def headers
|
47
|
+
{ 'Content-type' => "multipart/form-data, boundary=#{BOUNDARY}" }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'mimemagic'
|
3
|
+
require 'rainforest_cli/uploader/multi_form_post_request'
|
4
|
+
|
5
|
+
class RainforestCli::Uploader::UploadableParser
|
6
|
+
def initialize(rfml_test, test_id, uploaded_files)
|
7
|
+
@rfml_test = rfml_test
|
8
|
+
@test_id = test_id
|
9
|
+
@uploaded_files = uploaded_files
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_files!
|
13
|
+
@rfml_test.steps.each do |step|
|
14
|
+
next if step.type == :test
|
15
|
+
parse_action_files(step) if step.uploadable_in_action
|
16
|
+
parse_response_files(step) if step.uploadable_in_response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_action_files(step)
|
21
|
+
step.uploadable_in_action.each do |match|
|
22
|
+
step.action = replace_paths_in_text(step.action, match)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_response_files(step)
|
27
|
+
step.uploadable_in_response.each do |match|
|
28
|
+
step.response = replace_paths_in_text(step.response, match)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def replace_paths_in_text(text, match)
|
33
|
+
step_var, relative_file_path = match
|
34
|
+
file_path = File.expand_path(File.join(test_directory, relative_file_path))
|
35
|
+
|
36
|
+
unless File.exist?(file_path)
|
37
|
+
logger.warn "\tError for test: #{@rfml_test.file_name}:"
|
38
|
+
logger.warn "\t\tNo such file exists: #{File.basename(file_path)}"
|
39
|
+
return text
|
40
|
+
end
|
41
|
+
|
42
|
+
file = File.open(file_path, 'rb')
|
43
|
+
aws_info = get_aws_upload_info(file)
|
44
|
+
|
45
|
+
sig = aws_info['file_signature'][0...6]
|
46
|
+
if step_var == 'screenshot'
|
47
|
+
text.gsub(relative_file_path, "#{aws_info['file_id']}, #{sig}")
|
48
|
+
elsif step_var == 'download'
|
49
|
+
text.gsub(relative_file_path, "#{aws_info['file_id']}, #{sig}, #{File.basename(file_path)}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_aws_upload_info(file)
|
54
|
+
if file_already_uploaded?(file)
|
55
|
+
aws_info = get_uploaded_data(file)
|
56
|
+
else
|
57
|
+
aws_info = upload_to_rainforest(file)
|
58
|
+
add_file_to_uploaded_collection(file, aws_info)
|
59
|
+
upload_to_aws(file, aws_info)
|
60
|
+
end
|
61
|
+
|
62
|
+
aws_info
|
63
|
+
end
|
64
|
+
|
65
|
+
def upload_to_rainforest(file)
|
66
|
+
logger.info "\tUploading file metadata for #{file.path}..."
|
67
|
+
|
68
|
+
resp = http_client.post(
|
69
|
+
"/tests/#{@test_id}/files",
|
70
|
+
mime_type: MimeMagic.by_path(file).to_s,
|
71
|
+
size: file.size,
|
72
|
+
name: File.basename(file.path),
|
73
|
+
digest: file_digest(file)
|
74
|
+
)
|
75
|
+
|
76
|
+
if resp['aws_url'].nil?
|
77
|
+
logger.fatal "\tThere was a problem with uploading your file: #{file_path}."
|
78
|
+
logger.fatal "\t\t#{resp.to_json}"
|
79
|
+
exit 2
|
80
|
+
end
|
81
|
+
|
82
|
+
resp
|
83
|
+
end
|
84
|
+
|
85
|
+
def upload_to_aws(file, aws_info)
|
86
|
+
logger.info "\tUploading file data for #{file.path}..."
|
87
|
+
|
88
|
+
resp = RainforestCli::Uploader::MultiFormPostRequest.request(
|
89
|
+
aws_info['aws_url'],
|
90
|
+
'key' => aws_info['aws_key'],
|
91
|
+
'AWSAccessKeyId' => aws_info['aws_access_id'],
|
92
|
+
'acl' => aws_info['aws_acl'],
|
93
|
+
'policy' => aws_info['aws_policy'],
|
94
|
+
'signature' => aws_info['aws_signature'],
|
95
|
+
'Content-Type' => MimeMagic.by_path(file),
|
96
|
+
'file' => file,
|
97
|
+
)
|
98
|
+
|
99
|
+
unless resp.code.between?(200, 299)
|
100
|
+
logger.fatal "\tThere was a problem with uploading your file: #{file.path}."
|
101
|
+
logger.fatal "\t\t#{resp.to_json}"
|
102
|
+
exit 3
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_file_to_uploaded_collection(file, aws_info)
|
107
|
+
@uploaded_files.push({
|
108
|
+
'id' => aws_info['file_id'],
|
109
|
+
'signature' => aws_info['file_signature'],
|
110
|
+
'digest' => file_digest(file),
|
111
|
+
})
|
112
|
+
end
|
113
|
+
|
114
|
+
def file_already_uploaded?(file)
|
115
|
+
@uploaded_files.any? { |f| f['digest'] == file_digest(file) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def get_uploaded_data(file)
|
119
|
+
file_data = @uploaded_files.find { |f| f['digest'] == file_digest(file) }
|
120
|
+
{
|
121
|
+
'file_signature' => file_data['signature'],
|
122
|
+
'file_id' => file_data['id'],
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def file_digest(file)
|
129
|
+
Digest::MD5.file(file).hexdigest
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_directory
|
133
|
+
@test_directory ||= File.dirname(@rfml_test.file_name)
|
134
|
+
end
|
135
|
+
|
136
|
+
def http_client
|
137
|
+
RainforestCli.http_client
|
138
|
+
end
|
139
|
+
|
140
|
+
def logger
|
141
|
+
RainforestCli.logger
|
142
|
+
end
|
143
|
+
end
|