Recipes
This page covers common patterns and solutions for real-world testing scenarios that go beyond the basic tutorial.
Dynamic Selectors
Dynamic IDs and data-testid
When your app generates dynamic IDs (e.g., todo-${id} or user-${uuid}), use partial matching or parent scoping:
# Use text content to find specific itemssee a li (text: 'Buy milk')
# Scope by parent container firstWithin the Todo List (type: element, testid: 'todo-list') see a li (text: 'Buy milk') click button (text: 'Delete')Matching by CSS class patterns
For items with state-based classes like completed or selected:
# Find completed todo itemssee a li (class: 'todo-item completed')
# Find the active navigation linksee a a (class: 'nav-link active')
# Multiple classes are space-separatedsee a div (class: 'card highlighted selected')Scoping by parent text
When you need to find elements within a specific parent identified by text:
Within the Browser (plugin: playwright-python, type: browser) # First scope to the right todo item by its text Within the Todo Item (type: element, text: 'Buy groceries') # Then interact with buttons inside that item click button (testid: 'toggle-complete') see a span (class: 'completed-indicator')ARIA Role Selection
Buttons with accessible names
# Best practice: use role + name for interactive elementsclick button (role: 'button', name: 'Submit')click button (role: 'button', name: 'Cancel')
# For form controlsclick input (role: 'checkbox', name: 'I agree to terms')click input (role: 'radio', name: 'Option A')Links and navigation
# Links by accessible nameclick a (role: 'link', name: 'Home')click a (role: 'link', name: 'Settings')
# Navigation landmarksWithin the Navigation (type: element, role: 'navigation') click a (text: 'Dashboard')Dialogs and modals
# Wait for dialog to appearwait for dialog (role: 'dialog', name: 'Confirm Delete')
# Interact with dialog buttonsclick button (role: 'button', name: 'Confirm')Asserting State Changes
Toggle completion state
Within the Browser (plugin: playwright-python, type: browser) go to "/todos"
# Initial state: todo is not completed see a li (class: 'todo-item', text: 'Buy milk') see no li (class: 'todo-item completed', text: 'Buy milk')
# Click the checkbox to complete Within the Todo Item (type: element, text: 'Buy milk') click input (role: 'checkbox')
# After toggle: todo should have completed class see a li (class: 'todo-item completed', text: 'Buy milk')Form validation states
Within the Browser (plugin: playwright-python, type: browser) go to "/register"
# Submit empty form click button (role: 'button', name: 'Register')
# Assert validation errors appear see a span (class: 'error', text: 'Email is required') see a input (class: 'invalid', testid: 'email-input')
# Fill in valid data type "user@example.com" into input (testid: 'email-input') click button (role: 'button', name: 'Register')
# Assert validation passes see no span (class: 'error')URL Assertions
After navigation
Within the Browser (plugin: playwright-python, type: browser) go to "/login"
# Fill and submit login form type "user@example.com" into input (testid: 'email') type "password123" into input (testid: 'password') click button (role: 'button', name: 'Sign In')
# Assert redirect to dashboard see url "/dashboard" see a h1 (text: 'Welcome back')Partial URL matching
# Assert URL contains a path segmentsee url contains "/users/"
# Useful for dynamic routes like /users/123see url contains "/orders/"Waiting for Dynamic Content
AJAX-loaded content
Within the Browser (plugin: playwright-python, type: browser) go to "/dashboard"
# Wait for loading spinner to disappear wait for div (class: 'loading-spinner', state: 'hidden')
# Or wait for content to appear wait for table (testid: 'data-table', state: 'visible')
# Now interact with loaded content see a td (text: 'John Doe')Async form submission
Within the Browser (plugin: playwright-python, type: browser) # Submit form click button (role: 'button', name: 'Save')
# Wait for success message wait for div (class: 'toast success', timeout: '5000') see a div (text: 'Changes saved successfully')Empty Container Assertions
# This fails on an empty todo list (no items = no height)see a ul (testid: 'todo-list')
# This works - just checks the element existssee a ul (testid: 'todo-list', state: 'attached')
# Assert list is empty by checking for no itemssee no li (class: 'todo-item')Combining Modifiers
Class + Text filtering
# Find a completed todo with specific textsee a li (class: 'completed', text: 'Buy milk')
# Find an error message in an alertsee a div (class: 'alert alert-danger', text: 'Invalid credentials')Role + Name for buttons
# Most reliable way to find buttonsclick button (role: 'button', name: 'Submit Order')
# For icon-only buttons, the accessible name comes from aria-labelclick button (role: 'button', name: 'Close')Test ID + Text for specificity
# When test IDs aren't unique enoughsee a div (testid: 'notification', text: 'Order confirmed')Reusable Selectors with Define
Common page elements
Within the Browser (plugin: playwright-python, type: browser) # Define reusable selectors at the start Define submit-btn (role: 'button', name: 'Submit') Define cancel-btn (role: 'button', name: 'Cancel') Define email-input (testid: 'email-field') Define password-input (testid: 'password-field') Define error-message (class: 'error-message')
# Use them throughout the test type "user@example.com" into email-input type "secret123" into password-input click submit-btn see no error-messageScoped to containers
Within the Login Form (type: element, testid: 'login-form') Define email (label: 'Email address') Define password (label: 'Password') Define submit (role: 'button', name: 'Sign in')
type "user@example.com" into email type "password123" into password click submit