qase-python-commons 3.3.1__tar.gz → 3.4.0__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.1 → qase_python_commons-3.4.0}/PKG-INFO +4 -4
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/pyproject.toml +3 -3
- qase_python_commons-3.4.0/src/qase/commons/client/api_v1_client.py +137 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/client/api_v2_client.py +3 -3
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/config.py +0 -5
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/__init__.py +2 -3
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/testops.py +0 -4
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/result.py +3 -89
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/reporters/core.py +9 -16
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/reporters/report.py +3 -3
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/reporters/testops.py +11 -15
- qase_python_commons-3.4.0/src/qase/commons/util/__init__.py +9 -0
- qase_python_commons-3.4.0/src/qase/commons/util/host_data.py +140 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase_python_commons.egg-info/PKG-INFO +4 -4
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase_python_commons.egg-info/SOURCES.txt +2 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase_python_commons.egg-info/requires.txt +2 -2
- qase_python_commons-3.3.1/src/qase/commons/client/api_v1_client.py +0 -302
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/README.md +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/setup.cfg +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/__init__.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/client/base_api_client.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/exceptions/reporter.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/loader.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/logger.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/attachment.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/basemodel.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/api.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/batch.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/connection.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/framework.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/plan.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/qaseconfig.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/report.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/run.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/relation.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/run.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/runtime.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/step.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/profilers/__init__.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/profilers/db.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/profilers/network.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/profilers/sleep.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/reporters/__init__.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/utils.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/validators/base.py +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase_python_commons.egg-info/dependency_links.txt +0 -0
- {qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase_python_commons.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qase-python-commons
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
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.0"
|
|
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.1 → qase_python_commons-3.4.0}/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),
|
|
@@ -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
|
]
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/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
|
|
|
@@ -71,7 +70,7 @@ class Result(BaseModel):
|
|
|
71
70
|
self.title: str = title
|
|
72
71
|
self.signature: str = signature
|
|
73
72
|
self.run_id: Optional[str] = None
|
|
74
|
-
self.
|
|
73
|
+
self.testops_ids: Optional[List[int]] = None
|
|
75
74
|
self.execution: Type[Execution] = Execution()
|
|
76
75
|
self.fields: Dict[Type[Field]] = {}
|
|
77
76
|
self.attachments: List[Attachment] = []
|
|
@@ -119,96 +118,11 @@ class Result(BaseModel):
|
|
|
119
118
|
return self.fields[name]
|
|
120
119
|
return None
|
|
121
120
|
|
|
122
|
-
def
|
|
123
|
-
return self.
|
|
121
|
+
def get_testops_ids(self) -> Optional[List[int]]:
|
|
122
|
+
return self.testops_ids
|
|
124
123
|
|
|
125
124
|
def get_duration(self) -> int:
|
|
126
125
|
return self.execution.duration
|
|
127
126
|
|
|
128
127
|
def set_run_id(self, run_id: str) -> None:
|
|
129
128
|
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
|
|
@@ -6,10 +6,12 @@ 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
|
|
|
13
|
+
from ..util import get_host_info
|
|
14
|
+
|
|
13
15
|
"""
|
|
14
16
|
CoreReporter is a facade for all reporters and it is used to initialize and manage them.
|
|
15
17
|
It is also used to pass configuration and logger to reporters, handle fallback logic and error handling.
|
|
@@ -17,7 +19,7 @@ from typing import Union, List
|
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class QaseCoreReporter:
|
|
20
|
-
def __init__(self, config: ConfigManager):
|
|
22
|
+
def __init__(self, config: ConfigManager, framework: Union[str, None] = None, reporter_name: Union[str, None] = None):
|
|
21
23
|
config.validate_config()
|
|
22
24
|
self.config = config.config
|
|
23
25
|
self.logger = Logger(self.config.debug)
|
|
@@ -30,6 +32,9 @@ class QaseCoreReporter:
|
|
|
30
32
|
|
|
31
33
|
self.logger.log_debug(f"Config: {self.config}")
|
|
32
34
|
|
|
35
|
+
host_data = get_host_info(framework, reporter_name)
|
|
36
|
+
self.logger.log_debug(f"Host data: {host_data}")
|
|
37
|
+
|
|
33
38
|
# Reading reporter mode from config file
|
|
34
39
|
mode = self.config.mode
|
|
35
40
|
|
|
@@ -79,20 +84,8 @@ class QaseCoreReporter:
|
|
|
79
84
|
try:
|
|
80
85
|
ts = time.time()
|
|
81
86
|
self.logger.log_debug(f"Adding result {result}")
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if ids is None:
|
|
85
|
-
int_result = InternalResult.convert_from_result(result)
|
|
86
|
-
self.reporter.add_result(int_result)
|
|
87
|
-
else:
|
|
88
|
-
first = True
|
|
89
|
-
for testops_id in ids:
|
|
90
|
-
int_result = InternalResult.convert_from_result(result, testops_id)
|
|
91
|
-
if not first:
|
|
92
|
-
int_result.execution.duration = 0
|
|
93
|
-
else:
|
|
94
|
-
first = False
|
|
95
|
-
self.reporter.add_result(int_result)
|
|
87
|
+
|
|
88
|
+
self.reporter.add_result(result)
|
|
96
89
|
|
|
97
90
|
self.logger.log_debug(f"Result {result.get_title()} added")
|
|
98
91
|
self.overhead += time.time() - ts
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/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,7 +40,7 @@ class QaseReport:
|
|
|
40
40
|
def complete_worker(self):
|
|
41
41
|
pass
|
|
42
42
|
|
|
43
|
-
def add_result(self, result:
|
|
43
|
+
def add_result(self, result: Result):
|
|
44
44
|
result.set_run_id(self.run_id)
|
|
45
45
|
for attachment in result.attachments:
|
|
46
46
|
self._persist_attachment(attachment)
|
|
@@ -91,7 +91,7 @@ class QaseReport:
|
|
|
91
91
|
self._persist_attachments_in_steps(step.steps)
|
|
92
92
|
|
|
93
93
|
# Method saves result to a file
|
|
94
|
-
def _store_result(self, result:
|
|
94
|
+
def _store_result(self, result: Result):
|
|
95
95
|
self._store_object(result, self.report_path + "/results/", result.id)
|
|
96
96
|
|
|
97
97
|
def _check_report_path(self):
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/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
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Optional, Dict
|
|
8
|
+
|
|
9
|
+
HostData = Dict[str, str]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def exec_command(command: str, default_value: str = "") -> str:
|
|
13
|
+
try:
|
|
14
|
+
result = subprocess.run(
|
|
15
|
+
command,
|
|
16
|
+
shell=True,
|
|
17
|
+
check=True,
|
|
18
|
+
stdout=subprocess.PIPE,
|
|
19
|
+
stderr=subprocess.PIPE,
|
|
20
|
+
text=True
|
|
21
|
+
)
|
|
22
|
+
return result.stdout.strip()
|
|
23
|
+
except Exception as e:
|
|
24
|
+
print(f"Error executing command '{command}': {e}", file=sys.stderr)
|
|
25
|
+
return default_value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_detailed_os_info() -> str:
|
|
29
|
+
system = platform.system().lower()
|
|
30
|
+
try:
|
|
31
|
+
if system == "windows":
|
|
32
|
+
return exec_command("ver")
|
|
33
|
+
elif system == "darwin":
|
|
34
|
+
return exec_command("sw_vers -productVersion")
|
|
35
|
+
else:
|
|
36
|
+
try:
|
|
37
|
+
if os.path.exists("/etc/os-release"):
|
|
38
|
+
with open("/etc/os-release", "r") as f:
|
|
39
|
+
os_release = f.read()
|
|
40
|
+
match = re.search(r'PRETTY_NAME="(.+?)"', os_release)
|
|
41
|
+
if match:
|
|
42
|
+
return match.group(1)
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
return platform.release()
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"Error getting detailed OS info: {e}", file=sys.stderr)
|
|
48
|
+
return platform.release()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_package_version(package_name: Optional[str]) -> Optional[str]:
|
|
52
|
+
if not package_name:
|
|
53
|
+
return ""
|
|
54
|
+
try:
|
|
55
|
+
pip_output = exec_command(f"pip show {package_name}")
|
|
56
|
+
if pip_output:
|
|
57
|
+
version_match = re.search(r'Version:\s*(\S+)', pip_output)
|
|
58
|
+
if version_match:
|
|
59
|
+
return version_match.group(1)
|
|
60
|
+
pip_list_output = exec_command("pip list --format=json")
|
|
61
|
+
if pip_list_output:
|
|
62
|
+
packages = json.loads(pip_list_output)
|
|
63
|
+
for pkg in packages:
|
|
64
|
+
if pkg.get("name", "").lower() == package_name.lower():
|
|
65
|
+
return pkg.get("version")
|
|
66
|
+
try:
|
|
67
|
+
import importlib.metadata
|
|
68
|
+
return importlib.metadata.version(package_name)
|
|
69
|
+
except (ImportError, importlib.metadata.PackageNotFoundError):
|
|
70
|
+
pass
|
|
71
|
+
return ""
|
|
72
|
+
except Exception as e:
|
|
73
|
+
print(f"Error getting version for package {package_name}: {e}", file=sys.stderr)
|
|
74
|
+
return ""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def find_package_in_requirements(package_name: Optional[str]) -> Optional[str]:
|
|
78
|
+
if not package_name:
|
|
79
|
+
return ""
|
|
80
|
+
try:
|
|
81
|
+
possible_req_files = ['requirements.txt', 'requirements/base.txt', 'requirements/dev.txt']
|
|
82
|
+
for req_file in possible_req_files:
|
|
83
|
+
if os.path.exists(req_file):
|
|
84
|
+
with open(req_file, 'r') as f:
|
|
85
|
+
for line in f:
|
|
86
|
+
if line.startswith(package_name):
|
|
87
|
+
version_match = re.search(r'[=<>~]{1,2}([\d.]+)', line)
|
|
88
|
+
if version_match:
|
|
89
|
+
return version_match.group(1)
|
|
90
|
+
return ""
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"Error reading requirements for {package_name}: {e}", file=sys.stderr)
|
|
93
|
+
return ""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_host_info(framework: Optional[str], reporter_name: Optional[str]) -> HostData:
|
|
97
|
+
try:
|
|
98
|
+
python_version = platform.python_version()
|
|
99
|
+
pip_version = exec_command("pip --version")
|
|
100
|
+
if pip_version:
|
|
101
|
+
pip_match = re.search(r'pip\s+(\S+)', pip_version)
|
|
102
|
+
pip_version = pip_match.group(1) if pip_match else ""
|
|
103
|
+
framework_version = get_package_version(framework) or find_package_in_requirements(framework) or ""
|
|
104
|
+
reporter_version = get_package_version(reporter_name) or find_package_in_requirements(reporter_name) or ""
|
|
105
|
+
commons_version = get_package_version("qase-python-commons") or find_package_in_requirements(
|
|
106
|
+
"qase-python-commons") or ""
|
|
107
|
+
api_client_version_1 = get_package_version("qase-api-client") or find_package_in_requirements(
|
|
108
|
+
"qase-api-client") or ""
|
|
109
|
+
api_client_version_2 = get_package_version("qase-api-v2-client") or find_package_in_requirements(
|
|
110
|
+
"qase-api-v2-client") or ""
|
|
111
|
+
return {
|
|
112
|
+
"system": platform.system().lower(),
|
|
113
|
+
"machineName": platform.node(),
|
|
114
|
+
"release": platform.release(),
|
|
115
|
+
"version": get_detailed_os_info(),
|
|
116
|
+
"arch": platform.machine(),
|
|
117
|
+
"python": python_version,
|
|
118
|
+
"pip": pip_version,
|
|
119
|
+
"framework": framework_version,
|
|
120
|
+
"reporter": reporter_version,
|
|
121
|
+
"commons": commons_version,
|
|
122
|
+
"apiClientV1": api_client_version_1,
|
|
123
|
+
"apiClientV2": api_client_version_2
|
|
124
|
+
}
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"Error getting host info: {e}", file=sys.stderr)
|
|
127
|
+
return {
|
|
128
|
+
"system": platform.system().lower(),
|
|
129
|
+
"machineName": platform.node(),
|
|
130
|
+
"release": platform.release(),
|
|
131
|
+
"version": "",
|
|
132
|
+
"arch": platform.machine(),
|
|
133
|
+
"python": "",
|
|
134
|
+
"pip": "",
|
|
135
|
+
"framework": "",
|
|
136
|
+
"reporter": "",
|
|
137
|
+
"commons": "",
|
|
138
|
+
"apiClientV1": "",
|
|
139
|
+
"apiClientV2": ""
|
|
140
|
+
}
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase_python_commons.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qase-python-commons
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
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"
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase_python_commons.egg-info/SOURCES.txt
RENAMED
|
@@ -34,6 +34,8 @@ src/qase/commons/reporters/__init__.py
|
|
|
34
34
|
src/qase/commons/reporters/core.py
|
|
35
35
|
src/qase/commons/reporters/report.py
|
|
36
36
|
src/qase/commons/reporters/testops.py
|
|
37
|
+
src/qase/commons/util/__init__.py
|
|
38
|
+
src/qase/commons/util/host_data.py
|
|
37
39
|
src/qase/commons/validators/base.py
|
|
38
40
|
src/qase_python_commons.egg-info/PKG-INFO
|
|
39
41
|
src/qase_python_commons.egg-info/SOURCES.txt
|
|
@@ -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.1 → qase_python_commons-3.4.0}/src/qase/commons/client/base_api_client.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/exceptions/reporter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/attachment.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/basemodel.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/api.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/batch.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/connection.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/framework.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/plan.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/qaseconfig.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/report.py
RENAMED
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/models/config/run.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/profilers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/profilers/network.py
RENAMED
|
File without changes
|
|
File without changes
|
{qase_python_commons-3.3.1 → qase_python_commons-3.4.0}/src/qase/commons/reporters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|