pytest-html
pytest-html copied to clipboard
Unable to save additional screenshots
Sure, is not related to this at all.
I have the following code to display the screenshots I'm taking during my tests in the report:
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
"""
This method takes the images in the folder of the report and writes them inside the report.
"""
pytest_html = item.config.pluginmanager.getplugin("html")
outcome = yield
# Get the images to be displayed inside the report
report = outcome.get_result()
extra = getattr(report, "extra", [])
if report.when == "call":
node_id = item.funcargs['request'].node.nodeid
# Get output directory
directory = _get_report_folder()
# Extract images for current tc
for file in os.listdir(directory):
if re.search(re.escape(f'{node_id}_'), file):
image = os.path.join(directory, file)
with open(image, "rb") as image_file:
screenshot = base64.b64encode(image_file.read())
extra.append(pytest_html.extras.image(screenshot.decode(), ''))
# Remove as it is already saved
try:
os.remove(image)
except OSError:
pass
report.extra = extra
In the new version it throws:
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\_pytest\main.py", line 270, in wrap_session
INTERNALERROR> session.exitstatus = doit(config, session) or 0
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\_pytest\main.py", line 324, in _main
INTERNALERROR> config.hook.pytest_runtestloop(session=session)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
INTERNALERROR> return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_callers.py", line 60, in _multicall
INTERNALERROR> return outcome.get_result()
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_result.py", line 60, in get_result
INTERNALERROR> raise ex[1].with_traceback(ex[2])
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
INTERNALERROR> res = hook_impl.function(*args)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\_pytest\main.py", line 349, in pytest_runtestloop
INTERNALERROR> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
INTERNALERROR> return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_callers.py", line 60, in _multicall
INTERNALERROR> return outcome.get_result()
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_result.py", line 60, in get_result
INTERNALERROR> raise ex[1].with_traceback(ex[2])
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
INTERNALERROR> res = hook_impl.function(*args)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\_pytest\runner.py", line 112, in pytest_runtest_protocol
INTERNALERROR> runtestprotocol(item, nextitem=nextitem)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\_pytest\runner.py", line 131, in runtestprotocol
INTERNALERROR> reports.append(call_and_report(item, "call", log))
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\_pytest\runner.py", line 222, in call_and_report
INTERNALERROR> report: TestReport = hook.pytest_runtest_makereport(item=item, call=call)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
INTERNALERROR> return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR> File "<my_path>\.venv\lib\site-packages\pluggy\_callers.py", line 55, in _multicall
INTERNALERROR> gen.send(outcome)
INTERNALERROR> File "<my_path>\conftest.py", line 76, in pytest_runtest_makereport
INTERNALERROR> extra.append(pytest_html.extras.image(screenshot.decode(), ''))
INTERNALERROR> AttributeError: 'function' object has no attribute 'image'
Originally posted by @nck974 in https://github.com/pytest-dev/pytest-html/issues/581#issuecomment-1464632768
Some context on why did I do this: This was created for the following requirements:
- I want to be able to multithread some selenium tests, therefore I need to save the images in a directory and collect them at the end of the tests with a unique identifier for the test, I have been using the following code to get the id:
import re
def get_node_id(request):
"""
Return the node id as a string without special characters so that it can be saved as a file.
"""
node_id = request.node.nodeid
return re.sub('[^a-zA-Z0-9]', '_', node_id) # remove special characters
- The screenshots are taken from selenium. Some tests need more than one screenshot as proof:
def save_proof_screenshot(driver: WebDriver, folder_name: str, node: str) -> None:
"""
Take a screenshot of the current page
"""
directory = os.path.join(
'.',
'output',
'report',
folder_name,
datetime.now().strftime("%Y-%m-%d"),
)
time = int(datetime.now().timestamp())
Path(directory).mkdir(parents=True, exist_ok=True)
file_path = os.path.join(directory, f'{node}_{time}.png')
driver.save_screenshot(file_path) # has scrollbar
- I want the screenshots displayed together with the logs.
- I want the report to contain the images (base64).
And currently until the last RC it was working with that code. Example:

@nck974
I found the root cause for this. It's this line:
pytest_html = item.config.pluginmanager.getplugin("html")
It's broken in 4.0.0rc4 (and earlier versions). But I'm adding a fix for it since it seems to be a common way.
If you want to fix it, you can replace it with:
import pytest_html
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
"""
This method takes the images in the folder of the report and writes them inside the report.
"""
outcome = yield
# Get the images to be displayed inside the report
report = outcome.get_result()
extras = getattr(report, "extras", [])
if report.when == "call":
node_id = item.funcargs['request'].node.nodeid
# Get output directory
directory = _get_report_folder()
# Extract images for current tc
for file in os.listdir(directory):
if re.search(re.escape(f'{node_id}_'), file):
image = os.path.join(directory, file)
with open(image, "rb") as image_file:
screenshot = base64.b64encode(image_file.read())
extras.append(pytest_html.extras.image(screenshot.decode(), ''))
# Remove as it is already saved
try:
os.remove(image)
except OSError:
pass
report.extras = extras
and it should work again.