testrailtagging 0.3.6.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|