| name | webapp-testing |
| description | Comprehensive web application testing patterns with Playwright TypeScript, selectors, wait strategies, and best practices |
| user-invocable | false |
| disable-model-invocation | true |
| progressive_disclosure | {"entry_point":{"summary":"Comprehensive web application testing patterns with Playwright TypeScript, 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 (TypeScript)
Complete guide to Playwright automation patterns, selectors, and best practices using TypeScript.
Table of Contents
Selectors
Text Selectors
Most readable and maintainable approach when text is unique:
await page.click('text=Login')
await page.click('text="Sign Up"')
await page.click('text=/log.*in/i')
Role-Based Selectors
Semantic selectors based on ARIA roles:
await page.click('role=button[name="Submit"]')
await page.fill('role=textbox[name="Email"]', 'user@example.com')
await page.click('role=link[name="Learn more"]')
await page.check('role=checkbox[name="Accept terms"]')
CSS Selectors
Traditional CSS selectors for precise targeting:
await page.click('#submit-button')
await page.fill('.email-input', 'user@example.com')
await page.click('button.primary')
await page.click('nav > ul > li:first-child')
XPath Selectors
For complex DOM navigation:
await page.click('xpath=//button[contains(text(), "Submit")]')
await page.click('xpath=//div[@class="modal"]//button[@type="submit"]')
Data Attributes
Best practice for test-specific selectors:
await page.click('[data-testid="submit-btn"]')
await page.fill('[data-test="email-input"]', 'test@example.com')
Chaining Selectors
Combine selectors for precision:
await page.locator('div.modal').locator('button.submit').click()
await page.locator('role=dialog').locator('text=Confirm').click()
Selector Best Practices
Priority order (most stable to least stable):
data-testid attributes (most stable)
role= selectors (semantic, accessible)
text= selectors (readable, but text may change)
id attributes (stable if not dynamic)
- CSS classes (less stable, may change with styling)
- XPath (fragile, avoid if possible)
Wait Strategies
Load State Waits
Essential for dynamic applications:
await page.goto('http://localhost:3000')
await page.waitForLoadState('networkidle')
await page.waitForLoadState('domcontentloaded')
await page.waitForLoadState('load')
Element Waits
Wait for specific elements before interacting:
await page.waitForSelector('button.submit', { state: 'visible' })
await page.waitForSelector('.loading-spinner', { state: 'hidden' })
await page.waitForSelector('.modal', { state: 'attached' })
await page.waitForSelector('.error-message', { state: 'detached' })
Timeout Waits
Fixed time delays (use sparingly):
await page.waitForTimeout(500)
await page.waitForTimeout(2000)
Custom Wait Conditions
Wait for JavaScript conditions:
await page.waitForFunction('() => document.querySelector(".data").innerText !== "Loading..."')
await page.waitForFunction('() => window.appReady === true')
Auto-Waiting
Playwright automatically waits for elements to be actionable:
await page.click('button.submit')
await page.fill('input.email', 'test@example.com')
Element Interactions
Clicking
await page.click('button.submit')
await page.click('button.submit', { button: 'right' })
await page.click('button.submit', { clickCount: 2 })
await page.click('button.submit', { modifiers: ['Control'] })
await page.click('button.submit', { force: true })
Filling Forms
await page.fill('input[name="email"]', 'user@example.com')
await page.type('input[name="search"]', 'query', { delay: 100 })
await page.fill('input[name="email"]', '')
await page.fill('input[name="email"]', 'new@example.com')
await page.press('input[name="search"]', 'Enter')
await page.press('input[name="text"]', 'Control+A')
Dropdowns and Selects
await page.selectOption('select[name="country"]', { label: 'United States' })
await page.selectOption('select[name="country"]', { value: 'us' })
await page.selectOption('select[name="country"]', { index: 2 })
await page.selectOption('select[multiple]', ['option1', 'option2'])
Checkboxes and Radio Buttons
await page.check('input[type="checkbox"]')
await page.uncheck('input[type="checkbox"]')
await page.check('input[value="option1"]')
if (await page.isChecked('input[type="checkbox"]')) {
await page.uncheck('input[type="checkbox"]')
} else {
await page.check('input[type="checkbox"]')
}
File Uploads
await page.setInputFiles('input[type="file"]', '/path/to/file.pdf')
await page.setInputFiles('input[type="file"]', ['/path/to/file1.pdf', '/path/to/file2.pdf'])
await page.setInputFiles('input[type="file"]', [])
Hover and Focus
await page.hover('button.tooltip-trigger')
await page.focus('input[name="email"]')
await page.evaluate('document.activeElement.blur()')
Assertions
Element Visibility
import { expect } from '@playwright/test'
await expect(page.locator('button.submit')).toBeVisible()
await expect(page.locator('.error-message')).toBeHidden()
Text Content
await expect(page.locator('.title')).toHaveText('Welcome')
await expect(page.locator('.message')).toContainText('success')
await expect(page.locator('.code')).toHaveText(/\d{6}/)
Element State
await expect(page.locator('button.submit')).toBeEnabled()
await expect(page.locator('button.submit')).toBeDisabled()
await expect(page.locator('input[type="checkbox"]')).toBeChecked()
await expect(page.locator('input[name="email"]')).toBeEditable()
Attributes and Values
await expect(page.locator('img')).toHaveAttribute('src', '/logo.png')
await expect(page.locator('button')).toHaveClass('btn-primary')
await expect(page.locator('input[name="email"]')).toHaveValue('user@example.com')
Count and Collections
await expect(page.locator('li')).toHaveCount(5)
const items = await page.locator('li').all()
expect(items.length).toBe(5)
Test Organization
Basic Test Structure
import { test, expect } from '@playwright/test'
test('basic test example', async ({ page }) => {
await page.goto('http://localhost:3000')
await page.waitForLoadState('networkidle')
})
Using Playwright Test (Recommended)
import { test, expect } from '@playwright/test'
test('login test', async ({ page }) => {
await page.goto('http://localhost:3000')
await page.fill('input[name="email"]', 'user@example.com')
await page.fill('input[name="password"]', 'password123')
await page.click('button[type="submit"]')
await expect(page.locator('.welcome-message')).toBeVisible()
})
Test Grouping with Describe Blocks
import { test, expect } from '@playwright/test'
test.describe('Authentication', () => {
test('successful login', async ({ page }) => {
})
test('failed login', async ({ page }) => {
})
test('logout', async ({ page }) => {
})
})
Setup and Teardown
import { test, expect } from '@playwright/test'
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000')
await page.waitForLoadState('networkidle')
})
test.afterEach(async ({ page }) => {
await page.evaluate(() => localStorage.clear())
})
Network Interception
Mock API Responses
await page.route('**/api/data', async (route) => {
await route.fulfill({
status: 200,
body: JSON.stringify({ success: true, data: 'mocked' }),
headers: { 'Content-Type': 'application/json' },
})
})
await page.goto('http://localhost:3000')
Block Resources
await page.route('**/*.{png,jpg,jpeg,gif,svg,css}', (route) => route.abort())
Wait for Network Responses
const [response] = await Promise.all([
page.waitForResponse('**/api/users'),
page.click('button.load-users'),
])
expect(response.status()).toBe(200)
Screenshots and Videos
Screenshots
await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true })
await page.locator('.modal').screenshot({ path: '/tmp/modal.png' })
await page.setViewportSize({ width: 1920, height: 1080 })
await page.screenshot({ path: '/tmp/desktop.png' })
Video Recording
const context = await browser.newContext({
recordVideo: { dir: '/tmp/videos/' },
})
const page = await context.newPage()
await context.close()
Debugging
Pause Execution
await page.pause()
Console Logs
page.on('console', (msg) => {
console.log(`[${msg.type()}] ${msg.text()}`)
})
Slow Motion
const browser = await chromium.launch({ headless: false, slowMo: 1000 })
Verbose Logging
DEBUG=pw:api npx playwright test
Parallel Execution
Playwright Test Parallelism
import { defineConfig } from '@playwright/test'
export default defineConfig({
fullyParallel: true,
workers: process.env.CI ? 1 : undefined,
})
Browser Context Isolation
import { test as base } from '@playwright/test'
const test = base.extend<{ context: BrowserContext }>({
context: async ({ browser }, use) => {
const context = await browser.newContext()
await use(context)
await context.close()
},
})