2022-05-05 22:00:06 +00:00
|
|
|
import base64
|
|
|
|
import itertools
|
2018-04-26 12:34:15 +00:00
|
|
|
import json
|
2019-07-03 15:28:16 +00:00
|
|
|
import logging
|
2022-05-05 22:00:06 +00:00
|
|
|
import re
|
|
|
|
from json import JSONDecodeError
|
2018-04-26 12:34:15 +00:00
|
|
|
from os import environ
|
2020-12-29 15:44:08 +00:00
|
|
|
from sys import argv
|
2022-05-05 22:00:06 +00:00
|
|
|
|
|
|
|
import emoji
|
|
|
|
import requests
|
|
|
|
|
|
|
|
from support.base_test_report import BaseTestReport
|
2018-04-26 12:34:15 +00:00
|
|
|
|
2021-11-18 15:16:48 +00:00
|
|
|
|
2018-04-26 12:34:15 +00:00
|
|
|
class TestrailReport(BaseTestReport):
|
|
|
|
|
2018-10-26 13:32:05 +00:00
|
|
|
def __init__(self):
|
|
|
|
super(TestrailReport, self).__init__()
|
2018-04-26 12:34:15 +00:00
|
|
|
|
|
|
|
self.password = environ.get('TESTRAIL_PASS')
|
|
|
|
self.user = environ.get('TESTRAIL_USER')
|
|
|
|
|
|
|
|
self.run_id = None
|
2022-12-06 11:00:03 +00:00
|
|
|
# self.suite_id = 48
|
2022-10-26 15:14:25 +00:00
|
|
|
self.suite_id = 5274
|
2018-09-28 15:30:06 +00:00
|
|
|
self.project_id = 14
|
2018-04-26 12:34:15 +00:00
|
|
|
|
|
|
|
self.outcomes = {
|
|
|
|
'passed': 1,
|
2023-01-13 11:09:33 +00:00
|
|
|
'undefined_fail': 10}
|
2018-04-26 12:34:15 +00:00
|
|
|
|
|
|
|
self.headers = dict()
|
|
|
|
self.headers['Authorization'] = 'Basic %s' % str(
|
|
|
|
base64.b64encode(bytes('%s:%s' % (self.user, self.password), 'utf-8')), 'ascii').strip()
|
|
|
|
self.headers['Content-Type'] = 'application/json'
|
2021-09-20 10:08:04 +00:00
|
|
|
self.headers['x-api-ident'] = 'beta'
|
2018-04-26 12:34:15 +00:00
|
|
|
|
2018-10-26 13:32:05 +00:00
|
|
|
self.url = 'https://ethstatus.testrail.net/index.php?/'
|
|
|
|
self.api_url = self.url + 'api/v2/'
|
2018-04-26 12:34:15 +00:00
|
|
|
|
|
|
|
def get(self, method):
|
2019-06-17 16:36:28 +00:00
|
|
|
rval = requests.get(self.api_url + method, headers=self.headers).json()
|
|
|
|
if 'error' in rval:
|
2019-07-03 15:28:16 +00:00
|
|
|
logging.error("Failed TestRail request: %s" % rval['error'])
|
2019-06-17 16:36:28 +00:00
|
|
|
return rval
|
2018-04-26 12:34:15 +00:00
|
|
|
|
|
|
|
def post(self, method, data):
|
|
|
|
data = bytes(json.dumps(data), 'utf-8')
|
2018-10-26 13:32:05 +00:00
|
|
|
return requests.post(self.api_url + method, data=data, headers=self.headers).json()
|
2018-04-26 12:34:15 +00:00
|
|
|
|
2021-11-11 11:40:31 +00:00
|
|
|
def add_attachment(self, method, path):
|
|
|
|
files = {'attachment': (open(path, 'rb'))}
|
2021-11-18 15:16:48 +00:00
|
|
|
result = requests.post(self.api_url + method, headers={'Authorization': self.headers['Authorization']},
|
|
|
|
files=files)
|
2021-11-11 11:40:31 +00:00
|
|
|
files['attachment'].close()
|
2021-11-26 14:15:26 +00:00
|
|
|
try:
|
|
|
|
return result.json()
|
|
|
|
except JSONDecodeError:
|
|
|
|
pass
|
2021-11-11 11:40:31 +00:00
|
|
|
|
2018-04-26 12:34:15 +00:00
|
|
|
def get_suites(self):
|
|
|
|
return self.get('get_suites/%s' % self.project_id)
|
|
|
|
|
|
|
|
def get_tests(self):
|
2021-09-20 10:08:04 +00:00
|
|
|
return self.get('get_tests/%s' % self.run_id)['tests']
|
2018-04-26 12:34:15 +00:00
|
|
|
|
|
|
|
def get_milestones(self):
|
2021-09-20 10:08:04 +00:00
|
|
|
return self.get('get_milestones/%s' % self.project_id)['milestones']
|
2018-04-26 12:34:15 +00:00
|
|
|
|
2018-08-07 16:38:12 +00:00
|
|
|
def get_runs(self, pr_number):
|
2021-09-20 10:08:04 +00:00
|
|
|
return [i for i in self.get('get_runs/%s' % self.project_id)['runs'] if 'PR-%s ' % pr_number in i['name']]
|
2018-08-07 16:38:12 +00:00
|
|
|
|
|
|
|
def get_run(self, run_id: int):
|
|
|
|
return self.get('get_run/%s' % run_id)
|
|
|
|
|
|
|
|
def get_last_pr_run(self, pr_number):
|
|
|
|
run_id = max([run['id'] for run in self.get_runs(pr_number)])
|
|
|
|
return self.get_run(run_id=run_id)
|
|
|
|
|
2018-04-26 12:34:15 +00:00
|
|
|
@property
|
|
|
|
def actual_milestone_id(self):
|
|
|
|
return self.get_milestones()[-1]['id']
|
|
|
|
|
|
|
|
def add_run(self, run_name):
|
|
|
|
request_body = {'suite_id': self.suite_id,
|
|
|
|
'name': run_name,
|
2018-06-30 12:17:38 +00:00
|
|
|
'milestone_id': self.actual_milestone_id,
|
2020-09-04 15:34:58 +00:00
|
|
|
'case_ids': self.get_regression_cases(),
|
2018-06-30 12:17:38 +00:00
|
|
|
'include_all': False}
|
2018-04-26 12:34:15 +00:00
|
|
|
run = self.post('add_run/%s' % self.project_id, request_body)
|
2024-04-19 15:33:19 +00:00
|
|
|
try:
|
|
|
|
self.run_id = run['id']
|
|
|
|
except KeyError:
|
|
|
|
print("TestRail error when creating a run: %s" % run)
|
2022-10-04 15:44:55 +00:00
|
|
|
print("Testrun: %sruns/view/%s" % (self.url, self.run_id))
|
2018-04-26 12:34:15 +00:00
|
|
|
|
2019-10-10 17:38:50 +00:00
|
|
|
def get_cases(self, section_ids):
|
|
|
|
test_cases = list()
|
|
|
|
for section_id in section_ids:
|
2021-11-18 15:16:48 +00:00
|
|
|
test_cases.append(
|
|
|
|
self.get('get_cases/%s&suite_id=%s§ion_id=%s' % (self.project_id, self.suite_id, section_id))[
|
|
|
|
'cases'])
|
2019-10-10 17:38:50 +00:00
|
|
|
return itertools.chain.from_iterable(test_cases)
|
2018-06-30 12:17:38 +00:00
|
|
|
|
2020-09-04 15:34:58 +00:00
|
|
|
def get_regression_cases(self):
|
2018-06-30 12:17:38 +00:00
|
|
|
test_cases = dict()
|
2022-03-29 13:45:31 +00:00
|
|
|
test_cases['pr'] = dict()
|
|
|
|
test_cases['nightly'] = dict()
|
|
|
|
test_cases['upgrade'] = dict()
|
2022-10-26 15:14:25 +00:00
|
|
|
|
2024-07-12 16:29:44 +00:00
|
|
|
# PR e2e
|
2022-10-26 15:14:25 +00:00
|
|
|
test_cases['pr']['critical'] = 50955
|
2024-07-12 16:29:44 +00:00
|
|
|
# test_cases['pr']['one_to_one_chat'] = 50956
|
|
|
|
# test_cases['pr']['community_single'] = 50983
|
2024-04-02 14:59:56 +00:00
|
|
|
test_cases['pr']['wallet'] = 59443
|
2022-10-26 15:14:25 +00:00
|
|
|
|
2024-07-12 16:29:44 +00:00
|
|
|
# Nightly e2e
|
|
|
|
test_cases['nightly']['critical'] = 50955
|
|
|
|
test_cases['nightly']['one_to_one_chat'] = 50956
|
|
|
|
test_cases['nightly']['deep_links'] = 51535
|
|
|
|
test_cases['nightly']['group_chat'] = 50964
|
|
|
|
test_cases['nightly']['community_single'] = 50983
|
|
|
|
test_cases['nightly']['community_multiple'] = 50982
|
|
|
|
test_cases['nightly']['activity_centre_contact_request'] = 50984
|
|
|
|
test_cases['nightly']['activity_centre_other'] = 51005
|
|
|
|
test_cases['nightly']['wallet'] = 59443
|
2022-11-21 04:49:53 +00:00
|
|
|
|
2022-03-29 13:45:31 +00:00
|
|
|
## Upgrade e2e
|
2022-10-26 15:14:25 +00:00
|
|
|
# test_cases['upgrade']['general'] = 881
|
2022-01-27 20:41:57 +00:00
|
|
|
|
2018-06-30 12:17:38 +00:00
|
|
|
case_ids = list()
|
2020-12-29 15:44:08 +00:00
|
|
|
for arg in argv:
|
|
|
|
if "run_testrail_ids" in arg:
|
|
|
|
key, value = arg.split('=')
|
|
|
|
case_ids = value.split(',')
|
|
|
|
if len(case_ids) == 0:
|
2024-07-12 16:29:44 +00:00
|
|
|
if 'smoke' in argv:
|
2022-03-29 13:45:31 +00:00
|
|
|
for category in test_cases['pr']:
|
|
|
|
for case in self.get_cases([test_cases['pr'][category]]):
|
|
|
|
case_ids.append(case['id'])
|
2024-07-25 16:05:30 +00:00
|
|
|
case_ids.extend([703133, 702742, 702745, 702843])
|
|
|
|
# elif 'nightly' in argv:
|
|
|
|
else:
|
2024-07-12 16:29:44 +00:00
|
|
|
for category in test_cases['nightly']:
|
|
|
|
for case in self.get_cases([test_cases['nightly'][category]]):
|
|
|
|
case_ids.append(case['id'])
|
2018-06-30 12:17:38 +00:00
|
|
|
return case_ids
|
|
|
|
|
2018-04-26 12:34:15 +00:00
|
|
|
def add_results(self):
|
2022-05-05 22:00:06 +00:00
|
|
|
data = list()
|
|
|
|
all_tests = self.get_all_tests()
|
|
|
|
for test in all_tests:
|
2018-04-26 12:34:15 +00:00
|
|
|
test_steps = "# Steps: \n"
|
|
|
|
devices = str()
|
2018-04-28 09:02:39 +00:00
|
|
|
last_testrun = test.testruns[-1]
|
|
|
|
for step in last_testrun.steps:
|
2018-04-26 12:34:15 +00:00
|
|
|
test_steps += step + "\n"
|
2018-04-28 09:02:39 +00:00
|
|
|
for i, device in enumerate(last_testrun.jobs):
|
2022-01-24 11:40:02 +00:00
|
|
|
if last_testrun.first_commands:
|
2024-06-25 16:06:19 +00:00
|
|
|
try:
|
|
|
|
first_command = last_testrun.first_commands[device]
|
|
|
|
except KeyError:
|
|
|
|
first_command = 0
|
2022-01-24 11:40:02 +00:00
|
|
|
else:
|
2024-04-17 00:46:59 +00:00
|
|
|
first_command = 0
|
|
|
|
try:
|
|
|
|
devices += "# [Device %d](%s) \n" % (i + 1, self.get_sauce_job_url(job_id=device,
|
|
|
|
first_command=first_command))
|
|
|
|
except KeyError:
|
|
|
|
devices += "# Device %s: SauceLabs session was not found \n" % (i + 1)
|
2022-04-14 12:59:14 +00:00
|
|
|
comment = str()
|
|
|
|
if test.group_name:
|
|
|
|
comment += "# Class: %s \n" % test.group_name
|
2023-01-13 11:09:33 +00:00
|
|
|
if last_testrun.error:
|
2022-06-14 14:02:48 +00:00
|
|
|
full_error = last_testrun.error
|
|
|
|
(code_error, no_code_error_str, issue_id) = self.separate_xfail_error(full_error)
|
|
|
|
if issue_id:
|
|
|
|
test_rail_xfail = self.make_error_with_gh_issue_link(no_code_error_str, issue_id)
|
|
|
|
error = "%s %s" % (code_error, test_rail_xfail)
|
|
|
|
else:
|
|
|
|
error = full_error
|
2022-06-21 22:44:42 +00:00
|
|
|
error = error.replace("[[", "**").replace("]]", "**")
|
2022-06-14 14:02:48 +00:00
|
|
|
comment += '%s' % ('# Error: \n %s \n' % emoji.demojize(error)) + devices + test_steps
|
2022-04-14 12:59:14 +00:00
|
|
|
else:
|
|
|
|
comment += devices + test_steps
|
2022-05-05 22:00:06 +00:00
|
|
|
data.append(
|
|
|
|
{'case_id': test.testrail_case_id,
|
2023-01-13 11:09:33 +00:00
|
|
|
'status_id': self.outcomes['undefined_fail'] if last_testrun.error else self.outcomes['passed'],
|
2022-05-05 22:00:06 +00:00
|
|
|
'comment': comment})
|
|
|
|
|
|
|
|
results = self.post('add_results_for_cases/%s' % self.run_id, data={"results": data})
|
|
|
|
try:
|
|
|
|
results[0]
|
2022-05-17 02:52:15 +00:00
|
|
|
except (IndexError, KeyError):
|
2022-05-05 22:00:06 +00:00
|
|
|
print("Got TestRail error when adding results: \n%s" % results)
|
|
|
|
|
|
|
|
for test in all_tests:
|
|
|
|
last_testrun = test.testruns[-1]
|
2021-11-11 11:40:31 +00:00
|
|
|
if last_testrun.error:
|
2022-01-25 10:27:37 +00:00
|
|
|
try:
|
2022-05-05 22:00:06 +00:00
|
|
|
device = list(last_testrun.jobs.keys())[0]
|
|
|
|
except IndexError:
|
|
|
|
continue
|
|
|
|
for res in results:
|
2022-05-06 14:48:30 +00:00
|
|
|
if last_testrun.first_commands:
|
2024-06-25 16:06:19 +00:00
|
|
|
try:
|
|
|
|
pattern = r"%s\?auth=.*#%s" % (device, str(last_testrun.first_commands[device]))
|
|
|
|
except KeyError:
|
|
|
|
pattern = device
|
2022-05-06 14:48:30 +00:00
|
|
|
else:
|
|
|
|
pattern = device
|
|
|
|
if re.findall(pattern, res['comment']):
|
2022-05-05 22:00:06 +00:00
|
|
|
res_id = res['id']
|
|
|
|
try:
|
2024-09-17 19:31:34 +00:00
|
|
|
for log in test.logs_paths.keys():
|
2022-05-05 22:00:06 +00:00
|
|
|
self.add_attachment(method='add_attachment_to_result/%s' % str(res_id),
|
2024-09-17 19:31:34 +00:00
|
|
|
path=test.logs_paths[log])
|
2023-01-18 13:06:05 +00:00
|
|
|
except (AttributeError, FileNotFoundError):
|
2022-05-05 22:00:06 +00:00
|
|
|
pass
|
|
|
|
break
|
|
|
|
|
2020-12-04 17:32:05 +00:00
|
|
|
self.change_test_run_description()
|
|
|
|
|
|
|
|
def change_test_run_description(self):
|
|
|
|
tests = self.get_all_tests()
|
2023-10-18 03:16:05 +00:00
|
|
|
passed, failed, xfailed = self.get_tests_by_status()
|
2022-04-23 22:41:11 +00:00
|
|
|
not_executed_tests = self.get_not_executed_tests(self.run_id)
|
2021-01-13 10:14:32 +00:00
|
|
|
final_description = "Nothing to report this time..."
|
2020-12-04 17:32:05 +00:00
|
|
|
if len(tests) > 0:
|
2023-10-18 03:16:05 +00:00
|
|
|
description_title = "# %.0f%% of end-end tests have passed\n" % (len(passed) / len(tests) * 100)
|
2020-12-04 17:32:05 +00:00
|
|
|
description_title += "\n"
|
|
|
|
description_title += "Total executed tests: %d\n" % len(tests)
|
2023-10-18 03:16:05 +00:00
|
|
|
description_title += "Failed tests: %d\n" % len(failed)
|
|
|
|
description_title += "Expected to fail tests: %d\n" % len(xfailed)
|
|
|
|
description_title += "Passed tests: %d\n" % len(passed)
|
2022-04-23 22:41:11 +00:00
|
|
|
if not_executed_tests:
|
|
|
|
description_title += "Not executed tests: %d\n" % len(not_executed_tests)
|
2020-12-04 17:32:05 +00:00
|
|
|
description_title += "\n"
|
2023-01-13 11:09:33 +00:00
|
|
|
ids_failed_test = []
|
|
|
|
single_devices_block, group_blocks, case_info = str(), dict(), str()
|
2023-10-18 03:16:05 +00:00
|
|
|
if failed:
|
|
|
|
for test in failed:
|
2023-01-13 11:09:33 +00:00
|
|
|
if test.group_name:
|
|
|
|
group_blocks[test.group_name] = "\n-------\n## Class: %s:\n" % test.group_name
|
2023-10-18 03:16:05 +00:00
|
|
|
for test in failed:
|
2023-01-13 11:09:33 +00:00
|
|
|
last_testrun = test.testruns[-1]
|
|
|
|
test_rail_link = self.get_test_result_link(self.run_id, test.testrail_case_id)
|
|
|
|
ids_failed_test.append(test.testrail_case_id)
|
|
|
|
case_title = '\n'
|
|
|
|
case_title += '-------\n'
|
|
|
|
case_title += "### ID %s: [%s](%s) \n" % (test.testrail_case_id, test.name, test_rail_link)
|
|
|
|
full_error = last_testrun.error[-255:]
|
|
|
|
(code_error, no_code_error_str, issue_id) = self.separate_xfail_error(full_error)
|
|
|
|
if issue_id:
|
|
|
|
test_rail_xfail = self.make_error_with_gh_issue_link(no_code_error_str, issue_id)
|
|
|
|
error = "```%s```\n **%s** \n" % (code_error, test_rail_xfail)
|
|
|
|
else:
|
|
|
|
error = "```%s```\n **%s** \n" % (code_error, no_code_error_str)
|
|
|
|
for job_id, f in last_testrun.jobs.items():
|
|
|
|
if last_testrun.first_commands:
|
2024-06-25 16:06:19 +00:00
|
|
|
try:
|
|
|
|
first_command = last_testrun.first_commands[job_id]
|
|
|
|
except KeyError:
|
|
|
|
first_command = 0
|
2023-01-13 11:09:33 +00:00
|
|
|
else:
|
2024-06-25 16:06:19 +00:00
|
|
|
first_command = 0
|
|
|
|
job_url = self.get_sauce_job_url(job_id=job_id, first_command=first_command)
|
2023-01-13 11:09:33 +00:00
|
|
|
case_info = "Logs for device %d: [steps](%s), [failure screenshot](%s)" \
|
|
|
|
% (f, job_url, self.get_sauce_final_screenshot_url(job_id))
|
|
|
|
|
|
|
|
if test.group_name:
|
|
|
|
group_blocks[test.group_name] += case_title + error + case_info
|
|
|
|
else:
|
|
|
|
single_devices_block += case_title + error + case_info
|
|
|
|
description_title += '## Failed tests: %s \n' % ','.join(map(str, ids_failed_test))
|
2022-04-23 22:41:11 +00:00
|
|
|
if not_executed_tests:
|
|
|
|
description_title += "## Not executed tests: %s\n" % ','.join([str(i) for i in not_executed_tests])
|
2023-01-13 11:09:33 +00:00
|
|
|
final_description = description_title + single_devices_block + ''.join([i for i in group_blocks.values()])
|
2020-12-04 17:32:05 +00:00
|
|
|
|
|
|
|
request_body = {'description': final_description}
|
|
|
|
return self.post('update_run/%s' % self.run_id, request_body)
|
|
|
|
|
2022-04-14 12:59:14 +00:00
|
|
|
def get_run_results(self, test_run_id=None):
|
|
|
|
return self.get('get_results_for_run/%s' % (test_run_id if test_run_id else self.run_id))['results']
|
2018-11-24 16:18:31 +00:00
|
|
|
|
|
|
|
def is_run_successful(self):
|
|
|
|
for test in self.get_run_results():
|
|
|
|
if test['status_id'] != 1:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
2018-10-26 13:32:05 +00:00
|
|
|
def get_test_result_link(self, test_run_id, test_case_id):
|
|
|
|
try:
|
2021-09-20 15:23:06 +00:00
|
|
|
test_id = self.get('get_results_for_case/%s/%s' % (test_run_id, test_case_id))['results'][0]['test_id']
|
2018-10-26 13:32:05 +00:00
|
|
|
return '%stests/view/%s' % (self.url, test_id)
|
2022-10-06 22:31:27 +00:00
|
|
|
except (KeyError, JSONDecodeError):
|
|
|
|
print('Cannot extract result for %s e2e' % test_case_id)
|
2022-04-14 12:59:14 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
def get_not_executed_tests(self, test_run_id):
|
2022-11-03 11:26:53 +00:00
|
|
|
try:
|
|
|
|
results = self.get("get_tests/%s&status_id=3" % test_run_id)
|
|
|
|
return [result['case_id'] for result in results["tests"]]
|
|
|
|
except KeyError:
|
|
|
|
print('Cannot extract result for %s' % test_run_id)
|
|
|
|
pass
|
2022-06-14 14:02:48 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def make_error_with_gh_issue_link(error, issue_id):
|
2022-06-21 22:44:42 +00:00
|
|
|
return error.replace(issue_id,
|
2022-07-17 12:37:46 +00:00
|
|
|
'[%s](https://github.com/status-im/status-mobile/issues/%s)' % (issue_id, issue_id[1:]))
|