qastudio-pytest 1.0.4__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.
- qastudio_pytest/__init__.py +13 -0
- qastudio_pytest/api_client.py +379 -0
- qastudio_pytest/models.py +259 -0
- qastudio_pytest/plugin.py +497 -0
- qastudio_pytest/utils.py +284 -0
- qastudio_pytest-1.0.4.dist-info/METADATA +310 -0
- qastudio_pytest-1.0.4.dist-info/RECORD +11 -0
- qastudio_pytest-1.0.4.dist-info/WHEEL +5 -0
- qastudio_pytest-1.0.4.dist-info/entry_points.txt +2 -0
- qastudio_pytest-1.0.4.dist-info/licenses/LICENSE +21 -0
- qastudio_pytest-1.0.4.dist-info/top_level.txt +1 -0
qastudio_pytest/utils.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Utility functions for QAStudio pytest plugin."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, List, Optional, TypeVar
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def extract_test_case_id(item: Any) -> Optional[str]:
|
|
11
|
+
"""
|
|
12
|
+
Extract QAStudio test case ID from pytest item.
|
|
13
|
+
|
|
14
|
+
Checks in order:
|
|
15
|
+
1. @pytest.mark.qastudio_id marker
|
|
16
|
+
2. Test name pattern (test_QA123_name or test_name_QA123)
|
|
17
|
+
3. Docstring pattern (QAStudio ID: QA-123)
|
|
18
|
+
"""
|
|
19
|
+
# Check for qastudio_id marker
|
|
20
|
+
marker = item.get_closest_marker("qastudio_id")
|
|
21
|
+
if marker and marker.args:
|
|
22
|
+
return str(marker.args[0])
|
|
23
|
+
|
|
24
|
+
# Check test name for ID pattern
|
|
25
|
+
name = item.name
|
|
26
|
+
# Pattern: QA-123 or QA123
|
|
27
|
+
match = re.search(r"QA[-_]?(\d+)", name, re.IGNORECASE)
|
|
28
|
+
if match:
|
|
29
|
+
return f"QA-{match.group(1)}"
|
|
30
|
+
|
|
31
|
+
# Check docstring
|
|
32
|
+
if item.function.__doc__:
|
|
33
|
+
doc = item.function.__doc__
|
|
34
|
+
match = re.search(r"QAStudio\s+ID:\s*(QA[-_]?\d+)", doc, re.IGNORECASE)
|
|
35
|
+
if match:
|
|
36
|
+
test_id = match.group(1).upper()
|
|
37
|
+
# Normalize to QA-123 format
|
|
38
|
+
return re.sub(r"QA[-_]?(\d+)", r"QA-\1", test_id)
|
|
39
|
+
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_full_test_name(item: Any) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Get full test name including class and module hierarchy.
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
test_example.py::test_function
|
|
49
|
+
test_example.py::TestClass::test_method
|
|
50
|
+
test_example.py::TestClass::test_method[param]
|
|
51
|
+
"""
|
|
52
|
+
parts = []
|
|
53
|
+
|
|
54
|
+
# Add module/file name
|
|
55
|
+
if hasattr(item, "fspath"):
|
|
56
|
+
parts.append(item.fspath.basename)
|
|
57
|
+
|
|
58
|
+
# Add class name if exists
|
|
59
|
+
if item.cls:
|
|
60
|
+
parts.append(item.cls.__name__)
|
|
61
|
+
|
|
62
|
+
# Add test function name
|
|
63
|
+
parts.append(item.name)
|
|
64
|
+
|
|
65
|
+
return "::".join(parts)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def generate_test_run_name() -> str:
|
|
69
|
+
"""Generate default test run name with timestamp."""
|
|
70
|
+
now = datetime.now()
|
|
71
|
+
date_str = now.strftime("%Y-%m-%d")
|
|
72
|
+
time_str = now.strftime("%H-%M-%S")
|
|
73
|
+
return f"pytest Run - {date_str} {time_str}"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def batch_list(items: List[T], batch_size: int) -> List[List[T]]:
|
|
77
|
+
"""Split list into batches of specified size."""
|
|
78
|
+
return [items[i : i + batch_size] for i in range(0, len(items), batch_size)]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def format_duration(seconds: float) -> str:
|
|
82
|
+
"""Format duration in human-readable format."""
|
|
83
|
+
if seconds < 1:
|
|
84
|
+
return f"{int(seconds * 1000)}ms"
|
|
85
|
+
|
|
86
|
+
total_seconds = int(seconds)
|
|
87
|
+
hours = total_seconds // 3600
|
|
88
|
+
minutes = (total_seconds % 3600) // 60
|
|
89
|
+
secs = total_seconds % 60
|
|
90
|
+
|
|
91
|
+
if hours > 0:
|
|
92
|
+
return f"{hours}h {minutes}m {secs}s"
|
|
93
|
+
elif minutes > 0:
|
|
94
|
+
return f"{minutes}m {secs}s"
|
|
95
|
+
else:
|
|
96
|
+
return f"{secs}s"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def strip_ansi(text: str) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Strip ANSI escape codes from string.
|
|
102
|
+
|
|
103
|
+
Removes color codes and terminal formatting that can interfere with API calls.
|
|
104
|
+
"""
|
|
105
|
+
if not text:
|
|
106
|
+
return text
|
|
107
|
+
|
|
108
|
+
# Remove ANSI escape sequences
|
|
109
|
+
text = re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", text)
|
|
110
|
+
# Remove bracket codes
|
|
111
|
+
text = re.sub(r"\[\d+m", "", text)
|
|
112
|
+
# Remove multi-digit bracket codes
|
|
113
|
+
text = re.sub(r"\[\d+;\d+m", "", text)
|
|
114
|
+
|
|
115
|
+
return text.strip()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def sanitize_string(text: Optional[str]) -> Optional[str]:
|
|
119
|
+
"""Sanitize string by removing ANSI codes."""
|
|
120
|
+
if text is None:
|
|
121
|
+
return None
|
|
122
|
+
return strip_ansi(text)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def validate_config(config: Any) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Validate required configuration options.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
ValueError: If required config is missing
|
|
131
|
+
"""
|
|
132
|
+
if not config.api_url:
|
|
133
|
+
raise ValueError("QAStudio API URL is required")
|
|
134
|
+
|
|
135
|
+
if not config.api_key:
|
|
136
|
+
raise ValueError("QAStudio API key is required")
|
|
137
|
+
|
|
138
|
+
if not config.project_id:
|
|
139
|
+
raise ValueError("QAStudio project ID is required")
|
|
140
|
+
|
|
141
|
+
# Validate URL format
|
|
142
|
+
if not config.api_url.startswith(("http://", "https://")):
|
|
143
|
+
raise ValueError(f"Invalid API URL format: {config.api_url}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def extract_error_snippet(report: Any) -> Optional[str]:
|
|
147
|
+
"""
|
|
148
|
+
Extract code snippet showing where the error occurred.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
report: pytest test report
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Code snippet with context around the error line, or None if not available
|
|
155
|
+
"""
|
|
156
|
+
if not hasattr(report, "longrepr") or not report.longrepr:
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
# Try to extract from longrepr
|
|
161
|
+
longrepr_str = str(report.longrepr)
|
|
162
|
+
|
|
163
|
+
# Look for code sections in the traceback (lines starting with >)
|
|
164
|
+
lines = longrepr_str.split("\n")
|
|
165
|
+
snippet_lines = []
|
|
166
|
+
in_code_section = False
|
|
167
|
+
|
|
168
|
+
for line in lines:
|
|
169
|
+
# Detect code lines (often prefixed with spaces or >)
|
|
170
|
+
if line.strip().startswith(">") or (in_code_section and line.startswith(" " * 4)):
|
|
171
|
+
snippet_lines.append(line)
|
|
172
|
+
in_code_section = True
|
|
173
|
+
elif in_code_section and line.strip():
|
|
174
|
+
# Continue collecting until we hit a non-code line
|
|
175
|
+
if not line.startswith("E "):
|
|
176
|
+
snippet_lines.append(line)
|
|
177
|
+
else:
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
if snippet_lines:
|
|
181
|
+
return "\n".join(snippet_lines[:10]) # Limit to 10 lines
|
|
182
|
+
|
|
183
|
+
except Exception:
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def extract_error_location(report: Any) -> Optional[dict]:
|
|
190
|
+
"""
|
|
191
|
+
Extract precise error location (file, line, column).
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
report: pytest test report
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Dictionary with file, line, and column information, or None if not available
|
|
198
|
+
"""
|
|
199
|
+
if not hasattr(report, "longrepr") or not report.longrepr:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
# Try to get location from reprcrash
|
|
204
|
+
if hasattr(report.longrepr, "reprcrash"):
|
|
205
|
+
crash = report.longrepr.reprcrash
|
|
206
|
+
return {
|
|
207
|
+
"file": str(crash.path) if hasattr(crash, "path") else None,
|
|
208
|
+
"line": crash.lineno if hasattr(crash, "lineno") else None,
|
|
209
|
+
"column": 0, # pytest doesn't provide column info
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# Try to parse from longrepr string
|
|
213
|
+
longrepr_str = str(report.longrepr)
|
|
214
|
+
# Look for file:line pattern
|
|
215
|
+
match = re.search(r"([^\s]+\.py):(\d+):", longrepr_str)
|
|
216
|
+
if match:
|
|
217
|
+
return {"file": match.group(1), "line": int(match.group(2)), "column": 0}
|
|
218
|
+
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def extract_console_output(report: Any) -> Optional[dict]:
|
|
226
|
+
"""
|
|
227
|
+
Extract console output (stdout/stderr) from test execution.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
report: pytest test report
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Dictionary with stdout and stderr, or None if no output
|
|
234
|
+
"""
|
|
235
|
+
stdout = None
|
|
236
|
+
stderr = None
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
# Extract captured stdout
|
|
240
|
+
if hasattr(report, "capstdout") and report.capstdout:
|
|
241
|
+
stdout = sanitize_string(report.capstdout)
|
|
242
|
+
|
|
243
|
+
# Extract captured stderr
|
|
244
|
+
if hasattr(report, "capstderr") and report.capstderr:
|
|
245
|
+
stderr = sanitize_string(report.capstderr)
|
|
246
|
+
|
|
247
|
+
# Also try sections
|
|
248
|
+
if hasattr(report, "sections"):
|
|
249
|
+
for section_name, section_content in report.sections:
|
|
250
|
+
if "stdout" in section_name.lower():
|
|
251
|
+
stdout_text = sanitize_string(section_content)
|
|
252
|
+
if stdout_text:
|
|
253
|
+
stdout = stdout_text
|
|
254
|
+
elif "stderr" in section_name.lower():
|
|
255
|
+
stderr_text = sanitize_string(section_content)
|
|
256
|
+
if stderr_text:
|
|
257
|
+
stderr = stderr_text
|
|
258
|
+
|
|
259
|
+
except Exception:
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
if stdout or stderr:
|
|
263
|
+
return {"stdout": stdout, "stderr": stderr}
|
|
264
|
+
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def extract_test_steps(report: Any) -> Optional[List[dict]]:
|
|
269
|
+
"""
|
|
270
|
+
Extract test execution steps.
|
|
271
|
+
|
|
272
|
+
Note: pytest doesn't have built-in step tracking like Playwright.
|
|
273
|
+
This is a placeholder that returns None for now.
|
|
274
|
+
Users can implement custom step tracking using pytest plugins if needed.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
report: pytest test report
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
List of step dictionaries, or None if not available
|
|
281
|
+
"""
|
|
282
|
+
# pytest doesn't have native step tracking
|
|
283
|
+
# This could be enhanced with pytest-bdd or custom plugins in the future
|
|
284
|
+
return None
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qastudio-pytest
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: pytest plugin for QAStudio.dev test management platform
|
|
5
|
+
Author-email: QAStudio <ben@qastudio.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/QAStudio-Dev/playwright-reporter-python
|
|
8
|
+
Project-URL: Repository, https://github.com/QAStudio-Dev/playwright-reporter-python
|
|
9
|
+
Project-URL: Issues, https://github.com/QAStudio-Dev/playwright-reporter-python/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/QAStudio-Dev/playwright-reporter-python#readme
|
|
11
|
+
Keywords: pytest,testing,test-management,qa,qastudio
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: Pytest
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: pytest>=7.0.0
|
|
28
|
+
Requires-Dist: requests>=2.28.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: types-requests>=2.28.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# QAStudio pytest Plugin
|
|
39
|
+
|
|
40
|
+
A pytest plugin that integrates with [QAStudio.dev](https://qastudio.dev) test management platform.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- 🔄 Automatic test result reporting to QAStudio.dev
|
|
45
|
+
- 📊 Real-time test run tracking
|
|
46
|
+
- 🏷️ Test case linking via markers or test IDs
|
|
47
|
+
- 📸 Screenshot and attachment support
|
|
48
|
+
- 🔧 Configurable via pytest.ini, command line, or environment variables
|
|
49
|
+
- 🎯 Batch result submission for performance
|
|
50
|
+
- 🛡️ Silent mode - won't fail tests if API is unavailable
|
|
51
|
+
- 📝 Error code snippets with context around failure points
|
|
52
|
+
- 📍 Precise error location tracking (file, line, column)
|
|
53
|
+
- 📤 Console output capture (stdout/stderr)
|
|
54
|
+
- 🔍 Enhanced debugging context for test failures
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install qastudio-pytest
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
### 1. Configure via pytest.ini
|
|
65
|
+
|
|
66
|
+
```ini
|
|
67
|
+
[pytest]
|
|
68
|
+
qastudio_api_url = https://qastudio.dev/api
|
|
69
|
+
qastudio_api_key = your-api-key
|
|
70
|
+
qastudio_project_id = your-project-id
|
|
71
|
+
qastudio_environment = CI
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Configure via Environment Variables
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
export QASTUDIO_API_URL=https://qastudio.dev/api
|
|
78
|
+
export QASTUDIO_API_KEY=your-api-key
|
|
79
|
+
export QASTUDIO_PROJECT_ID=your-project-id
|
|
80
|
+
export QASTUDIO_ENVIRONMENT=CI
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. Configure via Command Line
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pytest --qastudio-api-url=https://qastudio.dev/api \
|
|
87
|
+
--qastudio-api-key=your-api-key \
|
|
88
|
+
--qastudio-project-id=your-project-id
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
### Linking Tests to QAStudio Test Cases
|
|
94
|
+
|
|
95
|
+
#### Method 1: Using pytest markers
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import pytest
|
|
99
|
+
|
|
100
|
+
@pytest.mark.qastudio_id("QA-123")
|
|
101
|
+
def test_login():
|
|
102
|
+
assert user.login("user", "pass")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Method 2: Using test ID in test name
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
def test_QA123_login():
|
|
109
|
+
"""Test case QA-123"""
|
|
110
|
+
assert user.login("user", "pass")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Method 3: Using docstring
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
def test_login():
|
|
117
|
+
"""
|
|
118
|
+
Test user login functionality
|
|
119
|
+
|
|
120
|
+
QAStudio ID: QA-123
|
|
121
|
+
"""
|
|
122
|
+
assert user.login("user", "pass")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Adding Custom Metadata
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import pytest
|
|
129
|
+
|
|
130
|
+
@pytest.mark.qastudio_id("QA-456")
|
|
131
|
+
@pytest.mark.qastudio_priority("high")
|
|
132
|
+
@pytest.mark.qastudio_tags("smoke", "authentication")
|
|
133
|
+
def test_important_feature():
|
|
134
|
+
assert True
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Configuration Options
|
|
138
|
+
|
|
139
|
+
| Option | Environment Variable | Description | Default |
|
|
140
|
+
|--------|---------------------|-------------|---------|
|
|
141
|
+
| `qastudio_api_url` | `QASTUDIO_API_URL` | QAStudio.dev API URL | `https://qastudio.dev/api` |
|
|
142
|
+
| `qastudio_api_key` | `QASTUDIO_API_KEY` | API authentication key | Required |
|
|
143
|
+
| `qastudio_project_id` | `QASTUDIO_PROJECT_ID` | Project ID | Required |
|
|
144
|
+
| `qastudio_environment` | `QASTUDIO_ENVIRONMENT` | Environment name | `default` |
|
|
145
|
+
| `qastudio_test_run_name` | `QASTUDIO_TEST_RUN_NAME` | Custom test run name | Auto-generated |
|
|
146
|
+
| `qastudio_test_run_id` | `QASTUDIO_TEST_RUN_ID` | Existing test run ID | None |
|
|
147
|
+
| `qastudio_create_test_run` | `QASTUDIO_CREATE_TEST_RUN` | Create new test run | `true` |
|
|
148
|
+
| `qastudio_batch_size` | `QASTUDIO_BATCH_SIZE` | Results batch size | `10` |
|
|
149
|
+
| `qastudio_silent` | `QASTUDIO_SILENT` | Fail silently on API errors | `true` |
|
|
150
|
+
| `qastudio_verbose` | `QASTUDIO_VERBOSE` | Enable verbose logging | `false` |
|
|
151
|
+
| `qastudio_include_error_snippet` | `QASTUDIO_INCLUDE_ERROR_SNIPPET` | Include error code snippet | `true` |
|
|
152
|
+
| `qastudio_include_error_location` | `QASTUDIO_INCLUDE_ERROR_LOCATION` | Include precise error location | `true` |
|
|
153
|
+
| `qastudio_include_test_steps` | `QASTUDIO_INCLUDE_TEST_STEPS` | Include test execution steps | `true` |
|
|
154
|
+
| `qastudio_include_console_output` | `QASTUDIO_INCLUDE_CONSOLE_OUTPUT` | Include console output | `false` |
|
|
155
|
+
|
|
156
|
+
## Error Context and Debugging
|
|
157
|
+
|
|
158
|
+
The plugin automatically captures rich debugging context for failed tests:
|
|
159
|
+
|
|
160
|
+
### Error Code Snippets
|
|
161
|
+
|
|
162
|
+
When a test fails, the plugin captures the relevant code snippet showing where the error occurred:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
def test_calculation():
|
|
166
|
+
result = calculate(5, 0) # This line will be captured in the error snippet
|
|
167
|
+
assert result == 10
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Disable with:
|
|
171
|
+
```ini
|
|
172
|
+
[pytest]
|
|
173
|
+
qastudio_include_error_snippet = false
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Error Location
|
|
177
|
+
|
|
178
|
+
Precise error location information (file path, line number) is automatically captured:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"errorLocation": {
|
|
183
|
+
"file": "tests/test_example.py",
|
|
184
|
+
"line": 42,
|
|
185
|
+
"column": 0
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Disable with:
|
|
191
|
+
```ini
|
|
192
|
+
[pytest]
|
|
193
|
+
qastudio_include_error_location = false
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Console Output
|
|
197
|
+
|
|
198
|
+
Capture stdout and stderr from test execution (disabled by default to avoid sensitive data):
|
|
199
|
+
|
|
200
|
+
```ini
|
|
201
|
+
[pytest]
|
|
202
|
+
qastudio_include_console_output = true
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Or via command line:
|
|
206
|
+
```bash
|
|
207
|
+
pytest --qastudio-include-console-output
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Advanced Usage
|
|
211
|
+
|
|
212
|
+
### Using with pytest-xdist (Parallel Testing)
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
pytest -n auto --qastudio-api-key=your-api-key
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The plugin handles parallel test execution automatically.
|
|
219
|
+
|
|
220
|
+
### CI/CD Integration
|
|
221
|
+
|
|
222
|
+
#### GitHub Actions
|
|
223
|
+
|
|
224
|
+
```yaml
|
|
225
|
+
- name: Run Tests
|
|
226
|
+
run: |
|
|
227
|
+
pytest --junitxml=report.xml
|
|
228
|
+
env:
|
|
229
|
+
QASTUDIO_API_KEY: ${{ secrets.QASTUDIO_API_KEY }}
|
|
230
|
+
QASTUDIO_PROJECT_ID: ${{ secrets.QASTUDIO_PROJECT_ID }}
|
|
231
|
+
QASTUDIO_ENVIRONMENT: CI
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### GitLab CI
|
|
235
|
+
|
|
236
|
+
```yaml
|
|
237
|
+
test:
|
|
238
|
+
script:
|
|
239
|
+
- pytest
|
|
240
|
+
variables:
|
|
241
|
+
QASTUDIO_API_KEY: $QASTUDIO_API_KEY
|
|
242
|
+
QASTUDIO_PROJECT_ID: $QASTUDIO_PROJECT_ID
|
|
243
|
+
QASTUDIO_ENVIRONMENT: CI
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Examples
|
|
247
|
+
|
|
248
|
+
### Playwright Example
|
|
249
|
+
|
|
250
|
+
A complete Playwright test framework example is available in `examples/playwright_tests/`:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Navigate to example directory
|
|
254
|
+
cd examples/playwright_tests
|
|
255
|
+
|
|
256
|
+
# Install dependencies
|
|
257
|
+
pip install -r requirements.txt
|
|
258
|
+
playwright install chromium
|
|
259
|
+
|
|
260
|
+
# Run the tests
|
|
261
|
+
./run_tests.sh
|
|
262
|
+
|
|
263
|
+
# Or run directly with pytest
|
|
264
|
+
pytest -v
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The example demonstrates:
|
|
268
|
+
- ✅ Testing the QAStudio.dev website
|
|
269
|
+
- ✅ Automatic screenshot capture
|
|
270
|
+
- ✅ Playwright trace recording (`.zip` files)
|
|
271
|
+
- ✅ Integration with qastudio-pytest reporter
|
|
272
|
+
- ✅ Test case linking with `@pytest.mark.qastudio_id()`
|
|
273
|
+
|
|
274
|
+
See [`examples/playwright_tests/README.md`](examples/playwright_tests/README.md) for detailed documentation.
|
|
275
|
+
|
|
276
|
+
## Development
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Clone repository
|
|
280
|
+
git clone https://github.com/QAStudio-Dev/playwright-reporter-python.git
|
|
281
|
+
cd playwright-reporter-python
|
|
282
|
+
|
|
283
|
+
# Create virtual environment
|
|
284
|
+
python -m venv venv
|
|
285
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
286
|
+
|
|
287
|
+
# Install in development mode
|
|
288
|
+
pip install -e .[dev]
|
|
289
|
+
|
|
290
|
+
# Run tests
|
|
291
|
+
pytest tests/
|
|
292
|
+
|
|
293
|
+
# Run linting
|
|
294
|
+
black src/ tests/
|
|
295
|
+
flake8 src/ tests/
|
|
296
|
+
mypy src/
|
|
297
|
+
|
|
298
|
+
# Build package
|
|
299
|
+
python -m build
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
305
|
+
|
|
306
|
+
## Support
|
|
307
|
+
|
|
308
|
+
- 📧 Email: ben@qastudio.dev
|
|
309
|
+
- 🐛 Issues: https://github.com/QAStudio-Dev/playwright-reporter-python/issues
|
|
310
|
+
- 📖 Documentation: https://qastudio.dev/docs
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
qastudio_pytest/__init__.py,sha256=YqS1t8gG-dWNmvgD_GvIlLkdC-xSReTO9FYZKA6Kiyc,293
|
|
2
|
+
qastudio_pytest/api_client.py,sha256=kTspiUPHd23V89cD8P-02q12LChLFz8FQlTQzYLBrZA,11433
|
|
3
|
+
qastudio_pytest/models.py,sha256=yfSr-kCCgb8Tq6ZMe8lzIJMLyUiM8ay26WHE9bFXlOs,9083
|
|
4
|
+
qastudio_pytest/plugin.py,sha256=fuNeGNeDus7iTRwupTh9jZtDxcukt10YBFd7mADuQnE,16604
|
|
5
|
+
qastudio_pytest/utils.py,sha256=2dqb5hbmC-PrXd_sPzN87AdLqWdgEgkGZ7m4dOabsvo,8170
|
|
6
|
+
qastudio_pytest-1.0.4.dist-info/licenses/LICENSE,sha256=in3Nfu01STBi3qLqTzYIcPufjpn2Ii5b6JqvW0L3xeY,1065
|
|
7
|
+
qastudio_pytest-1.0.4.dist-info/METADATA,sha256=zU8617ApJe2VC1GUkDo8Sn1Cz3MU2U9CUb8J-FphGXY,8246
|
|
8
|
+
qastudio_pytest-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
qastudio_pytest-1.0.4.dist-info/entry_points.txt,sha256=uaS6HwPI7xFBYraAVHljAqxa-tk87FH17celN75EsZA,45
|
|
10
|
+
qastudio_pytest-1.0.4.dist-info/top_level.txt,sha256=oBnv6a1ZsPrv_bgzcl-LXLna-M92zf1YYFkyzzSlYW0,16
|
|
11
|
+
qastudio_pytest-1.0.4.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 QAStudio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
qastudio_pytest
|