qase-python-commons 3.0.0__tar.gz → 3.0.2b2__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.
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/PKG-INFO +2 -2
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/pyproject.toml +3 -3
- qase_python_commons-3.0.2b2/src/qase/commons/client/api_v1_client.py +247 -0
- qase_python_commons-3.0.2b2/src/qase/commons/client/base_api_client.py +88 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/config.py +4 -1
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/loader.py +4 -4
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/logger.py +4 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/attachment.py +8 -5
- qase_python_commons-3.0.2b2/src/qase/commons/models/basemodel.py +7 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/relation.py +5 -2
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/result.py +8 -10
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/run.py +6 -6
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/step.py +12 -10
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/suite.py +4 -2
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/reporters/core.py +16 -6
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/reporters/report.py +1 -1
- qase_python_commons-3.0.2b2/src/qase/commons/reporters/testops.py +130 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase_python_commons.egg-info/PKG-INFO +2 -2
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase_python_commons.egg-info/SOURCES.txt +3 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase_python_commons.egg-info/requires.txt +1 -1
- qase_python_commons-3.0.0/src/qase/commons/reporters/testops.py +0 -369
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/README.md +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/setup.cfg +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/__init__.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/exceptions/reporter.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/__init__.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/runtime.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/profilers/__init__.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/profilers/db.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/profilers/network.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/profilers/sleep.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/reporters/__init__.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/utils.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/validators/base.py +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase_python_commons.egg-info/dependency_links.txt +0 -0
- {qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase_python_commons.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qase-python-commons
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2b2
|
|
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/master/qase-python-commons
|
|
@@ -22,7 +22,7 @@ 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:
|
|
25
|
+
Requires-Dist: qase-api-client~=1.0.0b2
|
|
26
26
|
Requires-Dist: more_itertools
|
|
27
27
|
Provides-Extra: testing
|
|
28
28
|
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.0.
|
|
7
|
+
version = "3.0.2b2"
|
|
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,7 +30,7 @@ requires-python = ">=3.7"
|
|
|
30
30
|
dependencies = [
|
|
31
31
|
"certifi>=2024.2.2",
|
|
32
32
|
"attrs>=23.2.0",
|
|
33
|
-
"
|
|
33
|
+
"qase-api-client~=1.0.0b2",
|
|
34
34
|
"more_itertools"
|
|
35
35
|
]
|
|
36
36
|
|
|
@@ -99,4 +99,4 @@ exclude_also = [
|
|
|
99
99
|
"@(abc\\.)?abstractmethod",
|
|
100
100
|
]
|
|
101
101
|
|
|
102
|
-
ignore_errors = false
|
|
102
|
+
ignore_errors = false
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
from typing import Dict, Union
|
|
2
|
+
|
|
3
|
+
import certifi
|
|
4
|
+
from qase.api_client_v1 import ApiClient, ProjectsApi, Project, EnvironmentsApi, RunsApi, AttachmentsApi, \
|
|
5
|
+
AttachmentGet, RunCreate, ResultsApi, ResultcreateBulk
|
|
6
|
+
from qase.api_client_v1.configuration import Configuration
|
|
7
|
+
from .. import ConfigManager, Logger
|
|
8
|
+
from .base_api_client import BaseApiClient
|
|
9
|
+
from ..exceptions.reporter import ReporterException
|
|
10
|
+
from ..models import Attachment, Result, Step
|
|
11
|
+
from ..models.step import StepType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ApiV1Client(BaseApiClient):
|
|
15
|
+
def __init__(self, config: ConfigManager, logger: Logger):
|
|
16
|
+
self.logger = logger
|
|
17
|
+
self.config = config
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
self.logger.log_debug("Preparing API client")
|
|
21
|
+
configuration = Configuration()
|
|
22
|
+
configuration.api_key['TokenAuth'] = self.config.get('testops.api.token')
|
|
23
|
+
configuration.ssl_ca_cert = certifi.where()
|
|
24
|
+
host = self.config.get('testops.api.host', 'qase.io')
|
|
25
|
+
if self.config.get('testops.api.enterprise', False, bool):
|
|
26
|
+
configuration.host = f'https://api-{host}/v1'
|
|
27
|
+
self.web = f'https://{host}'
|
|
28
|
+
else:
|
|
29
|
+
configuration.host = f'https://api.{host}/v1'
|
|
30
|
+
self.web = f'https://app.{host}'
|
|
31
|
+
|
|
32
|
+
self.client = ApiClient(configuration)
|
|
33
|
+
self.logger.log_debug("API client prepared")
|
|
34
|
+
except Exception as e:
|
|
35
|
+
self.logger.log(f"Error at preparing API client: {e}", "error")
|
|
36
|
+
raise ReporterException(e)
|
|
37
|
+
|
|
38
|
+
def get_project(self, project_code: str) -> Union[Project, None]:
|
|
39
|
+
try:
|
|
40
|
+
self.logger.log_debug(f"Getting project {project_code}")
|
|
41
|
+
response = ProjectsApi(self.client).get_project(code=project_code)
|
|
42
|
+
if hasattr(response, 'result'):
|
|
43
|
+
self.logger.log_debug(f"Project {project_code} found: {response.result.to_json()}")
|
|
44
|
+
return response.result
|
|
45
|
+
raise ReporterException("Unable to find given project code")
|
|
46
|
+
except Exception as e:
|
|
47
|
+
self.logger.log("Exception when calling ProjectApi->get_project: %s\n" % e, "error")
|
|
48
|
+
raise ReporterException("Exception when calling ProjectApi")
|
|
49
|
+
|
|
50
|
+
def get_environment(self, environment: str, project_code: str) -> Union[str, None]:
|
|
51
|
+
try:
|
|
52
|
+
self.logger.log_debug(f"Getting environment {environment}")
|
|
53
|
+
api_instance = EnvironmentsApi(self.client)
|
|
54
|
+
response = api_instance.get_environments(code=project_code)
|
|
55
|
+
if hasattr(response, 'result') and hasattr(response.result, 'entities'):
|
|
56
|
+
for env in response.result.entities:
|
|
57
|
+
if env.slug == environment:
|
|
58
|
+
self.logger.log_debug(f"Environment {environment} found: {env.to_json()}")
|
|
59
|
+
return env.id
|
|
60
|
+
self.logger.log_debug(f"Environment {environment} not found")
|
|
61
|
+
return None
|
|
62
|
+
except Exception as e:
|
|
63
|
+
self.logger.log("Exception when calling EnvironmentsApi->get_environments: %s\n" % e, "error")
|
|
64
|
+
raise ReporterException(e)
|
|
65
|
+
|
|
66
|
+
def complete_run(self, project_code: str, run_id: str) -> None:
|
|
67
|
+
api_runs = RunsApi(self.client)
|
|
68
|
+
self.logger.log_debug(f"Completing run {run_id}")
|
|
69
|
+
res = api_runs.get_run(project_code, run_id).result
|
|
70
|
+
if res.status == 1:
|
|
71
|
+
self.logger.log_debug(f"Run {run_id} already completed")
|
|
72
|
+
return
|
|
73
|
+
try:
|
|
74
|
+
api_runs.complete_run(project_code, run_id)
|
|
75
|
+
self.logger.log(f"Run {run_id} was completed successfully", "info")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
self.logger.log(f"Error at completing run {run_id}: {e}", "error")
|
|
78
|
+
raise ReporterException(e)
|
|
79
|
+
|
|
80
|
+
def _upload_attachment(self, project_code: str, attachment: Attachment) -> Union[AttachmentGet, None]:
|
|
81
|
+
try:
|
|
82
|
+
self.logger.log_debug(f"Uploading attachment {attachment.id} for project {project_code}")
|
|
83
|
+
attach_api = AttachmentsApi(self.client)
|
|
84
|
+
response = attach_api.upload_attachment(project_code, file=[attachment.get_for_upload()])
|
|
85
|
+
|
|
86
|
+
return response.result
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
self.logger.log(f"Error at uploading attachment: {e}", "error")
|
|
90
|
+
raise ReporterException(e)
|
|
91
|
+
|
|
92
|
+
def create_test_run(self, project_code: str, title: str, description: str, plan_id=None,
|
|
93
|
+
environment_id=None) -> str:
|
|
94
|
+
kwargs = dict(
|
|
95
|
+
title=title,
|
|
96
|
+
description=description,
|
|
97
|
+
environment_id=(int(environment_id) if environment_id else None),
|
|
98
|
+
plan_id=(int(plan_id) if plan_id else plan_id),
|
|
99
|
+
is_autotest=True
|
|
100
|
+
)
|
|
101
|
+
self.logger.log_debug(f"Creating test run with parameters: {kwargs}")
|
|
102
|
+
try:
|
|
103
|
+
result = RunsApi(self.client).create_run(
|
|
104
|
+
code=project_code,
|
|
105
|
+
run_create=RunCreate(**{k: v for k, v in kwargs.items() if v is not None})
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
self.logger.log(f"Test run was created: {self.web}/run/{project_code}/dashboard/{result.result.id}", "info")
|
|
109
|
+
|
|
110
|
+
return result.result.id
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
self.logger.log(f"Error at creating test run: {e}", "error")
|
|
114
|
+
raise ReporterException(e)
|
|
115
|
+
|
|
116
|
+
def check_test_run(self, project_code: str, run_id: int) -> bool:
|
|
117
|
+
api_runs = RunsApi(self.client)
|
|
118
|
+
run = api_runs.get_run(code=project_code, id=run_id)
|
|
119
|
+
if run.result.id:
|
|
120
|
+
return True
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
def send_results(self, project_code: str, run_id: int, results: []) -> None:
|
|
124
|
+
api_results = ResultsApi(self.client)
|
|
125
|
+
results_to_send = [self._prepare_result(project_code, result) for result in results]
|
|
126
|
+
self.logger.log_debug(f"Sending results for run {run_id}: {results_to_send}")
|
|
127
|
+
api_results.create_result_bulk(
|
|
128
|
+
code=project_code,
|
|
129
|
+
id=run_id,
|
|
130
|
+
resultcreate_bulk=ResultcreateBulk(
|
|
131
|
+
results=results_to_send
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
self.logger.log_debug(f"Results for run {run_id} sent successfully")
|
|
135
|
+
|
|
136
|
+
def _prepare_result(self, project_code: str, result: Result) -> Dict:
|
|
137
|
+
attached = []
|
|
138
|
+
if result.attachments:
|
|
139
|
+
for attachment in result.attachments:
|
|
140
|
+
attached.extend(self._upload_attachment(project_code, attachment))
|
|
141
|
+
|
|
142
|
+
steps = []
|
|
143
|
+
for step in result.steps:
|
|
144
|
+
prepared = self._prepare_step(project_code, step)
|
|
145
|
+
steps.append(prepared)
|
|
146
|
+
|
|
147
|
+
case_data = {
|
|
148
|
+
"title": result.get_title(),
|
|
149
|
+
"description": result.get_field('description'),
|
|
150
|
+
"preconditions": result.get_field('preconditions'),
|
|
151
|
+
"postconditions": result.get_field('postconditions'),
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for key, param in result.params.items():
|
|
155
|
+
# Hack to match old TestOps API
|
|
156
|
+
if param == "":
|
|
157
|
+
result.params[key] = "empty"
|
|
158
|
+
|
|
159
|
+
if result.get_field('severity'):
|
|
160
|
+
case_data["severity"] = result.get_field('severity')
|
|
161
|
+
|
|
162
|
+
if result.get_field('priority'):
|
|
163
|
+
case_data["priority"] = result.get_field('priority')
|
|
164
|
+
|
|
165
|
+
if result.get_field('layer'):
|
|
166
|
+
case_data["layer"] = result.get_field('layer')
|
|
167
|
+
|
|
168
|
+
if result.get_suite_title():
|
|
169
|
+
case_data["suite_title"] = "\t".join(result.get_suite_title().split("."))
|
|
170
|
+
|
|
171
|
+
result_model = {
|
|
172
|
+
"status": result.execution.status,
|
|
173
|
+
"stacktrace": result.execution.stacktrace,
|
|
174
|
+
"time_ms": result.execution.duration,
|
|
175
|
+
"comment": result.message,
|
|
176
|
+
"attachments": [attach.hash for attach in attached],
|
|
177
|
+
"steps": steps,
|
|
178
|
+
"param": result.params,
|
|
179
|
+
"defect": self.config.get('testops.defect', False, bool),
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
test_ops_id = result.get_testops_id()
|
|
183
|
+
|
|
184
|
+
if test_ops_id:
|
|
185
|
+
result_model["case_id"] = test_ops_id
|
|
186
|
+
result_model["case"] = None
|
|
187
|
+
return result_model
|
|
188
|
+
|
|
189
|
+
result_model["case_id"] = None
|
|
190
|
+
result_model["case"] = case_data
|
|
191
|
+
|
|
192
|
+
self.logger.log_debug(f"Prepared result: {result_model}")
|
|
193
|
+
|
|
194
|
+
return result_model
|
|
195
|
+
|
|
196
|
+
def _prepare_step(self, project_code: str, step: Step) -> Dict:
|
|
197
|
+
prepared_children = []
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
prepared_step = {"time": step.execution.duration, "status": step.execution.status}
|
|
201
|
+
|
|
202
|
+
if step.execution.status == 'untested':
|
|
203
|
+
prepared_step["status"] = 'passed'
|
|
204
|
+
|
|
205
|
+
if step.step_type == StepType.TEXT:
|
|
206
|
+
prepared_step['action'] = step.data.action
|
|
207
|
+
if step.data.expected_result:
|
|
208
|
+
prepared_step['expected_result'] = step.data.expected_result
|
|
209
|
+
|
|
210
|
+
if step.step_type == StepType.REQUEST:
|
|
211
|
+
prepared_step['action'] = step.data.request_method + " " + step.data.request_url
|
|
212
|
+
if step.data.request_body:
|
|
213
|
+
step.attachments.append(
|
|
214
|
+
Attachment(file_name='request_body.txt', content=step.data.request_body, mime_type='text/plain',
|
|
215
|
+
temporary=True))
|
|
216
|
+
if step.data.request_headers:
|
|
217
|
+
step.attachments.append(
|
|
218
|
+
Attachment(file_name='request_headers.txt', content=step.data.request_headers,
|
|
219
|
+
mime_type='text/plain', temporary=True))
|
|
220
|
+
if step.data.response_body:
|
|
221
|
+
step.attachments.append(Attachment(file_name='response_body.txt', content=step.data.response_body,
|
|
222
|
+
mime_type='text/plain', temporary=True))
|
|
223
|
+
if step.data.response_headers:
|
|
224
|
+
step.attachments.append(
|
|
225
|
+
Attachment(file_name='response_headers.txt', content=step.data.response_headers,
|
|
226
|
+
mime_type='text/plain', temporary=True))
|
|
227
|
+
|
|
228
|
+
if step.step_type == StepType.GHERKIN:
|
|
229
|
+
prepared_step['action'] = step.data.keyword
|
|
230
|
+
|
|
231
|
+
if step.step_type == StepType.SLEEP:
|
|
232
|
+
prepared_step['action'] = f"Sleep for {step.data.duration} seconds"
|
|
233
|
+
|
|
234
|
+
if step.attachments:
|
|
235
|
+
uploaded_attachments = []
|
|
236
|
+
for file in step.attachments:
|
|
237
|
+
uploaded_attachments.extend(self._upload_attachment(project_code, file))
|
|
238
|
+
prepared_step['attachments'] = [attach.hash for attach in uploaded_attachments]
|
|
239
|
+
|
|
240
|
+
if step.steps:
|
|
241
|
+
for substep in step.steps:
|
|
242
|
+
prepared_children.append(self._prepare_step(project_code, substep))
|
|
243
|
+
prepared_step["steps"] = prepared_children
|
|
244
|
+
return prepared_step
|
|
245
|
+
except Exception as e:
|
|
246
|
+
self.logger.log(f"Error at preparing step: {e}", "error")
|
|
247
|
+
raise ReporterException(e)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from qase.api_client_v1 import Project, AttachmentGet
|
|
5
|
+
|
|
6
|
+
from ..models import Attachment
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseApiClient(abc.ABC):
|
|
10
|
+
@abc.abstractmethod
|
|
11
|
+
def get_project(self, project_code: str) -> Union[Project, None]:
|
|
12
|
+
"""
|
|
13
|
+
Load a project from Qase TestOps by code and return project data
|
|
14
|
+
|
|
15
|
+
:param project_code: project code
|
|
16
|
+
:return: project data or None if project not found
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@abc.abstractmethod
|
|
22
|
+
def get_environment(self, environment: str, project_code: str) -> Union[str, None]:
|
|
23
|
+
"""
|
|
24
|
+
Load an environment from Qase TestOps by name and returns environment id
|
|
25
|
+
|
|
26
|
+
:param environment: environment name
|
|
27
|
+
:param project_code: project code
|
|
28
|
+
:return: environment id or None if environment not found
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
@abc.abstractmethod
|
|
33
|
+
def complete_run(self, project_code: str, run_id: str) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Complete a test run in Qase TestOps
|
|
36
|
+
|
|
37
|
+
:param project_code: project code
|
|
38
|
+
:param run_id: test run id
|
|
39
|
+
:return: None
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abc.abstractmethod
|
|
44
|
+
def _upload_attachment(self, project_code: str, attachment: Attachment) -> Union[AttachmentGet, None]:
|
|
45
|
+
"""
|
|
46
|
+
Upload an attachment to Qase TestOps
|
|
47
|
+
|
|
48
|
+
:param project_code: project code
|
|
49
|
+
:param attachment: attachment model
|
|
50
|
+
:return: attachment data or None if attachment not uploaded
|
|
51
|
+
"""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
@abc.abstractmethod
|
|
55
|
+
def create_test_run(self, project_code: str, title: str, description: str, plan_id=None,
|
|
56
|
+
environment_id=None) -> int:
|
|
57
|
+
"""
|
|
58
|
+
Create a test run in Qase TestOps
|
|
59
|
+
|
|
60
|
+
:param project_code: project code
|
|
61
|
+
:param title: test run title
|
|
62
|
+
:param description: test run description
|
|
63
|
+
:param plan_id: plan id
|
|
64
|
+
:param environment_id: environment id
|
|
65
|
+
:return: test run id
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@abc.abstractmethod
|
|
70
|
+
def check_test_run(self, project_code: str, run_id: int) -> bool:
|
|
71
|
+
"""
|
|
72
|
+
Check if test run exists in Qase TestOps
|
|
73
|
+
:param project_code: project code
|
|
74
|
+
:param run_id: test run id
|
|
75
|
+
:return: True if test run exists, False otherwise
|
|
76
|
+
"""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
@abc.abstractmethod
|
|
80
|
+
def send_results(self, project_code: str, run_id: int, results: []) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Send test results to Qase TestOps
|
|
83
|
+
:param project_code: project code
|
|
84
|
+
:param run_id: test run id
|
|
85
|
+
:param results: results data
|
|
86
|
+
:return: None
|
|
87
|
+
"""
|
|
88
|
+
pass
|
|
@@ -29,7 +29,7 @@ class ConfigManager:
|
|
|
29
29
|
# Use _get_config method to get the value. If None, return default.
|
|
30
30
|
value = self._get_config(key)
|
|
31
31
|
if value_type and value_type == bool:
|
|
32
|
-
return self.parseBool(value
|
|
32
|
+
return self.parseBool(value)
|
|
33
33
|
return value or default
|
|
34
34
|
|
|
35
35
|
def validate_config(self):
|
|
@@ -59,3 +59,6 @@ class ConfigManager:
|
|
|
59
59
|
for key in keys[:-1]:
|
|
60
60
|
config = config.get(key, {})
|
|
61
61
|
return config.get(keys[-1], None)
|
|
62
|
+
|
|
63
|
+
def __str__(self):
|
|
64
|
+
return json.dumps(self.config, indent=4, sort_keys=True)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from
|
|
4
|
-
from
|
|
1
|
+
from qase.api_client_v1.api_client import ApiClient
|
|
2
|
+
from qase.api_client_v1.configuration import Configuration
|
|
3
|
+
from qase.api_client_v1.api.plans_api import PlansApi
|
|
4
|
+
from qase.api_client_v1.exceptions import ApiException
|
|
5
5
|
|
|
6
6
|
import certifi
|
|
7
7
|
|
|
@@ -22,6 +22,10 @@ class Logger:
|
|
|
22
22
|
with open(self.log_file, 'a') as f:
|
|
23
23
|
f.write(log)
|
|
24
24
|
|
|
25
|
+
def log_debug(self, message: str):
|
|
26
|
+
if self.debug:
|
|
27
|
+
self.log(message, 'debug')
|
|
28
|
+
|
|
25
29
|
@staticmethod
|
|
26
30
|
def _get_timestamp(format: str = "%Y%m%d_%H:%M:%S"):
|
|
27
31
|
now = datetime.datetime.now()
|
{qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/attachment.py
RENAMED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import uuid
|
|
3
|
-
from typing import Optional, Union
|
|
4
|
-
from io import BytesIO, StringIO
|
|
5
3
|
import json
|
|
6
4
|
import pathlib
|
|
7
5
|
|
|
6
|
+
from typing import Optional, Union
|
|
7
|
+
from io import BytesIO, StringIO
|
|
8
|
+
from .basemodel import BaseModel
|
|
9
|
+
|
|
8
10
|
|
|
9
|
-
class Attachment:
|
|
11
|
+
class Attachment(BaseModel):
|
|
10
12
|
def __init__(self,
|
|
11
13
|
file_name: str,
|
|
12
14
|
mime_type: str,
|
|
@@ -40,10 +42,11 @@ class Attachment:
|
|
|
40
42
|
|
|
41
43
|
def get_for_upload(self) -> BytesIO:
|
|
42
44
|
if self.file_path:
|
|
43
|
-
|
|
45
|
+
with open(self.file_path, "rb") as fc:
|
|
46
|
+
content = BytesIO(fc.read())
|
|
44
47
|
else:
|
|
45
48
|
if isinstance(self.content, str):
|
|
46
|
-
content = BytesIO(self.content
|
|
49
|
+
content = BytesIO(bytes(self.content, 'utf-8'))
|
|
47
50
|
elif isinstance(self.content, bytes):
|
|
48
51
|
content = BytesIO(self.content)
|
|
49
52
|
content.name = self.file_name
|
{qase_python_commons-3.0.0 → qase_python_commons-3.0.2b2}/src/qase/commons/models/relation.py
RENAMED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
from .basemodel import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RelationSuite(BaseModel):
|
|
2
5
|
def __init__(self, suite_id: int, title: str) -> None:
|
|
3
6
|
self.suite_id = suite_id
|
|
4
7
|
self.title = title
|
|
5
8
|
|
|
6
9
|
|
|
7
|
-
class Relation(
|
|
10
|
+
class Relation(BaseModel):
|
|
8
11
|
def __init__(self, type: str, data: RelationSuite):
|
|
9
12
|
self.type = type
|
|
10
13
|
self.data = data
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
from typing import Type, Optional, Union, Dict, List
|
|
2
|
-
from pathlib import PosixPath
|
|
3
1
|
import time
|
|
4
2
|
import uuid
|
|
5
3
|
import json
|
|
4
|
+
|
|
5
|
+
from typing import Type, Optional, Union, Dict, List
|
|
6
|
+
from pathlib import PosixPath
|
|
7
|
+
from .basemodel import BaseModel
|
|
6
8
|
from .step import Step
|
|
7
9
|
from .suite import Suite
|
|
8
10
|
from .attachment import Attachment
|
|
@@ -10,7 +12,7 @@ from .relation import Relation
|
|
|
10
12
|
from .. import QaseUtils
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
class Field:
|
|
15
|
+
class Field(BaseModel):
|
|
14
16
|
def __init__(self,
|
|
15
17
|
name: str,
|
|
16
18
|
value: Union[str, list]):
|
|
@@ -18,7 +20,7 @@ class Field:
|
|
|
18
20
|
self.value = value
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
class Execution(
|
|
23
|
+
class Execution(BaseModel):
|
|
22
24
|
def __init__(self,
|
|
23
25
|
status: Optional[str] = None,
|
|
24
26
|
end_time: int = 0,
|
|
@@ -47,7 +49,7 @@ class Execution(object):
|
|
|
47
49
|
self.duration = (int)((self.end_time - self.start_time) * 1000)
|
|
48
50
|
|
|
49
51
|
|
|
50
|
-
class Request(
|
|
52
|
+
class Request(BaseModel):
|
|
51
53
|
def __init__(self,
|
|
52
54
|
method: str,
|
|
53
55
|
url: str,
|
|
@@ -65,7 +67,7 @@ class Request(object):
|
|
|
65
67
|
self.response_body = response_body
|
|
66
68
|
|
|
67
69
|
|
|
68
|
-
class Result(
|
|
70
|
+
class Result(BaseModel):
|
|
69
71
|
def __init__(self, title: str, signature: str) -> None:
|
|
70
72
|
self.id: str = str(uuid.uuid4())
|
|
71
73
|
self.title: str = title
|
|
@@ -137,7 +139,3 @@ class Result(object):
|
|
|
137
139
|
|
|
138
140
|
def set_run_id(self, run_id: str) -> None:
|
|
139
141
|
self.run_id = run_id
|
|
140
|
-
|
|
141
|
-
def to_json(self) -> str:
|
|
142
|
-
return json.dumps(self, default=lambda o: o.__str__() if isinstance(o, PosixPath) else o.__dict__,
|
|
143
|
-
sort_keys=False, indent=4)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
|
+
|
|
2
3
|
from typing import Optional, List
|
|
3
4
|
|
|
5
|
+
from .basemodel import BaseModel
|
|
6
|
+
|
|
4
7
|
|
|
5
|
-
class RunExecution(
|
|
8
|
+
class RunExecution(BaseModel):
|
|
6
9
|
def __init__(self,
|
|
7
10
|
start_time: float,
|
|
8
11
|
end_time: float,
|
|
@@ -17,7 +20,7 @@ class RunExecution(object):
|
|
|
17
20
|
self.cumulative_duration += result["execution"]["duration"]
|
|
18
21
|
|
|
19
22
|
|
|
20
|
-
class RunStats(
|
|
23
|
+
class RunStats(BaseModel):
|
|
21
24
|
def __init__(self) -> None:
|
|
22
25
|
self.passed = 0
|
|
23
26
|
self.failed = 0
|
|
@@ -41,7 +44,7 @@ class RunStats(object):
|
|
|
41
44
|
self.muted += 1
|
|
42
45
|
|
|
43
46
|
|
|
44
|
-
class Run(
|
|
47
|
+
class Run(BaseModel):
|
|
45
48
|
def __init__(self,
|
|
46
49
|
title: str,
|
|
47
50
|
start_time: float,
|
|
@@ -60,9 +63,6 @@ class Run(object):
|
|
|
60
63
|
self.suites = suites
|
|
61
64
|
self.environment = environment
|
|
62
65
|
|
|
63
|
-
def to_json(self) -> str:
|
|
64
|
-
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=False, indent=4)
|
|
65
|
-
|
|
66
66
|
def add_result(self, result: dict):
|
|
67
67
|
compact_result = {
|
|
68
68
|
"id": result["id"],
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
|
-
from typing import Optional, Union, Dict, List, Type
|
|
3
1
|
import time
|
|
4
2
|
import uuid
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Optional, Union, Dict, List, Type
|
|
5
6
|
from .attachment import Attachment
|
|
7
|
+
from .basemodel import BaseModel
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class StepType(Enum):
|
|
@@ -14,27 +16,27 @@ class StepType(Enum):
|
|
|
14
16
|
SLEEP = 'sleep'
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
class StepTextData(
|
|
19
|
+
class StepTextData(BaseModel):
|
|
18
20
|
def __init__(self, action: str, expected_result: Optional[str] = None):
|
|
19
21
|
self.action = action
|
|
20
22
|
self.expected_result = expected_result
|
|
21
23
|
|
|
22
24
|
|
|
23
|
-
class StepAssertData(
|
|
25
|
+
class StepAssertData(BaseModel):
|
|
24
26
|
def __init__(self, expected: str, actual: str, message: str):
|
|
25
27
|
self.expected = expected
|
|
26
28
|
self.actual = actual
|
|
27
29
|
self.message = message
|
|
28
30
|
|
|
29
31
|
|
|
30
|
-
class StepGherkinData(
|
|
32
|
+
class StepGherkinData(BaseModel):
|
|
31
33
|
def __init__(self, keyword: str, name: str, line: int):
|
|
32
34
|
self.keyword = keyword
|
|
33
35
|
self.name = name
|
|
34
36
|
self.line = line
|
|
35
37
|
|
|
36
38
|
|
|
37
|
-
class StepRequestData(
|
|
39
|
+
class StepRequestData(BaseModel):
|
|
38
40
|
def __init__(self, request_body: str, request_headers: Dict[str, str], request_method: str, request_url: str):
|
|
39
41
|
self.response_headers = None
|
|
40
42
|
self.response_body = None
|
|
@@ -60,17 +62,17 @@ class StepRequestData(object):
|
|
|
60
62
|
self.response_headers = response_headers
|
|
61
63
|
|
|
62
64
|
|
|
63
|
-
class StepDbQueryData(
|
|
65
|
+
class StepDbQueryData(BaseModel):
|
|
64
66
|
def __init__(self, query: str, expected_result: str):
|
|
65
67
|
self.query = query
|
|
66
68
|
|
|
67
69
|
|
|
68
|
-
class StepSleepData(
|
|
70
|
+
class StepSleepData(BaseModel):
|
|
69
71
|
def __init__(self, duration: int):
|
|
70
72
|
self.duration = duration
|
|
71
73
|
|
|
72
74
|
|
|
73
|
-
class StepExecution(
|
|
75
|
+
class StepExecution(BaseModel):
|
|
74
76
|
def __init__(self, status: Optional[str] = 'untested', end_time: int = 0, duration: int = 0):
|
|
75
77
|
self.start_time = time.time()
|
|
76
78
|
self.status = status
|
|
@@ -88,7 +90,7 @@ class StepExecution(object):
|
|
|
88
90
|
self.duration = int((self.end_time - self.start_time) * 1000)
|
|
89
91
|
|
|
90
92
|
|
|
91
|
-
class Step(
|
|
93
|
+
class Step(BaseModel):
|
|
92
94
|
def __init__(self,
|
|
93
95
|
step_type: StepType,
|
|
94
96
|
id: Optional[str],
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
2
1
|
import uuid
|
|
3
2
|
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from .basemodel import BaseModel
|
|
4
6
|
|
|
5
|
-
class Suite():
|
|
6
7
|
|
|
8
|
+
class Suite(BaseModel):
|
|
7
9
|
def __init__(self, title: str, description: Optional[str] = None, parent_id: Optional[str] = None):
|
|
8
10
|
self.title = title
|
|
9
11
|
self.description = description
|