Webapp Testing

by bobmatnyc718070a7d622No license77 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 2 months ago

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

Instructions onlySoftware Development
AI-generated overview

Reference guide of Playwright patterns for writing web application tests, covering selectors, waits, assertions and debugging.

What it does
This skill provides a reference document of Playwright automation patterns for web application testing. It covers selector strategies, wait strategies, element interactions, assertions, test organization with pytest, network interception, screenshots and video, debugging, and parallel execution. It produces guidance and code examples rather than runnable test files.
When to use it
Use it when writing or reviewing browser-based tests for a web application and you need concrete Playwright selector, waiting, or assertion patterns. It is also useful when structuring pytest fixtures, mocking network responses, or debugging flaky UI tests.
Requirements
No scripts are shipped; it is instructions only. Applying the examples requires Python with Playwright installed and, for the pytest patterns, pytest and optionally pytest-xdist; running tests needs a browser and access to the application under test.

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:

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

Role-Based Selectors

Semantic selectors based on ARIA roles:

python
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:

python
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:

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

Data Attributes

Best practice for test-specific selectors:

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

Chaining Selectors

Combine selectors for precision:

python
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:

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

Element Waits

Wait for specific elements before interacting:

python
# Wait for element to be visiblepage.wait_for_selector('button.submit', state='visible')
# Wait for element to be hiddenpage.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 DOMpage.wait_for_selector('.error-message', state='detached')

Timeout Waits

Fixed time delays (use sparingly):

python
# Wait for animations to completepage.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:

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

Auto-Waiting

Playwright automatically waits for elements to be actionable:

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

Element Interactions

Clicking

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

Filling Forms

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

Dropdowns and Selects

python
# Select by labelpage.select_option('select[name="country"]', label='United States')
# Select by valuepage.select_option('select[name="country"]', value='us')
# Select by indexpage.select_option('select[name="country"]', index=2)
# Select multiple optionspage.select_option('select[multiple]', ['option1', 'option2'])

Checkboxes and Radio Buttons

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

File Uploads

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

Hover and Focus

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

Assertions

Element Visibility

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

Text Content

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

Element State

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

Attributes and Values

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

Count and Collections

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

Test Organization

Basic Test Structure

python
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)

python
import pytestfrom 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.fixturedef 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

python
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

python
@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

python
# Intercept and mock API responsedef 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

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

Wait for Network Responses

python
# Wait for specific API callwith page.expect_response('**/api/users') as response_info:    page.click('button.load-users')response = response_info.valueassert response.status == 200

Screenshots and Videos

Screenshots

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

Video Recording

python
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

python
page.pause()  # Opens Playwright Inspector

Console Logs

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

Slow Motion

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

Verbose Logging

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

Parallel Execution

Pytest Parallel

bash
# Install pytest-xdistpip install pytest-xdist
# Run tests in parallelpytest -n auto  # Auto-detect CPU corespytest -n 4     # Run with 4 workers

Browser Context Isolation

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

Source and attribution

Source:bobmatnyc/claude-mpm-skillsinuniversal/testing/webapp-testingat commit718070a

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

Webapp Testing Agent Skill | SourceWeft