Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting dynamic folder and report name in pytest

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!

like image 764
MrTeatime Avatar asked Mar 06 '20 21:03

MrTeatime


People also ask

How do you name a pytest file?

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.

How do I structure a pytest folder?

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.

How do I create a report in pytest?

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.

What is the name of the directory that pytest will look at to detect tests?

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.

How do I get the test ID in pytest?

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.

How does pytest determine the rootdir of a test?

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.

How to use pytest in structures that don't have a configuration file?

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.

Is it possible to dynamically retrieve pytest fixtures by name?

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


1 Answers

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(...)
like image 115
hoefling Avatar answered Oct 27 '22 08:10

hoefling