4

I am trying to generate a self contained html report using pytest-html and selenium. I have been trying to imbedded screenshots into the report but they are not being displayed. Example

My conftest.py looks like this

@pytest.fixture()
def chrome_driver_init(request, path_to_chrome):
    driver = webdriver.Chrome(options=opts, executable_path=path_to_chrome)
    request.cls.driver = driver
    page_object_init(request, driver)
    driver.get(URL)
    driver.maximize_window()
    yield driver
    driver.quit()


# Hook that takes a screenshot of the web browser for failed tests and adds it to the HTML report
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
    pytest_html = item.config.pluginmanager.getplugin("html")
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, "extra", [])
    if report.when == "call":
        feature_request = item.funcargs['request']
        driver = feature_request.getfixturevalue('chrome_driver_init')
        nodeid = item.nodeid
        xfail = hasattr(report, "wasxfail")
        if (report.skipped and xfail) or (report.failed and not xfail):
            file_name = f'{nodeid}_{datetime.today().strftime("%Y-%m-%d_%H_%M")}.png'.replace("/", "_").replace("::", "_").replace(".py", "")
            driver.save_screenshot("./reports/screenshots/"+file_name)
            extra.append(pytest_html.extras.image("/screenshots/"+file_name))
        report.extra = extra

I am convinced the problem is with the path to the image, and I have tried so many str combinations, os.path and pathlib but nothing has worked. The screenshot is being saved in the expected location and I can open it like any other image. Its just not displaying on the report.

<div class="image"><img src="data:image/png;base64,screenshots\scr_tests_test_example_TestExample_test_fail_example_2022-01-18_16_26.png"/></div>

EDIT: For addional clairification. I have tried to use absolute path in the extra.append but it kept giving me a Cant Resolve File error in the HTML file. My absoulte path was(with some personal details redacted) C:\Users\c.Me\OneDrive - Me\Documents\GitHub\project\build\reports\screenshots\filename.png I have tried it with both '/' and '\'

Also my File structure

project
├───build
│   ├───reports
│       ├───screenshots
│           ├───filename.png
|       ├───report.html
|   ├───run.py # I am running the test suite from here
├───scr
|   ├───settings.py
│   ├───tests
│       ├───confest.py

run.py

if __name__ == "__main__":
    os.system(f"pytest --no-header -v ../scr/tests/ --html=./reports/Test_Report_{today}.html --self-contained-html")

For Prophet, may be bless me this day To get the Cannot Resolve Directory error my code is the following

file_name = f'{nodeid}_{datetime.today().strftime("%Y-%m-%d_%H_%M")}.png'.replace("/", "_").replace("::", "_").replace(".py", "")
img_path = os.path.join(REPORT_PATH, 'screenshots', file_name)
driver.save_screenshot(img_path)
extra.append(pytest_html.extras.image(img_path))

The variable REPORT_PATH is imported from the settings.py (see directory tree above) and is created by

PROJ_PATH = Path(__file__).parent.parent
REPORT_PATH = PROJ_PATH.joinpath("build\reports")

also fun fact if I do img_path.replace("\\", "/") the error changes to Cannot Resolve File

cdub
  • 330
  • 4
  • 19

2 Answers2

9

I have learned so much in this painful journey. Mostly I have learned I am an idiot. The problem was that I wanted to make a self contained HTML. Pytest-html does not work as expected with adding images to a self contained report. Before you can you have to convert the image into its text base64 version first. So the answers to all my owes was a single line of code.

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
    pytest_html = item.config.pluginmanager.getplugin("html")
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, "extra", [])
    if report.when == "call":
        feature_request = item.funcargs['request']
        driver = feature_request.getfixturevalue('chrome_driver_init')
        nodeid = item.nodeid
        xfail = hasattr(report, "wasxfail")
        if (report.skipped and xfail) or (report.failed and not xfail):
            file_name = f'{nodeid}_{datetime.today().strftime("%Y-%m-%d_%H_%M")}.png'.replace("/", "_").replace("::", "_").replace(".py", "")
            img_path = os.path.join(REPORT_PATH, "screenshots", file_name)
            driver.save_screenshot(img_path)
            screenshot = driver.get_screenshot_as_base64() # the hero
            extra.append(pytest_html.extras.image(screenshot, ''))
        report.extra = extra

Thank you Prophet for guiding on this pilgrimage. Now I must rest.

cdub
  • 330
  • 4
  • 19
  • 2
    You are not an idiot, you learned by yourself very meaningful things. What really matters here is that you made efforts, tried, learned and finally find the solution. This is what making us engineers. Not only some amount of knowledges, Google knows much more, you know, but an ability to learn new things, to find solutions. – Prophet Jan 19 '22 at 10:41
  • feature_request.getfixturevalue('chrome_driver_init') -> Where does feature_request come from? I'm trying to replicate something similar. – Justin Furuness Sep 29 '22 at 04:04
  • 1
    @JustinFuruness he defined chrome_driver_init as a pytest fixture in conftest.py Also, I think this fix (converting the screenshot to base64 before attaching as an extra) is necessary even if you're not generating a --self-contained-html report; I was getting encoding errors even without that flag. – Spearson Feb 14 '23 at 22:08
  • another so [answer](https://stackoverflow.com/a/50805707/1518100) says *the standard behaviour of pytest-html is that even if you pass the image as base64 string, it will still store a file in the assets directory.* – Lei Yang Apr 27 '23 at 05:44
  • Saved my Day! Thank you so much @cdub – Anmol Parida Jul 13 '23 at 04:52
2

I'm not completely sure how it works with PyTest, however we have similar issue with Java Extent Manager.
There you have to pass the absolute path of the image file, not the relative path.
As I can see here the current working directory can be achieved as following:

import pathlib
pathlib.Path().resolve()

So, if I understand that correctly you should change your code from

extra.append(pytest_html.extras.image("/screenshots/"+file_name))

to

working_root = pathlib.Path().resolve()
extra.append(pytest_html.extras.image(working_root + "/screenshots/"+file_name))

UPD
I think you are missing a reports subfolder here.
Instead of

working_root = pathlib.Path().resolve()
extra.append(pytest_html.extras.image(working_root + "/screenshots/"+file_name))

Try using

working_root = pathlib.Path().resolve()
extra.append(pytest_html.extras.image(working_root + "/reports/screenshots/"+file_name))
Prophet
  • 32,350
  • 22
  • 54
  • 79
  • I tried using absolute paths and kept getting `can't resolve directory` errors – cdub Jan 18 '22 at 20:28
  • What is the absolute directory expression you sending to the `extra.append`? Can you share it and validate it is correct? – Prophet Jan 18 '22 at 20:37
  • Added it to the original question – cdub Jan 18 '22 at 20:41
  • I'm not sure I see it there – Prophet Jan 18 '22 at 20:45
  • Sorry edits are done now @Prophet – cdub Jan 18 '22 at 20:57
  • Please see my updated answer – Prophet Jan 18 '22 at 21:11
  • class Path does not define __add__ so '+' does nothing. `working_root.stem` turns it into a usable str. Still this didnt work. Added one more detail to the question I thought might be useful – cdub Jan 18 '22 at 21:30
  • OK. In your first comment here you wrote me you see the `can't resolve directory` error. Can you show me what is the actual expression you passing to the method what causing this error? I see what the correct absolute path should be. The question what is the actual path is? – Prophet Jan 18 '22 at 21:39
  • added it to bottom of question. its a bit of a mess – cdub Jan 18 '22 at 21:47