Skip to content

Commit 8fb619f

Browse files
authored
add support for allure report (#12)
1 parent feba0f4 commit 8fb619f

File tree

7 files changed

+675
-39
lines changed

7 files changed

+675
-39
lines changed

README.md

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,37 @@
11
# pytest-httpdbg
22

3-
A pytest plugin to record HTTP(S) requests with stack trace.
4-
5-
![](ui.png)
3+
A pytest plugin for recording HTTP(S) requests and saving them in your test report.
64

75
## installation
86

97
```
108
pip install pytest-httpdbg
119
```
1210

13-
## usage
14-
15-
### pytest custom options
16-
17-
```
18-
--httpdbg record HTTP(S) requests
19-
--httpdbg-dir=HTTPDBG_DIR
20-
save httpdbg traces in a directory
21-
--httpdbg-no-clean do not clean the httpdbg directory
22-
--httpdbg-initiator=HTTPDBG_INITIATOR
23-
add a new initiator (package) for httpdbg
24-
```
25-
### option httpdbg
26-
27-
Enables the record of the HTTP requests.
11+
## Allure report
2812

29-
### option httpdbg-dir
13+
If you use the [allure-pytest](https://pypi.org/project/allure-pytest/) plugin to generate an [Allure](https://allurereport.org/docs/pytest/) report, you can use [pytest-httpdbg](https://pypi.org/project/pytest-httpdbg/) to include HTTP request traces in your test report without any code modifications.
3014

31-
Indicates where to save the log files.
15+
All you need to do is add the `--httpdbg-allure` option to your pytest command line:
3216

33-
### option httpdbg-no-clean
34-
35-
Does not clean existing log files if the log directory is not empty.
17+
```
18+
pytest ../httpdbg-docs/examples/ --alluredir=./allure-results --httpdbg-allure
19+
```
3620

37-
### option http-initiator
21+
If an HTTP request is made by the test (or within a fixture, during the setup or teardown phase), the request will be saved in the Allure report under a step called `httpdbg`.
3822

39-
An initiator is the function/method that is at the origin of the HTTP requests. By default, we already support some packages but you can add your own initiators.
23+
![](https://github.com/cle-b/pytest-httpdbg/blob/main/pytest-httpdbg-allure-0.8.0.png?raw=true)
4024

41-
To add a new package in the list of initiators, you can use the `http-initiator` command line argument.
4225

43-
You can use any package as an initiator, this is not limited to HTTP requests.
26+
## Custom test report
4427

45-
## test report
28+
You can add HTTP traces to any test report of your choice. To do this, you can use the HTTP traces saved by the plugin in Markdown format.
4629

47-
When the test is finished (teardown step included), one log file in markdown format is written. The path to this log file is stashed in the item when the test starts (before the setup step), even if the file not exists yet.
30+
When a test finishes (including the teardown step), a log file in Markdown format is generated. The path to this log file is stored in the test item when the test starts (before the setup step), even if the file does not yet exist.
4831

4932
### pytest-html
5033

51-
You can copy the following code in your top-level `conftest.py` to include the logs into your pytest-html report.
34+
You can copy the following code in your top-level `conftest.py` to include the logs into your `pytest-html` report.
5235

5336
```python
5437
import os
@@ -82,6 +65,36 @@ This example works if you use the same directory for the html test report file a
8265

8366
If this is not the case, you must adapt it to your configuration.
8467

68+
![](https://github.com/cle-b/pytest-httpdbg/blob/main/ui.png?raw=true)
69+
70+
## pytest command line options
71+
72+
```
73+
reporting:
74+
75+
--httpdbg record HTTP(S) requests
76+
--httpdbg-dir=HTTPDBG_DIR save httpdbg traces in a directory
77+
--httpdbg-no-clean do not clean the httpdbg directory
78+
79+
--httpdbg-allure save HTTP(S) traces into the allure report
80+
--httpdbg-no-headers save the HTTP headers
81+
--httpdbg-no-binary do not save the HTTP payload if it's a binary content
82+
--httpdbg-only-on-failure save the HTTP requests only if the test failed
83+
84+
--httpdbg-initiator=HTTPDBG_INITIATOR add a new initiator (package) for httpdbg
85+
86+
```
87+
88+
## httpdbg
89+
90+
This plugin is based on the [httpdbg](https://pypi.org/project/httpdbg/) Python tool. You can use it to trace all HTTP requests in your tests and view them in a more detailed user interface using the `pyhttpdbg` command.
91+
92+
```
93+
pyhttpdbg -m pytest -v examples/
94+
```
95+
96+
![](https://github.com/cle-b/pytest-httpdbg/blob/main/httpdbg-pytest-1.2.1.png?raw=true)
97+
8598
## documentation
8699

87100
https://httpdbg.readthedocs.io/en/latest/pytest/

httpdbg-pytest-1.2.1.png

89.3 KB
Loading

pytest-httpdbg-allure-0.8.0.png

108 KB
Loading

pytest_httpdbg/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# -*- coding: utf-8 -*-
22
from pytest_httpdbg.plugin import httpdbg_record_filename # noqa F401
33

4-
__version__ = "0.7.0"
4+
__version__ = "0.8.0"

pytest_httpdbg/plugin.py

Lines changed: 196 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import glob
33
import os
44
import time
5+
import traceback
56
from typing import Optional
67

78
import pytest
@@ -62,27 +63,66 @@ def record_to_md(record, initiators):
6263

6364

6465
def pytest_addoption(parser):
65-
parser.addoption("--httpdbg", action="store_true", help="record HTTP(S) requests")
66-
parser.addoption(
66+
67+
reporting_group = parser.getgroup("reporting")
68+
69+
# mode custom
70+
reporting_group.addoption(
71+
"--httpdbg", action="store_true", help="record HTTP(S) requests"
72+
)
73+
74+
reporting_group.addoption(
6775
"--httpdbg-dir", type=str, default="", help="save httpdbg traces in a directory"
6876
)
69-
parser.addoption(
77+
78+
reporting_group.addoption(
7079
"--httpdbg-no-clean",
7180
action="store_true",
7281
default=False,
7382
help="do not clean the httpdbg directory",
7483
)
75-
parser.addoption(
84+
85+
# mode allure
86+
reporting_group.addoption(
87+
"--httpdbg-allure",
88+
action="store_true",
89+
help="save HTTP(S) traces into the allure report",
90+
)
91+
92+
reporting_group.addoption(
93+
"--httpdbg-no-headers",
94+
action="store_true",
95+
default=False,
96+
help="save the HTTP headers",
97+
)
98+
99+
reporting_group.addoption(
100+
"--httpdbg-no-binary",
101+
action="store_true",
102+
default=False,
103+
help="do not save the HTTP payload if it's a binary content",
104+
)
105+
106+
reporting_group.addoption(
107+
"--httpdbg-only-on-failure",
108+
action="store_true",
109+
default=False,
110+
help="save the HTTP requests only if the test failed",
111+
)
112+
113+
reporting_group.addoption(
76114
"--httpdbg-initiator",
77115
action="append",
78116
help="add a new initiator (package) for httpdbg",
79117
)
80118

81119

82120
def pytest_configure(config):
83-
# add a flag to indicates to HTTPDBG to not set specific initiator
84-
if config.option.httpdbg:
85-
os.environ["HTTPDBG_PYTEST_PLUGIN"] = "1"
121+
122+
if config.option.httpdbg is True and config.option.httpdbg_allure is True:
123+
pytest.exit(
124+
"Error: --httpdbg and --httpdbg-allure are mutually exclusive. Please specify only one."
125+
)
86126

87127
# clean logs directory
88128
httpdbg_dir = config.option.httpdbg_dir
@@ -124,3 +164,152 @@ def pytest_runtest_protocol(item: pytest.Item, nextitem: Optional[pytest.Item]):
124164
f.write(f"{record_to_md(record, records.initiators)}\n")
125165
else:
126166
yield
167+
168+
169+
# Allure mode: HTTP requests are recorded throughout the entire session and
170+
# saved in the Allure report at the test level.
171+
def pytest_sessionstart(session):
172+
if session.config.option.httpdbg_allure:
173+
session.httpdbg_recorder = httprecord(
174+
initiators=session.config.option.httpdbg_initiator
175+
)
176+
session.httpdbg_records = session.httpdbg_recorder.__enter__()
177+
178+
179+
def pytest_sessionfinish(session, exitstatus):
180+
if session.config.option.httpdbg_allure:
181+
session.httpdbg_recorder.__exit__(None, None, None)
182+
183+
184+
def get_allure_attachment_type_from_content_type(content_type: str):
185+
try:
186+
import allure
187+
188+
for attachment_type in allure.attachment_type:
189+
if attachment_type.mime_type == content_type:
190+
return attachment_type
191+
except ImportError:
192+
pass
193+
return None
194+
195+
196+
def req_resp_steps(label, req, save_headers, save_binary_payload):
197+
try:
198+
import allure
199+
200+
# we generate the payload first because we do not want to add a step
201+
# if there is no headers and no payload to save
202+
content = req.preview
203+
payload = None
204+
if content.get("text"):
205+
payload = content.get("text")
206+
elif save_binary_payload:
207+
payload = req.content
208+
209+
if save_headers or payload:
210+
with allure.step(label):
211+
if save_headers:
212+
allure.attach(
213+
req.rawheaders.decode("utf-8"),
214+
name="headers",
215+
attachment_type=allure.attachment_type.TEXT,
216+
)
217+
if payload:
218+
attachment_type = get_allure_attachment_type_from_content_type(
219+
content.get("content_type")
220+
)
221+
allure.attach(
222+
payload, name="payload", attachment_type=attachment_type
223+
)
224+
except ImportError:
225+
pass
226+
227+
228+
@pytest.hookimpl(hookwrapper=True)
229+
def pytest_runtest_makereport(item, call):
230+
231+
outcome = yield
232+
report = outcome.get_result()
233+
234+
if item.config.option.httpdbg_allure:
235+
# we keep the information about the status of the test for all phases
236+
item.passed = getattr(item, "passed", True) and report.passed
237+
238+
if report.when == "teardown":
239+
if (not item.config.option.httpdbg_only_on_failure) or (not item.passed):
240+
try:
241+
import allure
242+
243+
with allure.step("httpdbg"):
244+
245+
records = item.session.httpdbg_records
246+
247+
for record in records:
248+
249+
label = ""
250+
251+
if record.response.status_code:
252+
label += f"{record.response.status_code} "
253+
254+
if record.request.method:
255+
label += f"{record.request.method} "
256+
257+
if record.request.uri:
258+
url = record.request.uri
259+
else:
260+
url = record.url
261+
if len(url) > 200:
262+
url = url[:100] + "..." + url[-97:]
263+
ex = (
264+
(str(type(record.exception)) + " ")
265+
if record.exception is not None
266+
else ""
267+
)
268+
label += f"{ex}{url}"
269+
270+
if record.tag:
271+
label += f" (from {record.tag})"
272+
273+
with allure.step(label):
274+
details = record.url
275+
details += f"\n\nstatus: {record.response.status_code} {record.response.message}"
276+
details += f"\n\nstart: {record.tbegin.isoformat()}"
277+
details += f"\nend: {record.last_update.isoformat()}"
278+
279+
if record.initiator_id in records.initiators:
280+
details += f"\n\n{records.initiators[record.initiator_id].short_stack}"
281+
282+
if record.exception is not None:
283+
details += (
284+
f"\n\nException: {type(record.exception)}\n"
285+
)
286+
details += "".join(
287+
traceback.format_exception(
288+
type(record.exception),
289+
record.exception,
290+
record.exception.__traceback__,
291+
)
292+
)
293+
294+
allure.attach(
295+
details,
296+
name="details",
297+
attachment_type=allure.attachment_type.TEXT,
298+
)
299+
300+
req_resp_steps(
301+
"request",
302+
record.request,
303+
not item.config.option.httpdbg_no_headers,
304+
not item.config.option.httpdbg_no_binary,
305+
)
306+
req_resp_steps(
307+
"response",
308+
record.response,
309+
not item.config.option.httpdbg_no_headers,
310+
not item.config.option.httpdbg_no_binary,
311+
)
312+
except ImportError:
313+
pass
314+
315+
item.session.httpdbg_records.reset()

requirements-dev.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ Werkzeug<2.1
88
flask>2
99
pytest-httpbin
1010
pytest-html
11-
pytest-xdist
11+
pytest-xdist
12+
allure-pytest

0 commit comments

Comments
 (0)