pytest-reporter-html 1.0.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.
Files changed (38) hide show
  1. pytest_reporter_html-1.0.0/.gitignore +18 -0
  2. pytest_reporter_html-1.0.0/LICENSE +21 -0
  3. pytest_reporter_html-1.0.0/PKG-INFO +487 -0
  4. pytest_reporter_html-1.0.0/README.md +457 -0
  5. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773065204141.json +16 -0
  6. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773065236123.json +16 -0
  7. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773065272236.json +16 -0
  8. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773065291146.json +16 -0
  9. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773065313823.json +16 -0
  10. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773065329211.json +16 -0
  11. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773065912152.json +31 -0
  12. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773066001967.json +31 -0
  13. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773066521097.json +31 -0
  14. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773066588963.json +31 -0
  15. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773066726179.json +31 -0
  16. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773066767412.json +31 -0
  17. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773067129719.json +31 -0
  18. pytest_reporter_html-1.0.0/custom_output/json/test_no_report_html_flag_1773067472163.json +31 -0
  19. pytest_reporter_html-1.0.0/pyproject.toml +52 -0
  20. pytest_reporter_html-1.0.0/run_tests.sh +9 -0
  21. pytest_reporter_html-1.0.0/src/pytest_reporter_html/__init__.py +27 -0
  22. pytest_reporter_html-1.0.0/src/pytest_reporter_html/html_report.py +1266 -0
  23. pytest_reporter_html-1.0.0/src/pytest_reporter_html/logger.py +115 -0
  24. pytest_reporter_html-1.0.0/src/pytest_reporter_html/logging_bridge.py +135 -0
  25. pytest_reporter_html-1.0.0/src/pytest_reporter_html/plugin.py +415 -0
  26. pytest_reporter_html-1.0.0/src/pytest_reporter_html/reporter.py +200 -0
  27. pytest_reporter_html-1.0.0/src/pytest_reporter_html/s3_utils.py +113 -0
  28. pytest_reporter_html-1.0.0/src/pytest_reporter_html/step.py +120 -0
  29. pytest_reporter_html-1.0.0/src/pytest_reporter_html/types.py +100 -0
  30. pytest_reporter_html-1.0.0/tests/conftest.py +51 -0
  31. pytest_reporter_html-1.0.0/tests/test_html_report.py +452 -0
  32. pytest_reporter_html-1.0.0/tests/test_logger.py +147 -0
  33. pytest_reporter_html-1.0.0/tests/test_logging_bridge.py +262 -0
  34. pytest_reporter_html-1.0.0/tests/test_plugin.py +1177 -0
  35. pytest_reporter_html-1.0.0/tests/test_reporter.py +243 -0
  36. pytest_reporter_html-1.0.0/tests/test_step.py +218 -0
  37. pytest_reporter_html-1.0.0/tests/test_types.py +164 -0
  38. pytest_reporter_html-1.0.0/uv.lock +269 -0
@@ -0,0 +1,18 @@
1
+ # Virtual environments
2
+ .venv/
3
+ venv/
4
+
5
+ # IDE
6
+ .idea/
7
+
8
+ # Python
9
+ __pycache__/
10
+ *.pyc
11
+ *.pyo
12
+ *.egg-info/
13
+ dist/
14
+ build/
15
+ .pypirc
16
+ # Secrets
17
+ # OS
18
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yevgeny Farber
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,487 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-reporter-html
3
+ Version: 1.0.0
4
+ Summary: Pytest plugin that generates rich HTML test reports with step tracking, log capture, and interactive filtering
5
+ Project-URL: Homepage, https://github.com/YevgenyFarber/pytest-reporter-html
6
+ Project-URL: Documentation, https://github.com/YevgenyFarber/pytest-reporter-html
7
+ Project-URL: Source, https://github.com/YevgenyFarber/pytest-reporter-html
8
+ Project-URL: Issues, https://github.com/YevgenyFarber/pytest-reporter-html/issues
9
+ Author: Yevgeny Farber
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: html,pytest,reporter,test-report,test-reporting
13
+ Classifier: Framework :: Pytest
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Testing
23
+ Requires-Python: >=3.10
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
26
+ Requires-Dist: pytest>=7.0; extra == 'dev'
27
+ Provides-Extra: s3
28
+ Requires-Dist: boto3>=1.28; extra == 's3'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # pytest-reporter-html
32
+
33
+ [![PyPI version](https://img.shields.io/pypi/v/pytest-reporter-html)](https://pypi.org/project/pytest-reporter-html/)
34
+ [![PyPI downloads](https://img.shields.io/pypi/dm/pytest-reporter-html)](https://pypistats.org/packages/pytest-reporter-html)
35
+ [![Python versions](https://img.shields.io/pypi/pyversions/pytest-reporter-html)](https://pypi.org/project/pytest-reporter-html/)
36
+ [![License](https://img.shields.io/pypi/l/pytest-reporter-html)](https://github.com/YevgenyFarber/pytest-reporter-html/blob/main/LICENSE)
37
+
38
+ A pytest plugin that automatically generates rich, interactive HTML test reports with **log capture**, **step tracking**, **exception rendering**, and **real-time filtering**.
39
+
40
+ Install it, configure a capture mode, run `pytest`, get a report.
41
+
42
+ ---
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pip install pytest-reporter-html
48
+ ```
49
+
50
+ For S3 upload support (CI environments):
51
+
52
+ ```bash
53
+ pip install pytest-reporter-html[s3]
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ Enable a capture mode in `pyproject.toml`:
59
+
60
+ ```toml
61
+ [tool.pytest.ini_options]
62
+ report_mode = "all" # auto + step + manual (see Capture Modes below)
63
+ ```
64
+
65
+ Run pytest:
66
+
67
+ ```bash
68
+ pytest
69
+ ```
70
+
71
+ After the run:
72
+
73
+ ```
74
+ ================================================================================
75
+ All Tests Completed - Aggregated Report:
76
+ /absolute/path/to/build/test-reports/TestReport_Latest.html
77
+ Open in browser: file:///absolute/path/to/build/test-reports/TestReport_Latest.html
78
+ ================================================================================
79
+ ```
80
+
81
+ Open the HTML file in any browser.
82
+
83
+ ---
84
+
85
+ ## How It Works
86
+
87
+ The plugin hooks into pytest's test lifecycle and builds a structured report for every test, organized as:
88
+
89
+ ```
90
+ Session
91
+ └── Test Class / Module (collapsible group)
92
+ └── Test Function (collapsible)
93
+ └── Step (auto-created per test phase)
94
+ └── Event (captured log line)
95
+ ```
96
+
97
+ ### Zero-Config Log Capture
98
+
99
+ The plugin attaches a handler to Python's **root logger** at test start and removes it at teardown. Any code that uses standard `logging` — your application, third-party libraries, anything — has its output captured as **events** in the report. No imports, no fixtures, no code changes.
100
+
101
+ ```python
102
+ # your application code (not test code)
103
+ import logging
104
+ logger = logging.getLogger(__name__)
105
+
106
+ def create_order(items):
107
+ logger.info(f"Creating order with {len(items)} items")
108
+ order = db.insert(items)
109
+ logger.info(f"Order created: {order.id}")
110
+ return order
111
+
112
+ def validate_payment(order_id, method):
113
+ logger.debug(f"Validating payment for order {order_id}")
114
+ if method not in SUPPORTED_METHODS:
115
+ logger.error(f"Unsupported payment method: {method}")
116
+ raise ValueError(f"Unsupported: {method}")
117
+ logger.info("Payment validated")
118
+ ```
119
+
120
+ ```python
121
+ # your test — no plugin imports needed
122
+ def test_place_order():
123
+ order = create_order([{"sku": "A1", "qty": 2}])
124
+ validate_payment(order.id, "card")
125
+ assert order.status == "confirmed"
126
+ ```
127
+
128
+ The report captures every `logger.*()` call from `create_order` and `validate_payment` as events, even though the test itself has no logging code:
129
+
130
+ ```
131
+ Test: test_place_order PASSED
132
+ └── test_place_order PASSED 85ms
133
+ ├── INFO Creating order with 1 items orders.py:5
134
+ ├── INFO Order created: ord-123 orders.py:7
135
+ ├── DEBUG Validating payment for order ord-123 payments.py:3
136
+ └── INFO Payment validated payments.py:7
137
+ ```
138
+
139
+ Each captured event records:
140
+ - **Level** — `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`
141
+ - **Message** — the formatted log message
142
+ - **Source file** and **line number** — from the actual call site
143
+ - **Timestamp** — millisecond precision
144
+
145
+ ### Exception Capture
146
+
147
+ Exceptions logged via `logger.exception()` or `logger.error(..., exc_info=True)` are captured with their **full formatted traceback**, rendered as a collapsible block in the HTML report — exactly as they would appear at runtime.
148
+
149
+ ### Automatic Phase Steps
150
+
151
+ For each test, the plugin automatically creates steps for the pytest lifecycle phases:
152
+
153
+ - **Setup** — fixture setup (hidden if empty and passed)
154
+ - **\<test function name\>** — the test body itself
155
+ - **Teardown** — fixture teardown (hidden if empty and passed)
156
+
157
+ This means every test has at least one step (its own name) where all captured events are grouped — without any code on your part.
158
+
159
+ ### Adding Descriptive Logs to Tests
160
+
161
+ While the plugin captures application-level logging automatically, you can add descriptive logs directly in your tests to make the report more readable:
162
+
163
+ ```python
164
+ import logging
165
+
166
+ logger = logging.getLogger(__name__)
167
+
168
+ class TestCheckout:
169
+ def test_empty_cart_rejected(self):
170
+ logger.info("Attempting checkout with empty cart")
171
+ response = client.post("/checkout", json={"items": []})
172
+
173
+ logger.info(f"Response status: {response.status_code}")
174
+ assert response.status_code == 400
175
+
176
+ logger.info("Empty cart correctly rejected")
177
+
178
+ def test_successful_purchase(self):
179
+ logger.info("Adding item to cart and checking out")
180
+ cart = Cart()
181
+ cart.add("SKU-42", qty=2)
182
+ response = client.post("/checkout", json=cart.to_dict())
183
+
184
+ logger.info(f"Order ID: {response.json()['order_id']}")
185
+ assert response.status_code == 201
186
+ ```
187
+
188
+ These `logger.info()` calls appear as events in the report, making it clear what each test does when you read the HTML output.
189
+
190
+ ---
191
+
192
+ ## Explicit Steps (Optional)
193
+
194
+ For tests with multiple logical phases, you can optionally use the `step` context manager to group events into **named steps**. This is entirely optional — most tests work fine with just logging.
195
+
196
+ ### `step` as Context Manager
197
+
198
+ ```python
199
+ from pytest_reporter_html import step
200
+
201
+ def test_user_lifecycle():
202
+ with step("Create user"):
203
+ response = client.post("/users", json={"name": "Alice"})
204
+ assert response.status_code == 201
205
+ user_id = response.json()["id"]
206
+
207
+ with step("Update profile"):
208
+ client.patch(f"/users/{user_id}", json={"role": "admin"})
209
+
210
+ with step("Verify changes"):
211
+ user = client.get(f"/users/{user_id}").json()
212
+ assert user["role"] == "admin"
213
+ ```
214
+
215
+ Report output:
216
+
217
+ ```
218
+ Test: test_user_lifecycle PASSED
219
+ ├── Step 01: Create user PASSED 120ms
220
+ ├── Step 02: Update profile PASSED 45ms
221
+ └── Step 03: Verify changes PASSED 30ms
222
+ ```
223
+
224
+ If a step raises, it is marked **FAILED** with the exception message and stack trace.
225
+
226
+ ### `step` as Decorator
227
+
228
+ ```python
229
+ from pytest_reporter_html import step
230
+
231
+ @step("Create admin user")
232
+ def create_admin(client, name):
233
+ return client.post("/admin", json={"name": name}).json()
234
+
235
+ def test_admin_flow():
236
+ admin = create_admin(client, "Bob") # → Step 01: Create admin user
237
+ assert admin["role"] == "admin"
238
+ ```
239
+
240
+ Async functions are supported:
241
+
242
+ ```python
243
+ @step("Fetch data")
244
+ async def fetch_data(session):
245
+ async with session.get("/data") as resp:
246
+ return await resp.json()
247
+ ```
248
+
249
+ ### `report_step` Fixture
250
+
251
+ If you prefer not to import `step` directly:
252
+
253
+ ```python
254
+ def test_example(report_step):
255
+ with report_step("Prepare"):
256
+ data = load_fixtures()
257
+ with report_step("Execute"):
258
+ result = process(data)
259
+ ```
260
+
261
+ ---
262
+
263
+ ## Fixtures Reference
264
+
265
+ All fixtures are available automatically — no imports or configuration needed.
266
+
267
+ ### `report_log`
268
+
269
+ A logger that writes events directly to the report (bypasses Python's logging system):
270
+
271
+ ```python
272
+ def test_example(report_log):
273
+ report_log.info("Starting test")
274
+ report_log.debug("Debug detail")
275
+ report_log.warn("Something unexpected")
276
+ report_log.error("Something failed")
277
+ ```
278
+
279
+ > Most projects don't need this — standard `logging` is captured automatically.
280
+
281
+ ### `report_step`
282
+
283
+ The `step` context manager, available as a fixture:
284
+
285
+ ```python
286
+ def test_example(report_step):
287
+ with report_step("Do something"):
288
+ ...
289
+ ```
290
+
291
+ ### `report_test_name`
292
+
293
+ Override the test name in the report (useful for parameterized tests):
294
+
295
+ ```python
296
+ def test_firewall_rule(rule, report_test_name):
297
+ report_test_name(rule.name) # "Allow-HTTP-Inbound" instead of "test_firewall_rule[rule0]"
298
+ ```
299
+
300
+ ---
301
+
302
+ ## Output Structure
303
+
304
+ ```
305
+ build/test-reports/
306
+ ├── json/
307
+ │ ├── test_login_1709912345000.json
308
+ │ ├── test_checkout_1709912345100.json
309
+ │ └── ...
310
+ ├── TestReport_Latest.html ← open this
311
+ └── TestReport_All_1709912345000.html ← timestamped copy
312
+ ```
313
+
314
+ - **JSON files** — one per test, containing steps, events, status, timing, and metadata
315
+ - **HTML report** — aggregated single-page dashboard
316
+
317
+ ### JSON Structure
318
+
319
+ ```json
320
+ {
321
+ "testStatus": "PASSED",
322
+ "className": "TestCheckout",
323
+ "steps": [
324
+ {
325
+ "name": "test_empty_cart_rejected",
326
+ "status": "PASSED",
327
+ "startTime": 1709912345000,
328
+ "endTime": 1709912345120,
329
+ "events": [
330
+ {
331
+ "level": "INFO",
332
+ "event": "Attempting checkout with empty cart",
333
+ "startTime": 1709912345010,
334
+ "sourceFileName": "test_checkout.py",
335
+ "sourceLineNumber": 8
336
+ },
337
+ {
338
+ "level": "INFO",
339
+ "event": "Response status: 400",
340
+ "startTime": 1709912345080,
341
+ "sourceFileName": "test_checkout.py",
342
+ "sourceLineNumber": 11
343
+ }
344
+ ]
345
+ }
346
+ ],
347
+ "AllTestMethods": ["test_empty_cart_rejected", "test_successful_purchase"]
348
+ }
349
+ ```
350
+
351
+ ---
352
+
353
+ ## HTML Report Features
354
+
355
+ - **Progress bar** — visual pass/fail ratio at the top
356
+ - **Search** — filter tests by name in real time
357
+ - **Status filter** — show All / Passed / Failed
358
+ - **Log level filter** — show events at TRACE / DEBUG / INFO / WARN / ERROR and above
359
+ - **Collapsible class groups** — tests grouped by class or module, with pass/fail badges
360
+ - **Collapsible tests** — click to expand and see steps and events
361
+ - **Collapsible steps** — each step shows its events, duration, and status
362
+ - **Auto-expand failures** — failed tests and steps open by default
363
+ - **Exception rendering** — full tracebacks in collapsible `<pre>` blocks
364
+ - **Expand All / Collapse All** — toolbar buttons
365
+ - **Copy report URL** — one-click copy
366
+
367
+ ---
368
+
369
+ ## Configuration
370
+
371
+ All options go in `pyproject.toml` under `[tool.pytest.ini_options]`:
372
+
373
+ ```toml
374
+ [tool.pytest.ini_options]
375
+ report_mode = "all"
376
+ report_dir = "build/test-reports"
377
+ report_title = "My Project"
378
+ report_log_level = "INFO"
379
+ report_html = true
380
+ report_exclude_loggers = "urllib3,botocore,httpcore"
381
+ ```
382
+
383
+ ### Options Reference
384
+
385
+ | Option | Type | Default | Description |
386
+ |--------|------|---------|-------------|
387
+ | `report_enabled` | `bool` | `true` | Master switch — set to `false` to completely disable the reporter |
388
+ | `report_mode` | `string` | *(empty — disabled)* | Capture mode (see [Capture Modes](#capture-modes)). **Must be set to enable reporting.** |
389
+ | `report_dir` | `string` | `build/test-reports` | Output directory for JSON and HTML reports |
390
+ | `report_title` | `string` | `Test Report` | Title shown in the HTML report header |
391
+ | `report_log_level` | `string` | `DEBUG` | Minimum log level to capture: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR` |
392
+ | `report_auto_log` | `bool` | `true` | Legacy toggle for auto log capture (prefer `report_mode`) |
393
+ | `report_html` | `bool` | `true` | Generate the aggregated HTML report at session end |
394
+ | `report_exclude_loggers` | `string` | *(empty)* | Comma-separated logger name prefixes to exclude from capture |
395
+
396
+ ### CLI Options
397
+
398
+ | Flag | Description |
399
+ |------|-------------|
400
+ | `--disable-reporter` | Completely disable the reporter (no JSON, no HTML) |
401
+ | `--no-report-html` | Disable HTML report generation (JSON still produced) |
402
+
403
+ ### Capture Modes
404
+
405
+ The plugin supports three capture modes that can be used individually or combined:
406
+
407
+ | Mode | What it captures |
408
+ |------|-----------------|
409
+ | `auto` | Python's standard `logging` output — zero-config, captures application and test logs |
410
+ | `step` | Explicit `step()` context manager and decorator calls |
411
+ | `manual` | The `report_log` fixture (`report_log.info(...)`, etc.) |
412
+
413
+ By default no mode is active — **you must set `report_mode` to enable reporting**. Set it in `pyproject.toml`:
414
+
415
+ ```toml
416
+ [tool.pytest.ini_options]
417
+ # Enable all modes
418
+ report_mode = "all"
419
+
420
+ # Only auto-capture Python logging (step() and report_log become no-ops)
421
+ report_mode = "auto"
422
+
423
+ # Only explicit steps (no auto-log, report_log becomes no-op)
424
+ report_mode = "step"
425
+
426
+ # Only report_log fixture (no auto-log, step() becomes no-op)
427
+ report_mode = "manual"
428
+
429
+ # Combine modes with comma-separated values
430
+ report_mode = "auto,step"
431
+ report_mode = "step,manual"
432
+ ```
433
+
434
+ When a mode is not selected, its API becomes a **silent no-op** — test code using `step()` or `report_log` will still run without errors, but events won't appear in the report.
435
+
436
+ ### Disabling the Reporter
437
+
438
+ To completely disable all reporting (no JSON, no HTML, no overhead):
439
+
440
+ ```bash
441
+ # Via CLI flag
442
+ pytest --disable-reporter
443
+
444
+ # Via ini option
445
+ # pyproject.toml
446
+ [tool.pytest.ini_options]
447
+ report_enabled = false
448
+ ```
449
+
450
+ When disabled, all hooks are skipped, `step()` is a no-op, and `report_log` is a no-op.
451
+
452
+ ### Excluding Noisy Loggers
453
+
454
+ ```toml
455
+ [tool.pytest.ini_options]
456
+ report_exclude_loggers = "urllib3,botocore,httpcore,asyncio"
457
+ ```
458
+
459
+ ---
460
+
461
+ ## S3 Upload (CI)
462
+
463
+ Set environment variables to upload reports to S3 automatically:
464
+
465
+ | Variable | Required | Description |
466
+ |----------|----------|-------------|
467
+ | `REPORT_CI_RUN` | Yes | Set to `true` to enable S3 upload |
468
+ | `REPORT_RUN_ID` | Yes | Unique run identifier |
469
+ | `REPORT_TIMESTAMP` | No | Run timestamp (defaults to current time) |
470
+ | `REPORT_S3_BUCKET` | No | S3 bucket (default: `external-test-results`) |
471
+ | `REPORT_S3_REGION` | No | S3 region (default: `eu-central-1`) |
472
+ | `REPORT_TEST_TYPE` | No | Test type for S3 key prefix |
473
+ | `REPORT_CYCLE` | No | Test cycle for S3 key prefix |
474
+ | `REPORT_SUITE_NAME` | No | Suite name for S3 key prefix |
475
+
476
+ Requires: `pip install pytest-reporter-html[s3]`
477
+
478
+ ## Compatibility
479
+
480
+ - Python 3.10+
481
+ - pytest 7.0+
482
+ - Works with `pytest-asyncio`, `pytest-xdist`, and standard pytest plugins
483
+ - Sync and async tests fully supported
484
+
485
+ ## License
486
+
487
+ MIT