pytest-json-ctrf 0.2.1__tar.gz → 0.3.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 (20) hide show
  1. {pytest_json_ctrf-0.2.1 → pytest_json_ctrf-0.3.0}/PKG-INFO +4 -1
  2. {pytest_json_ctrf-0.2.1 → pytest_json_ctrf-0.3.0}/README.md +3 -0
  3. {pytest_json_ctrf-0.2.1/plugin → pytest_json_ctrf-0.3.0/ctrf}/BaseMetadataReport.py +1 -2
  4. {pytest_json_ctrf-0.2.1/plugin → pytest_json_ctrf-0.3.0/ctrf}/CommonTestReportPlugin.py +1 -2
  5. {pytest_json_ctrf-0.2.1/plugin → pytest_json_ctrf-0.3.0/ctrf}/Report.py +17 -14
  6. {pytest_json_ctrf-0.2.1/plugin → pytest_json_ctrf-0.3.0/ctrf}/TestObject.py +39 -21
  7. {pytest_json_ctrf-0.2.1/plugin → pytest_json_ctrf-0.3.0/ctrf}/main.py +12 -10
  8. {pytest_json_ctrf-0.2.1 → pytest_json_ctrf-0.3.0}/pyproject.toml +6 -3
  9. {pytest_json_ctrf-0.2.1 → pytest_json_ctrf-0.3.0}/pytest_json_ctrf.egg-info/PKG-INFO +4 -1
  10. {pytest_json_ctrf-0.2.1 → pytest_json_ctrf-0.3.0}/pytest_json_ctrf.egg-info/SOURCES.txt +8 -7
  11. pytest_json_ctrf-0.3.0/pytest_json_ctrf.egg-info/entry_points.txt +2 -0
  12. pytest_json_ctrf-0.3.0/pytest_json_ctrf.egg-info/top_level.txt +2 -0
  13. pytest_json_ctrf-0.3.0/tests/test_plugin.py +91 -0
  14. pytest_json_ctrf-0.2.1/pytest_json_ctrf.egg-info/entry_points.txt +0 -2
  15. pytest_json_ctrf-0.2.1/pytest_json_ctrf.egg-info/top_level.txt +0 -1
  16. {pytest_json_ctrf-0.2.1/plugin → pytest_json_ctrf-0.3.0/ctrf}/__init__.py +0 -0
  17. {pytest_json_ctrf-0.2.1 → pytest_json_ctrf-0.3.0}/pytest_json_ctrf.egg-info/dependency_links.txt +0 -0
  18. {pytest_json_ctrf-0.2.1 → pytest_json_ctrf-0.3.0}/pytest_json_ctrf.egg-info/requires.txt +0 -0
  19. {pytest_json_ctrf-0.2.1 → pytest_json_ctrf-0.3.0}/setup.cfg +0 -0
  20. {pytest_json_ctrf-0.2.1 → pytest_json_ctrf-0.3.0}/tests/test_example.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytest-json-ctrf
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Pytest plugin to generate json report in CTRF (Common Test Report Format)
5
5
  Author: Oleksii Ostapov
6
6
  Project-URL: Infopulse, https://infopulse.com/
@@ -97,3 +97,6 @@ The `pytest_runtest_logreport` hook in the controller node is used to collect th
97
97
  - https://github.com/testomatio/pytestomatio -> Source of inspiration for creating pytest plugins
98
98
  - https://github.com/infopulse/Playwright-course-python -> The report will be used in the demo project as +1 report option
99
99
 
100
+ ### Roadmap
101
+ - Add screenshots handling
102
+ - Add hooks for the report extension
@@ -82,3 +82,6 @@ The `pytest_runtest_logreport` hook in the controller node is used to collect th
82
82
  - https://github.com/testomatio/pytestomatio -> Source of inspiration for creating pytest plugins
83
83
  - https://github.com/infopulse/Playwright-course-python -> The report will be used in the demo project as +1 report option
84
84
 
85
+ ### Roadmap
86
+ - Add screenshots handling
87
+ - Add hooks for the report extension
@@ -1,6 +1,5 @@
1
- from pytest import Config, hookimpl, Item, CallInfo, CollectReport, Function, TestReport, fixture, FixtureRequest
1
+ from pytest import Config, hookimpl, Item
2
2
  from .Report import Report
3
- from collections import OrderedDict
4
3
 
5
4
  metadata = '_ctrf_metadata'
6
5
 
@@ -1,5 +1,4 @@
1
- from pytest import Config, hookimpl, Item, CallInfo, CollectReport, Function, TestReport, fixture, FixtureRequest
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 Item, CallInfo, TestReport, Mark
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([test for test in self.prepared_tests.values() if test.status == TestStatus.PASSED]),
34
- 'failed': len([test for test in self.prepared_tests.values() if test.status == TestStatus.FAILED]),
35
- 'skipped': len([test for test in self.prepared_tests.values() if test.status == TestStatus.SKIPPED]),
36
- 'pending': len([test for test in self.prepared_tests.values() if test.status == TestStatus.PENDING]),
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
- test = TestObject()
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, getattr(report, 'worker_id', None))
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
- "tool": self._get_tool(),
64
- "summary": self._get_summary(),
65
- "tests": [test.serialize() for test in self.prepared_tests.values()]
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 = None
14
- status: TestStatus = TestStatus.PENDING
15
- raw_status: str = None
16
+ name: str
17
+ status: TestStatus
18
+ raw_status: str
16
19
  duration: float = 0
17
- start: float = None
18
- stop: float = None
19
- retries: int = 0
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
- worker_id: str = None
23
- file_path: str = None
24
- tags: list[str] = None
25
- browser: str = None
26
- trace: str = None
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) -> None:
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,25 +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, worker_id: str = None) -> None:
48
- self.name = report.head_line.split('[')[0]
68
+ def update(self, report: TestReport) -> None:
49
69
  self.set_status(report)
50
70
  self.duration += report.duration
51
- if report.when == "setup" and not self.start:
71
+ if report.when == "setup" and self.start == 0:
52
72
  self.start = report.start
53
- if report.when == "teardown" and not self.stop:
73
+ if report.when == "teardown" and self.stop == 0:
54
74
  self.stop = report.stop
55
- self.file_path = report.location[0]
56
75
  if report.longrepr and len(report.longreprtext) > 0:
57
76
  self.trace = report.longreprtext
58
77
  if hasattr(report, '_ctrf_metadata'):
59
78
  self.tags = report._ctrf_metadata.get('tags')
60
79
  self.browser = report._ctrf_metadata.get('browser')
61
- self.worker_id = worker_id
62
80
 
63
81
  def serialize(self) -> dict:
64
- result = {
82
+ result: dict[str, Any] = {
65
83
  'name': self.name,
66
84
  'status': self.status.value,
67
85
  'raw_status': self.raw_status,
@@ -1,12 +1,13 @@
1
- from pytest import Config, Parser, FixtureRequest, fixture, Function
2
- from plugin.CommonTestReportPlugin import CTRF
3
- from plugin.BaseMetadataReport import BaseMetadataReport
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.addoption('--ctrf',
8
- action='store',
9
- help='generate test report. Report file name is optional')
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='ctrf')
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('ctrf'):
28
- config.pluginmanager.unregister(name='ctrf')
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 = [".github", "tests", "build", "dist", ".venv", "pytestomatio.egg-info", ".env", ".gitignore", "CHANGELOG.md", "assets"]
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.2.1"
13
+ version = "0.3.0"
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
- pytestomatio = "plugin.main"
43
+ ctrf = "ctrf.main"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytest-json-ctrf
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Pytest plugin to generate json report in CTRF (Common Test Report Format)
5
5
  Author: Oleksii Ostapov
6
6
  Project-URL: Infopulse, https://infopulse.com/
@@ -97,3 +97,6 @@ The `pytest_runtest_logreport` hook in the controller node is used to collect th
97
97
  - https://github.com/testomatio/pytestomatio -> Source of inspiration for creating pytest plugins
98
98
  - https://github.com/infopulse/Playwright-course-python -> The report will be used in the demo project as +1 report option
99
99
 
100
+ ### Roadmap
101
+ - Add screenshots handling
102
+ - Add hooks for the report extension
@@ -1,15 +1,16 @@
1
1
  README.md
2
2
  pyproject.toml
3
- plugin/BaseMetadataReport.py
4
- plugin/CommonTestReportPlugin.py
5
- plugin/Report.py
6
- plugin/TestObject.py
7
- plugin/__init__.py
8
- plugin/main.py
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,2 @@
1
+ [pytest11]
2
+ ctrf = ctrf.main
@@ -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,2 +0,0 @@
1
- [pytest11]
2
- pytestomatio = plugin.main