Table of Contents
Introduction
For several days (at least a week) I researched on the internet trying to find a resolution to a problem. I was having a problem trying to use the Pytest-html plugin. I was doing what was said in the documentation at Pytest HTML DOC Site. But I was failing to customize the HTML output report using the PYTEST command. I even found what was supposed to be the latest version for install: Pytest-html Plugin Install.
But nowhere did I find information about the real solution. The real solution was the need for a configuration module that the plugin automatically calls to manage customization for the HTML report. I found out about the configuration module by posing a question to an AI tool. I think somebody forgot to publish the latest changes made to the plugin.
I published this article in the hopes of helping anybody else that might be frustrated trying to get the plugin to work. In summary, I encountered three problems trying to get the plugin to work.
Problem #1
Do you know why the following python program is not customizing the html report?
# test_selenium0.py |
from py.xml import html |
import Pytest |
import pytest_html |
@pytest.fixture() |
def pytest_html_report_title(report): |
report.title = “Custom Title” |
def pytest_html_results_table_header(cells): |
cells.insert(2, html.th(‘Description’)) |
cells.pop() |
def pytest_html_results_table_row(report, cells): |
cells.insert(2, html.td(report.description)) |
cells.pop() |
def test_good(): |
assert True |
Response #1
The reason why the HTML report is not being customized in the provided Python program is that the hooks for modifying the report table are not being registered properly with the Pytest-html plugin.
To register the hooks, define a pytest_configure hook in a conftest.py file in the root directory of your project. The pytest_configure hook will be automatically called by Pytest when the test script is executed.
Here’s an example of how to modify the HTML report using the Pytest-html plugin:
Copy code
# conftest.py From py.xml import html import pytest |
def pytest_configure(config): |
config._metadata[‘Project’] = ‘Custom Title’ |
config._metadata[‘Description’] = ‘Custom Description’ |
def pytest_html_results_table_header(cells): |
cells.insert(2, html.th(‘Description’)) |
cells.pop() |
def pytest_html_results_table_row(report, cells): |
cells.insert(2, html.td(report. Description)) |
cells.pop() |
The code above is a separate module named CONFTEST.PY that is placed in the root directory of your test project in whatever IDE in use.
With this CONFTEST.PY file in place, running Pytest with the –html=<output-file> option will generate an HTML report that includes the project title and description in the report header, as well as an additional column in the results table with the test descriptions.
Note that the pytest_html_report_title fixture defined in the original code is unnecessary and can be removed. The pytest_configure hook is used to set the report title and other metadata.
When you run Pytest, it automatically discovers the CONFTEST.PY file in the directory and executes any hooks defined in the file. In this case, the pytest_configure, pytest_html_results_table_header, and pytest_html_results_table_row hooks defined in CONFTEST.PY will be executed by the Pytest-html plugin, which will modify the generated HTML report accordingly.
So, after adding the CONFTEST.PY file to the project directory, simply run the Pytest command as usual to generate the HTML report with the customizations made by the hooks in CONFTEST.PY.
Customization
To customize the HTML report for other types of assertions, you can define additional hooks in the CONFTEST.PY file. The Pytest-html plugin provides a variety of hooks that you can use to customize the report in different ways.
Here’s an example of how to customize the report for failed tests by adding a screenshot of the browser window at the time of the failure:
Copy code
# conftest.py |
from py.xml import html |
import Pytest |
def pytest_html_report_title(report): |
report.title = “Custom Title” |
def pytest_html_results_table_header(cells): |
cells.insert(2, html.th(‘Description’)) |
cells.pop() |
def pytest_html_results_table_row(report, cells): |
cells.insert(2, html.td(report.description)) |
cells.pop() |
@pytest.hookimpl(trylast=True) |
def pytest_exception_interact(node, call, report): |
if node and “driver” in node.funcargs: |
driver = node.funcargs[‘driver’] |
screenshot_file = f”screenshot-{node.name}.png” |
driver.save_screenshot(screenshot_file) |
if report.when == “call”: |
html = f'<div><img src=”{screenshot_file}” ‘\ |
f’style=”max-width: 100%; height: auto;” ‘\ |
f’alt=”screenshot failed {node.name}”/></div>’ |
report.sections.append(html) |
In this example, a pytest_exception_interact hook is added, which is called after a test has failed. This hook checks if the test function has a driver fixture, which is used to control the browser in a Selenium test. If it does, it takes a screenshot of the browser window and saves it to a file named screenshot-<test-name>.png.
Next, if the failure occurred during the test execution phase (report.when == “call”), the hook creates an HTML section containing an <img> tag that references the screenshot file and appends it to the report’s sections list.
With this customization in place, the HTML report that is generated by Pytest-html will now include screenshots of the browser window for any tests that fail during the execution phase. Note that this customization assumes that the test functions have a driver fixture that provides access to a Selenium WebDriver instance.
Driver Fixture
A driver fixture is a fixture in Pytest that provides access to a Selenium WebDriver instance. A WebDriver is a software component that allows you to control a web browser programmatically, and it is commonly used in automated testing to simulate user interactions with a web application.
In Pytest, you can define a fixture that creates and configures a WebDriver instance, and then use it in your test functions to interact with the browser. For example, the following code defines a fixture named driver that creates a Chrome WebDriver instance. This code should be inserted into the CONFTEST.PY module. Here is the code:
Copy code
# conftest.py |
import pytest |
from selenium import webdriver |
@pytest.fixture(scope=’function’) |
def driver(request): |
options = webdriver.ChromeOptions() |
options.add_argument(‘–headless’) |
driver = webdriver.Chrome(options=options) |
yield driver |
driver.quit() |
In this code, the driver fixture creates a webdriver.Chrome instance with some options and yields it to the test function. The yield statement indicates the end of the fixture setup phase and the start of the teardown phase. When the test function completes, the driver.quit() method is called to close the browser window and release any associated resources.
With this fixture in place, test functions can be written that use the driver fixture to interact with the browser. For example:
Copy code
# test_sample.py |
def test_search_google(driver): |
driver.get(‘https://www.google.com’) |
assert driver.title == ‘Google’ |
In this test function, the driver fixture is used to open the Google homepage and verify that the title of the page is ‘Google’. The driver fixture provides us with a handle to the browser window, which can be used to simulate user interactions and perform assertions on the state of the web application.
Possible error condition
Problem #2
File “C:\Users\xxxx\Documents\Python\Demo\AUTH_Salesforce\conftest.py”, line 17, in pytest_html_results_table_row cells.insert(2, html.td(report.description))
AttributeError: ‘TestReport’ object has no attribute ‘description’
Response #2
The error message indicates that the TestReport object passed to the pytest_html_results_table_row hook does not have a description attribute, which is causing an AttributeError when the hook tries to access it.
This is likely because the description attribute was removed from the TestReport object in a recent version of the Pytest-html plugin. For an alternative, access the test description using the report.nodeid attribute.
Here’s an updated version of the pytest_html_results_table_row hook that uses the report.nodeid attribute to extract the test description:
Copy code
# conftest.py |
from py.xml import html |
import pytest |
def pytest_html_report_title(report): |
report.title = “Custom Title” |
def pytest_html_results_table_header(cells): |
cells.insert(2, html.th(‘Description’)) |
cells.pop() |
def pytest_html_results_table_row(report, cells): |
description = report.nodeid.split(‘::’)[-1] |
cells.insert(2, html.td(description)) |
cells.pop() |
In this code, the test description is extracted from the report.nodeid attribute, which is a string that contains the full node ID of the test item. Split the node ID using the ‘::’ separator and take the last element, which should be the test description. Then insert the description into the cells list at index 2 (right after the test name) as a new HTML <td> element.
This should display the test description in the HTML report using the updated hook.
Web Driver Manager
Problem #3
Where should the WebDriverManager be inserted to stop getting the following message: selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 109
Response #3
Use the webdriver_manager package to automatically download and install the correct version of the ChromeDriver executable for the version of Google Chrome installed. Here’s an example of how to use webdriver_manager to manage the ChromeDriver version. Update the DRIVER function in the CONFTEST.PY configuration module. Notice the driver assignment statement and the second FROM statement.
Copy code
# conftest.py |
from selenium import webdriver |
from webdriver_manager.chrome import ChromeDriverManager |
@pytest.fixture(scope=’function’) |
def driver(): |
options = webdriver.ChromeOptions() |
options.add_argument(‘–headless’) |
driver = webdriver.Chrome(ChromeDriverManager().install(), options=options) |
yield driver |
driver.quit() |
In this code, notice the “from statement” for the import ChromeDriverManager class is used to automatically download and install the correct version of ChromeDriver. Then in the function, pass the path of the installed ChromeDriver executable to the webdriver.Chrome() constructor to create a new Chrome WebDriver instance.
With this code in place, the SessionNotCreatedException error related to the version mismatch between ChromeDriver and Google Chrome should be resolved.
Note: install the webdriver_manager package first by running the appropriate PIP command.
- pip install webdriver-manager