qase-python-commons 3.1.9__py3-none-any.whl → 4.1.9__py3-none-any.whl
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/__init__.py +3 -0
- qase/commons/client/api_v1_client.py +269 -175
- qase/commons/client/api_v2_client.py +163 -26
- qase/commons/client/base_api_client.py +23 -6
- qase/commons/config.py +162 -23
- qase/commons/logger.py +82 -13
- qase/commons/models/__init__.py +0 -2
- qase/commons/models/attachment.py +11 -8
- qase/commons/models/basemodel.py +12 -3
- qase/commons/models/config/framework.py +17 -0
- qase/commons/models/config/qaseconfig.py +34 -0
- qase/commons/models/config/run.py +19 -0
- qase/commons/models/config/testops.py +45 -3
- qase/commons/models/external_link.py +41 -0
- qase/commons/models/relation.py +16 -6
- qase/commons/models/result.py +16 -31
- qase/commons/models/run.py +17 -2
- qase/commons/models/runtime.py +9 -0
- qase/commons/models/step.py +45 -12
- qase/commons/profilers/__init__.py +4 -3
- qase/commons/profilers/db.py +965 -5
- qase/commons/reporters/core.py +60 -10
- qase/commons/reporters/report.py +11 -6
- qase/commons/reporters/testops.py +56 -27
- qase/commons/status_mapping/__init__.py +12 -0
- qase/commons/status_mapping/status_mapping.py +237 -0
- qase/commons/util/__init__.py +9 -0
- qase/commons/util/host_data.py +147 -0
- qase/commons/utils.py +95 -0
- {qase_python_commons-3.1.9.dist-info → qase_python_commons-4.1.9.dist-info}/METADATA +16 -11
- qase_python_commons-4.1.9.dist-info/RECORD +45 -0
- {qase_python_commons-3.1.9.dist-info → qase_python_commons-4.1.9.dist-info}/WHEEL +1 -1
- qase/commons/models/suite.py +0 -13
- qase_python_commons-3.1.9.dist-info/RECORD +0 -40
- {qase_python_commons-3.1.9.dist-info → qase_python_commons-4.1.9.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from typing import Dict
|
|
1
|
+
from typing import Dict, Union, Optional
|
|
2
2
|
|
|
3
3
|
import certifi
|
|
4
|
-
from qase.api_client_v2 import ResultsApi
|
|
4
|
+
from qase.api_client_v2 import ResultsApi, ResultCreateFields
|
|
5
5
|
from qase.api_client_v2.api_client import ApiClient
|
|
6
6
|
from qase.api_client_v2.configuration import Configuration
|
|
7
7
|
from qase.api_client_v2.models.create_results_request_v2 import CreateResultsRequestV2
|
|
@@ -16,14 +16,20 @@ from qase.api_client_v2.models.result_steps_type import ResultStepsType
|
|
|
16
16
|
from .api_v1_client import ApiV1Client
|
|
17
17
|
from .. import Logger
|
|
18
18
|
from ..exceptions.reporter import ReporterException
|
|
19
|
+
from ..models.config.framework import Video, Trace
|
|
19
20
|
from ..models import Attachment, Result
|
|
20
21
|
from ..models.config.qaseconfig import QaseConfig
|
|
21
22
|
from ..models.step import StepType, Step
|
|
23
|
+
from ..util.host_data import HostData
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class ApiV2Client(ApiV1Client):
|
|
25
|
-
def __init__(self, config: QaseConfig, logger: Logger
|
|
27
|
+
def __init__(self, config: QaseConfig, logger: Logger, host_data: Optional[HostData] = None,
|
|
28
|
+
framework: Union[str, None] = None, reporter_name: Union[str, None] = None):
|
|
26
29
|
ApiV1Client.__init__(self, config, logger)
|
|
30
|
+
self.host_data = host_data or {}
|
|
31
|
+
self.framework = framework
|
|
32
|
+
self.reporter_name = reporter_name
|
|
27
33
|
|
|
28
34
|
try:
|
|
29
35
|
self.logger.log_debug("Preparing API V2 client")
|
|
@@ -39,10 +45,84 @@ class ApiV2Client(ApiV1Client):
|
|
|
39
45
|
self.web = f'https://{host}'
|
|
40
46
|
|
|
41
47
|
self.client_v2 = ApiClient(configuration)
|
|
48
|
+
|
|
49
|
+
# Add X-Client and X-Platform headers
|
|
50
|
+
self._add_client_headers()
|
|
51
|
+
|
|
42
52
|
self.logger.log_debug("API V2 client prepared")
|
|
43
53
|
except Exception as e:
|
|
44
54
|
self.logger.log(f"Error at preparing API V2 client: {e}", "error")
|
|
45
55
|
raise ReporterException(e)
|
|
56
|
+
|
|
57
|
+
def _add_client_headers(self):
|
|
58
|
+
"""Add X-Client and X-Platform headers to API client"""
|
|
59
|
+
try:
|
|
60
|
+
# Use host_data passed from Core reporter
|
|
61
|
+
host_data = self.host_data
|
|
62
|
+
|
|
63
|
+
# Use framework and reporter_name for names in X-Client header
|
|
64
|
+
framework = self.framework
|
|
65
|
+
reporter_name = self.reporter_name
|
|
66
|
+
|
|
67
|
+
# Build X-Client header
|
|
68
|
+
# Format: reporter=qase-pytest;reporter_version=v1.0.0;framework=pytest;framework_version=7.0.0;client_version_v1=v1.0.0;client_version_v2=v2.0.0;core_version=v1.5.0
|
|
69
|
+
x_client_parts = []
|
|
70
|
+
|
|
71
|
+
if reporter_name:
|
|
72
|
+
x_client_parts.append(f"reporter={reporter_name}")
|
|
73
|
+
reporter_version = host_data.get('reporter', '')
|
|
74
|
+
if reporter_version:
|
|
75
|
+
x_client_parts.append(f"reporter_version={reporter_version}")
|
|
76
|
+
|
|
77
|
+
if framework:
|
|
78
|
+
x_client_parts.append(f"framework={framework}")
|
|
79
|
+
framework_version = host_data.get('framework', '')
|
|
80
|
+
if framework_version:
|
|
81
|
+
x_client_parts.append(f"framework_version={framework_version}")
|
|
82
|
+
|
|
83
|
+
client_v1_version = host_data.get('apiClientV1', '')
|
|
84
|
+
if client_v1_version:
|
|
85
|
+
x_client_parts.append(f"client_version_v1={client_v1_version}")
|
|
86
|
+
|
|
87
|
+
client_v2_version = host_data.get('apiClientV2', '')
|
|
88
|
+
if client_v2_version:
|
|
89
|
+
x_client_parts.append(f"client_version_v2={client_v2_version}")
|
|
90
|
+
|
|
91
|
+
core_version = host_data.get('commons', '')
|
|
92
|
+
if core_version:
|
|
93
|
+
x_client_parts.append(f"core_version={core_version}")
|
|
94
|
+
|
|
95
|
+
x_client = ";".join(x_client_parts)
|
|
96
|
+
|
|
97
|
+
# Build X-Platform header
|
|
98
|
+
# Format: os=Linux;arch=aarch64;python=3.9.0;pip=22.0.0
|
|
99
|
+
x_platform_parts = []
|
|
100
|
+
|
|
101
|
+
os_name = host_data.get('system', '')
|
|
102
|
+
if os_name:
|
|
103
|
+
x_platform_parts.append(f"os={os_name}")
|
|
104
|
+
|
|
105
|
+
arch = host_data.get('arch', '')
|
|
106
|
+
if arch:
|
|
107
|
+
x_platform_parts.append(f"arch={arch}")
|
|
108
|
+
|
|
109
|
+
python_version = host_data.get('python', '')
|
|
110
|
+
if python_version:
|
|
111
|
+
x_platform_parts.append(f"python={python_version}")
|
|
112
|
+
|
|
113
|
+
pip_version = host_data.get('pip', '')
|
|
114
|
+
if pip_version:
|
|
115
|
+
x_platform_parts.append(f"pip={pip_version}")
|
|
116
|
+
|
|
117
|
+
x_platform = ";".join(x_platform_parts)
|
|
118
|
+
|
|
119
|
+
# Add headers to client
|
|
120
|
+
if x_client:
|
|
121
|
+
self.client_v2.default_headers['X-Client'] = x_client
|
|
122
|
+
if x_platform:
|
|
123
|
+
self.client_v2.default_headers['X-Platform'] = x_platform
|
|
124
|
+
except Exception as e:
|
|
125
|
+
self.logger.log(f"Error adding client headers: {e}", "error")
|
|
46
126
|
|
|
47
127
|
def send_results(self, project_code: str, run_id: str, results: []) -> None:
|
|
48
128
|
api_results = ResultsApi(self.client_v2)
|
|
@@ -55,8 +135,15 @@ class ApiV2Client(ApiV1Client):
|
|
|
55
135
|
def _prepare_result(self, project_code: str, result: Result) -> ResultCreate:
|
|
56
136
|
attached = []
|
|
57
137
|
if result.attachments:
|
|
58
|
-
|
|
59
|
-
|
|
138
|
+
# Collect all attachments that should be uploaded
|
|
139
|
+
attachments_to_upload = [
|
|
140
|
+
attachment for attachment in result.attachments
|
|
141
|
+
if not self.__should_skip_attachment(attachment, result)
|
|
142
|
+
]
|
|
143
|
+
if attachments_to_upload:
|
|
144
|
+
attach_id = self._upload_attachment(project_code, attachments_to_upload)
|
|
145
|
+
if attach_id:
|
|
146
|
+
attached.extend(attach_id)
|
|
60
147
|
|
|
61
148
|
steps = []
|
|
62
149
|
for step in result.steps:
|
|
@@ -71,27 +158,29 @@ class ApiV2Client(ApiV1Client):
|
|
|
71
158
|
result_model_v2 = ResultCreate(
|
|
72
159
|
title=result.get_title(),
|
|
73
160
|
signature=result.signature,
|
|
74
|
-
|
|
75
|
-
execution=ResultExecution(
|
|
76
|
-
|
|
161
|
+
testops_ids=result.get_testops_ids(),
|
|
162
|
+
execution=ResultExecution(status=result.execution.status, duration=result.execution.duration,
|
|
163
|
+
start_time=result.execution.start_time, end_time=result.execution.end_time,
|
|
77
164
|
stacktrace=result.execution.stacktrace, thread=result.execution.thread),
|
|
78
|
-
fields=result.fields,
|
|
165
|
+
fields=ResultCreateFields.from_dict(result.fields),
|
|
79
166
|
attachments=[attach.hash for attach in attached],
|
|
80
167
|
steps=steps,
|
|
81
|
-
|
|
82
|
-
params=result.params,
|
|
83
|
-
|
|
168
|
+
steps_type=ResultStepsType.CLASSIC,
|
|
169
|
+
params=result.params if not self.config.exclude_params else {key: value for key, value in result.params.items() if key not in self.config.exclude_params},
|
|
170
|
+
param_groups=result.param_groups,
|
|
84
171
|
message=result.message,
|
|
172
|
+
defect=self.config.testops.defect,
|
|
85
173
|
)
|
|
86
174
|
|
|
87
|
-
if result.
|
|
175
|
+
if result.relations is not None and result.relations.suite is not None and len(
|
|
176
|
+
result.relations.suite.data) != 0:
|
|
88
177
|
data = []
|
|
89
178
|
root_suite = self.config.root_suite
|
|
90
179
|
if root_suite:
|
|
91
180
|
data.append(RelationSuiteItem(title=root_suite))
|
|
92
181
|
|
|
93
|
-
for
|
|
94
|
-
data.append(RelationSuiteItem(title=
|
|
182
|
+
for raw in result.relations.suite.data:
|
|
183
|
+
data.append(RelationSuiteItem(title=raw.title))
|
|
95
184
|
|
|
96
185
|
result_model_v2.relations = ResultRelations(suite=RelationSuite(data=data))
|
|
97
186
|
|
|
@@ -104,8 +193,16 @@ class ApiV2Client(ApiV1Client):
|
|
|
104
193
|
|
|
105
194
|
try:
|
|
106
195
|
prepared_step = {'execution': {}, 'data': {}, 'steps': []}
|
|
107
|
-
|
|
108
|
-
|
|
196
|
+
if step.execution.status == 'untested':
|
|
197
|
+
prepared_step['execution']['status'] = ResultStepStatus('skipped')
|
|
198
|
+
prepared_step['execution']['duration'] = 0
|
|
199
|
+
prepared_step['execution']['start_time'] = None
|
|
200
|
+
prepared_step['execution']['end_time'] = None
|
|
201
|
+
else:
|
|
202
|
+
prepared_step['execution']['status'] = ResultStepStatus(step.execution.status)
|
|
203
|
+
prepared_step['execution']['duration'] = step.execution.duration
|
|
204
|
+
prepared_step['execution']['start_time'] = step.execution.start_time
|
|
205
|
+
prepared_step['execution']['end_time'] = step.execution.end_time
|
|
109
206
|
|
|
110
207
|
if step.step_type == StepType.TEXT:
|
|
111
208
|
prepared_step['data']['action'] = step.data.action
|
|
@@ -116,31 +213,60 @@ class ApiV2Client(ApiV1Client):
|
|
|
116
213
|
prepared_step['data']['action'] = step.data.request_method + " " + step.data.request_url
|
|
117
214
|
|
|
118
215
|
if step.data.request_body:
|
|
119
|
-
step.
|
|
216
|
+
step.add_attachment(
|
|
120
217
|
Attachment(file_name='request_body.txt', content=step.data.request_body, mime_type='text/plain',
|
|
121
218
|
temporary=True))
|
|
122
219
|
if step.data.request_headers:
|
|
123
|
-
step.
|
|
220
|
+
step.add_attachment(
|
|
124
221
|
Attachment(file_name='request_headers.txt', content=step.data.request_headers,
|
|
125
222
|
mime_type='text/plain', temporary=True))
|
|
126
223
|
if step.data.response_body:
|
|
127
|
-
step.
|
|
128
|
-
|
|
224
|
+
step.add_attachment(Attachment(file_name='response_body.txt', content=step.data.response_body,
|
|
225
|
+
mime_type='text/plain', temporary=True))
|
|
129
226
|
if step.data.response_headers:
|
|
130
|
-
step.
|
|
227
|
+
step.add_attachment(
|
|
131
228
|
Attachment(file_name='response_headers.txt', content=step.data.response_headers,
|
|
132
229
|
mime_type='text/plain', temporary=True))
|
|
133
230
|
|
|
134
231
|
if step.step_type == StepType.GHERKIN:
|
|
135
|
-
|
|
232
|
+
action = step.data.keyword
|
|
233
|
+
if step.data.keyword != step.data.name:
|
|
234
|
+
action += " " + step.data.name
|
|
235
|
+
prepared_step['data']['action'] = action
|
|
236
|
+
if step.data.data:
|
|
237
|
+
prepared_step['data']['input_data'] = step.data.data
|
|
136
238
|
|
|
137
239
|
if step.step_type == StepType.SLEEP:
|
|
138
240
|
prepared_step['data']['action'] = f"Sleep for {step.data.duration} seconds"
|
|
139
241
|
|
|
140
|
-
if step.
|
|
242
|
+
if step.step_type == StepType.DB_QUERY:
|
|
243
|
+
# Format database query as action
|
|
244
|
+
action_parts = []
|
|
245
|
+
if step.data.database_type:
|
|
246
|
+
action_parts.append(f"[{step.data.database_type}]")
|
|
247
|
+
action_parts.append(step.data.query)
|
|
248
|
+
prepared_step['data']['action'] = " ".join(action_parts)
|
|
249
|
+
|
|
250
|
+
# Add expected_result if available
|
|
251
|
+
if step.data.expected_result:
|
|
252
|
+
prepared_step['data']['expected_result'] = step.data.expected_result
|
|
253
|
+
|
|
254
|
+
# Add connection info and execution time as input_data
|
|
255
|
+
info_parts = []
|
|
256
|
+
if step.data.connection_info:
|
|
257
|
+
info_parts.append(f"Connection: {step.data.connection_info}")
|
|
258
|
+
if step.data.execution_time is not None:
|
|
259
|
+
info_parts.append(f"Execution time: {step.data.execution_time:.3f}s")
|
|
260
|
+
if step.data.rows_affected is not None:
|
|
261
|
+
info_parts.append(f"Rows affected: {step.data.rows_affected}")
|
|
262
|
+
if info_parts:
|
|
263
|
+
prepared_step['data']['input_data'] = " | ".join(info_parts)
|
|
264
|
+
|
|
265
|
+
if step.execution.attachments:
|
|
141
266
|
uploaded_attachments = []
|
|
142
|
-
|
|
143
|
-
|
|
267
|
+
attach_id = self._upload_attachment(project_code, step.execution.attachments)
|
|
268
|
+
if attach_id:
|
|
269
|
+
uploaded_attachments.extend(attach_id)
|
|
144
270
|
|
|
145
271
|
prepared_step['execution']['attachments'] = [attach.hash for attach in uploaded_attachments]
|
|
146
272
|
|
|
@@ -152,3 +278,14 @@ class ApiV2Client(ApiV1Client):
|
|
|
152
278
|
except Exception as e:
|
|
153
279
|
self.logger.log(f"Error at preparing step: {e}", "error")
|
|
154
280
|
raise ReporterException(e)
|
|
281
|
+
|
|
282
|
+
def __should_skip_attachment(self, attachment, result):
|
|
283
|
+
if (self.config.framework.playwright.video == Video.failed and
|
|
284
|
+
result.execution.status != 'failed' and
|
|
285
|
+
attachment.file_name == 'video.webm'):
|
|
286
|
+
return True
|
|
287
|
+
if (self.config.framework.playwright.trace == Trace.failed and
|
|
288
|
+
result.execution.status != 'failed' and
|
|
289
|
+
attachment.file_name == 'trace.zip'):
|
|
290
|
+
return True
|
|
291
|
+
return False
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import abc
|
|
2
|
-
from typing import Union
|
|
2
|
+
from typing import Union, List
|
|
3
3
|
|
|
4
4
|
from qase.api_client_v1 import Project, AttachmentGet
|
|
5
|
+
from qase.api_client_v1.models.attachmentupload import Attachmentupload
|
|
5
6
|
|
|
6
7
|
from ..models import Attachment
|
|
7
8
|
|
|
@@ -30,7 +31,7 @@ class BaseApiClient(abc.ABC):
|
|
|
30
31
|
pass
|
|
31
32
|
|
|
32
33
|
@abc.abstractmethod
|
|
33
|
-
def complete_run(self, project_code: str, run_id:
|
|
34
|
+
def complete_run(self, project_code: str, run_id: int) -> None:
|
|
34
35
|
"""
|
|
35
36
|
Complete a test run in Qase TestOps
|
|
36
37
|
|
|
@@ -41,13 +42,18 @@ class BaseApiClient(abc.ABC):
|
|
|
41
42
|
pass
|
|
42
43
|
|
|
43
44
|
@abc.abstractmethod
|
|
44
|
-
def _upload_attachment(self, project_code: str, attachment: Attachment) ->
|
|
45
|
+
def _upload_attachment(self, project_code: str, attachment: Union[Attachment, List[Attachment]]) -> List[Attachmentupload]:
|
|
45
46
|
"""
|
|
46
|
-
Upload
|
|
47
|
+
Upload one or multiple attachments to Qase TestOps with batching support.
|
|
48
|
+
|
|
49
|
+
The method automatically groups attachments into batches respecting the following limits:
|
|
50
|
+
- Up to 32 MB per file
|
|
51
|
+
- Up to 128 MB per single request
|
|
52
|
+
- Up to 20 files per single request
|
|
47
53
|
|
|
48
54
|
:param project_code: project code
|
|
49
|
-
:param attachment: attachment
|
|
50
|
-
:return:
|
|
55
|
+
:param attachment: single attachment or list of attachments
|
|
56
|
+
:return: list of uploaded attachment data
|
|
51
57
|
"""
|
|
52
58
|
pass
|
|
53
59
|
|
|
@@ -86,3 +92,14 @@ class BaseApiClient(abc.ABC):
|
|
|
86
92
|
:return: None
|
|
87
93
|
"""
|
|
88
94
|
pass
|
|
95
|
+
|
|
96
|
+
@abc.abstractmethod
|
|
97
|
+
def enable_public_report(self, project_code: str, run_id: int) -> str:
|
|
98
|
+
"""
|
|
99
|
+
Enable public report for a test run and return the public link
|
|
100
|
+
|
|
101
|
+
:param project_code: project code
|
|
102
|
+
:param run_id: test run id
|
|
103
|
+
:return: public report link or None if failed
|
|
104
|
+
"""
|
|
105
|
+
pass
|
qase/commons/config.py
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
|
-
from .logger import Logger
|
|
3
|
+
from .logger import Logger, LoggingOptions
|
|
4
4
|
from .models.config.qaseconfig import QaseConfig, Mode
|
|
5
|
+
from .utils import QaseUtils
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class ConfigManager:
|
|
8
9
|
|
|
9
10
|
def __init__(self, config_file='./qase.config.json'):
|
|
10
|
-
self.logger = Logger()
|
|
11
11
|
self.__config_file = config_file
|
|
12
12
|
self.config = QaseConfig()
|
|
13
13
|
|
|
14
|
+
# Initialize temporary logger for error handling during config loading
|
|
15
|
+
self.logger = Logger(debug=False)
|
|
16
|
+
|
|
14
17
|
self.__load_file_config()
|
|
15
18
|
self.__load_env_config()
|
|
19
|
+
|
|
20
|
+
# Re-initialize logger with proper logging options after config is loaded
|
|
21
|
+
logging_options = LoggingOptions(
|
|
22
|
+
console=self.config.logging.console if self.config.logging.console is not None else True,
|
|
23
|
+
file=self.config.logging.file if self.config.logging.file is not None else self.config.debug
|
|
24
|
+
)
|
|
25
|
+
self.logger = Logger(debug=self.config.debug, logging_options=logging_options)
|
|
16
26
|
|
|
17
27
|
def validate_config(self):
|
|
18
28
|
errors: list[str] = []
|
|
@@ -53,15 +63,26 @@ class ConfigManager:
|
|
|
53
63
|
if config.get("profilers"):
|
|
54
64
|
self.config.set_profilers(config.get("profilers"))
|
|
55
65
|
|
|
56
|
-
if config.get("debug"):
|
|
66
|
+
if config.get("debug") is not None:
|
|
57
67
|
self.config.set_debug(
|
|
58
68
|
config.get("debug")
|
|
59
69
|
)
|
|
60
70
|
|
|
71
|
+
if config.get("excludeParams"):
|
|
72
|
+
self.config.set_exclude_params(
|
|
73
|
+
config.get("excludeParams")
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if config.get("statusMapping"):
|
|
77
|
+
self.config.set_status_mapping(
|
|
78
|
+
config.get("statusMapping")
|
|
79
|
+
)
|
|
80
|
+
|
|
61
81
|
if config.get("executionPlan"):
|
|
62
82
|
execution_plan = config.get("executionPlan")
|
|
63
83
|
if execution_plan.get("path"):
|
|
64
|
-
self.config.execution_plan.set_path(
|
|
84
|
+
self.config.execution_plan.set_path(
|
|
85
|
+
execution_plan.get("path"))
|
|
65
86
|
|
|
66
87
|
if config.get("testops"):
|
|
67
88
|
testops = config.get("testops")
|
|
@@ -70,24 +91,22 @@ class ConfigManager:
|
|
|
70
91
|
api = testops.get("api")
|
|
71
92
|
|
|
72
93
|
if api.get("host"):
|
|
73
|
-
self.config.testops.api.set_host(
|
|
94
|
+
self.config.testops.api.set_host(
|
|
95
|
+
api.get("host"))
|
|
74
96
|
|
|
75
97
|
if api.get("token"):
|
|
76
|
-
self.config.testops.api.set_token(
|
|
98
|
+
self.config.testops.api.set_token(
|
|
99
|
+
api.get("token"))
|
|
77
100
|
|
|
78
101
|
if testops.get("project"):
|
|
79
|
-
self.config.testops.set_project(
|
|
102
|
+
self.config.testops.set_project(
|
|
103
|
+
testops.get("project"))
|
|
80
104
|
|
|
81
|
-
if testops.get("defect"):
|
|
105
|
+
if testops.get("defect") is not None:
|
|
82
106
|
self.config.testops.set_defect(
|
|
83
107
|
testops.get("defect")
|
|
84
108
|
)
|
|
85
109
|
|
|
86
|
-
if testops.get("useV2"):
|
|
87
|
-
self.config.testops.set_use_v2(
|
|
88
|
-
testops.get("useV2")
|
|
89
|
-
)
|
|
90
|
-
|
|
91
110
|
if testops.get("plan"):
|
|
92
111
|
plan = testops.get("plan")
|
|
93
112
|
|
|
@@ -101,21 +120,59 @@ class ConfigManager:
|
|
|
101
120
|
self.config.testops.run.set_id(run.get("id"))
|
|
102
121
|
|
|
103
122
|
if run.get("title"):
|
|
104
|
-
self.config.testops.run.set_title(
|
|
123
|
+
self.config.testops.run.set_title(
|
|
124
|
+
run.get("title"))
|
|
105
125
|
|
|
106
126
|
if run.get("description"):
|
|
107
|
-
self.config.testops.run.set_description(
|
|
127
|
+
self.config.testops.run.set_description(
|
|
128
|
+
run.get("description"))
|
|
108
129
|
|
|
109
|
-
if run.get("complete"):
|
|
130
|
+
if run.get("complete") is not None:
|
|
110
131
|
self.config.testops.run.set_complete(
|
|
111
132
|
run.get("complete")
|
|
112
133
|
)
|
|
113
134
|
|
|
135
|
+
if run.get("tags"):
|
|
136
|
+
self.config.testops.run.set_tags(
|
|
137
|
+
[tag.strip() for tag in run.get("tags")])
|
|
138
|
+
|
|
139
|
+
if run.get("externalLink"):
|
|
140
|
+
self.config.testops.run.set_external_link(
|
|
141
|
+
run.get("externalLink"))
|
|
142
|
+
|
|
114
143
|
if testops.get("batch"):
|
|
115
144
|
batch = testops.get("batch")
|
|
116
145
|
|
|
117
146
|
if batch.get("size"):
|
|
118
|
-
self.config.testops.batch.set_size(
|
|
147
|
+
self.config.testops.batch.set_size(
|
|
148
|
+
batch.get("size"))
|
|
149
|
+
|
|
150
|
+
if testops.get("configurations"):
|
|
151
|
+
configurations = testops.get("configurations")
|
|
152
|
+
|
|
153
|
+
if configurations.get("values"):
|
|
154
|
+
values = configurations.get("values")
|
|
155
|
+
for value in values:
|
|
156
|
+
if value.get("name") and value.get("value"):
|
|
157
|
+
self.config.testops.configurations.add_value(
|
|
158
|
+
value.get("name"), value.get("value"))
|
|
159
|
+
|
|
160
|
+
if configurations.get("createIfNotExists") is not None:
|
|
161
|
+
self.config.testops.configurations.set_create_if_not_exists(
|
|
162
|
+
configurations.get("createIfNotExists"))
|
|
163
|
+
|
|
164
|
+
if testops.get("statusFilter"):
|
|
165
|
+
status_filter = testops.get("statusFilter")
|
|
166
|
+
if isinstance(status_filter, list):
|
|
167
|
+
self.config.testops.set_status_filter(status_filter)
|
|
168
|
+
elif isinstance(status_filter, str):
|
|
169
|
+
# Parse comma-separated string
|
|
170
|
+
self.config.testops.set_status_filter([s.strip() for s in status_filter.split(',')])
|
|
171
|
+
|
|
172
|
+
if testops.get("showPublicReportLink") is not None:
|
|
173
|
+
self.config.testops.set_show_public_report_link(
|
|
174
|
+
testops.get("showPublicReportLink")
|
|
175
|
+
)
|
|
119
176
|
|
|
120
177
|
if config.get("report"):
|
|
121
178
|
report = config.get("report")
|
|
@@ -127,7 +184,8 @@ class ConfigManager:
|
|
|
127
184
|
connection = report.get("connection")
|
|
128
185
|
|
|
129
186
|
if connection.get("path"):
|
|
130
|
-
self.config.report.connection.set_path(
|
|
187
|
+
self.config.report.connection.set_path(
|
|
188
|
+
connection.get("path"))
|
|
131
189
|
|
|
132
190
|
if connection.get("format"):
|
|
133
191
|
self.config.report.connection.set_format(
|
|
@@ -140,11 +198,27 @@ class ConfigManager:
|
|
|
140
198
|
if framework.get("pytest"):
|
|
141
199
|
pytest = framework.get("pytest")
|
|
142
200
|
|
|
143
|
-
if pytest.get("captureLogs"):
|
|
201
|
+
if pytest.get("captureLogs") is not None:
|
|
144
202
|
self.config.framework.pytest.set_capture_logs(
|
|
145
203
|
pytest.get("captureLogs")
|
|
146
204
|
)
|
|
147
205
|
|
|
206
|
+
if pytest.get("xfailStatus"):
|
|
207
|
+
xfail_status = pytest.get("xfailStatus")
|
|
208
|
+
|
|
209
|
+
if xfail_status.get("xfail"):
|
|
210
|
+
self.config.framework.pytest.xfail_status.set_xfail(
|
|
211
|
+
xfail_status.get("xfail")
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if xfail_status.get("xpass"):
|
|
215
|
+
self.config.framework.pytest.xfail_status.set_xpass(
|
|
216
|
+
xfail_status.get("xpass")
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if config.get("logging"):
|
|
220
|
+
self.config.set_logging(config.get("logging"))
|
|
221
|
+
|
|
148
222
|
except Exception as e:
|
|
149
223
|
self.logger.log("Failed to load config from file", "error")
|
|
150
224
|
|
|
@@ -169,6 +243,23 @@ class ConfigManager:
|
|
|
169
243
|
if key == 'QASE_DEBUG':
|
|
170
244
|
self.config.set_debug(value)
|
|
171
245
|
|
|
246
|
+
if key == 'QASE_EXCLUDE_PARAMS':
|
|
247
|
+
self.config.set_exclude_params(
|
|
248
|
+
[param.strip() for param in value.split(',')])
|
|
249
|
+
|
|
250
|
+
if key == 'QASE_STATUS_MAPPING':
|
|
251
|
+
# Parse status mapping from environment variable
|
|
252
|
+
# Format: "source1=target1,source2=target2"
|
|
253
|
+
if value:
|
|
254
|
+
mapping_dict = {}
|
|
255
|
+
pairs = value.split(',')
|
|
256
|
+
for pair in pairs:
|
|
257
|
+
pair = pair.strip()
|
|
258
|
+
if pair and '=' in pair:
|
|
259
|
+
source_status, target_status = pair.split('=', 1)
|
|
260
|
+
mapping_dict[source_status.strip()] = target_status.strip()
|
|
261
|
+
self.config.set_status_mapping(mapping_dict)
|
|
262
|
+
|
|
172
263
|
if key == 'QASE_EXECUTION_PLAN_PATH':
|
|
173
264
|
self.config.execution_plan.set_path(value)
|
|
174
265
|
|
|
@@ -185,10 +276,10 @@ class ConfigManager:
|
|
|
185
276
|
self.config.testops.set_defect(value)
|
|
186
277
|
|
|
187
278
|
if key == 'QASE_TESTOPS_PLAN_ID':
|
|
188
|
-
self.config.testops.plan.set_id(value)
|
|
279
|
+
self.config.testops.plan.set_id(int(value.strip()))
|
|
189
280
|
|
|
190
281
|
if key == 'QASE_TESTOPS_RUN_ID':
|
|
191
|
-
self.config.testops.run.set_id(value)
|
|
282
|
+
self.config.testops.run.set_id(int(value.strip()))
|
|
192
283
|
|
|
193
284
|
if key == 'QASE_TESTOPS_RUN_TITLE':
|
|
194
285
|
self.config.testops.run.set_title(value)
|
|
@@ -199,8 +290,44 @@ class ConfigManager:
|
|
|
199
290
|
if key == 'QASE_TESTOPS_RUN_COMPLETE':
|
|
200
291
|
self.config.testops.run.set_complete(value)
|
|
201
292
|
|
|
293
|
+
if key == 'QASE_TESTOPS_RUN_TAGS':
|
|
294
|
+
self.config.testops.run.set_tags(
|
|
295
|
+
[tag.strip() for tag in value.split(',')])
|
|
296
|
+
|
|
297
|
+
if key == 'QASE_TESTOPS_RUN_EXTERNAL_LINK_TYPE':
|
|
298
|
+
if not self.config.testops.run.external_link:
|
|
299
|
+
from .models.external_link import ExternalLinkConfig
|
|
300
|
+
self.config.testops.run.external_link = ExternalLinkConfig()
|
|
301
|
+
self.config.testops.run.external_link.set_type(value)
|
|
302
|
+
|
|
303
|
+
if key == 'QASE_TESTOPS_RUN_EXTERNAL_LINK_URL':
|
|
304
|
+
if not self.config.testops.run.external_link:
|
|
305
|
+
from .models.external_link import ExternalLinkConfig
|
|
306
|
+
self.config.testops.run.external_link = ExternalLinkConfig()
|
|
307
|
+
self.config.testops.run.external_link.set_link(value)
|
|
308
|
+
|
|
202
309
|
if key == 'QASE_TESTOPS_BATCH_SIZE':
|
|
203
|
-
self.config.testops.batch.set_size(value)
|
|
310
|
+
self.config.testops.batch.set_size(int(value.strip()))
|
|
311
|
+
|
|
312
|
+
if key == 'QASE_TESTOPS_CONFIGURATIONS_VALUES':
|
|
313
|
+
# Parse configurations from environment variable
|
|
314
|
+
# Format: "group1=value1,group2=value2"
|
|
315
|
+
if value:
|
|
316
|
+
config_pairs = value.split(',')
|
|
317
|
+
for pair in config_pairs:
|
|
318
|
+
if '=' in pair:
|
|
319
|
+
name, config_value = pair.split('=', 1)
|
|
320
|
+
self.config.testops.configurations.add_value(name.strip(), config_value.strip())
|
|
321
|
+
|
|
322
|
+
if key == 'QASE_TESTOPS_CONFIGURATIONS_CREATE_IF_NOT_EXISTS':
|
|
323
|
+
self.config.testops.configurations.set_create_if_not_exists(value)
|
|
324
|
+
|
|
325
|
+
if key == 'QASE_TESTOPS_STATUS_FILTER':
|
|
326
|
+
# Parse comma-separated string
|
|
327
|
+
self.config.testops.set_status_filter([s.strip() for s in value.split(',')])
|
|
328
|
+
|
|
329
|
+
if key == 'QASE_TESTOPS_SHOW_PUBLIC_REPORT_LINK':
|
|
330
|
+
self.config.testops.set_show_public_report_link(value)
|
|
204
331
|
|
|
205
332
|
if key == 'QASE_REPORT_DRIVER':
|
|
206
333
|
self.config.report.set_driver(value)
|
|
@@ -214,5 +341,17 @@ class ConfigManager:
|
|
|
214
341
|
if key == 'QASE_PYTEST_CAPTURE_LOGS':
|
|
215
342
|
self.config.framework.pytest.set_capture_logs(value)
|
|
216
343
|
|
|
344
|
+
if key == 'QASE_PYTEST_XFAIL_STATUS_XFAIL':
|
|
345
|
+
self.config.framework.pytest.xfail_status.set_xfail(value)
|
|
346
|
+
|
|
347
|
+
if key == 'QASE_PYTEST_XFAIL_STATUS_XPASS':
|
|
348
|
+
self.config.framework.pytest.xfail_status.set_xpass(value)
|
|
349
|
+
|
|
350
|
+
if key == 'QASE_LOGGING_CONSOLE':
|
|
351
|
+
self.config.logging.set_console(QaseUtils.parse_bool(value))
|
|
352
|
+
|
|
353
|
+
if key == 'QASE_LOGGING_FILE':
|
|
354
|
+
self.config.logging.set_file(QaseUtils.parse_bool(value))
|
|
355
|
+
|
|
217
356
|
except Exception as e:
|
|
218
|
-
self.logger.log("Failed to load config from env vars {e}", "error")
|
|
357
|
+
self.logger.log(f"Failed to load config from env vars {e}", "error")
|