qase-python-commons 3.3.2__tar.gz → 3.4.1__tar.gz
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.
Potentially problematic release.
This version of qase-python-commons might be problematic. Click here for more details.
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/PKG-INFO +3 -3
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/pyproject.toml +3 -3
- qase_python_commons-3.4.1/src/qase/commons/client/api_v1_client.py +137 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/client/api_v2_client.py +10 -10
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/config.py +0 -5
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/logger.py +6 -1
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/__init__.py +2 -3
- qase_python_commons-3.4.1/src/qase/commons/models/basemodel.py +16 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/testops.py +0 -4
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/result.py +3 -95
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/run.py +14 -2
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/step.py +6 -2
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/reporters/core.py +3 -15
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/reporters/report.py +5 -6
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/reporters/testops.py +11 -15
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase_python_commons.egg-info/PKG-INFO +3 -3
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase_python_commons.egg-info/requires.txt +2 -2
- qase_python_commons-3.3.2/src/qase/commons/client/api_v1_client.py +0 -302
- qase_python_commons-3.3.2/src/qase/commons/models/basemodel.py +0 -7
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/README.md +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/setup.cfg +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/__init__.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/client/base_api_client.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/exceptions/reporter.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/loader.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/attachment.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/api.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/batch.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/connection.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/framework.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/plan.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/qaseconfig.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/report.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/run.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/relation.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/runtime.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/profilers/__init__.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/profilers/db.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/profilers/network.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/profilers/sleep.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/reporters/__init__.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/util/__init__.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/util/host_data.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/utils.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/validators/base.py +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase_python_commons.egg-info/SOURCES.txt +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase_python_commons.egg-info/dependency_links.txt +0 -0
- {qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase_python_commons.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qase-python-commons
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.1
|
|
4
4
|
Summary: A library for Qase TestOps and Qase Report
|
|
5
5
|
Author-email: Qase Team <support@qase.io>
|
|
6
6
|
Project-URL: Homepage, https://github.com/qase-tms/qase-python/tree/main/qase-python-commons
|
|
@@ -22,8 +22,8 @@ Requires-Python: >=3.7
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
Requires-Dist: certifi>=2024.2.2
|
|
24
24
|
Requires-Dist: attrs>=23.2.0
|
|
25
|
-
Requires-Dist: qase-api-client~=1.
|
|
26
|
-
Requires-Dist: qase-api-v2-client~=1.
|
|
25
|
+
Requires-Dist: qase-api-client~=1.2.0
|
|
26
|
+
Requires-Dist: qase-api-v2-client~=1.2.0
|
|
27
27
|
Requires-Dist: more_itertools
|
|
28
28
|
Provides-Extra: testing
|
|
29
29
|
Requires-Dist: pytest; extra == "testing"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "qase-python-commons"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.4.1"
|
|
8
8
|
description = "A library for Qase TestOps and Qase Report"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{name = "Qase Team", email = "support@qase.io"}]
|
|
@@ -30,8 +30,8 @@ requires-python = ">=3.7"
|
|
|
30
30
|
dependencies = [
|
|
31
31
|
"certifi>=2024.2.2",
|
|
32
32
|
"attrs>=23.2.0",
|
|
33
|
-
"qase-api-client~=1.
|
|
34
|
-
"qase-api-v2-client~=1.
|
|
33
|
+
"qase-api-client~=1.2.0",
|
|
34
|
+
"qase-api-v2-client~=1.2.0",
|
|
35
35
|
"more_itertools"
|
|
36
36
|
]
|
|
37
37
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
import certifi
|
|
5
|
+
from qase.api_client_v1 import ApiClient, ProjectsApi, Project, EnvironmentsApi, RunsApi, AttachmentsApi, \
|
|
6
|
+
AttachmentGet, RunCreate
|
|
7
|
+
from qase.api_client_v1.configuration import Configuration
|
|
8
|
+
from .. import Logger
|
|
9
|
+
from .base_api_client import BaseApiClient
|
|
10
|
+
from ..exceptions.reporter import ReporterException
|
|
11
|
+
from ..models import Attachment
|
|
12
|
+
from ..models.config.framework import Video, Trace
|
|
13
|
+
from ..models.config.qaseconfig import QaseConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ApiV1Client(BaseApiClient):
|
|
17
|
+
def __init__(self, config: QaseConfig, logger: Logger):
|
|
18
|
+
self.logger = logger
|
|
19
|
+
self.config = config
|
|
20
|
+
self.__authors = {}
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
self.logger.log_debug("Preparing API client")
|
|
24
|
+
configuration = Configuration()
|
|
25
|
+
configuration.api_key['TokenAuth'] = self.config.testops.api.token
|
|
26
|
+
configuration.ssl_ca_cert = certifi.where()
|
|
27
|
+
host = self.config.testops.api.host
|
|
28
|
+
if host == 'qase.io':
|
|
29
|
+
configuration.host = f'https://api.{host}/v1'
|
|
30
|
+
self.web = f'https://app.{host}'
|
|
31
|
+
else:
|
|
32
|
+
configuration.host = f'https://api-{host}/v1'
|
|
33
|
+
self.web = f'https://{host}'
|
|
34
|
+
|
|
35
|
+
self.client = ApiClient(configuration)
|
|
36
|
+
self.logger.log_debug("API client prepared")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
self.logger.log(f"Error at preparing API client: {e}", "error")
|
|
39
|
+
raise ReporterException(e)
|
|
40
|
+
|
|
41
|
+
def get_project(self, project_code: str) -> Union[Project, None]:
|
|
42
|
+
try:
|
|
43
|
+
self.logger.log_debug(f"Getting project {project_code}")
|
|
44
|
+
response = ProjectsApi(self.client).get_project(code=project_code)
|
|
45
|
+
if hasattr(response, 'result'):
|
|
46
|
+
self.logger.log_debug(f"Project {project_code} found: {response.result.to_json()}")
|
|
47
|
+
return response.result
|
|
48
|
+
raise ReporterException("Unable to find given project code")
|
|
49
|
+
except Exception as e:
|
|
50
|
+
self.logger.log("Exception when calling ProjectApi->get_project: %s\n" % e, "error")
|
|
51
|
+
raise ReporterException("Exception when calling ProjectApi")
|
|
52
|
+
|
|
53
|
+
def get_environment(self, environment: str, project_code: str) -> Union[str, None]:
|
|
54
|
+
try:
|
|
55
|
+
self.logger.log_debug(f"Getting environment {environment}")
|
|
56
|
+
api_instance = EnvironmentsApi(self.client)
|
|
57
|
+
response = api_instance.get_environments(code=project_code)
|
|
58
|
+
if hasattr(response, 'result') and hasattr(response.result, 'entities'):
|
|
59
|
+
for env in response.result.entities:
|
|
60
|
+
if env.slug == environment:
|
|
61
|
+
self.logger.log_debug(f"Environment {environment} found: {env.to_json()}")
|
|
62
|
+
return env.id
|
|
63
|
+
self.logger.log_debug(f"Environment {environment} not found")
|
|
64
|
+
return None
|
|
65
|
+
except Exception as e:
|
|
66
|
+
self.logger.log("Exception when calling EnvironmentsApi->get_environments: %s\n" % e, "error")
|
|
67
|
+
raise ReporterException(e)
|
|
68
|
+
|
|
69
|
+
def complete_run(self, project_code: str, run_id: str) -> None:
|
|
70
|
+
api_runs = RunsApi(self.client)
|
|
71
|
+
self.logger.log_debug(f"Completing run {run_id}")
|
|
72
|
+
res = api_runs.get_run(project_code, run_id).result
|
|
73
|
+
if res.status == 1:
|
|
74
|
+
self.logger.log_debug(f"Run {run_id} already completed")
|
|
75
|
+
return
|
|
76
|
+
try:
|
|
77
|
+
api_runs.complete_run(project_code, run_id)
|
|
78
|
+
self.logger.log(f"Test run link: {self.web}/run/{project_code}/dashboard/{run_id}", "info")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.logger.log(f"Error at completing run {run_id}: {e}", "error")
|
|
81
|
+
raise ReporterException(e)
|
|
82
|
+
|
|
83
|
+
def _upload_attachment(self, project_code: str, attachment: Attachment) -> Union[AttachmentGet, None]:
|
|
84
|
+
try:
|
|
85
|
+
self.logger.log_debug(f"Uploading attachment {attachment.id} for project {project_code}")
|
|
86
|
+
attach_api = AttachmentsApi(self.client)
|
|
87
|
+
response = attach_api.upload_attachment(project_code, file=[attachment.get_for_upload()])
|
|
88
|
+
|
|
89
|
+
return response.result
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
self.logger.log(f"Error at uploading attachment: {e}", "error")
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def create_test_run(self, project_code: str, title: str, description: str, plan_id=None,
|
|
96
|
+
environment_id=None) -> str:
|
|
97
|
+
kwargs = dict(
|
|
98
|
+
title=title,
|
|
99
|
+
description=description,
|
|
100
|
+
environment_id=(int(environment_id) if environment_id else None),
|
|
101
|
+
plan_id=(int(plan_id) if plan_id else plan_id),
|
|
102
|
+
is_autotest=True,
|
|
103
|
+
start_time=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
104
|
+
)
|
|
105
|
+
self.logger.log_debug(f"Creating test run with parameters: {kwargs}")
|
|
106
|
+
try:
|
|
107
|
+
result = RunsApi(self.client).create_run(
|
|
108
|
+
code=project_code,
|
|
109
|
+
run_create=RunCreate(**{k: v for k, v in kwargs.items() if v is not None})
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return result.result.id
|
|
113
|
+
|
|
114
|
+
except Exception as e:
|
|
115
|
+
self.logger.log(f"Error at creating test run: {e}", "error")
|
|
116
|
+
raise ReporterException(e)
|
|
117
|
+
|
|
118
|
+
def check_test_run(self, project_code: str, run_id: int) -> bool:
|
|
119
|
+
api_runs = RunsApi(self.client)
|
|
120
|
+
run = api_runs.get_run(code=project_code, id=run_id)
|
|
121
|
+
if run.result.id:
|
|
122
|
+
return True
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
def __should_skip_attachment(self, attachment, result):
|
|
126
|
+
if (self.config.framework.playwright.video == Video.failed and
|
|
127
|
+
result.execution.status != 'failed' and
|
|
128
|
+
attachment.file_name == 'video.webm'):
|
|
129
|
+
return True
|
|
130
|
+
if (self.config.framework.playwright.trace == Trace.failed and
|
|
131
|
+
result.execution.status != 'failed' and
|
|
132
|
+
attachment.file_name == 'trace.zip'):
|
|
133
|
+
return True
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def send_results(self, project_code: str, run_id: str, results: []) -> None:
|
|
137
|
+
raise NotImplementedError("use ApiV2Client instead")
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/client/api_v2_client.py
RENAMED
|
@@ -17,7 +17,7 @@ from .api_v1_client import ApiV1Client
|
|
|
17
17
|
from .. import Logger
|
|
18
18
|
from ..exceptions.reporter import ReporterException
|
|
19
19
|
from ..models.config.framework import Video, Trace
|
|
20
|
-
from ..models import Attachment,
|
|
20
|
+
from ..models import Attachment, Result
|
|
21
21
|
from ..models.config.qaseconfig import QaseConfig
|
|
22
22
|
from ..models.step import StepType, Step
|
|
23
23
|
|
|
@@ -53,7 +53,7 @@ class ApiV2Client(ApiV1Client):
|
|
|
53
53
|
create_results_request_v2=CreateResultsRequestV2(results=results_to_send))
|
|
54
54
|
self.logger.log_debug(f"Results for run {run_id} sent successfully")
|
|
55
55
|
|
|
56
|
-
def _prepare_result(self, project_code: str, result:
|
|
56
|
+
def _prepare_result(self, project_code: str, result: Result) -> ResultCreate:
|
|
57
57
|
attached = []
|
|
58
58
|
if result.attachments:
|
|
59
59
|
for attachment in result.attachments:
|
|
@@ -76,7 +76,7 @@ class ApiV2Client(ApiV1Client):
|
|
|
76
76
|
result_model_v2 = ResultCreate(
|
|
77
77
|
title=result.get_title(),
|
|
78
78
|
signature=result.signature,
|
|
79
|
-
|
|
79
|
+
testops_ids=result.get_testops_ids(),
|
|
80
80
|
execution=ResultExecution(status=result.execution.status, duration=result.execution.duration,
|
|
81
81
|
start_time=result.execution.start_time, end_time=result.execution.end_time,
|
|
82
82
|
stacktrace=result.execution.stacktrace, thread=result.execution.thread),
|
|
@@ -125,18 +125,18 @@ class ApiV2Client(ApiV1Client):
|
|
|
125
125
|
prepared_step['data']['action'] = step.data.request_method + " " + step.data.request_url
|
|
126
126
|
|
|
127
127
|
if step.data.request_body:
|
|
128
|
-
step.
|
|
128
|
+
step.add_attachment(
|
|
129
129
|
Attachment(file_name='request_body.txt', content=step.data.request_body, mime_type='text/plain',
|
|
130
130
|
temporary=True))
|
|
131
131
|
if step.data.request_headers:
|
|
132
|
-
step.
|
|
132
|
+
step.add_attachment(
|
|
133
133
|
Attachment(file_name='request_headers.txt', content=step.data.request_headers,
|
|
134
134
|
mime_type='text/plain', temporary=True))
|
|
135
135
|
if step.data.response_body:
|
|
136
|
-
step.
|
|
137
|
-
|
|
136
|
+
step.add_attachment(Attachment(file_name='response_body.txt', content=step.data.response_body,
|
|
137
|
+
mime_type='text/plain', temporary=True))
|
|
138
138
|
if step.data.response_headers:
|
|
139
|
-
step.
|
|
139
|
+
step.add_attachment(
|
|
140
140
|
Attachment(file_name='response_headers.txt', content=step.data.response_headers,
|
|
141
141
|
mime_type='text/plain', temporary=True))
|
|
142
142
|
|
|
@@ -149,9 +149,9 @@ class ApiV2Client(ApiV1Client):
|
|
|
149
149
|
if step.step_type == StepType.SLEEP:
|
|
150
150
|
prepared_step['data']['action'] = f"Sleep for {step.data.duration} seconds"
|
|
151
151
|
|
|
152
|
-
if step.attachments:
|
|
152
|
+
if step.execution.attachments:
|
|
153
153
|
uploaded_attachments = []
|
|
154
|
-
for file in step.attachments:
|
|
154
|
+
for file in step.execution.attachments:
|
|
155
155
|
attach_id = self._upload_attachment(project_code, file)
|
|
156
156
|
if attach_id:
|
|
157
157
|
uploaded_attachments.extend(attach_id)
|
|
@@ -24,7 +24,12 @@ class Logger:
|
|
|
24
24
|
def log(self, message: str, level: str = 'info'):
|
|
25
25
|
time_str = self._get_timestamp("%H:%M:%S")
|
|
26
26
|
log = f"[Qase][{time_str}][{level}] {message}\n"
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
print(log, end='')
|
|
30
|
+
except (OSError, IOError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
28
33
|
if self.debug:
|
|
29
34
|
with self.lock:
|
|
30
35
|
with open(Logger._log_file, 'a', encoding='utf-8') as f:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .result import Result,
|
|
1
|
+
from .result import Result, Field
|
|
2
2
|
from .run import Run
|
|
3
3
|
from .attachment import Attachment
|
|
4
4
|
from .relation import Relation
|
|
@@ -12,6 +12,5 @@ __all__ = [
|
|
|
12
12
|
Relation,
|
|
13
13
|
Step,
|
|
14
14
|
Runtime,
|
|
15
|
-
Field
|
|
16
|
-
InternalResult
|
|
15
|
+
Field
|
|
17
16
|
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseModel:
|
|
7
|
+
def __str__(self, enum_as_name=False) -> str:
|
|
8
|
+
def serialize(o):
|
|
9
|
+
if isinstance(o, Enum):
|
|
10
|
+
return o.name if enum_as_name else o.value
|
|
11
|
+
elif hasattr(o, '__dict__'):
|
|
12
|
+
return o.__dict__
|
|
13
|
+
else:
|
|
14
|
+
return str(o)
|
|
15
|
+
|
|
16
|
+
return json.dumps(self, default=serialize, indent=4, sort_keys=True)
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/testops.py
RENAMED
|
@@ -20,13 +20,9 @@ class TestopsConfig(BaseModel):
|
|
|
20
20
|
self.batch = BatchConfig()
|
|
21
21
|
self.plan = PlanConfig()
|
|
22
22
|
self.defect = False
|
|
23
|
-
self.use_v2 = True
|
|
24
23
|
|
|
25
24
|
def set_project(self, project: str):
|
|
26
25
|
self.project = project
|
|
27
26
|
|
|
28
27
|
def set_defect(self, defect):
|
|
29
28
|
self.defect = QaseUtils.parse_bool(defect)
|
|
30
|
-
|
|
31
|
-
def set_use_v2(self, use_v2):
|
|
32
|
-
self.use_v2 = QaseUtils.parse_bool(use_v2)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import copy
|
|
2
1
|
import time
|
|
3
2
|
import uuid
|
|
4
3
|
|
|
@@ -70,19 +69,16 @@ class Result(BaseModel):
|
|
|
70
69
|
self.id: str = str(uuid.uuid4())
|
|
71
70
|
self.title: str = title
|
|
72
71
|
self.signature: str = signature
|
|
73
|
-
self.
|
|
74
|
-
self.testops_id: Optional[List[int]] = None
|
|
72
|
+
self.testops_ids: Optional[List[int]] = None
|
|
75
73
|
self.execution: Type[Execution] = Execution()
|
|
76
74
|
self.fields: Dict[Type[Field]] = {}
|
|
77
75
|
self.attachments: List[Attachment] = []
|
|
78
76
|
self.steps: List[Type[Step]] = []
|
|
79
77
|
self.params: Optional[dict] = {}
|
|
80
78
|
self.param_groups: Optional[List[List[str]]] = []
|
|
81
|
-
self.author: Optional[str] = None
|
|
82
79
|
self.relations: Type[Relation] = None
|
|
83
80
|
self.muted: bool = False
|
|
84
81
|
self.message: Optional[str] = None
|
|
85
|
-
QaseUtils.get_host_data()
|
|
86
82
|
|
|
87
83
|
def add_message(self, message: str) -> None:
|
|
88
84
|
self.message = message
|
|
@@ -119,96 +115,8 @@ class Result(BaseModel):
|
|
|
119
115
|
return self.fields[name]
|
|
120
116
|
return None
|
|
121
117
|
|
|
122
|
-
def
|
|
123
|
-
return self.
|
|
118
|
+
def get_testops_ids(self) -> Optional[List[int]]:
|
|
119
|
+
return self.testops_ids
|
|
124
120
|
|
|
125
121
|
def get_duration(self) -> int:
|
|
126
122
|
return self.execution.duration
|
|
127
|
-
|
|
128
|
-
def set_run_id(self, run_id: str) -> None:
|
|
129
|
-
self.run_id = run_id
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class InternalResult(BaseModel):
|
|
133
|
-
def __init__(self, title: str, signature: str) -> None:
|
|
134
|
-
self.id: str = str(uuid.uuid4())
|
|
135
|
-
self.title: str = title
|
|
136
|
-
self.signature: str = signature
|
|
137
|
-
self.run_id: Optional[str] = None
|
|
138
|
-
self.testops_id: Optional[int] = None
|
|
139
|
-
self.execution: Type[Execution] = Execution()
|
|
140
|
-
self.fields: Dict[Type[Field]] = {}
|
|
141
|
-
self.attachments: List[Attachment] = []
|
|
142
|
-
self.steps: List[Type[Step]] = []
|
|
143
|
-
self.params: Optional[dict] = {}
|
|
144
|
-
self.param_groups: Optional[List[List[str]]] = []
|
|
145
|
-
self.author: Optional[str] = None
|
|
146
|
-
self.relations: Type[Relation] = None
|
|
147
|
-
self.muted: bool = False
|
|
148
|
-
self.message: Optional[str] = None
|
|
149
|
-
QaseUtils.get_host_data()
|
|
150
|
-
|
|
151
|
-
def add_message(self, message: str) -> None:
|
|
152
|
-
self.message = message
|
|
153
|
-
|
|
154
|
-
def add_field(self, field: Type[Field]) -> None:
|
|
155
|
-
self.fields[field.name] = field.value
|
|
156
|
-
|
|
157
|
-
def add_steps(self, steps: List[Type[Step]]) -> None:
|
|
158
|
-
self.steps = QaseUtils().build_tree(steps)
|
|
159
|
-
|
|
160
|
-
def add_attachment(self, attachment: Attachment) -> None:
|
|
161
|
-
self.attachments.append(attachment)
|
|
162
|
-
|
|
163
|
-
def add_param(self, key: str, value: str) -> None:
|
|
164
|
-
self.params[key] = value
|
|
165
|
-
|
|
166
|
-
def add_param_groups(self, values: List[str]) -> None:
|
|
167
|
-
self.param_groups.append(values)
|
|
168
|
-
|
|
169
|
-
def set_relation(self, relation: Relation) -> None:
|
|
170
|
-
self.relations = relation
|
|
171
|
-
|
|
172
|
-
def get_status(self) -> Optional[str]:
|
|
173
|
-
return self.execution.status
|
|
174
|
-
|
|
175
|
-
def get_id(self) -> str:
|
|
176
|
-
return self.id
|
|
177
|
-
|
|
178
|
-
def get_title(self) -> str:
|
|
179
|
-
return self.title
|
|
180
|
-
|
|
181
|
-
def get_field(self, name: str) -> Optional[Type[Field]]:
|
|
182
|
-
if name in self.fields:
|
|
183
|
-
return self.fields[name]
|
|
184
|
-
return None
|
|
185
|
-
|
|
186
|
-
def get_testops_id(self) -> Optional[int]:
|
|
187
|
-
return self.testops_id
|
|
188
|
-
|
|
189
|
-
def get_duration(self) -> int:
|
|
190
|
-
return self.execution.duration
|
|
191
|
-
|
|
192
|
-
def set_run_id(self, run_id: str) -> None:
|
|
193
|
-
self.run_id = run_id
|
|
194
|
-
|
|
195
|
-
@classmethod
|
|
196
|
-
def convert_from_result(cls, result: Result, testops_id: Optional[int] = None):
|
|
197
|
-
int_result = cls(result.title, result.signature)
|
|
198
|
-
|
|
199
|
-
int_result.id = result.id
|
|
200
|
-
int_result.title = result.title
|
|
201
|
-
int_result.signature = result.signature
|
|
202
|
-
int_result.run_id = result.run_id
|
|
203
|
-
int_result.testops_id = testops_id
|
|
204
|
-
int_result.execution = copy.deepcopy(result.execution)
|
|
205
|
-
int_result.fields = result.fields
|
|
206
|
-
int_result.attachments = result.attachments
|
|
207
|
-
int_result.steps = result.steps
|
|
208
|
-
int_result.params = result.params
|
|
209
|
-
int_result.author = result.author
|
|
210
|
-
int_result.relations = result.relations
|
|
211
|
-
int_result.muted = result.muted
|
|
212
|
-
int_result.message = result.message
|
|
213
|
-
|
|
214
|
-
return int_result
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
1
|
from typing import Optional, List
|
|
4
2
|
|
|
5
3
|
from .basemodel import BaseModel
|
|
@@ -71,6 +69,7 @@ class Run(BaseModel):
|
|
|
71
69
|
"duration": result["execution"]["duration"],
|
|
72
70
|
"thread": result["execution"]["thread"]
|
|
73
71
|
}
|
|
72
|
+
self._extract_path_from_relations(result)
|
|
74
73
|
self.results.append(compact_result)
|
|
75
74
|
self.execution.track(result)
|
|
76
75
|
self.stats.track(result)
|
|
@@ -79,3 +78,16 @@ class Run(BaseModel):
|
|
|
79
78
|
|
|
80
79
|
def add_host_data(self, host_data: dict):
|
|
81
80
|
self.host_data = host_data
|
|
81
|
+
|
|
82
|
+
def _extract_path_from_relations(self, relations_dict):
|
|
83
|
+
|
|
84
|
+
titles = []
|
|
85
|
+
if "relations" in relations_dict and "suite" in relations_dict["relations"]:
|
|
86
|
+
if "data" in relations_dict["relations"]["suite"]:
|
|
87
|
+
data_list = relations_dict["relations"]["suite"]["data"]
|
|
88
|
+
titles = [item["title"] for item in data_list if "title" in item]
|
|
89
|
+
|
|
90
|
+
path = "/".join(titles)
|
|
91
|
+
|
|
92
|
+
if path and path not in self.suites:
|
|
93
|
+
self.suites.append(path)
|
|
@@ -20,6 +20,7 @@ class StepTextData(BaseModel):
|
|
|
20
20
|
def __init__(self, action: str, expected_result: Optional[str] = None):
|
|
21
21
|
self.action = action
|
|
22
22
|
self.expected_result = expected_result
|
|
23
|
+
self.input_data = None
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class StepAssertData(BaseModel):
|
|
@@ -78,6 +79,7 @@ class StepExecution(BaseModel):
|
|
|
78
79
|
self.status = status
|
|
79
80
|
self.end_time = end_time
|
|
80
81
|
self.duration = duration
|
|
82
|
+
self.attachments = []
|
|
81
83
|
|
|
82
84
|
def set_status(self, status: Optional[str]):
|
|
83
85
|
if status in ['passed', 'failed', 'skipped', 'blocked', 'untested']:
|
|
@@ -89,6 +91,9 @@ class StepExecution(BaseModel):
|
|
|
89
91
|
self.end_time = time.time()
|
|
90
92
|
self.duration = int((self.end_time - self.start_time) * 1000)
|
|
91
93
|
|
|
94
|
+
def add_attachment(self, attachment: Attachment):
|
|
95
|
+
self.attachments.append(attachment)
|
|
96
|
+
|
|
92
97
|
|
|
93
98
|
class Step(BaseModel):
|
|
94
99
|
def __init__(self,
|
|
@@ -107,7 +112,6 @@ class Step(BaseModel):
|
|
|
107
112
|
self.data = data
|
|
108
113
|
self.parent_id = parent_id
|
|
109
114
|
self.execution = StepExecution()
|
|
110
|
-
self.attachments = []
|
|
111
115
|
self.steps = []
|
|
112
116
|
|
|
113
117
|
def set_parent_id(self, parent_id: Optional[str]):
|
|
@@ -132,4 +136,4 @@ class Step(BaseModel):
|
|
|
132
136
|
self.steps = steps
|
|
133
137
|
|
|
134
138
|
def add_attachment(self, attachment: Attachment):
|
|
135
|
-
self.
|
|
139
|
+
self.execution.add_attachment(attachment)
|
|
@@ -6,7 +6,7 @@ from ..logger import Logger
|
|
|
6
6
|
from .report import QaseReport
|
|
7
7
|
from .testops import QaseTestOps
|
|
8
8
|
|
|
9
|
-
from ..models import
|
|
9
|
+
from ..models import Result, Attachment, Runtime
|
|
10
10
|
from ..models.config.qaseconfig import Mode
|
|
11
11
|
from typing import Union, List
|
|
12
12
|
|
|
@@ -84,20 +84,8 @@ class QaseCoreReporter:
|
|
|
84
84
|
try:
|
|
85
85
|
ts = time.time()
|
|
86
86
|
self.logger.log_debug(f"Adding result {result}")
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if ids is None:
|
|
90
|
-
int_result = InternalResult.convert_from_result(result)
|
|
91
|
-
self.reporter.add_result(int_result)
|
|
92
|
-
else:
|
|
93
|
-
first = True
|
|
94
|
-
for testops_id in ids:
|
|
95
|
-
int_result = InternalResult.convert_from_result(result, testops_id)
|
|
96
|
-
if not first:
|
|
97
|
-
int_result.execution.duration = 0
|
|
98
|
-
else:
|
|
99
|
-
first = False
|
|
100
|
-
self.reporter.add_result(int_result)
|
|
87
|
+
|
|
88
|
+
self.reporter.add_result(result)
|
|
101
89
|
|
|
102
90
|
self.logger.log_debug(f"Result {result.get_title()} added")
|
|
103
91
|
self.overhead += time.time() - ts
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/reporters/report.py
RENAMED
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
import shutil
|
|
4
4
|
import json
|
|
5
5
|
import re
|
|
6
|
-
from ..models import
|
|
6
|
+
from ..models import Result, Run, Attachment
|
|
7
7
|
from .. import QaseUtils, Logger
|
|
8
8
|
from ..models.config.connection import Format
|
|
9
9
|
from ..models.config.qaseconfig import QaseConfig
|
|
@@ -40,8 +40,7 @@ class QaseReport:
|
|
|
40
40
|
def complete_worker(self):
|
|
41
41
|
pass
|
|
42
42
|
|
|
43
|
-
def add_result(self, result:
|
|
44
|
-
result.set_run_id(self.run_id)
|
|
43
|
+
def add_result(self, result: Result):
|
|
45
44
|
for attachment in result.attachments:
|
|
46
45
|
self._persist_attachment(attachment)
|
|
47
46
|
|
|
@@ -84,14 +83,14 @@ class QaseReport:
|
|
|
84
83
|
|
|
85
84
|
def _persist_attachments_in_steps(self, steps: list):
|
|
86
85
|
for step in steps:
|
|
87
|
-
if step.attachments:
|
|
88
|
-
for attachment in step.attachments:
|
|
86
|
+
if step.execution.attachments:
|
|
87
|
+
for attachment in step.execution.attachments:
|
|
89
88
|
self._persist_attachment(attachment)
|
|
90
89
|
if step.steps:
|
|
91
90
|
self._persist_attachments_in_steps(step.steps)
|
|
92
91
|
|
|
93
92
|
# Method saves result to a file
|
|
94
|
-
def _store_result(self, result:
|
|
93
|
+
def _store_result(self, result: Result):
|
|
95
94
|
self._store_object(result, self.report_path + "/results/", result.id)
|
|
96
95
|
|
|
97
96
|
def _check_report_path(self):
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/reporters/testops.py
RENAMED
|
@@ -2,11 +2,11 @@ import threading
|
|
|
2
2
|
import urllib.parse
|
|
3
3
|
|
|
4
4
|
from datetime import datetime
|
|
5
|
-
from typing import List
|
|
5
|
+
from typing import List, Union
|
|
6
6
|
from .. import Logger, ReporterException
|
|
7
|
-
from ..client.
|
|
7
|
+
from ..client.api_v2_client import ApiV2Client
|
|
8
8
|
from ..client.base_api_client import BaseApiClient
|
|
9
|
-
from ..models import
|
|
9
|
+
from ..models import Result
|
|
10
10
|
from ..models.config.qaseconfig import QaseConfig
|
|
11
11
|
|
|
12
12
|
DEFAULT_BATCH_SIZE = 200
|
|
@@ -69,10 +69,7 @@ class QaseTestOps:
|
|
|
69
69
|
self.client.get_project(self.project_code)
|
|
70
70
|
|
|
71
71
|
def _prepare_client(self) -> BaseApiClient:
|
|
72
|
-
|
|
73
|
-
from ..client.api_v2_client import ApiV2Client
|
|
74
|
-
return ApiV2Client(self.config, self.logger)
|
|
75
|
-
return ApiV1Client(self.config, self.logger)
|
|
72
|
+
return ApiV2Client(self.config, self.logger)
|
|
76
73
|
|
|
77
74
|
def _send_results_threaded(self, results):
|
|
78
75
|
try:
|
|
@@ -129,9 +126,9 @@ class QaseTestOps:
|
|
|
129
126
|
if len(self.results) > 0:
|
|
130
127
|
self._send_results()
|
|
131
128
|
|
|
132
|
-
def add_result(self, result:
|
|
129
|
+
def add_result(self, result: Result) -> None:
|
|
133
130
|
if result.get_status() == 'failed':
|
|
134
|
-
self.__show_link(result.
|
|
131
|
+
self.__show_link(result.testops_ids, result.title)
|
|
135
132
|
self.results.append(result)
|
|
136
133
|
if len(self.results) >= self.batch_size:
|
|
137
134
|
self._send_results()
|
|
@@ -142,15 +139,14 @@ class QaseTestOps:
|
|
|
142
139
|
def set_results(self, results) -> None:
|
|
143
140
|
self.results = results
|
|
144
141
|
|
|
145
|
-
def __show_link(self,
|
|
146
|
-
link = self.__prepare_link(
|
|
142
|
+
def __show_link(self, ids: Union[None, List[int]], title: str):
|
|
143
|
+
link = self.__prepare_link(ids, title)
|
|
147
144
|
self.logger.log(f"See why this test failed: {link}", "info")
|
|
148
145
|
|
|
149
|
-
def __prepare_link(self,
|
|
146
|
+
def __prepare_link(self, ids: Union[None, List[int]], title: str):
|
|
150
147
|
link = f"{self.__baseUrl}/run/{self.project_code}/dashboard/{self.run_id}?source=logs&status=%5B2%5D&search="
|
|
151
|
-
if
|
|
152
|
-
return f"{link}{
|
|
153
|
-
|
|
148
|
+
if ids is not None and len(ids) > 0:
|
|
149
|
+
return f"{link}{ids[0]}"
|
|
154
150
|
return f"{link}{urllib.parse.quote_plus(title)}"
|
|
155
151
|
|
|
156
152
|
@staticmethod
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase_python_commons.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qase-python-commons
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.1
|
|
4
4
|
Summary: A library for Qase TestOps and Qase Report
|
|
5
5
|
Author-email: Qase Team <support@qase.io>
|
|
6
6
|
Project-URL: Homepage, https://github.com/qase-tms/qase-python/tree/main/qase-python-commons
|
|
@@ -22,8 +22,8 @@ Requires-Python: >=3.7
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
Requires-Dist: certifi>=2024.2.2
|
|
24
24
|
Requires-Dist: attrs>=23.2.0
|
|
25
|
-
Requires-Dist: qase-api-client~=1.
|
|
26
|
-
Requires-Dist: qase-api-v2-client~=1.
|
|
25
|
+
Requires-Dist: qase-api-client~=1.2.0
|
|
26
|
+
Requires-Dist: qase-api-v2-client~=1.2.0
|
|
27
27
|
Requires-Dist: more_itertools
|
|
28
28
|
Provides-Extra: testing
|
|
29
29
|
Requires-Dist: pytest; extra == "testing"
|
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
from datetime import datetime, timezone
|
|
2
|
-
from typing import Dict, Union
|
|
3
|
-
|
|
4
|
-
import certifi
|
|
5
|
-
from qase.api_client_v1 import ApiClient, ProjectsApi, Project, EnvironmentsApi, RunsApi, AttachmentsApi, \
|
|
6
|
-
AttachmentGet, RunCreate, ResultsApi, ResultcreateBulk, AuthorsApi
|
|
7
|
-
from qase.api_client_v1.configuration import Configuration
|
|
8
|
-
from .. import Logger
|
|
9
|
-
from .base_api_client import BaseApiClient
|
|
10
|
-
from ..exceptions.reporter import ReporterException
|
|
11
|
-
from ..models import Attachment, InternalResult, Step
|
|
12
|
-
from ..models.config.framework import Video, Trace
|
|
13
|
-
from ..models.config.qaseconfig import QaseConfig
|
|
14
|
-
from ..models.step import StepType
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class ApiV1Client(BaseApiClient):
|
|
18
|
-
def __init__(self, config: QaseConfig, logger: Logger):
|
|
19
|
-
self.logger = logger
|
|
20
|
-
self.config = config
|
|
21
|
-
self.__authors = {}
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
self.logger.log_debug("Preparing API client")
|
|
25
|
-
configuration = Configuration()
|
|
26
|
-
configuration.api_key['TokenAuth'] = self.config.testops.api.token
|
|
27
|
-
configuration.ssl_ca_cert = certifi.where()
|
|
28
|
-
host = self.config.testops.api.host
|
|
29
|
-
if host == 'qase.io':
|
|
30
|
-
configuration.host = f'https://api.{host}/v1'
|
|
31
|
-
self.web = f'https://app.{host}'
|
|
32
|
-
else:
|
|
33
|
-
configuration.host = f'https://api-{host}/v1'
|
|
34
|
-
self.web = f'https://{host}'
|
|
35
|
-
|
|
36
|
-
self.client = ApiClient(configuration)
|
|
37
|
-
self.logger.log_debug("API client prepared")
|
|
38
|
-
except Exception as e:
|
|
39
|
-
self.logger.log(f"Error at preparing API client: {e}", "error")
|
|
40
|
-
raise ReporterException(e)
|
|
41
|
-
|
|
42
|
-
def get_project(self, project_code: str) -> Union[Project, None]:
|
|
43
|
-
try:
|
|
44
|
-
self.logger.log_debug(f"Getting project {project_code}")
|
|
45
|
-
response = ProjectsApi(self.client).get_project(code=project_code)
|
|
46
|
-
if hasattr(response, 'result'):
|
|
47
|
-
self.logger.log_debug(f"Project {project_code} found: {response.result.to_json()}")
|
|
48
|
-
return response.result
|
|
49
|
-
raise ReporterException("Unable to find given project code")
|
|
50
|
-
except Exception as e:
|
|
51
|
-
self.logger.log("Exception when calling ProjectApi->get_project: %s\n" % e, "error")
|
|
52
|
-
raise ReporterException("Exception when calling ProjectApi")
|
|
53
|
-
|
|
54
|
-
def get_environment(self, environment: str, project_code: str) -> Union[str, None]:
|
|
55
|
-
try:
|
|
56
|
-
self.logger.log_debug(f"Getting environment {environment}")
|
|
57
|
-
api_instance = EnvironmentsApi(self.client)
|
|
58
|
-
response = api_instance.get_environments(code=project_code)
|
|
59
|
-
if hasattr(response, 'result') and hasattr(response.result, 'entities'):
|
|
60
|
-
for env in response.result.entities:
|
|
61
|
-
if env.slug == environment:
|
|
62
|
-
self.logger.log_debug(f"Environment {environment} found: {env.to_json()}")
|
|
63
|
-
return env.id
|
|
64
|
-
self.logger.log_debug(f"Environment {environment} not found")
|
|
65
|
-
return None
|
|
66
|
-
except Exception as e:
|
|
67
|
-
self.logger.log("Exception when calling EnvironmentsApi->get_environments: %s\n" % e, "error")
|
|
68
|
-
raise ReporterException(e)
|
|
69
|
-
|
|
70
|
-
def complete_run(self, project_code: str, run_id: str) -> None:
|
|
71
|
-
api_runs = RunsApi(self.client)
|
|
72
|
-
self.logger.log_debug(f"Completing run {run_id}")
|
|
73
|
-
res = api_runs.get_run(project_code, run_id).result
|
|
74
|
-
if res.status == 1:
|
|
75
|
-
self.logger.log_debug(f"Run {run_id} already completed")
|
|
76
|
-
return
|
|
77
|
-
try:
|
|
78
|
-
api_runs.complete_run(project_code, run_id)
|
|
79
|
-
self.logger.log(f"Test run link: {self.web}/run/{project_code}/dashboard/{run_id}", "info")
|
|
80
|
-
except Exception as e:
|
|
81
|
-
self.logger.log(f"Error at completing run {run_id}: {e}", "error")
|
|
82
|
-
raise ReporterException(e)
|
|
83
|
-
|
|
84
|
-
def _upload_attachment(self, project_code: str, attachment: Attachment) -> Union[AttachmentGet, None]:
|
|
85
|
-
try:
|
|
86
|
-
self.logger.log_debug(f"Uploading attachment {attachment.id} for project {project_code}")
|
|
87
|
-
attach_api = AttachmentsApi(self.client)
|
|
88
|
-
response = attach_api.upload_attachment(project_code, file=[attachment.get_for_upload()])
|
|
89
|
-
|
|
90
|
-
return response.result
|
|
91
|
-
|
|
92
|
-
except Exception as e:
|
|
93
|
-
self.logger.log(f"Error at uploading attachment: {e}", "error")
|
|
94
|
-
return None
|
|
95
|
-
|
|
96
|
-
def create_test_run(self, project_code: str, title: str, description: str, plan_id=None,
|
|
97
|
-
environment_id=None) -> str:
|
|
98
|
-
kwargs = dict(
|
|
99
|
-
title=title,
|
|
100
|
-
description=description,
|
|
101
|
-
environment_id=(int(environment_id) if environment_id else None),
|
|
102
|
-
plan_id=(int(plan_id) if plan_id else plan_id),
|
|
103
|
-
is_autotest=True,
|
|
104
|
-
start_time=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
105
|
-
)
|
|
106
|
-
self.logger.log_debug(f"Creating test run with parameters: {kwargs}")
|
|
107
|
-
try:
|
|
108
|
-
result = RunsApi(self.client).create_run(
|
|
109
|
-
code=project_code,
|
|
110
|
-
run_create=RunCreate(**{k: v for k, v in kwargs.items() if v is not None})
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
return result.result.id
|
|
114
|
-
|
|
115
|
-
except Exception as e:
|
|
116
|
-
self.logger.log(f"Error at creating test run: {e}", "error")
|
|
117
|
-
raise ReporterException(e)
|
|
118
|
-
|
|
119
|
-
def check_test_run(self, project_code: str, run_id: int) -> bool:
|
|
120
|
-
api_runs = RunsApi(self.client)
|
|
121
|
-
run = api_runs.get_run(code=project_code, id=run_id)
|
|
122
|
-
if run.result.id:
|
|
123
|
-
return True
|
|
124
|
-
return False
|
|
125
|
-
|
|
126
|
-
def send_results(self, project_code: str, run_id: str, results: []) -> None:
|
|
127
|
-
api_results = ResultsApi(self.client)
|
|
128
|
-
results_to_send = [self._prepare_result(project_code, result) for result in results]
|
|
129
|
-
self.logger.log_debug(f"Sending results for run {run_id}: {results_to_send}")
|
|
130
|
-
api_results.create_result_bulk(
|
|
131
|
-
code=project_code,
|
|
132
|
-
id=run_id,
|
|
133
|
-
resultcreate_bulk=ResultcreateBulk(
|
|
134
|
-
results=results_to_send
|
|
135
|
-
)
|
|
136
|
-
)
|
|
137
|
-
self.logger.log_debug(f"Results for run {run_id} sent successfully")
|
|
138
|
-
|
|
139
|
-
def _prepare_result(self, project_code: str, result: InternalResult) -> Dict:
|
|
140
|
-
attached = []
|
|
141
|
-
if result.attachments:
|
|
142
|
-
for attachment in result.attachments:
|
|
143
|
-
if self.__should_skip_attachment(attachment, result):
|
|
144
|
-
continue
|
|
145
|
-
attach_id = self._upload_attachment(project_code, attachment)
|
|
146
|
-
if attach_id:
|
|
147
|
-
attached.extend(attach_id)
|
|
148
|
-
|
|
149
|
-
steps = []
|
|
150
|
-
for step in result.steps:
|
|
151
|
-
prepared = self._prepare_step(project_code, step)
|
|
152
|
-
steps.append(prepared)
|
|
153
|
-
|
|
154
|
-
case_data = {
|
|
155
|
-
"title": result.get_title(),
|
|
156
|
-
"description": result.get_field('description'),
|
|
157
|
-
"preconditions": result.get_field('preconditions'),
|
|
158
|
-
"postconditions": result.get_field('postconditions'),
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
for key, param in result.params.items():
|
|
162
|
-
# Hack to match old TestOps API
|
|
163
|
-
if param == "":
|
|
164
|
-
result.params[key] = "empty"
|
|
165
|
-
|
|
166
|
-
if result.get_field('severity'):
|
|
167
|
-
case_data["severity"] = result.get_field('severity')
|
|
168
|
-
|
|
169
|
-
if result.get_field('priority'):
|
|
170
|
-
case_data["priority"] = result.get_field('priority')
|
|
171
|
-
|
|
172
|
-
if result.get_field('layer'):
|
|
173
|
-
case_data["layer"] = result.get_field('layer')
|
|
174
|
-
|
|
175
|
-
suite = None
|
|
176
|
-
if result.relations is not None and result.relations.suite is not None and len(
|
|
177
|
-
result.relations.suite.data) != 0:
|
|
178
|
-
suites = []
|
|
179
|
-
|
|
180
|
-
for raw in result.relations.suite.data:
|
|
181
|
-
suites.append(raw.title)
|
|
182
|
-
|
|
183
|
-
suite = "\t".join(suites)
|
|
184
|
-
|
|
185
|
-
if result.get_field('suite'):
|
|
186
|
-
suite = result.get_field('suite')
|
|
187
|
-
|
|
188
|
-
root_suite = self.config.root_suite
|
|
189
|
-
if root_suite:
|
|
190
|
-
suite = f"{root_suite}\t{suite}"
|
|
191
|
-
|
|
192
|
-
if suite:
|
|
193
|
-
case_data["suite_title"] = suite
|
|
194
|
-
|
|
195
|
-
result_model = {
|
|
196
|
-
"status": result.execution.status,
|
|
197
|
-
"stacktrace": result.execution.stacktrace,
|
|
198
|
-
"time_ms": result.execution.duration,
|
|
199
|
-
"comment": result.message,
|
|
200
|
-
"attachments": [attach.hash for attach in attached],
|
|
201
|
-
"steps": steps,
|
|
202
|
-
"param": result.params,
|
|
203
|
-
"param_groups": result.param_groups,
|
|
204
|
-
"defect": self.config.testops.defect,
|
|
205
|
-
"case": case_data
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
test_ops_id = result.get_testops_id()
|
|
209
|
-
|
|
210
|
-
if test_ops_id:
|
|
211
|
-
result_model["case_id"] = test_ops_id
|
|
212
|
-
|
|
213
|
-
if result.get_field('author'):
|
|
214
|
-
author_id = self._get_author_id(result.get_field('author'))
|
|
215
|
-
if author_id:
|
|
216
|
-
result_model["author_id"] = author_id
|
|
217
|
-
|
|
218
|
-
self.logger.log_debug(f"Prepared result: {result_model}")
|
|
219
|
-
|
|
220
|
-
return result_model
|
|
221
|
-
|
|
222
|
-
def _prepare_step(self, project_code: str, step: Step) -> Dict:
|
|
223
|
-
prepared_children = []
|
|
224
|
-
|
|
225
|
-
try:
|
|
226
|
-
prepared_step = {"time": step.execution.duration, "status": step.execution.status}
|
|
227
|
-
|
|
228
|
-
if step.execution.status == 'untested':
|
|
229
|
-
prepared_step["status"] = 'passed'
|
|
230
|
-
|
|
231
|
-
if step.execution.status == 'skipped':
|
|
232
|
-
prepared_step["status"] = 'blocked'
|
|
233
|
-
|
|
234
|
-
if step.step_type == StepType.TEXT:
|
|
235
|
-
prepared_step['action'] = step.data.action
|
|
236
|
-
if step.data.expected_result:
|
|
237
|
-
prepared_step['expected_result'] = step.data.expected_result
|
|
238
|
-
|
|
239
|
-
if step.step_type == StepType.REQUEST:
|
|
240
|
-
prepared_step['action'] = step.data.request_method + " " + step.data.request_url
|
|
241
|
-
if step.data.request_body:
|
|
242
|
-
step.attachments.append(
|
|
243
|
-
Attachment(file_name='request_body.txt', content=step.data.request_body, mime_type='text/plain',
|
|
244
|
-
temporary=True))
|
|
245
|
-
if step.data.request_headers:
|
|
246
|
-
step.attachments.append(
|
|
247
|
-
Attachment(file_name='request_headers.txt', content=step.data.request_headers,
|
|
248
|
-
mime_type='text/plain', temporary=True))
|
|
249
|
-
if step.data.response_body:
|
|
250
|
-
step.attachments.append(Attachment(file_name='response_body.txt', content=step.data.response_body,
|
|
251
|
-
mime_type='text/plain', temporary=True))
|
|
252
|
-
if step.data.response_headers:
|
|
253
|
-
step.attachments.append(
|
|
254
|
-
Attachment(file_name='response_headers.txt', content=step.data.response_headers,
|
|
255
|
-
mime_type='text/plain', temporary=True))
|
|
256
|
-
|
|
257
|
-
if step.step_type == StepType.GHERKIN:
|
|
258
|
-
prepared_step['action'] = step.data.keyword
|
|
259
|
-
|
|
260
|
-
if step.step_type == StepType.SLEEP:
|
|
261
|
-
prepared_step['action'] = f"Sleep for {step.data.duration} seconds"
|
|
262
|
-
|
|
263
|
-
if step.attachments:
|
|
264
|
-
uploaded_attachments = []
|
|
265
|
-
for file in step.attachments:
|
|
266
|
-
attach_id = self._upload_attachment(project_code, file)
|
|
267
|
-
if attach_id:
|
|
268
|
-
uploaded_attachments.extend(attach_id)
|
|
269
|
-
prepared_step['attachments'] = [attach.hash for attach in uploaded_attachments]
|
|
270
|
-
|
|
271
|
-
if step.steps:
|
|
272
|
-
for substep in step.steps:
|
|
273
|
-
prepared_children.append(self._prepare_step(project_code, substep))
|
|
274
|
-
prepared_step["steps"] = prepared_children
|
|
275
|
-
return prepared_step
|
|
276
|
-
except Exception as e:
|
|
277
|
-
self.logger.log(f"Error at preparing step: {e}", "error")
|
|
278
|
-
raise ReporterException(e)
|
|
279
|
-
|
|
280
|
-
def _get_author_id(self, author: str) -> Union[str, None]:
|
|
281
|
-
if author in self.__authors:
|
|
282
|
-
return self.__authors[author]
|
|
283
|
-
|
|
284
|
-
author_api = AuthorsApi(self.client)
|
|
285
|
-
authors = author_api.get_authors(search=author)
|
|
286
|
-
if authors.result.total == 0:
|
|
287
|
-
return None
|
|
288
|
-
|
|
289
|
-
self.__authors[author] = authors.result.entities[0].author_id
|
|
290
|
-
|
|
291
|
-
return authors.result.entities[0].author_id
|
|
292
|
-
|
|
293
|
-
def __should_skip_attachment(self, attachment, result):
|
|
294
|
-
if (self.config.framework.playwright.video == Video.failed and
|
|
295
|
-
result.execution.status != 'failed' and
|
|
296
|
-
attachment.file_name == 'video.webm'):
|
|
297
|
-
return True
|
|
298
|
-
if (self.config.framework.playwright.trace == Trace.failed and
|
|
299
|
-
result.execution.status != 'failed' and
|
|
300
|
-
attachment.file_name == 'trace.zip'):
|
|
301
|
-
return True
|
|
302
|
-
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/client/base_api_client.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/exceptions/reporter.py
RENAMED
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/attachment.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/api.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/batch.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/connection.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/framework.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/plan.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/qaseconfig.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/report.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/models/config/run.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/profilers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/profilers/network.py
RENAMED
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase/commons/reporters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.2 → qase_python_commons-3.4.1}/src/qase_python_commons.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|