| name | write-e2e-test |
| description | Scaffold E2E UI test files following project patterns — Playwright assertions, Page Objects, Components, markers, fixtures, Allure decorators, BrowserStorage |
| argument-hint | <domain> <test-scenario> |
E2E Test Scaffolding
When writing an E2E test, follow these patterns exactly.
Required Imports
import allure
import pytest
from playwright.sync_api import Page, expect
from core.global_settings import GlobalSettings
from page_objects.pages import CartPage, HomePage, SignInPage
from page_objects.components import ClearCartModal, LineItem
from tests.context import Context
Allure Conventions
@allure.feature("<Domain> (E2E)")
@allure.title("<Action description>")
Pattern 1: Cart Fixture-Driven Test
Use when the with_cart marker handles cart setup and teardown automatically.
_PRODUCT_ID = "product-acme-laptop-asus-zenbook-a14-ux3407"
_QUANTITY = 3
@pytest.mark.e2e
@allure.feature("Cart (E2E)")
@allure.title("Clear cart")
@pytest.mark.with_cart([(_PRODUCT_ID, _QUANTITY)])
def test_cart_clear(page: Page, global_settings: GlobalSettings) -> None:
cart_page = CartPage(global_settings=global_settings, page=page)
cart_page.navigate()
expect(cart_page.line_items).to_be_visible()
expect(cart_page.line_items).to_have_count(1)
expect(cart_page.clear_cart_button).to_be_visible()
expect(cart_page.clear_cart_button).to_be_enabled()
cart_page.clear_cart_button.click()
clear_cart_modal = ClearCartModal(
root=page.locator("[data-test-id='clear-cart-modal']")
)
expect(clear_cart_modal.root).to_be_visible()
expect(clear_cart_modal.yes_button).to_be_visible()
clear_cart_modal.yes_button.click()
expect(clear_cart_modal.root).not_to_be_visible()
expect(cart_page.line_items).not_to_be_visible()
Pattern 2: User Authentication with Form Interaction
Use when testing login flows or pages that require manual form filling.
_USERNAME = "acme_store_employee_1@acme.com"
@pytest.mark.e2e
@allure.feature("Authentication (E2E)")
@allure.title("Sign in with valid credentials")
def test_sign_in_success(global_settings: GlobalSettings, page: Page, ctx: Context) -> None:
sign_in_page = SignInPage(global_settings=global_settings, page=page)
sign_in_page.navigate()
expect(sign_in_page.email_input).to_be_visible()
expect(sign_in_page.password_input).to_be_visible()
expect(sign_in_page.sign_in_button).to_be_visible()
sign_in_page.email_input.fill(_USERNAME)
sign_in_page.password_input.fill(global_settings.users_password.get_secret_value())
sign_in_page.sign_in_button.click()
home_page = HomePage(global_settings=global_settings, page=page)
expect(page).to_have_url(home_page.url)
expect(home_page.top_header.account_button.root).to_be_visible()
Pattern 3: Authenticated User with Cart (marker-driven)
Use when a test needs both a signed-in user and a pre-seeded cart.
_USERNAME = "acme_store_maintainer_1@acme.com"
_PRODUCT_ID = "product-acme-laptop-asus-zenbook-a14-ux3407"
@pytest.mark.e2e
@allure.feature("Cart (E2E)")
@allure.title("Update cart item quantity")
@pytest.mark.with_user(_USERNAME)
@pytest.mark.with_cart([(_PRODUCT_ID, 1)])
def test_cart_item_update(page: Page, global_settings: GlobalSettings) -> None:
cart_page = CartPage(global_settings=global_settings, page=page)
cart_page.navigate()
Page Object Instantiation
Always pass keyword arguments:
cart_page = CartPage(global_settings=global_settings, page=page)
cart_page.navigate()
clear_cart_modal = ClearCartModal(root=page.locator("[data-test-id='clear-cart-modal']"))
line_item = cart_page.find_line_item(sku="product-sku")
Pre-Authenticated Page Context (with_page_context)
Use when a test needs a Playwright page context that's already signed in as the given user — no manual sign-in via the UI required. The fixture yields a fresh Page bound to a context with auth cookies/localStorage already injected.
_USERNAME = "acme_store_employee_1@acme.com"
@pytest.mark.e2e
@allure.feature("Authentication (E2E)")
@allure.title("Account button visible for signed-in user")
@pytest.mark.with_page_context(_USERNAME)
def test_account_visible(page: Page, global_settings: GlobalSettings) -> None:
home_page = HomePage(global_settings=global_settings, page=page)
home_page.navigate()
expect(home_page.top_header.account_button.root).to_be_visible()
When to use: test needs a signed-in user from the first navigation (skips UI sign-in flow). For tests that exercise the sign-in flow itself, use Pattern 2 above.
BrowserStorage (auth injection)
The with_user fixture auto-injects auth to localStorage for E2E tests. For manual auth:
from page_objects.browser_storage import BrowserStorage
storage = BrowserStorage(page)
storage.set_user_id(user_id)
storage.set_auth(provider.token_info)
Available Fixtures
| Fixture | Scope | Description |
|---|
page: Page | function | Playwright page (from pytest-playwright) |
global_settings: GlobalSettings | session | Environment configuration |
ctx: Context | function | Test context from dataset + markers |
with_cart: Cart | None | function (autouse) | Seeded from @pytest.mark.with_cart |
with_user: AuthProvider | function | Auth + auto localStorage for E2E |
dataset: dict | session | Raw test data from JSON files |
Assertion Patterns
from playwright.sync_api import expect
expect(page).to_have_url(expected_url)
expect(element).to_be_visible()
expect(element).not_to_be_visible()
expect(element).to_be_enabled()
expect(element).to_have_text("Expected text")
expect(element).to_have_value(str(quantity))
expect(locator).to_have_count(3)
Locator Conventions
page.locator("[data-test-id='clear-cart-button']")
element.get_attribute("data-product-sku")
page.locator(f"[data-product-sku='{sku}']")
self._root.locator("[data-test-id='quantity-stepper']")
Rules
- Every test function MUST have
@pytest.mark.e2e
- Every test function MUST have
@allure.feature() and @allure.title()
- Module-level constants for product IDs, usernames:
_PRODUCT_ID = "..."
- Return type annotation:
def test_...(fixtures) -> None:
- Use
expect() for all UI assertions — not bare assert
- Use
data-test-id attributes for locators — prefer over CSS/XPath
- Navigate via
page_object.navigate() — not raw page.goto()
- Prefer marker-driven setup (
with_cart, with_user) over manual BrowserStorage
- Use
allure.step() for logical groupings when tests have multiple phases
- No
time.sleep() — Playwright auto-waits; use expect() or wait_for_selector()