agskills.dev
MARKETPLACE

webapp-testing-patterns

Comprehensive web application testing patterns with Playwright selectors, wait strategies, and best practices

bobmatnyc155

Vorschau

SKILL.md
Metadata
name
webapp-testing-patterns
description
"Comprehensive web application testing patterns with Playwright selectors, wait strategies, and best practices"
summary
"Comprehensive web application testing patterns with Playwright selectors, wait strategies, and best practices"
when_to_use
"When writing tests, implementing webapp-testing-patterns, or ensuring code quality."
quick_start
"1. Review the core concepts below. 2. Apply patterns to your use case. 3. Follow best practices for implementation."

Playwright Patterns Reference

Complete guide to Playwright automation patterns, selectors, and best practices.

Table of Contents

Selectors

Text Selectors

Most readable and maintainable approach when text is unique:

page.click('text=Login') page.click('text="Sign Up"') # Exact match page.click('text=/log.*in/i') # Regex, case-insensitive

Role-Based Selectors

Semantic selectors based on ARIA roles:

page.click('role=button[name="Submit"]') page.fill('role=textbox[name="Email"]', '[email protected]') page.click('role=link[name="Learn more"]') page.check('role=checkbox[name="Accept terms"]')

CSS Selectors

Traditional CSS selectors for precise targeting:

page.click('#submit-button') page.fill('.email-input', '[email protected]') page.click('button.primary') page.click('nav > ul > li:first-child')

XPath Selectors

For complex DOM navigation:

page.click('xpath=//button[contains(text(), "Submit")]') page.click('xpath=//div[@class="modal"]//button[@type="submit"]')

Data Attributes

Best practice for test-specific selectors:

page.click('[data-testid="submit-btn"]') page.fill('[data-test="email-input"]', '[email protected]')

Chaining Selectors

Combine selectors for precision:

page.locator('div.modal').locator('button.submit').click() page.locator('role=dialog').locator('text=Confirm').click()

Selector Best Practices

Priority order (most stable to least stable):

  1. data-testid attributes (most stable)
  2. role= selectors (semantic, accessible)
  3. text= selectors (readable, but text may change)
  4. id attributes (stable if not dynamic)
  5. CSS classes (less stable, may change with styling)
  6. XPath (fragile, avoid if possible)

Wait Strategies

Load State Waits

Essential for dynamic applications:

# Wait for network to be idle (most common) page.goto('http://localhost:3000') page.wait_for_load_state('networkidle') # Wait for DOM to be ready page.wait_for_load_state('domcontentloaded') # Wait for full load including images page.wait_for_load_state('load')

Element Waits

Wait for specific elements before interacting:

# Wait for element to be visible page.wait_for_selector('button.submit', state='visible') # Wait for element to be hidden page.wait_for_selector('.loading-spinner', state='hidden') # Wait for element to exist in DOM (may not be visible) page.wait_for_selector('.modal', state='attached') # Wait for element to be removed from DOM page.wait_for_selector('.error-message', state='detached')

Timeout Waits

Fixed time delays (use sparingly):

# Wait for animations to complete page.wait_for_timeout(500) # Wait for delayed content (better to use wait_for_selector) page.wait_for_timeout(2000)

Custom Wait Conditions

Wait for JavaScript conditions:

# Wait for custom JavaScript condition page.wait_for_function('() => document.querySelector(".data").innerText !== "Loading..."') # Wait for variable to be set page.wait_for_function('() => window.appReady === true')

Auto-Waiting

Playwright automatically waits for elements to be actionable:

# These automatically wait for element to be: # - Visible # - Stable (not animating) # - Enabled (not disabled) # - Not obscured by other elements page.click('button.submit') # Auto-waits page.fill('input.email', '[email protected]') # Auto-waits

Element Interactions

Clicking

# Basic click page.click('button.submit') # Click with options page.click('button.submit', button='right') # Right-click page.click('button.submit', click_count=2) # Double-click page.click('button.submit', modifiers=['Control']) # Ctrl+click # Force click (bypass actionability checks) page.click('button.submit', force=True)

Filling Forms

# Text inputs page.fill('input[name="email"]', '[email protected]') page.type('input[name="search"]', 'query', delay=100) # Type with delay # Clear then fill page.fill('input[name="email"]', '') page.fill('input[name="email"]', '[email protected]') # Press keys page.press('input[name="search"]', 'Enter') page.press('input[name="text"]', 'Control+A')

Dropdowns and Selects

# Select by label page.select_option('select[name="country"]', label='United States') # Select by value page.select_option('select[name="country"]', value='us') # Select by index page.select_option('select[name="country"]', index=2) # Select multiple options page.select_option('select[multiple]', ['option1', 'option2'])

Checkboxes and Radio Buttons

# Check a checkbox page.check('input[type="checkbox"]') # Uncheck a checkbox page.uncheck('input[type="checkbox"]') # Check a radio button page.check('input[value="option1"]') # Toggle checkbox if page.is_checked('input[type="checkbox"]'): page.uncheck('input[type="checkbox"]') else: page.check('input[type="checkbox"]')

File Uploads

# Upload single file page.set_input_files('input[type="file"]', '/path/to/file.pdf') # Upload multiple files page.set_input_files('input[type="file"]', ['/path/to/file1.pdf', '/path/to/file2.pdf']) # Clear file input page.set_input_files('input[type="file"]', [])

Hover and Focus

# Hover over element page.hover('button.tooltip-trigger') # Focus element page.focus('input[name="email"]') # Blur element page.evaluate('document.activeElement.blur()')

Assertions

Element Visibility

from playwright.sync_api import expect # Expect element to be visible expect(page.locator('button.submit')).to_be_visible() # Expect element to be hidden expect(page.locator('.error-message')).to_be_hidden()

Text Content

# Expect exact text expect(page.locator('.title')).to_have_text('Welcome') # Expect partial text expect(page.locator('.message')).to_contain_text('success') # Expect text matching pattern expect(page.locator('.code')).to_have_text(re.compile(r'\d{6}'))

Element State

# Expect element to be enabled/disabled expect(page.locator('button.submit')).to_be_enabled() expect(page.locator('button.submit')).to_be_disabled() # Expect checkbox to be checked expect(page.locator('input[type="checkbox"]')).to_be_checked() # Expect element to be editable expect(page.locator('input[name="email"]')).to_be_editable()

Attributes and Values

# Expect attribute value expect(page.locator('img')).to_have_attribute('src', '/logo.png') # Expect CSS class expect(page.locator('button')).to_have_class('btn-primary') # Expect input value expect(page.locator('input[name="email"]')).to_have_value('[email protected]')

Count and Collections

# Expect specific count expect(page.locator('li')).to_have_count(5) # Get all elements and assert items = page.locator('li').all() assert len(items) == 5

Test Organization

Basic Test Structure

from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() # Test logic here page.goto('http://localhost:3000') page.wait_for_load_state('networkidle') browser.close()

Using Pytest (Recommended)

import pytest from playwright.sync_api import sync_playwright @pytest.fixture(scope="session") def browser(): with sync_playwright() as p: browser = p.chromium.launch(headless=True) yield browser browser.close() @pytest.fixture def page(browser): page = browser.new_page() yield page page.close() def test_login(page): page.goto('http://localhost:3000') page.fill('input[name="email"]', '[email protected]') page.fill('input[name="password"]', 'password123') page.click('button[type="submit"]') expect(page.locator('.welcome-message')).to_be_visible()

Test Grouping with Describe Blocks

class TestAuthentication: def test_successful_login(self, page): # Test successful login pass def test_failed_login(self, page): # Test failed login pass def test_logout(self, page): # Test logout pass

Setup and Teardown

@pytest.fixture(autouse=True) def setup_and_teardown(page): # Setup - runs before each test page.goto('http://localhost:3000') page.wait_for_load_state('networkidle') yield # Test runs here # Teardown - runs after each test page.evaluate('localStorage.clear()')

Network Interception

Mock API Responses

# Intercept and mock API response def handle_route(route): route.fulfill( status=200, body='{"success": true, "data": "mocked"}', headers={'Content-Type': 'application/json'} ) page.route('**/api/data', handle_route) page.goto('http://localhost:3000')

Block Resources

# Block images and stylesheets for faster tests page.route('**/*.{png,jpg,jpeg,gif,svg,css}', lambda route: route.abort())

Wait for Network Responses

# Wait for specific API call with page.expect_response('**/api/users') as response_info: page.click('button.load-users') response = response_info.value assert response.status == 200

Screenshots and Videos

Screenshots

# Full page screenshot page.screenshot(path='/tmp/screenshot.png', full_page=True) # Element screenshot page.locator('.modal').screenshot(path='/tmp/modal.png') # Screenshot with custom dimensions page.set_viewport_size({'width': 1920, 'height': 1080}) page.screenshot(path='/tmp/desktop.png')

Video Recording

browser = p.chromium.launch(headless=True) context = browser.new_context(record_video_dir='/tmp/videos/') page = context.new_page() # Perform actions... context.close() # Video saved on close

Debugging

Pause Execution

page.pause() # Opens Playwright Inspector

Console Logs

def handle_console(msg): print(f"[{msg.type}] {msg.text}") page.on("console", handle_console)

Slow Motion

browser = p.chromium.launch(headless=False, slow_mo=1000) # 1 second delay

Verbose Logging

# Set DEBUG environment variable # DEBUG=pw:api python test.py

Parallel Execution

Pytest Parallel

# Install pytest-xdist pip install pytest-xdist # Run tests in parallel pytest -n auto # Auto-detect CPU cores pytest -n 4 # Run with 4 workers

Browser Context Isolation

# Each test gets isolated context (cookies, localStorage, etc.) @pytest.fixture def context(browser): context = browser.new_context() yield context context.close() @pytest.fixture def page(context): return context.new_page()