tms-cli 0.0.pre.beta

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,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './testrail'
4
+ require 'json'
5
+ require 'fileutils'
6
+
7
+ module Tms
8
+ module Testcase
9
+ module_function
10
+
11
+ @@cases = {}
12
+ @@tc_ids = {}
13
+ @@test_results = []
14
+
15
+ def validate_testcase(client, section_id, test_title, test_status, refs_from_report, tr_version, error_comment)
16
+ # find case with the same title
17
+ # update existing case if found the same title
18
+ # and create new case if no case have the same title
19
+ tc = @@cases.find { |case_object| case_object['title'] == test_title }
20
+ if !tc.nil?
21
+ tc_refs = tc['refs'].nil? ? '' : tc['refs'].to_s
22
+ put_existing_tc_id(tc, test_status, section_id, error_comment)
23
+
24
+ # update tc refs if previous refs is different or empty
25
+ if validate_update_reference(refs_from_report, tc_refs) || tc_refs.eql?('')
26
+ update_test_refs(client, tc['id'], refs_from_report)
27
+ end
28
+ else
29
+ put_new_tc_id(client, section_id, test_title, test_status, error_comment, refs_from_report)
30
+ end
31
+ end
32
+
33
+ # this method will directly update test case when ref available in report file
34
+ def validate_testcase_bdd(client, section_id, test_id, refs_from_report, tr_version)
35
+ tc = get_tc_by_test_id(client, test_id)
36
+ tc_refs = tc['refs'].nil? ? '' : tc['refs'].to_s
37
+ if validate_update_reference(refs_from_report, tc_refs) || tc_refs.empty?
38
+ update_test_refs(client, tc['case_id'], refs_from_report)
39
+ end
40
+ end
41
+
42
+ def put_existing_tc_id(tc, test_status, section_id, error_comment)
43
+ tc_arr = []
44
+ tmp_hash = {}
45
+
46
+ tc_arr = @@tc_ids[section_id] unless @@tc_ids[section_id].nil?
47
+ tc_arr.push(tc['id'])
48
+ @@tc_ids[section_id] = tc_arr
49
+ tmp_hash['case_id'] = tc['id']
50
+ tmp_hash['status_id'] = test_status
51
+ tmp_hash['comment'] = error_comment
52
+
53
+ @@test_results.push(tmp_hash) unless @@test_results.include? tmp_hash
54
+ end
55
+
56
+ def update_test_refs(client, tc_id, refs)
57
+ payload = { refs: refs.to_s }
58
+ client.send_post("update_case/#{tc_id}", payload)
59
+ end
60
+
61
+ def create_testcase(client, section_id, test_title, refs)
62
+ begin
63
+ payload = { title: test_title.to_s, refs: refs.to_s, template_id: 1 }
64
+ client.send_post("add_case/#{section_id}", payload)
65
+ rescue TestRail::APIError => e
66
+ puts "Error occurred while creating test case with title: '#{test_title}'"
67
+ puts "Error message: #{e.message}"
68
+ end
69
+ end
70
+
71
+ def put_new_tc_id(client, section_id, test_title, test_status, error_comment, refs)
72
+ tc_arr = []
73
+ response = create_testcase(client, section_id, test_title, refs)
74
+ @@cases.push(response)
75
+ tc_id = response['id']
76
+ tc_arr = @@tc_ids[section_id] unless @@test_results.empty?
77
+
78
+ tc_arr.push(tc_id)
79
+ @@tc_ids[section_id] = tc_arr
80
+
81
+ tmp_hash = {}
82
+
83
+ tmp_hash['case_id'] = tc_id
84
+ tmp_hash['status_id'] = test_status
85
+ tmp_hash['comment'] = error_comment
86
+ @@test_results.push(tmp_hash) unless @@test_results.include? tmp_hash
87
+ end
88
+
89
+ def get_cases(client, project_id, suite_id, section_id, offset = 0)
90
+ # handle cases more than 250 entries
91
+ offset = 0
92
+ cases = []
93
+
94
+ loop do
95
+ get_cases = client.send_get("get_cases/#{project_id}&suite_id=#{suite_id}&section_id=#{section_id}&offset=#{offset}")
96
+ cases.push get_cases['cases']
97
+ offset += 250
98
+ break if get_cases['_links']['next'].nil?
99
+ end
100
+ cases = cases.flatten
101
+
102
+ return cases
103
+ end
104
+
105
+ def get_status_id(status)
106
+ case status
107
+ when 'passed' then 1
108
+ when 'todo' then 2
109
+ when 'skipped' then 4
110
+ when 'failed' then 5
111
+ when 'pending' then 6
112
+ else abort('Invalid status on report json')
113
+ end
114
+ end
115
+
116
+ def validate_update_reference(refs_from_report, tc_refs)
117
+ refs_eql = false
118
+ unless tc_refs.nil? || refs_from_report.nil?
119
+ tc_refs.split(',').each do |tc_ref|
120
+ refs_from_report.split(',').each do |ref|
121
+ if tc_ref.eql?(ref)
122
+ refs_eql = true
123
+ break
124
+ end
125
+ end
126
+ end
127
+ end
128
+ refs_eql
129
+ end
130
+
131
+ def test_ids
132
+ @@tc_ids
133
+ end
134
+
135
+ def test_results
136
+ @@test_results
137
+ end
138
+
139
+ def validate_refs(refs)
140
+ refs.include?('-') &&
141
+ refs.split('-').size.eql?(2) &&
142
+ (refs.split('-')[1] =~ /[^0-9]/).nil?
143
+ end
144
+
145
+ # This method will get case id by test id
146
+ # @param client testrail
147
+ # @param test_id is existing test run
148
+ def get_tc_by_test_id(client, test_id)
149
+ client.send_get("get_test/#{test_id}")
150
+ end
151
+
152
+ # This method will get case id by test id
153
+ # @param client testrail
154
+ # @param suite_id is the id of the suite
155
+ def delete_cases_from_suite(client, suite_id, case_ids)
156
+ payload = { case_ids: case_ids }
157
+ client.send_post("delete_cases/#{suite_id}&soft=1", payload)
158
+ end
159
+
160
+ def add_case(client, section_id, refs, title, label, bdd)
161
+ puts "Create test case: #{title}"
162
+ begin
163
+ payload = {
164
+ title: title,
165
+ refs: refs,
166
+ custom_label_id: label,
167
+ template_id: 4,
168
+ custom_testrail_bdd_scenario:[{content: bdd}]
169
+ }
170
+ client.send_post("add_case/#{section_id}", payload)
171
+ rescue TestRail::APIError => e
172
+ puts "Error occurred while creating test case with title: '#{test_title}'"
173
+ puts "Error message: #{e.message}"
174
+ end
175
+ end
176
+
177
+ # This method will set reference into '' on test case
178
+ # @param client testrail
179
+ # @param test case id
180
+ def update_case(client, tc_id, refs, title, label, bdd)
181
+ puts "Update test case: #{title}"
182
+ begin
183
+ payload = {
184
+ title: title,
185
+ refs: refs,
186
+ custom_label_id: label,
187
+ custom_testrail_bdd_scenario:[{content: bdd}]
188
+ }
189
+ client.send_post("update_case/#{tc_id}", payload)
190
+ rescue TestRail::APIError => e
191
+ puts "Error occurred while creating test case with title: '#{title}'"
192
+ puts "Error message: #{e.message}"
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './testrail'
4
+ require_relative './dataprep'
5
+ require_relative './projects'
6
+ require 'json'
7
+ require 'fileutils'
8
+
9
+ module Tms
10
+ module Testplan
11
+ module_function
12
+
13
+ include Dataprep
14
+ include Projects
15
+
16
+ # Returns an existing test plan
17
+ # @param client testrail
18
+ # @param plan id
19
+ def get_plan(client, plan_id)
20
+ client.send_get("get_plan/#{plan_id}")
21
+ end
22
+
23
+ # Returns all list existing test plans and handle plans more than 250
24
+ # @param client testrail
25
+ # @param project_id
26
+ def get_plans(client, project_id, filter = '')
27
+ offset = 0
28
+ plans = []
29
+
30
+ puts "Retrieving test plans from project id #{project_id} ..."
31
+
32
+ loop do
33
+ get_plans = client.send_get("get_plans/#{project_id}&#{filter}&offset=#{offset}&is_completed=0")
34
+ plans.push get_plans['plans']
35
+ offset += 250
36
+ break if get_plans['_links']['next'].nil?
37
+ end
38
+
39
+ plans = plans.flatten
40
+ return plans
41
+ end
42
+
43
+ # Creates a new test plan
44
+ # @param client testrail
45
+ # @param project id
46
+ # @param payload object
47
+ def create_testplan(client, project_id, payload)
48
+ client.send_post("add_plan/#{project_id}", payload)
49
+ end
50
+
51
+ # Adds one or more new test runs to a test plan
52
+ # @param client testrail
53
+ # @param plan id
54
+ # @param suite id
55
+ # @param array of test runs
56
+ def add_plan_entry(client, plan_id, suite_id, runs)
57
+ payload = {
58
+ suite_id: suite_id,
59
+ include_all: false,
60
+ entries: runs
61
+ }
62
+ puts payload
63
+ client.send_post("add_plan_entry/#{plan_id}", payload)
64
+ end
65
+
66
+ # Close the active test plan in the testrail project
67
+ # @param client testrail
68
+ # @param plan id
69
+ def close_test_plan(client, plan_id)
70
+ client.send_post("close_plan/#{plan_id}", {})
71
+ end
72
+
73
+ # Closes test plans created before a specified number of months ago.
74
+ #
75
+ # This method closes test plans that were created before the specified number
76
+ # of months ago for all projects. It iterates through the projects, retrieves
77
+ # the test plans that meet the criteria, and then closes each test run.
78
+ #
79
+ # @param client [DataprepClient] The Dataprep client instance.
80
+ # @param month [Integer] The number of months ago to consider for closing test plans.
81
+ def close_test_plan_months_before(client, month)
82
+ filter = "created_before=#{month}&created_by=1"
83
+ Projects.get_list_project_ids(client).each do |project_id|
84
+ get_plans(client, project_id, filter).each do |run_detail|
85
+ plan_id = run_detail['id']
86
+ created_on = run_detail['created_on']
87
+ close_test_plan(client, plan_id)
88
+ puts "Close Testrail Plan ID: #{plan_id} with started on date: #{created_on}"
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,136 @@
1
+ #
2
+ # TestRail API binding for Java (API v2, available since TestRail 3.0)
3
+ # Updated for TestRail 5.7
4
+ #
5
+ # Learn more:
6
+ #
7
+ # http://docs.gurock.com/testrail-api2/start
8
+ # http://docs.gurock.com/testrail-api2/accessing
9
+ #
10
+ # Copyright Gurock Software GmbH. See license.md for details.
11
+ #
12
+
13
+ require 'net/http'
14
+ require 'net/https'
15
+ require 'uri'
16
+ require 'json'
17
+
18
+ module TestRail
19
+ class APIClient
20
+ @url = ''
21
+ @user = ''
22
+ @password = ''
23
+ @openTimeout = 360
24
+ @readTimeout = 360
25
+
26
+ attr_accessor :user, :password
27
+
28
+ def initialize(base_url)
29
+ base_url += '/' unless base_url.match(%r{/$})
30
+ @url = base_url + 'index.php?/api/v2/'
31
+ end
32
+
33
+ #
34
+ # Send Get
35
+ #
36
+ # Issues a GET request (read) against the API and returns the result
37
+ # (as Ruby hash).
38
+ # If 'get_attachment/{id}' is successful, returns the data parameter
39
+ #
40
+ # Arguments:
41
+ #
42
+ # uri The API method to call including parameters
43
+ # (e.g. get_case/1)
44
+ # data When using get_attachment/{id}, this should be
45
+ # the file path (including filename) where the
46
+ # attachment should be saved
47
+ #
48
+ def send_get(uri, data = nil)
49
+ _send_request('GET', uri, data)
50
+ end
51
+
52
+ #
53
+ # Send POST
54
+ #
55
+ # Issues a POST request (write) against the API and returns the result
56
+ # (as Ruby hash).
57
+ #
58
+ # Arguments:
59
+ #
60
+ # uri The API method to call including parameters
61
+ # (e.g. add_case/1)
62
+ # data The data to submit as part of the request (as
63
+ # Ruby hash, strings must be UTF-8 encoded)
64
+ # If adding an attachment, should be the path
65
+ # to the file
66
+ #
67
+ def send_post(uri, data)
68
+ _send_request('POST', uri, data)
69
+ end
70
+
71
+ private
72
+
73
+ def _send_request(method, uri, data)
74
+ url = URI.parse(@url + uri)
75
+ if method == 'POST'
76
+ request = Net::HTTP::Post.new(url.path + '?' + url.query, :open_timeout => @openTimeout)
77
+ if uri.start_with?('add_attachment') || uri.start_with?('add_bdd')
78
+ # SOURCE: https://yukimotopress.github.io/http
79
+ boundary = 'TestRailAPIAttachmentBoundary'
80
+ post_body = []
81
+ post_body << "--#{boundary}\r\n"
82
+ post_body << "Content-Disposition: form-data; name=\"attachment\"; filename=\"#{File.basename(data)}\"\r\n"
83
+ post_body << "\r\n"
84
+ post_body << File.open(data, 'rb') { |io| io.read }
85
+ post_body << "\r\n--#{boundary}--\r\n"
86
+
87
+ request.body = post_body.join
88
+ request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
89
+ else
90
+ request['Content-Type'] = 'application/json'
91
+ request.body = JSON.dump(data)
92
+ end
93
+ else
94
+ request = Net::HTTP::Get.new(url.path + '?' + url.query, :open_timeout => @openTimeout, :read_timeout => @readTimeout)
95
+ request['Content-Type'] = 'application/json'
96
+ end
97
+ request.basic_auth(@user, @password)
98
+
99
+ conn = Net::HTTP.new(url.host, url.port)
100
+ conn.open_timeout = @openTimeout
101
+ conn.read_timeout = @readTimeout
102
+ if url.scheme == 'https'
103
+ conn.use_ssl = true
104
+ conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
105
+ end
106
+ response = conn.request(request)
107
+
108
+ if response.body && !response.body.empty? && (response.code == '200')
109
+ if uri.start_with?('get_attachment/')
110
+ File.open(data, 'w') { |file| file.write(response.body) }
111
+ result = data
112
+ elsif uri.start_with?('get_bdd/')
113
+ result = response.body
114
+ else
115
+ result = JSON.parse(response.body)
116
+ end
117
+ else
118
+ result = {}
119
+ end
120
+
121
+ if response.code != '200'
122
+ error = if result && result.key?('error')
123
+ '"' + result['error'] + '"'
124
+ else
125
+ 'No additional error message received'
126
+ end
127
+ raise APIError, format('TestRail API returned HTTP %s (%s)', response.code, response.body, error)
128
+ end
129
+
130
+ result
131
+ end
132
+ end
133
+
134
+ class APIError < StandardError
135
+ end
136
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './testrail'
4
+ require_relative './dataprep'
5
+ require_relative './projects'
6
+ require 'json'
7
+ require 'fileutils'
8
+
9
+ module Tms
10
+ module Testrun
11
+ module_function
12
+
13
+ include Dataprep
14
+ include Projects
15
+
16
+ # This method will get test run id on current milestone
17
+ # @param client testrail
18
+ # @param project id
19
+ # @param suite id
20
+ # @param milestone id
21
+ # @param section id
22
+ # @param section name
23
+ # @param tcs id is list of test case to put on test run
24
+ # @param env
25
+ def testrun_id(client, project_id, suite_id, milestone_id, section_id, section_name, tc_ids, env, platform_name)
26
+ list_tc = tc_ids[section_id]
27
+ run_id = ''
28
+ env = env.eql?('') ? '' : "_#{env}"
29
+ if platform_name != ''
30
+ test_run_name = "Test_Run_#{section_name}#{env}_#{platform_name}"
31
+ else
32
+ test_run_name = "Test_Run_#{section_name}#{env}"
33
+ end
34
+
35
+ is_run_name_avail = false
36
+
37
+ if !list_tc.nil?
38
+ response = client.send_get("get_runs/#{project_id}&milestone_id=#{milestone_id}")['runs']
39
+
40
+ if response.empty?
41
+ run_id = create_testrun(client, project_id, suite_id, milestone_id, list_tc, test_run_name)
42
+ else
43
+ response.each do |run|
44
+ next unless run['name'].eql? test_run_name
45
+
46
+ is_run_name_avail = true
47
+ run_id = update_testrun(client, run['id'], milestone_id, list_tc)
48
+ puts "TEST RUN ID : #{run_id}"
49
+ break
50
+ end
51
+
52
+ run_id = create_testrun(client, project_id, suite_id, milestone_id, list_tc, test_run_name) unless is_run_name_avail
53
+ end
54
+ else
55
+ abort('List test cases is empty')
56
+ end
57
+ run_id
58
+ end
59
+
60
+ # This method will get test run id on current milestone
61
+ # @param client testrail
62
+ # @param project id
63
+ # @param milestone id
64
+ def testrun_id_by_milestone(client, project_id, milestone_id)
65
+ client.send_get("get_runs/#{project_id}&milestone_id=#{milestone_id}")['runs']
66
+ end
67
+
68
+ # This method will get test run for a specific suite
69
+ # @param client testrail
70
+ # @param project id
71
+ # @param milestone id
72
+ # @param suite id
73
+ def testrun_id_by_suite(client, project_id, milestone_id, suite_id)
74
+ offset = 0
75
+ runs = []
76
+
77
+ loop do
78
+ get_runs = client.send_get("get_runs/#{project_id}&milestone_id=#{milestone_id}&suite_id=#{suite_id}&offset=#{offset}&is_completed=0")
79
+ runs.push get_runs['runs']
80
+ offset += 250
81
+ break if get_runs['_links']['next'].nil?
82
+ end
83
+ runs = runs.flatten
84
+ return runs
85
+ end
86
+
87
+ # This method will create test run
88
+ # @param client testrail
89
+ # @param project id
90
+ # @param suite id
91
+ # @param milestone id
92
+ # @param tcs id is list of test case to put on test run
93
+ # @param test_run_name
94
+ def create_testrun(client, project_id, suite_id, milestone_id = 0, list_tc, test_run_name)
95
+ payload = { suite_id: suite_id.to_i, name: test_run_name.to_s,
96
+ case_ids: list_tc, include_all: false, milestone_id: milestone_id }
97
+ payload.delete(:milestone_id) if milestone_id.eql?(0)
98
+ client.send_post("add_run/#{project_id}", payload)['id']
99
+ end
100
+
101
+ # This method will update list of test case on existing test run
102
+ # @param client testrail
103
+ # @param test_run_id is existing test run
104
+ # @param milestone id
105
+ # @param tcs id is list of test case to put on test run
106
+ def update_testrun(client, test_run_id, milestone_id, list_tc)
107
+ payload = { case_ids: list_tc, include_all: false, milestone_id: milestone_id}
108
+ client.send_post("update_run/#{test_run_id}", payload)['id']
109
+ end
110
+
111
+ # This method will get all test result on test run
112
+ # @param client testrail
113
+ # @param test_run_id is existing test run
114
+ # @param offset is the number that sets the position response start from
115
+ # handle tests more than 250 entries
116
+ def test_ids(client, test_run_id, offset = 0)
117
+ offset = 0
118
+ tests = []
119
+
120
+ loop do
121
+ get_tests = client.send_get("get_tests/#{test_run_id}&offset=#{offset}")
122
+ tests.push get_tests['tests']
123
+ offset += 250
124
+ break if get_tests['_links']['next'].nil?
125
+ end
126
+ tests = tests.flatten
127
+ return tests
128
+ end
129
+
130
+ # This method will get all test result on test run
131
+ # @param client testrail
132
+ # @param test_run_id is existing test run
133
+ def get_runs(client, project_id, filter = '')
134
+ offset = 0
135
+ runs = []
136
+
137
+ puts "Retrieving test runs from project id #{project_id} ..."
138
+
139
+ loop do
140
+ get_runs = client.send_get("get_runs/#{project_id}&#{filter}&offset=#{offset}&is_completed=0")
141
+ runs.push get_runs['runs']
142
+ offset += 250
143
+ break if get_runs['_links']['next'].nil?
144
+ end
145
+ runs = runs.flatten
146
+ return runs
147
+ end
148
+
149
+ # This method will delete test run
150
+ # @param client testrail
151
+ # @param test run id
152
+ def delete_testrun(client, run_id)
153
+ client.send_post("delete_run/#{run_id}", {})
154
+ end
155
+
156
+ # This method will close test run
157
+ # @param client testrail
158
+ # @param test run id
159
+ def close_test_run(client, run_id)
160
+ client.send_post("close_run/#{run_id}", {})
161
+ puts "Close the Test Run with ID: #{run_id}"
162
+ end
163
+
164
+ # Closes test runs created before a specified number of months ago.
165
+ #
166
+ # This method closes test runs that were created before the specified number
167
+ # of months ago for all projects. It iterates through the projects, retrieves
168
+ # the test runs that meet the criteria, and then closes each test run.
169
+ #
170
+ # @param client [DataprepClient] The Dataprep client instance.
171
+ # @param month [Integer] The number of months ago to consider for closing test runs.
172
+ def close_test_run_months_before(client, month)
173
+ filter = "created_before=#{month}&created_by=1"
174
+ Projects.get_list_project_ids(client).each do |project_id|
175
+ get_runs(client, project_id, filter).each do |run_detail|
176
+ run_id = run_detail['id']
177
+ created_on = run_detail['created_on']
178
+ close_test_run(client, run_id)
179
+ puts "Close Testrail Run ID: #{run_id} with started on date: #{created_on}"
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './testrail'
4
+ require 'json'
5
+ require 'fileutils'
6
+
7
+ module Tms
8
+ module Testsuite
9
+ module_function
10
+
11
+ # This method will get suite id
12
+ # will create new test suite with name 'master' if there is no test suite
13
+ # @param client testrail
14
+ # @param project id
15
+ def testsuite_id(client, project_id, suite_name)
16
+ response = client.send_get("get_suites/#{project_id}")
17
+ if response == []
18
+ payload = { name: 'master', description: 'created by automation' }
19
+ client.send_post("add_suite/#{project_id}", payload)
20
+ client.send_get("get_suites/#{project_id}")
21
+ else
22
+ found_suites = response.select { |suites| suites['name']&.include?(suite_name) }
23
+
24
+ if found_suites.empty?
25
+ "Creating suites of #{suite_name}..."
26
+ payload = { name: 'master', description: 'created by automation' }
27
+ client.send_post("add_suite/#{project_id}", payload)
28
+ else
29
+ puts "Fetching suites of #{suite_name}..."
30
+ return found_suites
31
+ end
32
+ end
33
+ end
34
+
35
+ # Returns a list of test suites for a project.
36
+ # @param client testrail
37
+ # @param project id
38
+ def suites(client, project_id)
39
+ client.send_get("get_suites/#{project_id}")
40
+ end
41
+
42
+ # Returns an existing test suite
43
+ # @param client testrail
44
+ # @param suite id
45
+ def suite_id(client, suite_id)
46
+ client.send_get("get_suite/#{suite_id}")
47
+ end
48
+ end
49
+ end