testrailtagging 0.3.6.8
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/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +75 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/files/RSpecParser.rb +177 -0
- data/lib/files/TestCase.rb +38 -0
- data/lib/files/testcase_modifications.rb +393 -0
- data/lib/files/testrail_apiclient_retry.rb +46 -0
- data/lib/files/testrail_operations.rb +369 -0
- data/lib/files/testrail_queries.rb +108 -0
- data/lib/files/testrail_rspec_integration.rb +385 -0
- data/lib/files/version.rb +3 -0
- data/lib/testrailtagging.rb +4 -0
- data/testrailtagging.gemspec +28 -0
- metadata +133 -0
@@ -0,0 +1,369 @@
|
|
1
|
+
require_relative "testrail_apiclient_retry"
|
2
|
+
require_relative "TestCase"
|
3
|
+
|
4
|
+
# =================================================================================
|
5
|
+
#
|
6
|
+
# API's for Test Rail
|
7
|
+
#
|
8
|
+
# =================================================================================
|
9
|
+
|
10
|
+
module TestRailOperations
|
11
|
+
def self.set_testrail_ids(pid, sid)
|
12
|
+
@@testrail_project_id = pid
|
13
|
+
@@testrail_suite_id = sid
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.project_id
|
17
|
+
@@testrail_project_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.suite_id
|
21
|
+
@@testrail_suite_id
|
22
|
+
end
|
23
|
+
|
24
|
+
# Splits a string by a command and returns an array.
|
25
|
+
def self.split_by_comma(device_string)
|
26
|
+
splits = device_string.split(",")
|
27
|
+
key = splits[0].chomp
|
28
|
+
val = splits[1].lstrip
|
29
|
+
[key, val]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Gets the Test Rail client
|
33
|
+
# http://docs.gurock.com/testrail-api2/bindings-ruby
|
34
|
+
def self.get_test_rail_client
|
35
|
+
url = "https://canvas.testrail.com"
|
36
|
+
trclient = TestRail::APIClient.new(url)
|
37
|
+
trclient.user = ENV["TESTRAIL_USER"]
|
38
|
+
trclient.password = ENV["TESTRAIL_PASSWORD"]
|
39
|
+
trclient
|
40
|
+
end
|
41
|
+
|
42
|
+
# Gets the definition of device types that are assigned to our test rail cases.
|
43
|
+
# It looks like this:
|
44
|
+
# {1=>"Desktop", 2=>"Tablet", 3=>"Phone"}
|
45
|
+
def self.get_test_rail_screen_size_codes
|
46
|
+
trclient = get_test_rail_client
|
47
|
+
case_fields = trclient.send_get("get_case_fields")
|
48
|
+
|
49
|
+
results = {}
|
50
|
+
case_fields.each do |case_field|
|
51
|
+
if (case_field["name"] == "screen_size")
|
52
|
+
config_array = case_field["configs"] # The array usually has a size of 1, but sometimes 0
|
53
|
+
if config_array.size > 0
|
54
|
+
config = config_array[0]
|
55
|
+
# Some configs have items, like screen size
|
56
|
+
items = config["options"]["items"]
|
57
|
+
if items
|
58
|
+
devices = items.split("\n")
|
59
|
+
devices.each do |device|
|
60
|
+
key, value = split_by_comma(device)
|
61
|
+
results[key.to_i] = value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
results
|
68
|
+
end
|
69
|
+
|
70
|
+
# Gets the definition of the priority codes that are assigned to our test rail cases.
|
71
|
+
# The priority codes on testrail do not match our nice 1,2,3 numbers. Their codes are
|
72
|
+
# different from ours. i.e.
|
73
|
+
#
|
74
|
+
# {1=>"3 - Low Priority", 3=>"2 - Test If Time", 4=>"1 - Must Test", 6=>"Smoke Test"}
|
75
|
+
#
|
76
|
+
# Therefore this function creates a more bridge friendly translation.
|
77
|
+
def self.get_test_rail_priority_codes
|
78
|
+
trclient = get_test_rail_client
|
79
|
+
# retreive priority information
|
80
|
+
# http://docs.gurock.com/testrail-api2/reference-priorities
|
81
|
+
priority_response = trclient.send_get("get_priorities")
|
82
|
+
|
83
|
+
results = {}
|
84
|
+
priority_response.each do |priority|
|
85
|
+
# find the numeric priority
|
86
|
+
name = priority["name"]
|
87
|
+
splits = name.split("-")
|
88
|
+
key = priority["id"].to_i
|
89
|
+
if splits.size == 2 && splits[0].to_i
|
90
|
+
val = { name: name, user_friendly_priority: splits[0].to_i }
|
91
|
+
results[key] = val
|
92
|
+
elsif name == "Smoke Test"
|
93
|
+
val = { name: "Smoke", user_friendly_priority: 0 }
|
94
|
+
results[key] = val
|
95
|
+
elsif name == "STUB"
|
96
|
+
val = { name: "Stub", user_friendly_priority: 7 }
|
97
|
+
results[key] = val
|
98
|
+
end
|
99
|
+
end
|
100
|
+
results
|
101
|
+
end
|
102
|
+
|
103
|
+
# Gets all the test cases for a particular test suite.
|
104
|
+
# The keys are the numeric(integer) test rail case ID's.
|
105
|
+
# The Values are the instance of TestCase.
|
106
|
+
# Each TestCase instance corresponds to a test case in test rail.
|
107
|
+
# return - A hash of TestCase instances
|
108
|
+
def self.get_test_rail_cases
|
109
|
+
trclient = get_test_rail_client
|
110
|
+
screen_sizes = get_test_rail_screen_size_codes
|
111
|
+
priorities = get_test_rail_priority_codes
|
112
|
+
test_cases = {}
|
113
|
+
|
114
|
+
# retrieve test cases
|
115
|
+
testcases_url = "get_cases/#{self.project_id}&suite_id=#{self.suite_id}"
|
116
|
+
response = trclient.send_get(testcases_url)
|
117
|
+
response.each do |test_case|
|
118
|
+
id = test_case["id"]
|
119
|
+
size = test_case["custom_screen_size"]
|
120
|
+
screen_size_description = screen_sizes[size]
|
121
|
+
priority = test_case["priority_id"]
|
122
|
+
priority_description = priorities[priority]
|
123
|
+
if priority_description
|
124
|
+
priority_code = priority_description[:user_friendly_priority]
|
125
|
+
automated = test_case["custom_automated"]
|
126
|
+
run_once = test_case["custom_run_once"]
|
127
|
+
automatable = test_case["custom_to_be_automated"]
|
128
|
+
references = test_case["refs"]
|
129
|
+
tc = TestCase.new(
|
130
|
+
id.to_s, test_case["title"], priority_code, automated,
|
131
|
+
screen_size_description, automatable, references, run_once
|
132
|
+
)
|
133
|
+
tc.file = test_case["custom_spec_location"]
|
134
|
+
test_cases[id] = tc
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
test_cases
|
139
|
+
end
|
140
|
+
|
141
|
+
# Updates a testcase that corresponds to the provided
|
142
|
+
# testrail_id. The testcase's reference field will be
|
143
|
+
# linked to the provided Jira ticket, given in the format
|
144
|
+
# of PROJECT-ID (ex. "BR-1000")
|
145
|
+
# param - testrail_id. The integer ID of the testcase
|
146
|
+
# param - reference. The string of the JIRA ticket(s)
|
147
|
+
def self.update_references(testrail_id, reference)
|
148
|
+
puts "id: #{testrail_id} => refs: #{reference}"
|
149
|
+
url = "update_case/#{testrail_id}"
|
150
|
+
data = { "refs" => reference }
|
151
|
+
TestRailOperations.get_test_rail_client.send_post_retry(url, data)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Gets JSON data about the test runs on testrail for the given project and suite
|
155
|
+
def self.get_test_rail_runs
|
156
|
+
trclient = get_test_rail_client
|
157
|
+
request = "get_runs/#{self.project_id}"
|
158
|
+
trclient.send_get(request)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns a list of test plans for a project.
|
162
|
+
# http://docs.gurock.com/testrail-api2/reference-plans#get_plans
|
163
|
+
def self.get_test_rail_plans
|
164
|
+
trclient = get_test_rail_client
|
165
|
+
request = "get_plans/#{self.project_id}"
|
166
|
+
trclient.send_get(request)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Gets JSON data about an existing test plan
|
170
|
+
# http://docs.gurock.com/testrail-api2/reference-plans#get_plan
|
171
|
+
def self.get_test_rail_plan(plan_id)
|
172
|
+
trclient = get_test_rail_client
|
173
|
+
request = "get_plan/#{plan_id}"
|
174
|
+
trclient.send_get(request)
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.create_test_plan(name, description = 'created by api', entries = [])
|
178
|
+
request = "add_plan/#{self.project_id}"
|
179
|
+
data = {
|
180
|
+
"name" => name,
|
181
|
+
"description" => description,
|
182
|
+
"entries"=> entries}
|
183
|
+
get_test_rail_client.send_post(request, data)
|
184
|
+
end
|
185
|
+
|
186
|
+
# new method to create plan entry with runs
|
187
|
+
def self.create_test_plan_entry_with_runs(plan_id, data)
|
188
|
+
request = "add_plan_entry/#{plan_id}"
|
189
|
+
get_test_rail_client.send_post(request, data)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Adds one test run to a test plan
|
193
|
+
# Returns hash containing:
|
194
|
+
# 1. The test run ID of the test run.
|
195
|
+
# 2. The entry ID of the test run which is a large Guid like this:
|
196
|
+
# "id"=>"638fd46c-7c3e-4818-9c90-f411a2dec52a"
|
197
|
+
def self.create_test_plan_entry(plan_id, name, include_all_cases: true, case_ids: [])
|
198
|
+
if !include_all_cases && case_ids.count == 0
|
199
|
+
return "Error! Must create a test plan with at least one test case"
|
200
|
+
end
|
201
|
+
|
202
|
+
request = "add_plan_entry/#{plan_id}"
|
203
|
+
data = {
|
204
|
+
"suite_id" => self.suite_id,
|
205
|
+
"name" => name,
|
206
|
+
"include_all" => include_all_cases,
|
207
|
+
"case_ids" => case_ids
|
208
|
+
}
|
209
|
+
|
210
|
+
trclient = get_test_rail_client
|
211
|
+
response = trclient.send_post_retry(request, data)
|
212
|
+
{ entry_id: response["id"], run_id: response["runs"][0]["id"] }
|
213
|
+
end
|
214
|
+
|
215
|
+
# Updates a test plan with the given entry_id and array of test case IDSs
|
216
|
+
def self.add_test_case_to_test_plan(plan_id, entry_id, case_ids)
|
217
|
+
request = "update_plan_entry/#{plan_id}/#{entry_id}"
|
218
|
+
data = {
|
219
|
+
"suite_id" => self.suite_id,
|
220
|
+
"case_ids" => case_ids
|
221
|
+
}
|
222
|
+
|
223
|
+
trclient = get_test_rail_client
|
224
|
+
trclient.send_post_retry(request, data)
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.keep_only(plan_id, entry_id, case_ids)
|
228
|
+
request = "update_plan_entry/#{plan_id}/#{entry_id}"
|
229
|
+
data = {
|
230
|
+
"suite_id" => self.suite_id,
|
231
|
+
"include_all" => false,
|
232
|
+
"case_ids" => case_ids
|
233
|
+
}
|
234
|
+
|
235
|
+
trclient = get_test_rail_client
|
236
|
+
trclient.send_post_retry(request, data)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Creates a test run on testrail
|
240
|
+
# param project_ID - The integer identifier of the project on test rail.
|
241
|
+
# param suite_id - The integer identifier of the test suite on test rail.
|
242
|
+
# param name - The string name to call the new test run
|
243
|
+
# param test_case_ids - The array of numerical integers of test cases to add to the test run
|
244
|
+
# returns - The number ID of the test run.
|
245
|
+
def self.create_test_run(project_id, suite_id, name, test_case_ids)
|
246
|
+
request = "add_run/#{project_id}"
|
247
|
+
data = {
|
248
|
+
"suite_id" => suite_id,
|
249
|
+
"name" => name,
|
250
|
+
"include_all" => false,
|
251
|
+
"case_ids" => test_case_ids
|
252
|
+
}
|
253
|
+
|
254
|
+
trclient = get_test_rail_client
|
255
|
+
response = trclient.send_post_retry(request, data)
|
256
|
+
response["id"]
|
257
|
+
end
|
258
|
+
|
259
|
+
def self.add_testcase_to_test_run(test_run_id, case_ids)
|
260
|
+
request = "update_run/#{test_run_id}"
|
261
|
+
data = { "case_ids" => case_ids }
|
262
|
+
trclient = get_test_rail_client
|
263
|
+
trclient.send_post_retry(request, data)
|
264
|
+
end
|
265
|
+
|
266
|
+
PASSED = 1
|
267
|
+
BLOCKED = 2
|
268
|
+
UNTESTED = 3
|
269
|
+
RETEST = 4
|
270
|
+
FAILED = 5
|
271
|
+
PENDING = 6
|
272
|
+
@rspec_to_testrail_status_map = {
|
273
|
+
passed: PASSED, blocked: BLOCKED, untested: UNTESTED, retest: RETEST, failed: FAILED, pending: PENDING
|
274
|
+
}
|
275
|
+
# Converts an rspec test result (a symbol) to an integer that TestRail understands.
|
276
|
+
def self.status_rspec_to_testrail(result_symbol)
|
277
|
+
@rspec_to_testrail_status_map[result_symbol]
|
278
|
+
end
|
279
|
+
|
280
|
+
# Converts the a TestRail integer result status into a symbol (that rspec uses) that is human readable
|
281
|
+
def self.status_testrail_to_rspec(int_id)
|
282
|
+
@rspec_to_testrail_status_map.key(int_id)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Sends test result data back to testrail to update cases in a test run
|
286
|
+
# param run_id - integer value designating the test run to update
|
287
|
+
# param data - A hash containing an array of hashes with test results
|
288
|
+
def self.post_run_results(run_id, data)
|
289
|
+
trclient = get_test_rail_client
|
290
|
+
uri = "add_results/#{run_id}"
|
291
|
+
trclient.send_post_retry(uri, "results" => data)
|
292
|
+
end
|
293
|
+
|
294
|
+
# When a test run is created, the test cases have new, sort of temporary ID's.
|
295
|
+
# These are completely different from the permanent static ID's of the test cases.
|
296
|
+
# Given a test run ID, this gets the test cases that are assigned to that test run.
|
297
|
+
# It returns a hash where the key is the integer permanent ID, and the value is a TestCase instance.
|
298
|
+
def self.get_test_run_cases(run_id)
|
299
|
+
result = {}
|
300
|
+
trclient = get_test_rail_client
|
301
|
+
response = trclient.send_get("get_tests/#{run_id}")
|
302
|
+
response.each do |r|
|
303
|
+
# Each response hash for a test case in a run looks like this:
|
304
|
+
#
|
305
|
+
# {"id"=>481664, "case_id"=>152222, "status_id"=>5, "assignedto_id"=>49, "run_id"=>2641,
|
306
|
+
# "title"=>"The my Learning page has all my courses listed. Overdue, Next Up, Optional,
|
307
|
+
# Completed", "type_id"=>2, "priority_id"=>3, "estimate"=>nil, "estimate_forecast"=>nil,
|
308
|
+
# "refs"=>nil, "milestone_id"=>nil, "custom_sprint_date"=>nil, "custom_commit_url"=>nil,
|
309
|
+
# "custom_reviewed"=>false, "custom_automated"=>true,
|
310
|
+
# "custom_spec_location"=>"regression_spec/learner_management/mylearning_spec.rb",
|
311
|
+
# "custom_test_order"=>nil, "custom_screen_size"=>3, "custom_preconds"=>nil,
|
312
|
+
# "custom_steps_separated"=>nil, "custom_browser_skip"=>[]}
|
313
|
+
#
|
314
|
+
|
315
|
+
permanent_id = r["case_id"].to_i
|
316
|
+
tc = TestCase.new(
|
317
|
+
permanent_id, r["title"], r["priority_id"].to_i,
|
318
|
+
r["custom_automated"], r["custom_screen_size"].to_i,
|
319
|
+
r["custom_to_be_automated"], r["refs"], r["custom_run_once"]
|
320
|
+
)
|
321
|
+
tc.temp_id = r["id"].to_i
|
322
|
+
tc.assigned_to = r["assignedto_id"].to_i
|
323
|
+
tc.set_status(status_testrail_to_rspec(r["status_id"].to_i), nil)
|
324
|
+
|
325
|
+
value = tc
|
326
|
+
key = permanent_id
|
327
|
+
result[key] = value
|
328
|
+
end
|
329
|
+
result
|
330
|
+
end
|
331
|
+
|
332
|
+
# Gets the string name of the test run, given the test run id as an integer
|
333
|
+
# if the test id is not found, it returns an empty string
|
334
|
+
def self.get_test_run_name(run_id)
|
335
|
+
runs = get_test_rail_runs(self.project_id)
|
336
|
+
runs.each do |run|
|
337
|
+
if run_id == run["id"].to_i
|
338
|
+
return run["name"]
|
339
|
+
end
|
340
|
+
end
|
341
|
+
""
|
342
|
+
end
|
343
|
+
|
344
|
+
def self.get_test_rail_users
|
345
|
+
# Returns an array of user hashes. Like this:
|
346
|
+
# [
|
347
|
+
# {
|
348
|
+
# "email": "alexis@example.com",
|
349
|
+
# "id": 1,
|
350
|
+
# "is_active": true,
|
351
|
+
# "name": "Alexis Gonzalez"
|
352
|
+
# },
|
353
|
+
# ....
|
354
|
+
# ]
|
355
|
+
get_test_rail_client.send_get("get_users")
|
356
|
+
end
|
357
|
+
|
358
|
+
# Given an email address, gets the user json data
|
359
|
+
def self.get_test_rail_user_by_email(email)
|
360
|
+
# The response looks like this:
|
361
|
+
# {
|
362
|
+
# "email": "alexis@example.com",
|
363
|
+
# "id": 1,
|
364
|
+
# "is_active": true,
|
365
|
+
# "name": "Alexis Gonzalez"
|
366
|
+
# }
|
367
|
+
get_test_rail_client.send_get("get_user_by_email&email=#{email}")
|
368
|
+
end
|
369
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require_relative "testrail_operations"
|
2
|
+
|
3
|
+
# =================================================================================
|
4
|
+
#
|
5
|
+
# Test case printing and inspections
|
6
|
+
#
|
7
|
+
# =================================================================================
|
8
|
+
|
9
|
+
module TestRailQueries
|
10
|
+
# Prints to the console, the test cases sorted by priority (ascending).
|
11
|
+
def self.print_priority_sorted_test_cases
|
12
|
+
test_cases = TestRailOperations.get_test_rail_cases
|
13
|
+
sorted_cases = test_cases.values.sort_by do |tc|
|
14
|
+
tc.priority
|
15
|
+
end
|
16
|
+
|
17
|
+
automated_count = 0
|
18
|
+
sorted_cases.each do |tc|
|
19
|
+
tc.print
|
20
|
+
automated_count += 1 if tc.automated
|
21
|
+
end
|
22
|
+
|
23
|
+
puts "Number Test Cases: #{test_cases.size}"
|
24
|
+
puts "Number Test Cases Automated: #{automated_count}"
|
25
|
+
percentage = (automated_count / test_cases.size.to_f) * 100.0
|
26
|
+
puts "Percentage Automated #{percentage.round(1)}%"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Prints to the console, the test cases that work on phone devices. (Sorted by test ID)
|
30
|
+
def self.print_phone_test_cases
|
31
|
+
test_cases = TestRailOperations.get_test_rail_cases
|
32
|
+
|
33
|
+
phones = []
|
34
|
+
test_cases.values.each do |tc|
|
35
|
+
phones << tc if tc.screen_size == "Phone"
|
36
|
+
end
|
37
|
+
|
38
|
+
sorted_ids = phones.each do |tc|
|
39
|
+
tc.id
|
40
|
+
end
|
41
|
+
|
42
|
+
sorted_ids.each do |tc|
|
43
|
+
tc.print
|
44
|
+
end
|
45
|
+
|
46
|
+
puts "Number Test Cases: #{test_cases.size}"
|
47
|
+
puts "Number Test Cases Phone: #{phones.size}"
|
48
|
+
percentage = (phones.size / test_cases.size.to_f) * 100.0
|
49
|
+
puts "Percentage Phone #{percentage.round(1)}%"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Prints to the console, all the test case ID's and the minimum screen size they work on
|
53
|
+
def self.print_test_case_devices
|
54
|
+
test_cases = TestRailOperations.get_test_rail_cases
|
55
|
+
|
56
|
+
test_cases.each do |key, value|
|
57
|
+
puts "ID: #{key}, #{value.screen_size}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Prints to the console, all the test runs
|
62
|
+
def self.print_all_test_runs
|
63
|
+
test_runs = TestRailOperations.get_test_rail_runs
|
64
|
+
count_runs = 0
|
65
|
+
count_open = 0
|
66
|
+
count_close = 0
|
67
|
+
test_runs.each do |test_run|
|
68
|
+
puts "Run: #{test_run["name"]}, id: #{test_run["id"]} complete: #{test_run["is_completed"]} "
|
69
|
+
if test_run["is_completed"]
|
70
|
+
count_close += 1
|
71
|
+
else
|
72
|
+
count_open += 1
|
73
|
+
end
|
74
|
+
|
75
|
+
count_runs += 1
|
76
|
+
end
|
77
|
+
puts "========================================"
|
78
|
+
puts "Total Test Runs: #{count_runs}"
|
79
|
+
puts "Total Open: #{count_open}"
|
80
|
+
puts "Total Closed: #{count_close}"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Prints to the console all the test plans in bridge
|
84
|
+
def self.print_all_test_plans
|
85
|
+
test_plans = TestRailOperations.get_test_rail_plans
|
86
|
+
count_plans = 0
|
87
|
+
test_plans.each do |plan|
|
88
|
+
id = plan["id"]
|
89
|
+
puts "Plan: ID: #{id}, #{plan["name"]}"
|
90
|
+
count_plans += 1
|
91
|
+
pjson = TestRailOperations.get_test_rail_plan(id)
|
92
|
+
puts " passed: #{pjson["passed_count"]}, failed: #{pjson["failed_count"]}, retest: #{pjson["retest_count"]}, blocked: #{pjson["blocked_count"]}"
|
93
|
+
puts " entries count: #{pjson["entries"].count}"
|
94
|
+
pjson["entries"].each do |entry|
|
95
|
+
puts " entry: #{entry["id"]} - #{entry["name"]}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
puts "========================================"
|
99
|
+
puts "Total Test Plans: #{count_plans}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.print_all_users
|
103
|
+
users = TestRailOperations.get_test_rail_users
|
104
|
+
users.each do |user|
|
105
|
+
puts user
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|