Skip to main content

Command Palette

Search for a command to run...

Hooks in Pytest

Published
6 min readView as Markdown

Hooks in pytest are special functions that let you tap into pytest’s test execution process — before, during, and after certain events — to customize behavior, collect data, or modify reports.


1️⃣ What Are Hooks in pytest?

  • Hooks are functions defined with the pytest_ prefix inside:

  • Pytest automatically detects and calls them at specific points in the test run lifecycle.

  • You can use hooks to:

    • Modify test collection

    • Change test execution flow

    • Add logs/screenshots to reports

    • Integrate with tools like Allure or Extent Reports

    • Implement retries, custom markers, etc.


2️⃣ How Hooks Work

  • Hooks are predefined by pytest (you don’t invent new hook names).

  • The hook functions receive arguments relevant to that stage of execution.

  • You can override or extend pytest’s default behavior.

Example:

# conftest.py
def pytest_runtest_setup(item):
    print(f"\n[HOOK] Setting up test: {item.name}")

def pytest_runtest_teardown(item):
    print(f"[HOOK] Tearing down test: {item.name}")

3️⃣ Commonly Used Hooks (with Playwright context)

Here are the most useful hooks for automation work:

Hook NameTrigger PointUse Case
pytest_addoption(parser)Before tests startAdd custom CLI arguments for pytest
pytest_configure(config)After CLI options parsedConfigure plugins, set env variables
pytest_collection_modifyitems(items)After collecting all testsReorder, skip, or tag tests dynamically
pytest_runtest_setup(item)Before running each testCustom setup logic (e.g., log in)
pytest_runtest_call(item)When executing the test functionWrap execution logic
pytest_runtest_teardown(item)After test finishesCleanup resources
pytest_runtest_makereport(item, call)After setup/call/teardown phaseAdd extra info to reports (screenshots, logs)
pytest_sessionstart(session)At the very start of pytest runInitialize big resources (browser session, DB)
pytest_sessionfinish(session)At the very end of pytest runCleanup global resources
pytest_terminal_summary(terminalreporter)After tests endPrint custom summary to terminal

4️⃣ Example: Adding CLI Options and Using Them

# conftest.py
def pytest_addoption(parser):
    parser.addoption("--browser", action="store", default="chromium", help="Browser type: chromium, firefox, webkit")

@pytest.fixture(scope="session")
def browser_type(pytestconfig):
    return pytestconfig.getoption("--browser")

def pytest_configure(config):
    print(f"[HOOK] Configuring pytest with browser: {config.getoption('--browser')}")

Run:

pytest --browser=firefox

5️⃣ Example: Adding Screenshots on Failure in Playwright

# conftest.py
import pytest
import allure

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()

    if report.when == "call" and report.failed:
        page = item.funcargs.get("page", None)
        if page:
            screenshot_path = f"screenshots/{item.name}.png"
            page.screenshot(path=screenshot_path)
            allure.attach.file(screenshot_path, name="Failure Screenshot", attachment_type=allure.attachment_type.PNG)

🔹 This hook:

  • Runs after each test phase (setup, call, teardown)

  • Checks if the test failed during execution

  • Captures and attaches a Playwright screenshot to Allure


6️⃣ Example: Dynamically Skipping Tests

def pytest_collection_modifyitems(items):
    skip_marker = pytest.mark.skip(reason="Skipping all tests temporarily")
    for item in items:
        if "skip_this" in item.name:
            item.add_marker(skip_marker)

7️⃣ Hook Execution Order

Hooks are called in a fixed lifecycle order:

  1. Session start hookspytest_sessionstart

  2. Test collection hookspytest_collection_modifyitems

  3. Per-test hooks:

    • pytest_runtest_setup

    • pytest_runtest_call

    • pytest_runtest_teardown

  4. Report hookspytest_runtest_makereport

  5. Session finish hookspytest_terminal_summary, pytest_sessionfinish


8️⃣ Plugins and Hooks

  • Many plugins (like pytest-html, pytest-allure) rely on hooks to inject extra reporting logic.

  • You can also create your own plugin by writing hooks in a .py file and loading it via pytest_plugins.


Key Takeaways

  • Hooks give you full control over pytest’s test lifecycle.

  • Most automation frameworks (including Playwright-based ones) use pytest_runtest_makereport for screenshots, logs, and report attachments.

  • pytest_addoption and pytest_configure are must-know hooks for parameterizing test runs.

2️⃣ Pytest Test Lifecycle Overview

Here’s a simplified execution flow:

Session Start
│
├── pytest_sessionstart()
│
├── pytest_collection() → pytest_collection_modifyitems()
│
├── For each test:
│     ├── pytest_runtest_setup()
│     ├── pytest_runtest_call()
│     ├── pytest_runtest_teardown()
│     └── pytest_runtest_makereport()
│
├── pytest_sessionfinish()
│
└── pytest_terminal_summary()

3️⃣ Hooks by Category

A. Session-level hooks

These run once per pytest run.

pytest_addoption(parser)

  • Runs before tests start.

  • Lets you add custom CLI options.

  • Example:

      def pytest_addoption(parser):
          parser.addoption("--env", action="store", default="dev", help="Environment to run tests against")
    

pytest_configure(config)

  • Called after command-line parsing.

  • Use it to configure pytest or register markers.

  • Example:

      def pytest_configure(config):
          config.addinivalue_line("markers", "smoke: mark test as smoke test")
    

pytest_sessionstart(session)

  • Runs before any tests are collected or executed.

  • Good for global setup like starting a database.

  • Example:

      def pytest_sessionstart(session):
          print("=== Pytest session started ===")
    

pytest_sessionfinish(session, exitstatus)

  • Runs after all tests finish.

  • Good for cleanup (close DB connections, stop servers).

  • Example:

      def pytest_sessionfinish(session, exitstatus):
          print(f"Session ended with exit code {exitstatus}")
    

B. Test collection hooks

These control how pytest finds tests.

pytest_collection_modifyitems(session, config, items)

  • Called after tests are collected.

  • Can reorder, filter, or mark tests dynamically.

  • Example: run smoke tests first:

      def pytest_collection_modifyitems(items):
          items.sort(key=lambda item: "smoke" not in item.keywords)
    

pytest_ignore_collect(path, config)

  • Decide whether pytest should ignore a file or directory during collection.

  • Example:

      def pytest_ignore_collect(path, config):
          return "skip_me" in str(path)
    

pytest_collect_file(path, parent)

  • Allows collecting custom file types as tests.

  • Example: collect .yaml as test sources.


C. Fixture and setup hooks

These control fixture execution and test setup.

pytest_fixture_setup(fixturedef, request)

  • Runs before a fixture is executed.

  • Lets you override how a fixture is created.

pytest_fixture_post_finalizer(fixturedef, request)

  • Runs after a fixture is torn down.

D. Test execution hooks

pytest_runtest_setup(item)

  • Runs before a test starts.

  • Good for per-test setup (DB reset, login).

  • Example:

      def pytest_runtest_setup(item):
          print(f"Setting up test: {item.name}")
    

pytest_runtest_call(item)

  • Runs when the test function body is executed.

pytest_runtest_teardown(item, nextitem)

  • Runs after a test finishes.

  • nextitem is the next test to be run (or None).

pytest_runtest_protocol(item, nextitem)

  • Low-level hook that can override the entire execution of a test.

E. Reporting hooks

These are very common for screenshots, videos, and logs.

pytest_runtest_makereport(item, call)

  • Runs after each phase (setup/call/teardown) of a test.

  • rep.when = "setup", "call", or "teardown".

  • Great for attaching failure screenshots.

  • Example:

      def pytest_runtest_makereport(item, call):
          if call.when == "call" and call.excinfo is not None:
              print(f"Test {item.name} failed")
    

pytest_report_teststatus(report)

  • Modify how the status is shown in terminal output.

pytest_terminal_summary(terminalreporter, exitstatus, config)

  • Runs at the very end.

  • Add custom summary info to the terminal.

  • Example:

      def pytest_terminal_summary(terminalreporter, exitstatus, config):
          terminalreporter.write("Custom Summary\n")
    

F. Command-line & plugin hooks

pytest_cmdline_main(config)

  • Can completely take over pytest’s main execution.

pytest_cmdline_preparse(config, args)

  • Modify command-line args before parsing.