← Back

Playwright Fixtures & Hooks

Complete guide to Playwright locators with real-time web application examples, best practices, XPath/CSS strategies, filtering, chaining, auto-waiting, and Selenium comparisons.

1 What Are Fixtures in Playwright Python?

Fixtures provide reusable setup, test resources, and cleanup. Playwright's built-in page fixture is the starting point for most UI tests.

Python Playwright
from playwright.sync_api import Page, expect

def test_login_page(page: Page):
    page.goto("https://demo.example.com/login")
    expect(page.get_by_role("heading", name="Login")).to_be_visible()
Real-time example: Every test receives an isolated browser page through the built-in page fixture.
2 Built-in Fixtures: page, context, and browser

Use built-in fixtures instead of manually creating browser objects for every test. This keeps tests short and isolated.

Python Playwright
from playwright.sync_api import expect

def test_customer_profile(page):
    page.goto("https://shop.example.com/profile")
    expect(page.get_by_role("heading", name="My Profile")).to_be_visible()


def test_new_context(browser):
    context = browser.new_context()
    try:
        page = context.new_page()
        page.goto("https://shop.example.com")
        expect(page).to_have_title("Shop")
    finally:
        context.close()
Real-time example: Use page for normal tests. Use browser and context when a scenario requires custom browser state, such as a separate session, cookies, or permissions.
3 Create a Custom Fixture with pytest

Custom fixtures package repeated business setup so test cases can focus on what they are verifying.

Python Playwright
import pytest
from playwright.sync_api import expect

@pytest.fixture
def customer(page):
    page.goto("https://shop.example.com/login")
    page.get_by_label("Email").fill("qa@example.com")
    page.get_by_label("Password").fill("Password123")
    page.get_by_role("button", name="Login").click()
    return page


def test_customer_can_open_orders(customer):
    customer.get_by_role("link", name="Orders").click()
    expect(
        customer.get_by_role("heading", name="My Orders")
    ).to_be_visible()
Real-time example: All authenticated customer tests can reuse the same customer fixture instead of repeating the login steps.
4 Fixture Scope: function, class, module, and session

Fixture scope controls how often a fixture is created and reused. Choose the smallest safe scope that matches the resource.

Python pytest
import pytest

@pytest.fixture(scope="function")
def test_customer():
    return create_customer()


@pytest.fixture(scope="session")
def app_config():
    return load_environment_config()
Real-time example: Customer test data can be function-scoped to provide isolation, while read-only application configuration can be session-scoped and reused across the entire test run.
5 Fixture Setup and Cleanup with yield

Use yield when a fixture must perform cleanup after the test completes.

Python pytest
import pytest

@pytest.fixture
def temporary_customer():
    customer = create_customer()
    yield customer
    delete_customer(customer["id"])


def test_customer_profile(temporary_customer):
    assert temporary_customer["email"].endswith("@example.com")
Real-time example: Create a temporary customer before the test, provide it to the test through the fixture, and delete it afterward so test data does not accumulate.
6 conftest.py for Shared Fixtures

Place shared pytest fixtures in conftest.py so multiple test modules can use them without importing the fixture explicitly.

tests/conftest.py
import pytest

@pytest.fixture
def logged_in_page(page):
    page.goto("https://shop.example.com/login")
    page.get_by_label("Email").fill("qa@example.com")
    page.get_by_label("Password").fill("Password123")
    page.get_by_role("button", name="Login").click()
    return page
Real-time example: Define the login setup once in conftest.py and reuse the logged_in_page fixture across checkout, orders, and profile test suites.
7 Before-Test and After-Test Hooks

Pytest fixtures with yield are the preferred Python mechanism for setup and teardown. Autouse fixtures can behave like suite-level hooks by running automatically for each test.

Python pytest
import pytest

@pytest.fixture(autouse=True)
def test_logging():
    print("START TEST")
    yield
    print("END TEST")


def test_home_page(page):
    page.goto("https://shop.example.com")
Real-time example: Automatically log test lifecycle information before and after every test without adding setup or cleanup calls to each test function.
8 Autouse Fixtures

An autouse fixture runs automatically for every test within its scope. Use it for truly universal setup, not for hidden business dependencies.

Python pytest
import pytest
from playwright.sync_api import expect

@pytest.fixture(autouse=True)
def clear_test_state():
    reset_test_state()
    yield
    reset_test_state()


def test_cart_starts_empty(page):
    page.goto("https://shop.example.com/cart")
    expect(
        page.get_by_text("Your cart is empty")
    ).to_be_visible()
Real-time example: Reset shared test state before and after every test to prevent cross-test contamination and keep tests independent.
9 Fixture for API-Based Test Data

Create test data through an API fixture when UI setup would be slow. Then verify the resulting behavior through the UI.

Python Playwright
import pytest
from playwright.sync_api import expect

@pytest.fixture
def customer(api_request_context):
    response = api_request_context.post(
        "/api/customers",
        data={
            "email": "fixture@example.com",
            "name": "Fixture User"
        }
    )
    response.raise_for_status()
    return response.json()


def test_customer_appears_in_ui(page, customer):
    page.goto("https://shop.example.com/customers")
    expect(
        page.get_by_text(customer["name"])
    ).to_be_visible()
Real-time example: Seed a customer quickly through an API and verify that the web UI displays the newly created customer correctly.
10 Fixture for Authentication State

Reuse authenticated browser state for tests that do not need to test the login flow itself. This keeps authenticated tests focused on the behavior they are intended to verify.

Python Playwright
import pytest
from playwright.sync_api import expect

@pytest.fixture
def authenticated_page(page):
    page.goto("https://shop.example.com/login")
    page.get_by_label("Email").fill("qa@example.com")
    page.get_by_label("Password").fill("Password123")
    page.get_by_role("button", name="Login").click()
    return page


def test_checkout_requires_login(authenticated_page):
    authenticated_page.goto("https://shop.example.com/checkout")
    expect(
        authenticated_page.get_by_role("heading", name="Checkout")
    ).to_be_visible()
Real-time example: Checkout tests start with an authenticated user, while the login flow itself remains covered by dedicated authentication tests.
11 Fixture for Page Objects

Fixtures can construct page objects so test cases remain focused on business behavior rather than page-element setup.

Python Playwright
import pytest
from playwright.sync_api import expect


class LoginPage:
    def __init__(self, page):
        self.page = page
        self.email = page.get_by_label("Email")
        self.password = page.get_by_label("Password")
        self.login_button = page.get_by_role("button", name="Login")


@pytest.fixture
def login_page(page):
    return LoginPage(page)


def test_login_screen(login_page):
    login_page.page.goto("https://demo.example.com/login")
    expect(login_page.login_button).to_be_visible()
Real-time example: The fixture injects a ready-to-use LoginPage object into each authentication test, keeping page-object construction out of the test code.
12 Fixture for Environment Configuration

Centralize environment URLs and configuration so tests can run against different environments without hard-coding values in individual test cases.

Python Playwright
import os
import pytest
from playwright.sync_api import expect


@pytest.fixture(scope="session")
def base_url():
    return os.getenv("BASE_URL", "https://qa.example.com")


def test_home_page(page, base_url):
    page.goto(base_url)
    expect(
        page.get_by_role("heading", name="Welcome")
    ).to_be_visible()
Real-time example: Run the same test suite against QA or staging by changing the BASE_URL environment variable in CI, without modifying the test code.
13 Fixture for Browser Context Settings

Create a custom browser context when tests need a specific locale, timezone, permissions, viewport, or other browser-level settings.

Python Playwright
import pytest
from playwright.sync_api import expect


@pytest.fixture
def indian_context(browser):
    context = browser.new_context(
        locale="en-IN",
        timezone_id="Asia/Kolkata"
    )
    try:
        yield context
    finally:
        context.close()


def test_localized_checkout(indian_context):
    page = indian_context.new_page()
    page.goto("https://shop.example.com/checkout")
    expect(
        page.get_by_text("India")
    ).to_be_visible()
Real-time example: Validate localized checkout behavior by running the test with a controlled en-IN locale and Asia/Kolkata timezone.
14 Fixture for Multiple User Roles

Create separate fixtures for customer, manager, and admin identities when permissions and role-based access are part of the product.

Python Playwright
import pytest
from playwright.sync_api import expect


@pytest.fixture
def admin_page(page):
    login_as(page, "admin@example.com")
    return page


@pytest.fixture
def manager_page(page):
    login_as(page, "manager@example.com")
    return page


@pytest.fixture
def customer_page(page):
    login_as(page, "customer@example.com")
    return page


def test_admin_can_manage_users(admin_page):
    admin_page.goto("https://app.example.com/admin/users")
    expect(
        admin_page.get_by_role("heading", name="Users")
    ).to_be_visible()
Real-time example: Permission tests can inject the required user role through a fixture without repeating login mechanics in every test.
15 Fixture for Test Data Cleanup

Cleanup is essential when tests create orders, customers, files, or other persistent records. Use fixture teardown to keep the test environment clean and independent.

Python pytest
import pytest


@pytest.fixture
def test_order():
    order = create_order(status="NEW")
    yield order
    cancel_order(order["id"])


def test_order_can_be_cancelled(test_order):
    assert test_order["status"] == "NEW"
    cancel_order(test_order["id"])
Real-time example: Create a test order before the test and clean it up afterward so persistent test data does not accumulate. Fixture teardown runs even when the test itself fails.
16 Using Multiple Fixtures Together

Fixtures compose naturally. A test can receive several independent resources without manually coordinating their setup or cleanup.

Python Playwright
import pytest
from playwright.sync_api import expect


def test_customer_can_download_invoice(
    authenticated_page,
    test_order,
    invoice_page
):
    authenticated_page.goto(f"/orders/{test_order['id']}")
    invoice_page.download_latest_invoice()

    expect(
        invoice_page.success_message
    ).to_be_visible()
Real-time example: Authentication, order data, and invoice page-object behavior are injected independently into the same test, while each fixture manages its own setup and cleanup.
17 Fixtures for Database/API + UI Workflows

For end-to-end scenarios, use backend setup for reliable and fast data creation, then use Playwright for customer-facing verification.

Python Playwright
import pytest
from playwright.sync_api import expect


@pytest.fixture
def paid_order(api_request_context):
    return create_paid_order(api_request_context)


def test_paid_order_is_visible_in_history(
    authenticated_page,
    paid_order
):
    authenticated_page.goto("/orders")

    expect(
        authenticated_page.get_by_text(paid_order["number"])
    ).to_be_visible()
Real-time example: Create a paid order quickly through an API and verify through the Playwright UI that it appears in the customer's order history.
18 Failure Artifacts in a Fixture

A fixture can centralize diagnostics such as screenshots, logging, or other failure artifacts, while Playwright tracing remains available for deeper debugging.

Python Playwright
import pytest


@pytest.fixture
def diagnostic_page(page, request):
    yield page

    if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
        page.screenshot(
            path=f"artifacts/{request.node.name}.png",
            full_page=True
        )


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

    setattr(item, f"rep_{report.when}", report)
Real-time example: A shared fixture can automatically capture screenshots when tests fail, standardizing failure diagnostics across dozens of test modules while keeping deeper debugging available through tracing.
19 Fixtures vs Hooks: Practical Rule

In Python pytest, fixtures are the primary mechanism for setup and teardown. Treat fixtures as explicit dependencies whenever possible, and use autouse fixtures only for genuinely global behavior.

Python pytest
import pytest


# Explicit dependency — preferred
def test_cart(logged_in_page):
    ...


# Automatic behavior — use selectively
@pytest.fixture(autouse=True)
def reset_state():
    reset_state_before_test()
    yield
    reset_state_after_test()
Real-time example: Authentication is an explicit dependency, making the test's requirements clear, while universal state cleanup can be handled automatically with an autouse fixture.
20 10 Real-Time Fixture Patterns to Remember

These patterns cover common enterprise Playwright Python needs, including authentication, API data, cleanup, user roles, environment configuration, browser contexts, page objects, diagnostics, and reusable business workflows.

Python pytest
# 1. Login fixture
#    Reuse authentication setup across tests.

# 2. API test-data fixture
#    Create test data quickly through backend APIs.

# 3. Cleanup fixture
#    Remove temporary data after each test.

# 4. Admin/customer role fixtures
#    Test permissions with different user identities.

# 5. Environment fixture
#    Centralize URLs and environment-specific settings.

# 6. Browser-context fixture
#    Configure locale, timezone, permissions, or viewport.

# 7. Page-object fixture
#    Provide ready-to-use page objects to tests.

# 8. Authenticated-state fixture
#    Reuse saved authentication state when appropriate.

# 9. Diagnostic fixture
#    Capture screenshots, logs, or other failure artifacts.

# 10. Composite business-workflow fixture
#     Combine multiple fixtures for reusable business scenarios.
Real-time example: Combine these patterns to build a maintainable Playwright Python automation framework while keeping setup, cleanup, authentication, and business workflows reusable and avoiding duplicated test logic.