pytest-json-ctrf 0.2.3__tar.gz → 0.3.1__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.
- {pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/PKG-INFO +1 -1
- {pytest_json_ctrf-0.2.3/plugin → pytest_json_ctrf-0.3.1/ctrf}/BaseMetadataReport.py +1 -2
- {pytest_json_ctrf-0.2.3/plugin → pytest_json_ctrf-0.3.1/ctrf}/CommonTestReportPlugin.py +1 -2
- {pytest_json_ctrf-0.2.3/plugin → pytest_json_ctrf-0.3.1/ctrf}/Report.py +17 -14
- {pytest_json_ctrf-0.2.3/plugin → pytest_json_ctrf-0.3.1/ctrf}/TestObject.py +39 -22
- {pytest_json_ctrf-0.2.3/plugin → pytest_json_ctrf-0.3.1/ctrf}/main.py +12 -10
- {pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/pyproject.toml +6 -3
- {pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/pytest_json_ctrf.egg-info/PKG-INFO +1 -1
- {pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/pytest_json_ctrf.egg-info/SOURCES.txt +8 -7
- pytest_json_ctrf-0.3.1/pytest_json_ctrf.egg-info/entry_points.txt +2 -0
- pytest_json_ctrf-0.3.1/pytest_json_ctrf.egg-info/top_level.txt +2 -0
- pytest_json_ctrf-0.3.1/tests/test_plugin.py +91 -0
- pytest_json_ctrf-0.2.3/pytest_json_ctrf.egg-info/entry_points.txt +0 -2
- pytest_json_ctrf-0.2.3/pytest_json_ctrf.egg-info/top_level.txt +0 -1
- {pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/README.md +0 -0
- {pytest_json_ctrf-0.2.3/plugin → pytest_json_ctrf-0.3.1/ctrf}/__init__.py +0 -0
- {pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/pytest_json_ctrf.egg-info/dependency_links.txt +0 -0
- {pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/pytest_json_ctrf.egg-info/requires.txt +0 -0
- {pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/setup.cfg +0 -0
- {pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/tests/test_example.py +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
from pytest import
|
|
2
|
-
from .Report import Report
|
|
1
|
+
from pytest import hookimpl, TestReport
|
|
3
2
|
from collections import OrderedDict
|
|
4
3
|
from .BaseMetadataReport import metadata, BaseMetadataReport
|
|
5
4
|
|
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
import pytest
|
|
3
3
|
import time
|
|
4
4
|
import json
|
|
5
|
-
from pytest import
|
|
5
|
+
from pytest import TestReport
|
|
6
6
|
from collections import OrderedDict
|
|
7
7
|
from .TestObject import TestObject, TestStatus
|
|
8
8
|
|
|
@@ -27,13 +27,16 @@ class Report:
|
|
|
27
27
|
"version": str(pytest.__version__)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
def list_tests_by_status(self, status: TestStatus) -> list:
|
|
31
|
+
return [test for test in self.prepared_tests.values() if test.status == status]
|
|
32
|
+
|
|
30
33
|
def _get_summary(self) -> dict:
|
|
31
34
|
return {
|
|
32
35
|
'tests': len(self.prepared_tests),
|
|
33
|
-
'passed': len(
|
|
34
|
-
'failed': len(
|
|
35
|
-
'skipped': len(
|
|
36
|
-
'pending': len(
|
|
36
|
+
'passed': len(self.list_tests_by_status(TestStatus.PASSED)),
|
|
37
|
+
'failed': len(self.list_tests_by_status(TestStatus.FAILED)),
|
|
38
|
+
'skipped': len(self.list_tests_by_status(TestStatus.SKIPPED)),
|
|
39
|
+
'pending': len(self.list_tests_by_status(TestStatus.PENDING)),
|
|
37
40
|
'other': 0,
|
|
38
41
|
'start': self.start_time,
|
|
39
42
|
'stop': self.stop_time
|
|
@@ -41,10 +44,11 @@ class Report:
|
|
|
41
44
|
|
|
42
45
|
def collect(self, report: TestReport) -> None:
|
|
43
46
|
if report.nodeid not in self.test_items.keys():
|
|
44
|
-
|
|
47
|
+
worker_id = getattr(report, 'worker_id', None)
|
|
48
|
+
test = TestObject(report, worker_id)
|
|
45
49
|
else:
|
|
46
|
-
test = self.test_items.get(report.nodeid)
|
|
47
|
-
test.update(report
|
|
50
|
+
test = self.test_items.get(report.nodeid) # type: ignore
|
|
51
|
+
test.update(report)
|
|
48
52
|
self.test_items[report.nodeid] = test
|
|
49
53
|
|
|
50
54
|
def process_retries(self) -> None:
|
|
@@ -58,12 +62,11 @@ class Report:
|
|
|
58
62
|
|
|
59
63
|
def get_report(self) -> dict:
|
|
60
64
|
self.process_retries()
|
|
61
|
-
return {'results':
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
65
|
+
return {'results': {
|
|
66
|
+
"tool": self._get_tool(),
|
|
67
|
+
"summary": self._get_summary(),
|
|
68
|
+
"tests": [test.serialize() for test in self.prepared_tests.values()]
|
|
69
|
+
}
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
def save(self, report_file: str) -> None:
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
2
4
|
from pytest import TestReport
|
|
3
5
|
|
|
6
|
+
|
|
4
7
|
class TestStatus(Enum):
|
|
5
8
|
PASSED = "passed"
|
|
6
9
|
FAILED = "failed"
|
|
@@ -10,24 +13,41 @@ class TestStatus(Enum):
|
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class TestObject:
|
|
13
|
-
name: str
|
|
14
|
-
status: TestStatus
|
|
15
|
-
raw_status: str
|
|
16
|
+
name: str
|
|
17
|
+
status: TestStatus
|
|
18
|
+
raw_status: str
|
|
16
19
|
duration: float = 0
|
|
17
|
-
start: float
|
|
18
|
-
stop: float
|
|
19
|
-
retries: int
|
|
20
|
-
message: str
|
|
20
|
+
start: float
|
|
21
|
+
stop: float
|
|
22
|
+
retries: int
|
|
23
|
+
message: str
|
|
24
|
+
|
|
25
|
+
worker_id: str
|
|
26
|
+
file_path: str
|
|
27
|
+
tags: list[str]
|
|
28
|
+
browser: str
|
|
29
|
+
trace: str
|
|
30
|
+
|
|
31
|
+
def __init__(self, report: TestReport, worker_id: str or None = None):
|
|
32
|
+
self.name = report.nodeid.split('[')[0]
|
|
33
|
+
self.status = TestStatus.PENDING
|
|
34
|
+
self.status = self.set_status(report)
|
|
35
|
+
self.raw_status = None
|
|
36
|
+
self.duration = 0
|
|
37
|
+
self.start = 0
|
|
38
|
+
self.stop = 0
|
|
39
|
+
self.retries = 0
|
|
40
|
+
self.message = None
|
|
21
41
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
self.worker_id = worker_id
|
|
43
|
+
self.file_path = report.location[0]
|
|
44
|
+
self.tags = []
|
|
45
|
+
self.browser = None
|
|
46
|
+
self.trace = None
|
|
27
47
|
|
|
28
|
-
def set_status(self, report: TestReport) ->
|
|
48
|
+
def set_status(self, report: TestReport) -> TestStatus:
|
|
29
49
|
if self.status in (TestStatus.SKIPPED, TestStatus.FAILED):
|
|
30
|
-
return
|
|
50
|
+
return self.status
|
|
31
51
|
elif report.skipped:
|
|
32
52
|
self.status = TestStatus.SKIPPED
|
|
33
53
|
elif report.failed:
|
|
@@ -43,26 +63,23 @@ class TestObject:
|
|
|
43
63
|
self.status = TestStatus.PASSED
|
|
44
64
|
else:
|
|
45
65
|
self.status = TestStatus.OTHER
|
|
66
|
+
return self.status
|
|
46
67
|
|
|
47
|
-
def update(self, report: TestReport
|
|
48
|
-
self.name = report.nodeid.split('[')[0]
|
|
49
|
-
# self.name = report.head_line.split('[')[0]
|
|
68
|
+
def update(self, report: TestReport) -> None:
|
|
50
69
|
self.set_status(report)
|
|
51
70
|
self.duration += report.duration
|
|
52
|
-
if report.when == "setup" and
|
|
71
|
+
if report.when == "setup" and self.start == 0:
|
|
53
72
|
self.start = report.start
|
|
54
|
-
if report.when == "teardown" and
|
|
73
|
+
if report.when == "teardown" and self.stop == 0:
|
|
55
74
|
self.stop = report.stop
|
|
56
|
-
self.file_path = report.location[0]
|
|
57
75
|
if report.longrepr and len(report.longreprtext) > 0:
|
|
58
76
|
self.trace = report.longreprtext
|
|
59
77
|
if hasattr(report, '_ctrf_metadata'):
|
|
60
78
|
self.tags = report._ctrf_metadata.get('tags')
|
|
61
79
|
self.browser = report._ctrf_metadata.get('browser')
|
|
62
|
-
self.worker_id = worker_id
|
|
63
80
|
|
|
64
81
|
def serialize(self) -> dict:
|
|
65
|
-
result = {
|
|
82
|
+
result: dict[str, Any] = {
|
|
66
83
|
'name': self.name,
|
|
67
84
|
'status': self.status.value,
|
|
68
85
|
'raw_status': self.raw_status,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
from pytest import Config, Parser, FixtureRequest, fixture
|
|
2
|
-
from
|
|
3
|
-
from
|
|
1
|
+
from pytest import Config, Parser, FixtureRequest, fixture
|
|
2
|
+
from ctrf.CommonTestReportPlugin import CTRF
|
|
3
|
+
from ctrf.BaseMetadataReport import BaseMetadataReport
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def pytest_addoption(parser: Parser):
|
|
7
|
-
parser.
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
group = parser.getgroup('ctrf', 'generate test report in CTR format')
|
|
8
|
+
group.addoption('--ctrf',
|
|
9
|
+
action='store',
|
|
10
|
+
help='generate test report. Report file name is optional')
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def pytest_configure(config: Config):
|
|
@@ -17,15 +18,16 @@ def pytest_configure(config: Config):
|
|
|
17
18
|
else:
|
|
18
19
|
plugin = CTRF()
|
|
19
20
|
setattr(config, '_ctrf', plugin)
|
|
20
|
-
config.pluginmanager.register(plugin, name='
|
|
21
|
+
config.pluginmanager.register(plugin, name='ctrf_plugin')
|
|
22
|
+
pass
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def pytest_unconfigure(config: Config):
|
|
24
26
|
ctrf = getattr(config, '_ctrf', None)
|
|
25
27
|
if ctrf is not None:
|
|
26
28
|
delattr(config, '_ctrf')
|
|
27
|
-
if config.pluginmanager.hasplugin('
|
|
28
|
-
config.pluginmanager.unregister(name='
|
|
29
|
+
if config.pluginmanager.hasplugin('ctrf_plugin'):
|
|
30
|
+
config.pluginmanager.unregister(name='ctrf_plugin')
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
@fixture(autouse=True)
|
|
@@ -38,4 +40,4 @@ def ctrf_json_metadata(request: FixtureRequest):
|
|
|
38
40
|
browser = request.node.callspec.params.get('browser_name')
|
|
39
41
|
if browser:
|
|
40
42
|
request.node._ctrf_metadata.setdefault('browser', browser)
|
|
41
|
-
return request.node._ctrf_metadata
|
|
43
|
+
return request.node._ctrf_metadata
|
|
@@ -3,11 +3,14 @@ requires = ["setuptools>=65.5.1", "wheel"]
|
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[tool.setuptools.packages.find]
|
|
6
|
-
exclude = [
|
|
6
|
+
exclude = [
|
|
7
|
+
".github", "tests", "build", "dist", ".venv",
|
|
8
|
+
"pytest_json_ctrf.egg-info", ".env", ".gitignore",
|
|
9
|
+
"CHANGELOG.md", "assets", "test-results"]
|
|
7
10
|
|
|
8
11
|
[project]
|
|
9
12
|
name = "pytest-json-ctrf"
|
|
10
|
-
version = "0.
|
|
13
|
+
version = "0.3.1"
|
|
11
14
|
|
|
12
15
|
dependencies = [
|
|
13
16
|
"pytest>6.0.0",
|
|
@@ -37,4 +40,4 @@ markers = [
|
|
|
37
40
|
]
|
|
38
41
|
|
|
39
42
|
[project.entry-points.pytest11]
|
|
40
|
-
|
|
43
|
+
ctrf = "ctrf.main"
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
README.md
|
|
2
2
|
pyproject.toml
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
ctrf/BaseMetadataReport.py
|
|
4
|
+
ctrf/CommonTestReportPlugin.py
|
|
5
|
+
ctrf/Report.py
|
|
6
|
+
ctrf/TestObject.py
|
|
7
|
+
ctrf/__init__.py
|
|
8
|
+
ctrf/main.py
|
|
9
9
|
pytest_json_ctrf.egg-info/PKG-INFO
|
|
10
10
|
pytest_json_ctrf.egg-info/SOURCES.txt
|
|
11
11
|
pytest_json_ctrf.egg-info/dependency_links.txt
|
|
12
12
|
pytest_json_ctrf.egg-info/entry_points.txt
|
|
13
13
|
pytest_json_ctrf.egg-info/requires.txt
|
|
14
14
|
pytest_json_ctrf.egg-info/top_level.txt
|
|
15
|
-
tests/test_example.py
|
|
15
|
+
tests/test_example.py
|
|
16
|
+
tests/test_plugin.py
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pytest import Pytester, fixture
|
|
3
|
+
|
|
4
|
+
pytest_plugins = ["pytester", "xdist", "ctrf"]
|
|
5
|
+
test_file = "tests/test_example.py"
|
|
6
|
+
basic_test_args = ["-k", "test_example"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@fixture
|
|
10
|
+
def ctrf_report_sync(pytester: Pytester):
|
|
11
|
+
pytester.copy_example(test_file)
|
|
12
|
+
pytester.runpytest(*[*basic_test_args, "--ctrf", "report.json"])
|
|
13
|
+
with open(pytester.path / "report.json") as file:
|
|
14
|
+
yield json.load(file)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@fixture
|
|
18
|
+
def ctrf_report_xdist(pytester: Pytester):
|
|
19
|
+
pytester.copy_example(test_file)
|
|
20
|
+
pytester.runpytest(*[*basic_test_args, "--ctrf", "report.json", "-n", "3"])
|
|
21
|
+
with open(pytester.path / "report.json") as file:
|
|
22
|
+
yield json.load(file)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_without_plugin_no_xdist(pytester: Pytester):
|
|
26
|
+
pytester.copy_example(test_file)
|
|
27
|
+
result = pytester.runpytest(*basic_test_args)
|
|
28
|
+
result.assert_outcomes(passed=9, skipped=1, failed=2, errors=2)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_without_plugin_with_xdist(pytester: Pytester):
|
|
32
|
+
pytester.copy_example(test_file)
|
|
33
|
+
result = pytester.runpytest(*[*basic_test_args, "-n", "3"])
|
|
34
|
+
result.assert_outcomes(passed=9, skipped=1, failed=2, errors=2)
|
|
35
|
+
assert "created: 3/3 workers" in result.stdout.str()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_with_markers(pytester: Pytester):
|
|
39
|
+
pytester.copy_example(test_file)
|
|
40
|
+
args = [*basic_test_args, "--ctrf", "report.json", "-m smoke"]
|
|
41
|
+
pytester.runpytest(*args)
|
|
42
|
+
with open(pytester.path / "report.json") as file:
|
|
43
|
+
report = json.load(file)
|
|
44
|
+
assert report["results"]["summary"]["tests"] == 1
|
|
45
|
+
assert report["results"]["summary"]["passed"] == 1
|
|
46
|
+
assert report["results"]["summary"]["failed"] == 0
|
|
47
|
+
assert report["results"]["summary"]["skipped"] == 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_with_plugin_no_xdist(ctrf_report_sync):
|
|
51
|
+
assert ctrf_report_sync["results"]["summary"]["tests"] == 9
|
|
52
|
+
assert ctrf_report_sync["results"]["summary"]["passed"] == 4
|
|
53
|
+
assert ctrf_report_sync["results"]["summary"]["failed"] == 4
|
|
54
|
+
assert ctrf_report_sync["results"]["summary"]["skipped"] == 1
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_with_plugin_failed_details(ctrf_report_sync):
|
|
58
|
+
for test in ctrf_report_sync["results"]["tests"]:
|
|
59
|
+
if test["status"] == "failed" and test.get("raw_status") == "call_failed":
|
|
60
|
+
assert test.get('trace') is not None
|
|
61
|
+
assert test.get("message") is not None
|
|
62
|
+
assert test.get("raw_status") is not None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_any_test_has_timestamps(ctrf_report_sync):
|
|
66
|
+
for test in ctrf_report_sync["results"]["tests"]:
|
|
67
|
+
assert test.get("start") is not None
|
|
68
|
+
assert test.get("stop") is not None
|
|
69
|
+
assert test.get("duration") is not None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_with_plugin_with_xdist(ctrf_report_xdist):
|
|
73
|
+
assert ctrf_report_xdist["results"]["summary"]["tests"] == 9
|
|
74
|
+
assert ctrf_report_xdist["results"]["summary"]["passed"] == 4
|
|
75
|
+
assert ctrf_report_xdist["results"]["summary"]["failed"] == 4
|
|
76
|
+
assert ctrf_report_xdist["results"]["summary"]["skipped"] == 1
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_with_plugin_failed_details_xdist(ctrf_report_xdist):
|
|
80
|
+
for test in ctrf_report_xdist["results"]["tests"]:
|
|
81
|
+
if test["status"] == "failed" and test.get("raw_status") == "call_failed":
|
|
82
|
+
assert test.get('trace') is not None
|
|
83
|
+
assert test.get("message") is not None
|
|
84
|
+
assert test.get("raw_status") is not None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_any_test_has_timestamps_xdist(ctrf_report_xdist):
|
|
88
|
+
for test in ctrf_report_xdist["results"]["tests"]:
|
|
89
|
+
assert test.get("start") is not None
|
|
90
|
+
assert test.get("stop") is not None
|
|
91
|
+
assert test.get("duration") is not None
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
plugin
|
|
File without changes
|
|
File without changes
|
{pytest_json_ctrf-0.2.3 → pytest_json_ctrf-0.3.1}/pytest_json_ctrf.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|