itriagetestrail 1.0.36
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/itriagetestrail.rb +254 -0
- data/lib/itriagetestrail/framework_bindings/trcucumber13.rb +46 -0
- data/lib/itriagetestrail/framework_bindings/trcucumber20.rb +44 -0
- data/lib/itriagetestrail/framework_bindings/trcucumber30.rb +44 -0
- data/lib/itriagetestrail/framework_bindings/trminitest.rb +29 -0
- data/lib/itriagetestrail/pool.rb +33 -0
- data/lib/itriagetestrail/testrail_binding.rb +187 -0
- data/lib/itriagetestrail/testrail_objects/milestones.rb +135 -0
- data/lib/itriagetestrail/testrail_objects/projects.rb +52 -0
- data/lib/itriagetestrail/testrail_objects/sections.rb +46 -0
- data/lib/itriagetestrail/testrail_objects/suites.rb +32 -0
- data/lib/itriagetestrail/testrail_objects/test_cases.rb +83 -0
- data/lib/itriagetestrail/testrail_objects/test_plans.rb +91 -0
- data/lib/itriagetestrail/testrail_objects/test_results.rb +131 -0
- data/lib/itriagetestrail/testrail_objects/test_runs.rb +53 -0
- data/lib/itriagetestrail/version.rb +3 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7d3fb08d0333d280c30606a5cd177b439f668a94a8683bf0099c47dac4b563cc
|
4
|
+
data.tar.gz: 9dd89450a238a488c46783754df2ca4c312301e2e9dd807b31d30f01b80430e7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 64729b306598a415b93d91869a57d0be086772f39fe235159be11b17ea3e88c48186189207fe125ab0d93d9d9640db9de0da1817a829e210de94c89c4e41ce6d
|
7
|
+
data.tar.gz: ed2d5d73ea6c95e07d56f0b8355e0ec73b9d09420c083517e92f7500efa10f9971bc1822c96daf8d8b94834f272b393d33c986659251151bcfeacfe2d99e2db9
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'itriagetestrail/version'
|
4
|
+
require_relative 'itriagetestrail/testrail_binding'
|
5
|
+
|
6
|
+
require_relative 'itriagetestrail/pool'
|
7
|
+
require_relative 'itriagetestrail/testrail_objects/milestones'
|
8
|
+
require_relative 'itriagetestrail/testrail_objects/projects'
|
9
|
+
require_relative 'itriagetestrail/testrail_objects/sections'
|
10
|
+
require_relative 'itriagetestrail/testrail_objects/suites'
|
11
|
+
require_relative 'itriagetestrail/testrail_objects/test_cases'
|
12
|
+
require_relative 'itriagetestrail/testrail_objects/test_plans'
|
13
|
+
require_relative 'itriagetestrail/testrail_objects/test_results'
|
14
|
+
require_relative 'itriagetestrail/testrail_objects/test_runs'
|
15
|
+
|
16
|
+
require 'tzinfo'
|
17
|
+
|
18
|
+
module Itriagetestrail
|
19
|
+
class TestRailInterface
|
20
|
+
include Projects
|
21
|
+
include Suites
|
22
|
+
include Milestones
|
23
|
+
include Sections
|
24
|
+
include TestCases
|
25
|
+
include TestPlans
|
26
|
+
include TestRuns
|
27
|
+
include TestResults
|
28
|
+
|
29
|
+
attr_accessor :client
|
30
|
+
attr_accessor :sections
|
31
|
+
attr_accessor :test_cases
|
32
|
+
attr_accessor :test_case_ids
|
33
|
+
attr_accessor :run_id
|
34
|
+
attr_accessor :execute
|
35
|
+
attr_accessor :pool
|
36
|
+
attr_accessor :batch_size
|
37
|
+
attr_reader :results
|
38
|
+
attr_reader :external_results
|
39
|
+
|
40
|
+
def _time_zone
|
41
|
+
@time_zone = if @testrail_config[:tzinfoTimeZone].nil? || @testrail_config[:tzinfoTimeZone].empty?
|
42
|
+
TZInfo::Timezone.get('UTC')
|
43
|
+
else
|
44
|
+
TZInfo::Timezone.get(@testrail_config[:tzinfoTimeZone])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def testrail_online
|
49
|
+
# This is the very first call to TestRail
|
50
|
+
make_connection
|
51
|
+
projects
|
52
|
+
if @client.response_code == '200' || @client.response_code.nil?
|
53
|
+
true
|
54
|
+
else
|
55
|
+
puts "**** TESTRAIL IS OFFLINE for maintenance or other reason with status code #{@client.response_code}"
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def _execute
|
61
|
+
@execute = if @testrail_config[:user].nil? || @testrail_config[:user].empty?
|
62
|
+
false
|
63
|
+
else
|
64
|
+
testrail_online
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def initialize
|
69
|
+
@setup = false
|
70
|
+
config
|
71
|
+
|
72
|
+
if File.exist?(run_tempfile_name)
|
73
|
+
@run_id = File.read(run_tempfile_name)
|
74
|
+
@plan_id = File.read(plan_tempfile_name)
|
75
|
+
@shared_run = true
|
76
|
+
else
|
77
|
+
@run_id = 0
|
78
|
+
@plan_id = 0
|
79
|
+
end
|
80
|
+
|
81
|
+
@milestone_name = normalize_milestone
|
82
|
+
@jenkins_build = @testrail_config[:jenkinsBuild]
|
83
|
+
@app_version = @testrail_config[:appVersion]
|
84
|
+
@suite_name = @testrail_config[:suiteName]
|
85
|
+
|
86
|
+
_time_zone
|
87
|
+
|
88
|
+
_execute
|
89
|
+
|
90
|
+
clear_results
|
91
|
+
@submitted = { results: [] }
|
92
|
+
|
93
|
+
@external_results = { results: [] }
|
94
|
+
@batch_size = @testrail_config[:batch_size] || 0
|
95
|
+
end
|
96
|
+
|
97
|
+
def clear_results
|
98
|
+
@results = { results: [] }
|
99
|
+
end
|
100
|
+
|
101
|
+
def config
|
102
|
+
time_zone = if ENV['TZINFO_TIME_ZONE'].nil? || ENV['TZINFO_TIME_ZONE'].empty?
|
103
|
+
'America/Denver'
|
104
|
+
else
|
105
|
+
ENV['TZINFO_TIME_ZONE']
|
106
|
+
end
|
107
|
+
|
108
|
+
@testrail_config = {
|
109
|
+
site: ENV['TESTRAIL_SITE'],
|
110
|
+
user: ENV['TESTRAIL_USER'],
|
111
|
+
password: ENV['TESTRAIL_PASSWORD'],
|
112
|
+
tzinfoTimeZone: time_zone,
|
113
|
+
projectId: ENV['TESTRAIL_PROJECT_ID'],
|
114
|
+
milestone: ENV['TESTRAIL_MILESTONE'],
|
115
|
+
projectName: ENV['APP_NAME'],
|
116
|
+
suiteName: ENV['TESTRAIL_SUITE_NAME'],
|
117
|
+
run_id: ENV['TESTRAIL_RUN_ID'],
|
118
|
+
report_skips: ENV['TESTRAIL_REPORT_SKIPS'],
|
119
|
+
close_run: ENV['TESTRAIL_CLOSE_RUN'],
|
120
|
+
close_run_delay: ENV['TESTRAIL_CLOSE_RUN_DELAY'],
|
121
|
+
origin: ENV['GIT_BRANCH'],
|
122
|
+
jenkinsBuild: ENV['BUILD_NUMBER'],
|
123
|
+
appVersion: nil,
|
124
|
+
config: { ios: ENV['IOS_VERSION'],
|
125
|
+
android: ENV['ANDROID_VERSION'],
|
126
|
+
androidDevice: ENV['DEVICE_NAME'],
|
127
|
+
browser: ENV['BROWSER_NAME'], # Jenkins: SELENIUM_BROWSER
|
128
|
+
platform: ENV['PLATFORM'], # Jenkins: SELENIUM_PLATFORM
|
129
|
+
browserVersion: ENV['BROWSER_VERSION'] } # Jenkins: SELENIUM_VERSION
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
# These are generic, hardcoded tag translations that map to test ids in TestRail
|
134
|
+
def tag_ids(tag_names = [])
|
135
|
+
tag_ids = []
|
136
|
+
tag_names.each do |tag_name|
|
137
|
+
case tag_name
|
138
|
+
when '@wip'
|
139
|
+
tag_ids << 1
|
140
|
+
when '@flaky'
|
141
|
+
tag_ids << 2
|
142
|
+
when '@broken'
|
143
|
+
tag_ids << 3
|
144
|
+
when '@missingTheSauce'
|
145
|
+
tag_ids << 4
|
146
|
+
end
|
147
|
+
end
|
148
|
+
tag_ids
|
149
|
+
end
|
150
|
+
|
151
|
+
def setup?
|
152
|
+
@setup
|
153
|
+
end
|
154
|
+
|
155
|
+
def setup
|
156
|
+
return false if setup?
|
157
|
+
return false unless execute
|
158
|
+
|
159
|
+
@results = { results: [] }
|
160
|
+
@submitted = { results: [] }
|
161
|
+
|
162
|
+
@external_results = { results: [] }
|
163
|
+
@batch_size = @testrail_config[:batch_size] || 0
|
164
|
+
|
165
|
+
initialize_variables
|
166
|
+
|
167
|
+
@milestone_id = fetch_milestone(@milestone_name)
|
168
|
+
|
169
|
+
reset_milestone(@milestone_name)
|
170
|
+
|
171
|
+
#Supports tagging
|
172
|
+
@testrail_case_fields = @client.send_get('get_case_fields')
|
173
|
+
@pool = Pool.new(1)
|
174
|
+
@setup = true
|
175
|
+
end
|
176
|
+
|
177
|
+
def create_run_id
|
178
|
+
# TODO: Look into configuration_ids to add a plan
|
179
|
+
add_testrail_run if @run_id.to_i.zero?
|
180
|
+
end
|
181
|
+
|
182
|
+
def initialize_variables
|
183
|
+
make_connection
|
184
|
+
# Set the project id
|
185
|
+
set_project
|
186
|
+
# Get the test rail suites
|
187
|
+
testrail_suites
|
188
|
+
# Get the test rail sections
|
189
|
+
testrail_sections
|
190
|
+
# Get the test rail ids
|
191
|
+
testrail_ids
|
192
|
+
# Populate configurations
|
193
|
+
configurations
|
194
|
+
end
|
195
|
+
|
196
|
+
def run_tempfile_name
|
197
|
+
"./tmp/#{@testrail_config[:jenkinsBuild]}_testrail_run_id"
|
198
|
+
end
|
199
|
+
|
200
|
+
def plan_tempfile_name
|
201
|
+
"./tmp/#{@testrail_config[:jenkinsBuild]}_testrail_plan_id"
|
202
|
+
end
|
203
|
+
|
204
|
+
def make_connection
|
205
|
+
@client = TestRail::APIClient.new(@testrail_config[:site])
|
206
|
+
@client.user = @testrail_config[:user]
|
207
|
+
@client.password = @testrail_config[:password]
|
208
|
+
end
|
209
|
+
|
210
|
+
def close_run?
|
211
|
+
# do not close a run if a run id was supplied. other test suites related to the job will need it.
|
212
|
+
close_run = false
|
213
|
+
if @testrail_config[:close_run] == 'true' && (@testrail_config[:run_id].nil? || @testrail_config[:run_id].empty?)
|
214
|
+
close_run = true
|
215
|
+
end
|
216
|
+
close_run
|
217
|
+
end
|
218
|
+
|
219
|
+
# This method is only used publicly
|
220
|
+
def delete_temp_files
|
221
|
+
File.delete(run_tempfile_name) if File.exist?(run_tempfile_name)
|
222
|
+
File.delete(plan_tempfile_name) if File.exist?(plan_tempfile_name)
|
223
|
+
end
|
224
|
+
|
225
|
+
# This method is only used publicly
|
226
|
+
def initialize_temp_files
|
227
|
+
Dir.mkdir('./tmp') unless File.exist?('./tmp')
|
228
|
+
File.write(run_tempfile_name, @run_id)
|
229
|
+
File.write(plan_tempfile_name, @plan_id)
|
230
|
+
|
231
|
+
@shared_run = true
|
232
|
+
end
|
233
|
+
|
234
|
+
def close_run(run_id = @run_id, message = '')
|
235
|
+
# A delay is recommended to prevent an HTTP 400 race condition where final results are still
|
236
|
+
# being populated in the TESTRAIL DB while a close_run has been requested.
|
237
|
+
|
238
|
+
close_run_delay = @testrail_config[:close_run_delay] || 5
|
239
|
+
sleep close_run_delay.to_i
|
240
|
+
@client.send_post("update_run/#{run_id}",
|
241
|
+
include_all: false, case_ids: existing_cases_from_run(run_id),
|
242
|
+
description: 'Timestamp: ' + @time_zone.now.strftime('%m/%d/%Y %I:%M %p') +
|
243
|
+
"\nBranch: #{@testrail_config[:origin]}" + "\n#{message}")
|
244
|
+
|
245
|
+
@client.send_post("close_run/#{run_id}", {})
|
246
|
+
end
|
247
|
+
|
248
|
+
def shutdown(message = '')
|
249
|
+
@pool.shutdown
|
250
|
+
|
251
|
+
close_run(message) if close_run?
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'itriagetestrail'
|
4
|
+
module Itriagetestrail
|
5
|
+
class TRCucumber13 < TestRailInterface
|
6
|
+
def scenario_title_and_external_id(scenario)
|
7
|
+
if scenario.class.to_s == 'Cucumber::Ast::OutlineTable::ExampleRow'
|
8
|
+
scenario_cell_id = []
|
9
|
+
scenario.name.split('|')[1..50].each { |cell| scenario_cell_id << cell.gsub(/[\| "]/, '') }
|
10
|
+
@scenario_title = "#{scenario.scenario_outline.title}, Example: #{scenario_cell_id}"
|
11
|
+
@external_id = "#{scenario.scenario_outline.feature.name};#{@scenario_title}"[0, 249]
|
12
|
+
@scenario_steps = scenario.scenario_outline.raw_steps.select { 'name' }.collect(&:name).join("\n")
|
13
|
+
else
|
14
|
+
# identifiers: scenario.feature.name, scenario.name
|
15
|
+
@external_id = "#{scenario.feature.title};#{scenario.title}"
|
16
|
+
@scenario_title = scenario.title
|
17
|
+
@scenario_steps = scenario.steps.select { 'name' }.collect(&:name).join("\n")
|
18
|
+
end
|
19
|
+
@scenario_tags = scenario.source_tag_names
|
20
|
+
end
|
21
|
+
|
22
|
+
def record_result(scenario)
|
23
|
+
return if execute == false
|
24
|
+
begin
|
25
|
+
scenario_title_and_external_id(scenario)
|
26
|
+
|
27
|
+
if scenario.passed?
|
28
|
+
store_result(@scenario_title, @external_id, @scenario_steps, 1, '')
|
29
|
+
else
|
30
|
+
store_result(@scenario_title, @external_id, @scenario_steps,
|
31
|
+
(ENV['RERUN'] ? 5 : 4).to_s.to_i, "#{scenario.exception.class}\n" \
|
32
|
+
"#{scenario.exception}\n#{scenario.exception.backtrace}")
|
33
|
+
end
|
34
|
+
rescue StandardError => exception
|
35
|
+
puts "TESTRAIL ENCOUNTERED AN ERROR: #{exception}\n #{@external_id} \n\n"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def shutdown
|
40
|
+
return unless execute
|
41
|
+
sleep 3 # testrail service rate limit precaution
|
42
|
+
send_results_to_testrail
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'itriagetestrail'
|
4
|
+
module Itriagetestrail
|
5
|
+
class TRCucumber20 < TestRailInterface
|
6
|
+
def scenario_title_and_external_id(scenario)
|
7
|
+
if scenario.class.to_s == 'Cucumber::RunningTestCase::ScenarioOutlineExample'
|
8
|
+
scenario.cell_values.each { |cell| cell.gsub!(/\s+/, '') }
|
9
|
+
@scenario_title = "#{scenario.name.match(/\b.*Example/)}: #{scenario.cell_values}"
|
10
|
+
@external_id = "#{scenario.feature.short_name}\;#{@scenario_title}"[0, 249]
|
11
|
+
else
|
12
|
+
@external_id = "#{scenario.feature.short_name}\;#{scenario.name}"
|
13
|
+
@scenario_title = scenario.name
|
14
|
+
end
|
15
|
+
@scenario_tags = scenario.source_tag_names
|
16
|
+
end
|
17
|
+
|
18
|
+
def record_result(scenario)
|
19
|
+
return if execute == false
|
20
|
+
begin
|
21
|
+
scenario_title_and_external_id(scenario)
|
22
|
+
|
23
|
+
@scenario_steps = scenario.test_steps.select { 'name' }.collect(&:name).join("\n")
|
24
|
+
|
25
|
+
if scenario.passed?
|
26
|
+
store_result(@scenario_title, @external_id, @scenario_steps, 1, '')
|
27
|
+
else
|
28
|
+
store_result(@scenario_title, @external_id, @scenario_steps,
|
29
|
+
(ENV['RERUN'] ? 5 : 4).to_s.to_i, "#{scenario.exception.class}\n" \
|
30
|
+
"#{scenario.exception}\n#{scenario.exception.backtrace}")
|
31
|
+
end
|
32
|
+
rescue StandardError => exception
|
33
|
+
puts "TESTRAIL ENCOUNTERED AN ERROR: #{exception}\n #{@external_id} \n\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def shutdown
|
38
|
+
return unless execute
|
39
|
+
sleep 3 # testrail service rate limit precaution
|
40
|
+
send_results_to_testrail
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'itriagetestrail'
|
4
|
+
module Itriagetestrail
|
5
|
+
class TRCucumber30 < TestRailInterface
|
6
|
+
def scenario_title_and_external_id(scenario)
|
7
|
+
if scenario.class.to_s == 'Cucumber::RunningTestCase::ScenarioOutlineExample'
|
8
|
+
# Remove spaces within cells to match our Cucumber 1.3 implementation
|
9
|
+
scenario.cell_values.each { |cell| cell.gsub!(/\s+/, '') }
|
10
|
+
@scenario_title = "#{scenario.name.match(/\b.*Example/)}: #{scenario.cell_values}"
|
11
|
+
@external_id = "#{scenario.feature.short_name}\;#{@scenario_title}"[0, 249]
|
12
|
+
else
|
13
|
+
@external_id = "#{scenario.feature.short_name}\;#{scenario.name}"
|
14
|
+
@scenario_title = scenario.name
|
15
|
+
end
|
16
|
+
@scenario_tags = scenario.source_tag_names
|
17
|
+
end
|
18
|
+
|
19
|
+
def record_result(scenario)
|
20
|
+
return if execute == false
|
21
|
+
begin
|
22
|
+
scenario_title_and_external_id(scenario)
|
23
|
+
@scenario_steps = scenario.test_steps.collect(&:text).join("\n")
|
24
|
+
|
25
|
+
if scenario.passed?
|
26
|
+
store_result(@scenario_title, @external_id, @scenario_steps, 1, '')
|
27
|
+
else
|
28
|
+
store_result(@scenario_title, @external_id, @scenario_steps,
|
29
|
+
(ENV['RERUN'] ? 5 : 4).to_s.to_i, "#{scenario.exception.class}" \
|
30
|
+
"\n#{scenario.exception}\n#{scenario.exception.backtrace}")
|
31
|
+
end
|
32
|
+
rescue StandardError => exception
|
33
|
+
puts "TESTRAIL ENCOUNTERED AN ERROR: #{exception}\n #{@external_id} \n\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def shutdown
|
38
|
+
return unless execute
|
39
|
+
sleep 3 # testrail service rate limit precaution
|
40
|
+
send_results_to_testrail
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'itriagetestrail'
|
4
|
+
module Itriagetestrail
|
5
|
+
class TRMiniTest < TestRailInterface
|
6
|
+
def record_result(test, failures)
|
7
|
+
return if execute == false
|
8
|
+
begin
|
9
|
+
@external_id = "#{test.class}##{test.name}"
|
10
|
+
if test.passed?
|
11
|
+
store_result(test.name, @external_id, '', 1, '')
|
12
|
+
elsif failures.inspect.include? 'Minitest::Skip'
|
13
|
+
store_result(test.name, @external_id, '', 6, failures.inspect)
|
14
|
+
else
|
15
|
+
store_result(test.name, @external_id, '', 5, failures.inspect)
|
16
|
+
end
|
17
|
+
rescue StandardError => exception
|
18
|
+
puts "TESTRAIL ENCOUNTERED AN ERROR: #{exception}\n #{@external_id} \n\n"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def shutdown
|
23
|
+
return unless execute
|
24
|
+
sleep 3 # testrail service rate limit precaution
|
25
|
+
send_results_to_testrail
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Pool
|
4
|
+
def initialize(size)
|
5
|
+
@size = size
|
6
|
+
@jobs = Queue.new
|
7
|
+
|
8
|
+
@pool = Array.new(@size) do |i|
|
9
|
+
Thread.new do
|
10
|
+
Thread.current[:id] = i
|
11
|
+
|
12
|
+
catch(:exit) do
|
13
|
+
loop do
|
14
|
+
job, args = @jobs.pop
|
15
|
+
job.call(*args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def schedule(*args, &block)
|
23
|
+
@jobs << [block, args]
|
24
|
+
end
|
25
|
+
|
26
|
+
def shutdown
|
27
|
+
@size.times do
|
28
|
+
schedule { throw :exit }
|
29
|
+
end
|
30
|
+
|
31
|
+
@pool.map(&:join)
|
32
|
+
end
|
33
|
+
end
|