itriagetestrail 1.0.36
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 +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
|