I have a problem with setting report name and folder with it dynamically in Python's pytest.
For example: I've run all pytest's tests @ 2020-03-06 21:50 so I'd like to have my report stored in folder 20200306
with name report_2150.html
. I want it to be automated and triggered right after the tests are finished.
I'm working in VS Code and I'm aiming to share my work with colleagues with no automation experience so I'm aiming to use it as "click test to start".
My project structure:
webtools/
|── .vscode/
|──── settings.json
|── drivers/
|── pages/
|── reports/
|── tests/
|──── __init__.py
|──── config.json
|──── conftest.py
|──── test_1.py
|──── test_2.py
|── setup.py
Code samples:
settings.json
{
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.enabled": true,
"python.pythonPath": "C:\\Users\\user\\envs\\webtools\\Scripts\\python.exe",
"python.testing.pytestArgs": [
"tests",
"--self-contained-html",
"--html=./reports/tmp_report.html"
],
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.unittestArgs": [
"-v",
"-s",
"./tests",
"-p",
"test_*.py"
]
}
config.json
{
"browser": "chrome",
"wait_time": 10
}
conftest.py
import json
import pytest
from datetime import datetime
import time
import shutil
import os
from selenium import webdriver
from selenium.webdriver import Chrome
CONFIG_PATH = 'tests/config.json'
DEFAULT_WAIT_TIME = 10
SUPPORTED_BROWSERS = ['chrome', 'explorer']
@pytest.fixture(scope='session')
def config():
# Read the JSON config file and returns it as a parsed dict
with open(CONFIG_PATH) as config_file:
data = json.load(config_file)
return data
@pytest.fixture(scope='session')
def config_browser(config):
# Validate and return the browser choice from the config data
if 'browser' not in config:
raise Exception('The config file does not contain "browser"')
elif config['browser'] not in SUPPORTED_BROWSERS:
raise Exception(f'"{config["browser"]}" is not a supported browser')
return config['browser']
@pytest.fixture(scope='session')
def config_wait_time(config):
# Validate and return the wait time from the config data
return config['wait_time'] if 'wait_time' in config else DEFAULT_WAIT_TIME
@pytest.fixture
def browser(config_browser, config_wait_time):
# Initialize WebDriver
if config_browser == 'chrome':
driver = webdriver.Chrome(r"./drivers/chromedriver.exe")
elif config_browser == 'explorer':
driver = webdriver.Ie(r"./drivers/IEDriverServer.exe")
else:
raise Exception(f'"{config_browser}" is not a supported browser')
# Wait implicitly for elements to be ready before attempting interactions
driver.implicitly_wait(config_wait_time)
# Maximize window for test
driver.maximize_window()
# Return the driver object at the end of setup
yield driver
# For cleanup, quit the driver
driver.quit()
@pytest.fixture(scope='session')
def cleanup_report():
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
os.chdir("./reports")
os.mkdir(timestamp)
yield
shutil.move("./tmp_report.html", "./%s/test_report.html" % timestamp)
In current situation the report is created as tmp_report.html
in the reports folder, but I don't know how I can force running cleanup_report()
after all tests are completed and tmp_report.html
is present and complete in folder. For checking if complete I assume I'd have to verify if all html tags have their closing (or at least <html>
one).
Can somebody help me with that? If you need some further code portions I'll provide them as soon as possible.
Thank you in advance!
Running pytest without mentioning a filename will run all files of format test_*. py or *_test.py in the current directory and subdirectories. Pytest automatically identifies those files as test files. We can make pytest run other filenames by explicitly mentioning them.
Project Structure The modules containing pytests should be named “test_*. py” or “*_test.py”. While the pytest discovery mechanism can find tests anywhere, pytests must be placed into separate directories from the product code packages. These directories may either be under the project root or under the Python package.
To generate the report, we have to move from the current directory to the directory of the Pytest file that we want to execute. Then run the command: pytest --html=report. html. After this command is successfully executed, a new file called the report.
With no arguments, pytest looks at the current working directory (or some other preconfigured directory) and all subdirectories for test files and runs the test code it finds. Running all test files in the current directory. We can run a specific test file by giving its name as an argument.
pytest will build a string that is the test ID for each set of values in a parametrized test. These IDs can be used with -k to select specific cases to run, and they will also identify the specific case when one is failing. Running pytest with --collect-only will show the generated IDs.
pytest determines a rootdir for each test run which depends on the command line arguments (specified test files, paths) and on the existence of configuration files. The determined rootdir and configfile are printed as part of the pytest header during startup.
This allows the use of pytest in structures that are not part of a package and don’t have any particular configuration file. If no args are given, pytest collects test below the current working directory and also starts determining the rootdir from there. pytest.ini: will always match and take precedence, even if empty.
Consulting the pytest documentation leads us to a method to dynamically retrieve fixtures by name, so we try that: While working for None, it sadly fails for our indirectly invoked pairing fixture with the cryptical error message
You can customize the plugin options in a custom impl of the pytest_configure
hook. Put this example code in a conftest.py
file in your project root dir:
from datetime import datetime
from pathlib import Path
import pytest
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
# set custom options only if none are provided from command line
if not config.option.htmlpath:
now = datetime.now()
# create report target dir
reports_dir = Path('reports', now.strftime('%Y%m%d'))
reports_dir.mkdir(parents=True, exist_ok=True)
# custom report file
report = reports_dir / f"report_{now.strftime('%H%M')}.html"
# adjust plugin options
config.option.htmlpath = report
config.option.self_contained_html = True
If you want to completely ignore what's passed from command line, remove the if not config.option.htmlpath:
condition.
If you want to stick with your current impl, notice that on fixtures teardown, pytest-html
hasn't written the report yet. Move the code from cleanup_report
to a custom impl of the pytest_sessionfinish
hook to ensure pytest-html
has already written the default report file:
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
shutil.move(...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With