Hooks in Pytest
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:Your test files
Or a custom plugin
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 Name | Trigger Point | Use Case |
pytest_addoption(parser) | Before tests start | Add custom CLI arguments for pytest |
pytest_configure(config) | After CLI options parsed | Configure plugins, set env variables |
pytest_collection_modifyitems(items) | After collecting all tests | Reorder, skip, or tag tests dynamically |
pytest_runtest_setup(item) | Before running each test | Custom setup logic (e.g., log in) |
pytest_runtest_call(item) | When executing the test function | Wrap execution logic |
pytest_runtest_teardown(item) | After test finishes | Cleanup resources |
pytest_runtest_makereport(item, call) | After setup/call/teardown phase | Add extra info to reports (screenshots, logs) |
pytest_sessionstart(session) | At the very start of pytest run | Initialize big resources (browser session, DB) |
pytest_sessionfinish(session) | At the very end of pytest run | Cleanup global resources |
pytest_terminal_summary(terminalreporter) | After tests end | Print 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:
Session start hooks →
pytest_sessionstartTest collection hooks →
pytest_collection_modifyitemsPer-test hooks:
pytest_runtest_setuppytest_runtest_callpytest_runtest_teardown
Report hooks →
pytest_runtest_makereportSession finish hooks →
pytest_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
.pyfile and loading it viapytest_plugins.
✅ Key Takeaways
Hooks give you full control over pytest’s test lifecycle.
Most automation frameworks (including Playwright-based ones) use
pytest_runtest_makereportfor screenshots, logs, and report attachments.pytest_addoptionandpytest_configureare 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
.yamlas 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.
nextitemis 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.