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.
@@ -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