rainforest-cli 1.6.5 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|