A test case verifies one business behavior with controlled data, clear actions, and expected results.
def test_successful_login(page):
page.goto("https://demo.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()
expect(page.get_by_role("heading", name="Dashboard")).to_be_visible()A test suite groups related tests by feature, risk, release purpose, or business area.
tests/
├── auth/
├── catalog/
├── checkout/
└── orders/Define preconditions, test data, steps, and expected results before writing Playwright code.
# Test: customer adds Wireless Mouse to cart
# Given: product is in stock
# When: customer clicks Add to cart
# Then: cart shows quantity 1Use behavior-oriented names that explain the expected outcome.
def test_customer_can_add_in_stock_product_to_cart(page):
...
def test_login_shows_error_for_invalid_password(page):
...Separate setup, user action, and verification for readable, diagnosable tests.
def test_update_cart_quantity(page):
# Arrange
page.goto("https://shop.example.com/cart")
# Act
page.get_by_role("spinbutton", name="Quantity").fill("2")
# Assert
expect(page.get_by_text("Quantity: 2")).to_be_visible()Smoke tests cover critical paths and run after every deployment.
@pytest.mark.smoke
def test_customer_can_login(page):
...Regression tests protect previously working functionality across major features.
@pytest.mark.regression
def test_coupon_is_applied(page):
...
@pytest.mark.regression
def test_order_confirmation_is_shown(page):
...Organize files around product capabilities instead of one huge test file.
tests/
├── auth/test_login.py
├── catalog/test_search.py
├── checkout/test_payment.py
└── orders/test_history.pyFixtures centralize repeatable setup such as authenticated customers.
@pytest.fixture
def logged_in_customer(page):
login(page, "qa@example.com", "Password123")
return pageUse conftest.py for fixtures shared across many test modules.
@pytest.fixture
def customer_credentials():
return {"email":"qa@example.com", "password":"Password123"}Each test should create or obtain its own required state and must not depend on test order.
def test_order_history(page):
seed_order_for("qa@example.com")
page.goto("https://shop.example.com/orders")
expect(page.get_by_role("heading", name="My Orders")).to_be_visible()Do not assume another test left the browser on a specific page or with a specific cart.
def test_checkout_requires_address(page):
page.goto("https://shop.example.com/checkout")
expect(page.get_by_label("Billing address")).to_be_visible()Use pytest parametrization when behavior is identical but input data varies.
@pytest.mark.parametrize("amount,expected", [
("0", "Amount must be greater than 0"),
("1000000", "Amount exceeds daily limit")
])
def test_transfer_boundaries(page, amount, expected):
...Markers let you select smoke, regression, checkout, critical, or other groups.
@pytest.mark.checkout
@pytest.mark.smoke
def test_checkout(page):
...Keep a small set of revenue, authentication, and high-risk tests for frequent execution.
@pytest.mark.critical
def test_customer_can_purchase_product(page):
add_product(page, "Wireless Mouse")
checkout(page)
expect(page.get_by_text("Order confirmed")).to_be_visible()Move repeated UI actions and locators into page objects while tests describe business behavior.
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")
def login(self, email, password):
self.email.fill(email)
self.password.fill(password)
self.login_button.click()Keep test names and flows aligned to customer outcomes, hiding low-level UI details when useful.
def test_customer_can_download_invoice(invoice_page):
invoice_page.open_latest_order()
invoice_page.download_invoice()
expect(invoice_page.success_message).to_be_visible()Create expensive prerequisite data through an API and verify the user-facing result through the UI.
api.post("/customers", data={"email":"new@example.com"})
page.goto("https://shop.example.com/profile")
expect(page.get_by_text("new@example.com")).to_be_visible()Save authenticated browser state for suites that do not need to test the login flow itself.
# after successful login
page.context.storage_state(path="playwright/.auth/customer.json")Login correctness belongs in authentication tests; downstream tests should use authenticated fixtures.
def test_invalid_login(page):
...
def test_customer_can_open_orders(authenticated_page):
authenticated_page.goto("https://shop.example.com/orders")
...Validate invalid input, denied access, insufficient balance, duplicate actions, and other expected failures.
def test_transfer_rejects_insufficient_balance(page):
page.goto("https://bank.example.com/transfer")
page.get_by_label("Amount").fill("100000")
page.get_by_role("button", name="Transfer").click()
expect(page.get_by_text("Insufficient balance")).to_be_visible()Cover minimum, maximum, empty, and exact-limit values where business rules matter.
@pytest.mark.parametrize("value", ["0", "1000000"])
def test_amount_limits(page, value):
page.get_by_label("Amount").fill(value)
page.get_by_role("button", name="Transfer").click()Use fixtures for admin, manager, and customer roles and test authorization explicitly.
def test_customer_cannot_open_admin_page(customer_page):
customer_page.goto("https://app.example.com/admin/users")
expect(customer_page.get_by_text("Access denied")).to_be_visible()Use layered execution: smoke for health, regression for broad coverage, full for everything.
pytest -m smoke
pytest -m regression
pytestIf a true business flow is sequential, keep the lifecycle together instead of making separate tests depend on order.
def test_order_lifecycle(page):
create_order(page)
approve_order(page)
ship_order(page)
deliver_order(page)
expect(page.get_by_text("Delivered")).to_be_visible()Assertions should verify business outcomes, not just that a click completed.
page.get_by_role("button", name="Generate").click()
expect(page.get_by_text("Report ready")).to_be_visible()Map important tests to requirement or story IDs for coverage visibility.
@pytest.mark.requirement("CHK-104")
def test_customer_can_apply_valid_coupon(page):
...Folders provide discoverability while markers provide flexible execution across risk and feature dimensions.
tests/smoke/
tests/regression/
pytest tests/smoke
pytest -m checkoutRetries should not hide flaky behavior. Track unstable tests and investigate timing, data, environment, or locator causes.
@pytest.mark.flaky
def test_payment_confirmation(page):
...Use Playwright traces, screenshots, and videos according to the CI retention policy.
pytest --tracing=retain-on-failure --screenshot=only-on-failureIndependent tests can run across workers to shorten feedback time. Keep test data isolated.
pytest -n 4Use environment variables for URLs, credentials, and deployment-specific settings.
import os
BASE_URL = os.environ["BASE_URL"]
def test_home(page):
page.goto(BASE_URL)Use Playwright assertions and auto-waiting instead of fixed sleeps wherever possible.
page.get_by_role("button", name="Generate").click()
expect(page.get_by_text("Report ready")).to_be_visible()Run important scenarios across supported browser projects without multiplying every test unnecessarily.
# Playwright projects can cover chromium, firefox, and webkit.
# Keep the highest-risk scenarios in the cross-browser matrix.Layer checks from fastest to broadest: unit/API, smoke UI, targeted regression, then full cross-browser coverage.
pytest tests/smoke
pytest tests/regression -m "critical or checkout"
pytest tests/e2eCombine fixtures, page objects, markers, positive and negative cases around one business domain.
@pytest.mark.ecommerce
@pytest.mark.smoke
def test_customer_can_buy_product(authenticated_shop):
authenticated_shop.add_product("Wireless Mouse")
authenticated_shop.checkout()
expect(authenticated_shop.order_confirmation).to_be_visible()Separate configuration, fixtures, page objects, test data, and suites as the project grows.
playwright-python/
├── pages/
├── tests/smoke/
├── tests/regression/
├── test_data/
├── conftest.py
├── pytest.ini
└── requirements.txtThink: business behavior → test case → data → fixture → Playwright actions → assertions → suite/marker → CI. Keep tests independent, readable, deterministic, and risk-focused.
@pytest.mark.smoke
def test_customer_can_complete_checkout(authenticated_shop):
authenticated_shop.add_product("Wireless Mouse")
authenticated_shop.complete_payment("test-card")
expect(authenticated_shop.order_confirmation).to_be_visible()