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
qase/__init__.py
ADDED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
from
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
from typing import Union, List
|
|
2
3
|
|
|
3
4
|
import certifi
|
|
4
5
|
from qase.api_client_v1 import ApiClient, ProjectsApi, Project, EnvironmentsApi, RunsApi, AttachmentsApi, \
|
|
5
|
-
AttachmentGet, RunCreate,
|
|
6
|
+
AttachmentGet, RunCreate, ConfigurationsApi, ConfigurationCreate, ConfigurationGroupCreate, RunPublic
|
|
7
|
+
from qase.api_client_v1.models.attachmentupload import Attachmentupload
|
|
6
8
|
from qase.api_client_v1.configuration import Configuration
|
|
7
9
|
from .. import Logger
|
|
8
10
|
from .base_api_client import BaseApiClient
|
|
9
11
|
from ..exceptions.reporter import ReporterException
|
|
10
|
-
from ..models import Attachment
|
|
12
|
+
from ..models import Attachment
|
|
11
13
|
from ..models.config.framework import Video, Trace
|
|
12
14
|
from ..models.config.qaseconfig import QaseConfig
|
|
13
|
-
from ..models.
|
|
15
|
+
from ..models.config.testops import ConfigurationValue
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class ApiV1Client(BaseApiClient):
|
|
@@ -66,7 +68,74 @@ class ApiV1Client(BaseApiClient):
|
|
|
66
68
|
self.logger.log("Exception when calling EnvironmentsApi->get_environments: %s\n" % e, "error")
|
|
67
69
|
raise ReporterException(e)
|
|
68
70
|
|
|
69
|
-
def
|
|
71
|
+
def get_configurations(self, project_code: str):
|
|
72
|
+
"""Get all configurations for the project"""
|
|
73
|
+
try:
|
|
74
|
+
self.logger.log_debug(f"Getting configurations for project {project_code}")
|
|
75
|
+
api_instance = ConfigurationsApi(self.client)
|
|
76
|
+
response = api_instance.get_configurations(code=project_code)
|
|
77
|
+
if hasattr(response, 'result') and hasattr(response.result, 'entities'):
|
|
78
|
+
return response.result.entities
|
|
79
|
+
return []
|
|
80
|
+
except Exception as e:
|
|
81
|
+
self.logger.log(f"Exception when calling ConfigurationsApi->get_configurations: {e}", "error")
|
|
82
|
+
return []
|
|
83
|
+
|
|
84
|
+
def find_or_create_configuration(self, project_code: str, config_value: ConfigurationValue) -> Union[int, None]:
|
|
85
|
+
"""Find existing configuration or create new one if createIfNotExists is True"""
|
|
86
|
+
try:
|
|
87
|
+
configurations = self.get_configurations(project_code)
|
|
88
|
+
|
|
89
|
+
# Search for existing configuration
|
|
90
|
+
for group in configurations:
|
|
91
|
+
if hasattr(group, 'configurations'):
|
|
92
|
+
for config in group.configurations:
|
|
93
|
+
# API returns configurations with 'title' field, not 'name' and 'value'
|
|
94
|
+
# We need to match group.title with config_value.name and config.title with config_value.value
|
|
95
|
+
config_title = config.title if hasattr(config, 'title') else 'No title'
|
|
96
|
+
group_title = group.title if hasattr(group, 'title') else 'No title'
|
|
97
|
+
|
|
98
|
+
if (group_title == config_value.name and config_title == config_value.value):
|
|
99
|
+
return config.id
|
|
100
|
+
|
|
101
|
+
# Configuration not found
|
|
102
|
+
if not self.config.testops.configurations.create_if_not_exists:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
# Create new configuration
|
|
106
|
+
# First, try to find existing group or create new one
|
|
107
|
+
group_id = None
|
|
108
|
+
for group in configurations:
|
|
109
|
+
if hasattr(group, 'title') and group.title == config_value.name:
|
|
110
|
+
group_id = group.id
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
if group_id is None:
|
|
114
|
+
# Create new group
|
|
115
|
+
group_create = ConfigurationGroupCreate(title=config_value.name)
|
|
116
|
+
group_response = ConfigurationsApi(self.client).create_configuration_group(
|
|
117
|
+
code=project_code,
|
|
118
|
+
configuration_group_create=group_create
|
|
119
|
+
)
|
|
120
|
+
group_id = group_response.result.id
|
|
121
|
+
|
|
122
|
+
# Create configuration in the group
|
|
123
|
+
config_create = ConfigurationCreate(
|
|
124
|
+
title=config_value.value,
|
|
125
|
+
group_id=group_id
|
|
126
|
+
)
|
|
127
|
+
config_response = ConfigurationsApi(self.client).create_configuration(
|
|
128
|
+
code=project_code,
|
|
129
|
+
configuration_create=config_create
|
|
130
|
+
)
|
|
131
|
+
config_id = config_response.result.id
|
|
132
|
+
return config_id
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
self.logger.log(f"Error at finding/creating configuration {config_value.name}={config_value.value}: {e}", "error")
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def complete_run(self, project_code: str, run_id: int) -> None:
|
|
70
139
|
api_runs = RunsApi(self.client)
|
|
71
140
|
self.logger.log_debug(f"Completing run {run_id}")
|
|
72
141
|
res = api_runs.get_run(project_code, run_id).result
|
|
@@ -80,35 +149,158 @@ class ApiV1Client(BaseApiClient):
|
|
|
80
149
|
self.logger.log(f"Error at completing run {run_id}: {e}", "error")
|
|
81
150
|
raise ReporterException(e)
|
|
82
151
|
|
|
83
|
-
def _upload_attachment(self, project_code: str, attachment: Attachment) ->
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
152
|
+
def _upload_attachment(self, project_code: str, attachment: Union[Attachment, List[Attachment]]) -> List[Attachmentupload]:
|
|
153
|
+
"""
|
|
154
|
+
Upload one or multiple attachments to Qase TestOps with batching support.
|
|
155
|
+
|
|
156
|
+
The method automatically groups attachments into batches respecting the following limits:
|
|
157
|
+
- Up to 32 MB per file
|
|
158
|
+
- Up to 128 MB per single request
|
|
159
|
+
- Up to 20 files per single request
|
|
160
|
+
|
|
161
|
+
:param project_code: project code
|
|
162
|
+
:param attachment: single attachment or list of attachments
|
|
163
|
+
:return: list of uploaded attachment data
|
|
164
|
+
"""
|
|
165
|
+
# Normalize input to list
|
|
166
|
+
attachments = attachment if isinstance(attachment, list) else [attachment]
|
|
167
|
+
|
|
168
|
+
if not attachments:
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
# Constants for upload limits
|
|
172
|
+
MAX_FILE_SIZE = 32 * 1024 * 1024 # 32 MB in bytes
|
|
173
|
+
MAX_REQUEST_SIZE = 128 * 1024 * 1024 # 128 MB in bytes
|
|
174
|
+
MAX_FILES_PER_REQUEST = 20
|
|
175
|
+
|
|
176
|
+
# Prepare attachments with size information
|
|
177
|
+
attachments_with_size = []
|
|
178
|
+
for att in attachments:
|
|
179
|
+
try:
|
|
180
|
+
# Get file data to check size
|
|
181
|
+
file_tuple = att.get_for_upload()
|
|
182
|
+
file_data = file_tuple[1] # Get file data (second element of tuple)
|
|
183
|
+
file_size = len(file_data)
|
|
184
|
+
|
|
185
|
+
# Check individual file size limit
|
|
186
|
+
if file_size > MAX_FILE_SIZE:
|
|
187
|
+
self.logger.log(
|
|
188
|
+
f"Attachment {att.file_name} ({file_size / 1024 / 1024:.2f} MB) exceeds "
|
|
189
|
+
f"maximum file size limit of 32 MB. Skipping.",
|
|
190
|
+
"error"
|
|
191
|
+
)
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
attachments_with_size.append((att, file_size))
|
|
195
|
+
except Exception as e:
|
|
196
|
+
self.logger.log(f"Error preparing attachment {att.file_name}: {e}", "error")
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
if not attachments_with_size:
|
|
200
|
+
return []
|
|
201
|
+
|
|
202
|
+
# Group attachments into batches
|
|
203
|
+
batches = []
|
|
204
|
+
current_batch = []
|
|
205
|
+
current_batch_size = 0
|
|
206
|
+
|
|
207
|
+
for att, file_size in attachments_with_size:
|
|
208
|
+
# Check if adding this file would exceed limits
|
|
209
|
+
would_exceed_size = current_batch_size + file_size > MAX_REQUEST_SIZE
|
|
210
|
+
would_exceed_count = len(current_batch) >= MAX_FILES_PER_REQUEST
|
|
211
|
+
|
|
212
|
+
if would_exceed_size or would_exceed_count:
|
|
213
|
+
# Start a new batch
|
|
214
|
+
if current_batch:
|
|
215
|
+
batches.append(current_batch)
|
|
216
|
+
current_batch = [att]
|
|
217
|
+
current_batch_size = file_size
|
|
218
|
+
else:
|
|
219
|
+
# Add to current batch
|
|
220
|
+
current_batch.append(att)
|
|
221
|
+
current_batch_size += file_size
|
|
222
|
+
|
|
223
|
+
# Add the last batch if it has items
|
|
224
|
+
if current_batch:
|
|
225
|
+
batches.append(current_batch)
|
|
226
|
+
|
|
227
|
+
# Upload batches
|
|
228
|
+
all_uploaded = []
|
|
229
|
+
attach_api = AttachmentsApi(self.client)
|
|
230
|
+
|
|
231
|
+
for batch_idx, batch in enumerate(batches, 1):
|
|
232
|
+
try:
|
|
233
|
+
self.logger.log_debug(
|
|
234
|
+
f"Uploading batch {batch_idx}/{len(batches)} with {len(batch)} file(s) "
|
|
235
|
+
f"for project {project_code}"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Prepare files for upload
|
|
239
|
+
files_for_upload = [att.get_for_upload() for att in batch]
|
|
240
|
+
|
|
241
|
+
# Upload batch
|
|
242
|
+
response = attach_api.upload_attachment(project_code, file=files_for_upload)
|
|
243
|
+
|
|
244
|
+
if response.result:
|
|
245
|
+
all_uploaded.extend(response.result)
|
|
246
|
+
self.logger.log_debug(
|
|
247
|
+
f"Successfully uploaded batch {batch_idx}/{len(batches)}: "
|
|
248
|
+
f"{len(response.result)} file(s)"
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
self.logger.log(
|
|
252
|
+
f"Batch {batch_idx}/{len(batches)} upload returned no results",
|
|
253
|
+
"error"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
self.logger.log(
|
|
258
|
+
f"Error uploading batch {batch_idx}/{len(batches)}: {e}",
|
|
259
|
+
"error"
|
|
260
|
+
)
|
|
261
|
+
# Continue with next batch even if one fails
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
return all_uploaded
|
|
94
265
|
|
|
95
266
|
def create_test_run(self, project_code: str, title: str, description: str, plan_id=None,
|
|
96
267
|
environment_id=None) -> str:
|
|
268
|
+
# Process configurations
|
|
269
|
+
configuration_ids = []
|
|
270
|
+
|
|
271
|
+
if self.config.testops.configurations and self.config.testops.configurations.values:
|
|
272
|
+
for config_value in self.config.testops.configurations.values:
|
|
273
|
+
config_id = self.find_or_create_configuration(project_code, config_value)
|
|
274
|
+
if config_id:
|
|
275
|
+
configuration_ids.append(config_id)
|
|
276
|
+
|
|
97
277
|
kwargs = dict(
|
|
98
278
|
title=title,
|
|
99
279
|
description=description,
|
|
100
280
|
environment_id=(int(environment_id) if environment_id else None),
|
|
101
281
|
plan_id=(int(plan_id) if plan_id else plan_id),
|
|
102
|
-
is_autotest=True
|
|
282
|
+
is_autotest=True,
|
|
283
|
+
start_time=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
|
|
284
|
+
tags=self.config.testops.run.tags
|
|
103
285
|
)
|
|
104
|
-
|
|
286
|
+
|
|
287
|
+
# Add configurations if any found
|
|
288
|
+
if configuration_ids:
|
|
289
|
+
kwargs['configurations'] = configuration_ids
|
|
290
|
+
|
|
105
291
|
try:
|
|
106
292
|
result = RunsApi(self.client).create_run(
|
|
107
293
|
code=project_code,
|
|
108
294
|
run_create=RunCreate(**{k: v for k, v in kwargs.items() if v is not None})
|
|
109
295
|
)
|
|
110
296
|
|
|
111
|
-
|
|
297
|
+
run_id = result.result.id
|
|
298
|
+
|
|
299
|
+
# Update external link if configured
|
|
300
|
+
if self.config.testops.run.external_link and run_id:
|
|
301
|
+
self.update_external_link(project_code, run_id)
|
|
302
|
+
|
|
303
|
+
return run_id
|
|
112
304
|
|
|
113
305
|
except Exception as e:
|
|
114
306
|
self.logger.log(f"Error at creating test run: {e}", "error")
|
|
@@ -121,166 +313,65 @@ class ApiV1Client(BaseApiClient):
|
|
|
121
313
|
return True
|
|
122
314
|
return False
|
|
123
315
|
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
results=results_to_send
|
|
133
|
-
)
|
|
134
|
-
)
|
|
135
|
-
self.logger.log_debug(f"Results for run {run_id} sent successfully")
|
|
136
|
-
|
|
137
|
-
def _prepare_result(self, project_code: str, result: Result) -> Dict:
|
|
138
|
-
attached = []
|
|
139
|
-
if result.attachments:
|
|
140
|
-
for attachment in result.attachments:
|
|
141
|
-
if self.__should_skip_attachment(attachment, result):
|
|
142
|
-
continue
|
|
143
|
-
attach_id = self._upload_attachment(project_code, attachment)
|
|
144
|
-
if attach_id:
|
|
145
|
-
attached.extend(attach_id)
|
|
146
|
-
|
|
147
|
-
steps = []
|
|
148
|
-
for step in result.steps:
|
|
149
|
-
prepared = self._prepare_step(project_code, step)
|
|
150
|
-
steps.append(prepared)
|
|
151
|
-
|
|
152
|
-
case_data = {
|
|
153
|
-
"title": result.get_title(),
|
|
154
|
-
"description": result.get_field('description'),
|
|
155
|
-
"preconditions": result.get_field('preconditions'),
|
|
156
|
-
"postconditions": result.get_field('postconditions'),
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
for key, param in result.params.items():
|
|
160
|
-
# Hack to match old TestOps API
|
|
161
|
-
if param == "":
|
|
162
|
-
result.params[key] = "empty"
|
|
163
|
-
|
|
164
|
-
if result.get_field('severity'):
|
|
165
|
-
case_data["severity"] = result.get_field('severity')
|
|
166
|
-
|
|
167
|
-
if result.get_field('priority'):
|
|
168
|
-
case_data["priority"] = result.get_field('priority')
|
|
169
|
-
|
|
170
|
-
if result.get_field('layer'):
|
|
171
|
-
case_data["layer"] = result.get_field('layer')
|
|
172
|
-
|
|
173
|
-
suite = None
|
|
174
|
-
if result.get_suite_title():
|
|
175
|
-
suite = "\t".join(result.get_suite_title().split("."))
|
|
176
|
-
|
|
177
|
-
if result.get_field('suite'):
|
|
178
|
-
suite = result.get_field('suite')
|
|
179
|
-
|
|
180
|
-
root_suite = self.config.root_suite
|
|
181
|
-
if root_suite:
|
|
182
|
-
suite = f"{root_suite}\t{suite}"
|
|
183
|
-
|
|
184
|
-
if suite:
|
|
185
|
-
case_data["suite_title"] = suite
|
|
186
|
-
|
|
187
|
-
result_model = {
|
|
188
|
-
"status": result.execution.status,
|
|
189
|
-
"stacktrace": result.execution.stacktrace,
|
|
190
|
-
"time_ms": result.execution.duration,
|
|
191
|
-
"comment": result.message,
|
|
192
|
-
"attachments": [attach.hash for attach in attached],
|
|
193
|
-
"steps": steps,
|
|
194
|
-
"param": result.params,
|
|
195
|
-
"param_groups": result.param_groups,
|
|
196
|
-
"defect": self.config.testops.defect,
|
|
197
|
-
"case": case_data
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
test_ops_id = result.get_testops_id()
|
|
201
|
-
|
|
202
|
-
if test_ops_id:
|
|
203
|
-
result_model["case_id"] = test_ops_id
|
|
204
|
-
|
|
205
|
-
if result.get_field('author'):
|
|
206
|
-
author_id = self._get_author_id(result.get_field('author'))
|
|
207
|
-
if author_id:
|
|
208
|
-
result_model["author_id"] = author_id
|
|
209
|
-
|
|
210
|
-
self.logger.log_debug(f"Prepared result: {result_model}")
|
|
211
|
-
|
|
212
|
-
return result_model
|
|
213
|
-
|
|
214
|
-
def _prepare_step(self, project_code: str, step: Step) -> Dict:
|
|
215
|
-
prepared_children = []
|
|
216
|
-
|
|
316
|
+
def enable_public_report(self, project_code: str, run_id: int) -> str:
|
|
317
|
+
"""
|
|
318
|
+
Enable public report for a test run and return the public link
|
|
319
|
+
|
|
320
|
+
:param project_code: project code
|
|
321
|
+
:param run_id: test run id
|
|
322
|
+
:return: public report link or None if failed
|
|
323
|
+
"""
|
|
217
324
|
try:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
temporary=True))
|
|
237
|
-
if step.data.request_headers:
|
|
238
|
-
step.attachments.append(
|
|
239
|
-
Attachment(file_name='request_headers.txt', content=step.data.request_headers,
|
|
240
|
-
mime_type='text/plain', temporary=True))
|
|
241
|
-
if step.data.response_body:
|
|
242
|
-
step.attachments.append(Attachment(file_name='response_body.txt', content=step.data.response_body,
|
|
243
|
-
mime_type='text/plain', temporary=True))
|
|
244
|
-
if step.data.response_headers:
|
|
245
|
-
step.attachments.append(
|
|
246
|
-
Attachment(file_name='response_headers.txt', content=step.data.response_headers,
|
|
247
|
-
mime_type='text/plain', temporary=True))
|
|
248
|
-
|
|
249
|
-
if step.step_type == StepType.GHERKIN:
|
|
250
|
-
prepared_step['action'] = step.data.keyword
|
|
251
|
-
|
|
252
|
-
if step.step_type == StepType.SLEEP:
|
|
253
|
-
prepared_step['action'] = f"Sleep for {step.data.duration} seconds"
|
|
254
|
-
|
|
255
|
-
if step.attachments:
|
|
256
|
-
uploaded_attachments = []
|
|
257
|
-
for file in step.attachments:
|
|
258
|
-
attach_id = self._upload_attachment(project_code, file)
|
|
259
|
-
if attach_id:
|
|
260
|
-
uploaded_attachments.extend(attach_id)
|
|
261
|
-
prepared_step['attachments'] = [attach.hash for attach in uploaded_attachments]
|
|
262
|
-
|
|
263
|
-
if step.steps:
|
|
264
|
-
for substep in step.steps:
|
|
265
|
-
prepared_children.append(self._prepare_step(project_code, substep))
|
|
266
|
-
prepared_step["steps"] = prepared_children
|
|
267
|
-
return prepared_step
|
|
325
|
+
self.logger.log_debug(f"Enabling public report for run {run_id}")
|
|
326
|
+
api_runs = RunsApi(self.client)
|
|
327
|
+
|
|
328
|
+
# Create RunPublic object with status=True
|
|
329
|
+
run_public = RunPublic(status=True)
|
|
330
|
+
|
|
331
|
+
# Call the API to enable public report
|
|
332
|
+
response = api_runs.update_run_publicity(project_code, run_id, run_public)
|
|
333
|
+
|
|
334
|
+
# Extract the public URL from response
|
|
335
|
+
if response.result and response.result.url:
|
|
336
|
+
public_url = response.result.url
|
|
337
|
+
self.logger.log_debug(f"Public report enabled for run {run_id}: {public_url}")
|
|
338
|
+
return public_url
|
|
339
|
+
else:
|
|
340
|
+
self.logger.log_debug(f"Public report enabled for run {run_id} but no URL returned")
|
|
341
|
+
return None
|
|
342
|
+
|
|
268
343
|
except Exception as e:
|
|
269
|
-
self.logger.log(f"Error at
|
|
270
|
-
raise ReporterException(e)
|
|
271
|
-
|
|
272
|
-
def _get_author_id(self, author: str) -> Union[str, None]:
|
|
273
|
-
if author in self.__authors:
|
|
274
|
-
return self.__authors[author]
|
|
275
|
-
|
|
276
|
-
author_api = AuthorsApi(self.client)
|
|
277
|
-
authors = author_api.get_authors(search=author)
|
|
278
|
-
if authors.result.total == 0:
|
|
344
|
+
self.logger.log(f"Error at enabling public report for run {run_id}: {e}", "error")
|
|
279
345
|
return None
|
|
280
346
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
347
|
+
def update_external_link(self, project_code: str, run_id: int):
|
|
348
|
+
"""Update external link for a test run"""
|
|
349
|
+
try:
|
|
350
|
+
from qase.api_client_v1.models.runexternal_issues import RunexternalIssues
|
|
351
|
+
from qase.api_client_v1.models.runexternal_issues_links_inner import RunexternalIssuesLinksInner
|
|
352
|
+
|
|
353
|
+
external_link = self.config.testops.run.external_link
|
|
354
|
+
api_type = external_link.to_api_type()
|
|
355
|
+
|
|
356
|
+
run_external_issues = RunexternalIssues(
|
|
357
|
+
type=api_type,
|
|
358
|
+
links=[
|
|
359
|
+
RunexternalIssuesLinksInner(
|
|
360
|
+
run_id=run_id,
|
|
361
|
+
external_issue=external_link.link
|
|
362
|
+
)
|
|
363
|
+
]
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
RunsApi(self.client).run_update_external_issue(
|
|
367
|
+
code=project_code,
|
|
368
|
+
runexternal_issues=run_external_issues
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
self.logger.log(f"External link updated for run {run_id}: {external_link.link}", "debug")
|
|
372
|
+
|
|
373
|
+
except Exception as e:
|
|
374
|
+
self.logger.log(f"Error at updating external link: {e}", "error")
|
|
284
375
|
|
|
285
376
|
def __should_skip_attachment(self, attachment, result):
|
|
286
377
|
if (self.config.framework.playwright.video == Video.failed and
|
|
@@ -292,3 +383,6 @@ class ApiV1Client(BaseApiClient):
|
|
|
292
383
|
attachment.file_name == 'trace.zip'):
|
|
293
384
|
return True
|
|
294
385
|
return False
|
|
386
|
+
|
|
387
|
+
def send_results(self, project_code: str, run_id: str, results: []) -> None:
|
|
388
|
+
raise NotImplementedError("use ApiV2Client instead")
|