← Back

Handling Test Data with Playwright Python

Learn practical ways to create, manage, reuse, isolate, and clean test data in Playwright Python automation. These examples show real-world patterns for UI, API, database, authentication, parameterized, and end-to-end testing.

1 Hard-Coded Test Data

Hard-coded data is the simplest approach and can be useful for small, stable scenarios. Avoid relying on it for large or environment-dependent test suites.

Python Playwright
def test_customer_login(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()
Real-time example: Use fixed credentials for a dedicated QA account when the account is intentionally stable and controlled by the test environment.
2 Test Data as Python Dictionaries

Store related test values in dictionaries when a test needs structured customer, product, or order data.

Python
customer = {
    "first_name": "John",
    "last_name": "Smith",
    "email": "john.smith@example.com",
    "phone": "9876543210"
}

def test_customer_registration(page):
    page.goto("https://shop.example.com/register")

    page.get_by_label("First Name").fill(customer["first_name"])
    page.get_by_label("Last Name").fill(customer["last_name"])
    page.get_by_label("Email").fill(customer["email"])
    page.get_by_label("Phone").fill(customer["phone"])
Real-time example: Keep customer information together instead of scattering values throughout the test.
3 Test Data with Fixtures

Use pytest fixtures when multiple tests need the same type of test data.

Python pytest
import pytest

@pytest.fixture
def customer_data():
    return {
        "name": "John Smith",
        "email": "john@example.com",
        "password": "Password123"
    }


def test_customer_profile(page, customer_data):
    page.goto("https://shop.example.com/register")

    page.get_by_label("Name").fill(customer_data["name"])
    page.get_by_label("Email").fill(customer_data["email"])
Real-time example: Centralize reusable customer data and inject it only into tests that need it.
4 Dynamic Test Data

Generate unique values when tests create records that must not conflict with data created by other tests.

Python
from uuid import uuid4

def create_customer_data():
    unique_id = uuid4().hex[:8]

    return {
        "name": f"Test User {unique_id}",
        "email": f"test_{unique_id}@example.com"
    }


def test_unique_customer(page):
    customer = create_customer_data()

    page.goto("https://shop.example.com/register")
    page.get_by_label("Name").fill(customer["name"])
    page.get_by_label("Email").fill(customer["email"])
Real-time example: Generate a unique email for every registration test to prevent duplicate-account failures.
5 Faker for Realistic Test Data

The Faker library can generate realistic names, emails, addresses, phone numbers, and other values.

Python Faker + Playwright
from faker import Faker

fake = Faker()

def test_customer_registration(page):
    customer = {
        "name": fake.name(),
        "email": fake.unique.email(),
        "phone": fake.numerify("##########")
    }

    page.goto("https://shop.example.com/register")
    page.get_by_label("Name").fill(customer["name"])
    page.get_by_label("Email").fill(customer["email"])
    page.get_by_label("Phone").fill(customer["phone"])
Real-time example: Generate realistic customer profiles instead of repeatedly using the same fake name and email.
6 Parameterized Test Data

Use pytest parameterization when the same test must run against several data combinations.

Python pytest
import pytest

@pytest.mark.parametrize(
    "email,password",
    [
        ("valid@example.com", "Password123"),
        ("customer@example.com", "Password123"),
        ("admin@example.com", "Admin123")
    ]
)
def test_login(page, email, password):
    page.goto("https://shop.example.com/login")

    page.get_by_label("Email").fill(email)
    page.get_by_label("Password").fill(password)
    page.get_by_role("button", name="Login").click()
Real-time example: Validate the login workflow against multiple known user accounts without duplicating the test function.
7 Test Data from JSON Files

JSON files are useful when test data is large, structured, or maintained separately from the test implementation.

customers.json
[
    {
        "name": "John Smith",
        "email": "john@example.com"
    },
    {
        "name": "Mary Jones",
        "email": "mary@example.com"
    }
]
Python
import json

def load_customers():
    with open("testdata/customers.json", encoding="utf-8") as file:
        return json.load(file)


def test_customer_data(page):
    customers = load_customers()

    for customer in customers:
        page.goto("https://shop.example.com/register")
        page.get_by_label("Name").fill(customer["name"])
        page.get_by_label("Email").fill(customer["email"])
Real-time example: Keep dozens or hundreds of test customers in JSON instead of making the Python test file difficult to maintain.
8 Test Data from CSV

CSV files work well for tabular test data such as usernames, products, prices, or search terms.

customers.csv
name,email,role
John Smith,john@example.com,customer
Mary Jones,mary@example.com,manager
Admin User,admin@example.com,admin
Python
import csv

def load_customers():
    with open("testdata/customers.csv", newline="") as file:
        return list(csv.DictReader(file))


def test_customer_roles(page):
    customers = load_customers()

    for customer in customers:
        print(customer["name"], customer["role"])
Real-time example: QA teams can update CSV test data without changing the automation code.
9 Test Data from Environment Variables

Environment variables are appropriate for environment-specific values such as test accounts, URLs, and configuration.

Python
import os

BASE_URL = os.getenv(
    "BASE_URL",
    "https://qa.example.com"
)

TEST_EMAIL = os.getenv(
    "TEST_EMAIL",
    "qa@example.com"
)

TEST_PASSWORD = os.getenv(
    "TEST_PASSWORD",
    "Password123"
)


def test_login(page):
    page.goto(f"{BASE_URL}/login")
    page.get_by_label("Email").fill(TEST_EMAIL)
    page.get_by_label("Password").fill(TEST_PASSWORD)
Real-time example: CI can provide different credentials and URLs for QA, staging, and other controlled test environments.
10 API-Based Test Data Creation

Create records through an API when UI-based setup is slow or requires many unnecessary steps.

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={
            "name": "API Customer",
            "email": "api_customer@example.com"
        }
    )

    response.raise_for_status()
    return response.json()


def test_customer_visible_in_ui(page, customer):
    page.goto("https://shop.example.com/customers")

    expect(
        page.get_by_text(customer["name"])
    ).to_be_visible()
Real-time example: Create a customer in milliseconds through an API and then verify that the UI displays the customer correctly.
11 Database-Based Test Data

Database setup can be useful for controlled integration tests, especially when creating complex records through the UI would be expensive.

Python
import pytest

@pytest.fixture
def customer(db_connection):
    customer_id = create_customer_in_database(
        db_connection,
        name="Database Customer",
        email="db_customer@example.com"
    )

    yield customer_id

    delete_customer_from_database(
        db_connection,
        customer_id
    )


def test_database_customer_appears(page, customer):
    page.goto("https://shop.example.com/customers")
    page.get_by_text("Database Customer").wait_for()
Real-time example: Seed complicated test data directly in a controlled test database and validate the customer-facing result through Playwright.
12 Factory Functions for Test Data

Factory functions provide a clean way to create test objects with sensible defaults while allowing individual tests to override specific values.

Python
from uuid import uuid4

def customer_factory(**overrides):
    unique_id = uuid4().hex[:8]

    customer = {
        "name": f"Test Customer {unique_id}",
        "email": f"customer_{unique_id}@example.com",
        "role": "customer"
    }

    customer.update(overrides)
    return customer


def test_manager_profile(page):
    customer = customer_factory(
        role="manager"
    )

    print(customer)
Real-time example: Create standard customer data while allowing one test to change only the role, email, or another required field.
13 Fixture + Factory Combination

Combine factories with pytest fixtures when test data needs consistent creation and automatic cleanup.

Python pytest
import pytest

@pytest.fixture
def customer(api_request_context):
    data = customer_factory(
        role="customer"
    )

    response = api_request_context.post(
        "/api/customers",
        data=data
    )

    response.raise_for_status()

    customer = response.json()

    yield customer

    api_request_context.delete(
        f"/api/customers/{customer['id']}"
    )


def test_customer_profile(page, customer):
    page.goto(
        f"https://shop.example.com/customers/{customer['id']}"
    )
Real-time example: The factory creates flexible data, while the fixture handles API creation and cleanup.
14 Isolated Test Data

Tests should avoid depending on records created by other tests. Generate or provision isolated data whenever possible.

Python pytest
def test_update_customer(page):
    customer = customer_factory()

    create_customer(customer)

    page.goto(
        f"https://shop.example.com/customers/{customer['id']}"
    )

    page.get_by_label("Name").fill("Updated Customer")
    page.get_by_role("button", name="Save").click()
Real-time example: Each test owns the customer record it modifies instead of modifying a shared customer that another test might also use.
15 Cleanup Test Data with yield

Use fixture teardown to remove records created specifically for a test.

Python pytest
import pytest

@pytest.fixture
def test_product():
    product = create_product(
        name="Temporary Product"
    )

    yield product

    delete_product(product["id"])


def test_product_can_be_archived(test_product):
    archive_product(test_product["id"])

    assert product_is_archived(
        test_product["id"]
    )
Real-time example: Create a temporary product before the test and remove it after the test so persistent data does not accumulate.
16 Test Data for Multiple User Roles

Maintain separate test data for customer, manager, and admin scenarios when authorization affects application behavior.

Python pytest
import pytest

@pytest.fixture
def admin_data():
    return {
        "email": "admin@example.com",
        "password": "Admin123"
    }


@pytest.fixture
def customer_data():
    return {
        "email": "customer@example.com",
        "password": "Customer123"
    }


def test_admin_dashboard(page, admin_data):
    login(
        page,
        admin_data["email"],
        admin_data["password"]
    )

    page.goto("https://shop.example.com/admin")
Real-time example: Permission tests can receive the correct role-specific data without repeating account details throughout the test suite.
17 Authentication Test Data

Separate authentication data from ordinary customer data. Tests that require login should receive credentials through fixtures or environment configuration.

Python pytest
import os
import pytest

@pytest.fixture
def test_user():
    return {
        "email": os.getenv(
            "TEST_USER_EMAIL",
            "qa@example.com"
        ),
        "password": os.getenv(
            "TEST_USER_PASSWORD",
            "Password123"
        )
    }


def test_account_page(page, test_user):
    login(
        page,
        test_user["email"],
        test_user["password"]
    )

    page.goto("https://shop.example.com/account")
Real-time example: CI supplies the credentials while the test code remains unchanged across environments.
18 Boundary and Negative Test Data

Good test-data management also includes invalid, empty, maximum, minimum, and boundary values.

Python pytest
import pytest

@pytest.mark.parametrize(
    "email",
    [
        "",
        "invalid-email",
        "user@",
        "@example.com",
        "a" * 300 + "@example.com"
    ]
)
def test_invalid_email_validation(page, email):
    page.goto("https://shop.example.com/register")

    page.get_by_label("Email").fill(email)
    page.get_by_role("button", name="Register").click()

    assert page.get_by_text(
        "Enter a valid email address"
    ).is_visible()
Real-time example: Validate that registration handles empty, malformed, and excessively long email values correctly.
19 Composite Business Test Data

Complex workflows often require multiple related records such as a customer, product, address, cart, and order.

Python pytest
import pytest

@pytest.fixture
def checkout_data(api_request_context):
    customer = create_customer(api_request_context)
    product = create_product(api_request_context)
    address = create_address(api_request_context)

    return {
        "customer": customer,
        "product": product,
        "address": address
    }


def test_checkout(
    authenticated_page,
    checkout_data
):
    product = checkout_data["product"]

    authenticated_page.goto(
        f"/products/{product['id']}"
    )

    authenticated_page.get_by_role(
        "button",
        name="Add to Cart"
    ).click()

    authenticated_page.goto("/checkout")
Real-time example: A single business fixture prepares everything required for an end-to-end checkout scenario.
20 Practical Test Data Strategy

A maintainable automation framework normally combines several test-data techniques rather than relying on one approach.

Recommended Strategy
# Stable configuration
ENVIRONMENT = "qa"

# Reusable data factory
customer = customer_factory()

# API/database setup
create_customer(customer)

# UI verification
page.goto("/customers")

# Boundary data
invalid_email = "invalid-email"

# Cleanup
delete_customer(customer["id"])
Real-time example: Use factories for flexible data, APIs or databases for fast setup, Playwright for UI verification, parameterization for variations, and fixture teardown for cleanup.
✓ Quick Reference: Which Approach Should You Use?
Situation Recommended Approach Example
Small stable data Hard-coded values Known QA account
Reusable data pytest fixture Customer fixture
Unique records Dynamic data / Faker Unique email
Many combinations pytest parameterization Login scenarios
Large structured data JSON / CSV Customer datasets
Fast backend setup API fixtures Create order through API
Complex records Database setup Order + payment + customer
Flexible reusable objects Factory functions customer_factory()
Temporary records Fixture + yield cleanup Temporary order
End-to-end workflow Composite fixtures Customer + product + order