一键导入
webapp-testing
Comprehensive web application testing patterns with Playwright TypeScript, selectors, wait strategies, and best practices
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Comprehensive web application testing patterns with Playwright TypeScript, selectors, wait strategies, and best practices
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Yoizen UI design system standards. Trigger: Yoizen UI components, styling, colors, typography, visual polish or correction of any Yoizen Angular frontend.
Test-driven development loop — drive features through tests one vertical slice at a time (red → green → refactor). Use when the user asks for test-first development, mentions TDD or red-green-refactor, wants to build a feature through tests, or the orchestrator runs the TDD flow.
Trigger: Azure DevOps PRs, work items, profiles; review/vote/comment a PR; list/create/update work items. Drive Azure DevOps via the `ado` CLI instead of plugin tools.
Author and harden Dockerfiles for NestJS/Node, .NET, and Angular/nginx services on AKS. Use when creating a Dockerfile, reviewing/auditing/hardening one, shrinking image size or attack surface, or fixing container findings — root user, leaked secret, writable code, vuln scan. Reaches the .dockerignore, compose, and the entrypoints/configs baked into the image.
Teach the user a new skill or concept, within this workspace.
Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
| 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."}} |
Complete guide to Playwright automation patterns, selectors, and best practices using TypeScript.
Most readable and maintainable approach when text is unique:
await page.click('text=Login')
await page.click('text="Sign Up"') // Exact match
await page.click('text=/log.*in/i') // Regex, case-insensitive
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"]')
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')
For complex DOM navigation:
await page.click('xpath=//button[contains(text(), "Submit")]')
await page.click('xpath=//div[@class="modal"]//button[@type="submit"]')
Best practice for test-specific selectors:
await page.click('[data-testid="submit-btn"]')
await page.fill('[data-test="email-input"]', 'test@example.com')
Combine selectors for precision:
await page.locator('div.modal').locator('button.submit').click()
await page.locator('role=dialog').locator('text=Confirm').click()
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)Essential for dynamic applications:
// Wait for network to be idle (most common)
await page.goto('http://localhost:3000')
await page.waitForLoadState('networkidle')
// Wait for DOM to be ready
await page.waitForLoadState('domcontentloaded')
// Wait for full load including images
await page.waitForLoadState('load')
Wait for specific elements before interacting:
// Wait for element to be visible
await page.waitForSelector('button.submit', { state: 'visible' })
// Wait for element to be hidden
await page.waitForSelector('.loading-spinner', { state: 'hidden' })
// Wait for element to exist in DOM (may not be visible)
await page.waitForSelector('.modal', { state: 'attached' })
// Wait for element to be removed from DOM
await page.waitForSelector('.error-message', { state: 'detached' })
Fixed time delays (use sparingly):
// Wait for animations to complete
await page.waitForTimeout(500)
// Wait for delayed content (better to use waitForSelector)
await page.waitForTimeout(2000)
Wait for JavaScript conditions:
// Wait for custom JavaScript condition
await page.waitForFunction('() => document.querySelector(".data").innerText !== "Loading..."')
// Wait for variable to be set
await page.waitForFunction('() => window.appReady === true')
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
await page.click('button.submit') // Auto-waits
await page.fill('input.email', 'test@example.com') // Auto-waits
// Basic click
await page.click('button.submit')
// Click with options
await page.click('button.submit', { button: 'right' }) // Right-click
await page.click('button.submit', { clickCount: 2 }) // Double-click
await page.click('button.submit', { modifiers: ['Control'] }) // Ctrl+click
// Force click (bypass actionability checks)
await page.click('button.submit', { force: true })
// Text inputs
await page.fill('input[name="email"]', 'user@example.com')
await page.type('input[name="search"]', 'query', { delay: 100 }) // Type with delay
// Clear then fill
await page.fill('input[name="email"]', '')
await page.fill('input[name="email"]', 'new@example.com')
// Press keys
await page.press('input[name="search"]', 'Enter')
await page.press('input[name="text"]', 'Control+A')
// Select by label
await page.selectOption('select[name="country"]', { label: 'United States' })
// Select by value
await page.selectOption('select[name="country"]', { value: 'us' })
// Select by index
await page.selectOption('select[name="country"]', { index: 2 })
// Select multiple options
await page.selectOption('select[multiple]', ['option1', 'option2'])
// Check a checkbox
await page.check('input[type="checkbox"]')
// Uncheck a checkbox
await page.uncheck('input[type="checkbox"]')
// Check a radio button
await page.check('input[value="option1"]')
// Toggle checkbox
if (await page.isChecked('input[type="checkbox"]')) {
await page.uncheck('input[type="checkbox"]')
} else {
await page.check('input[type="checkbox"]')
}
// Upload single file
await page.setInputFiles('input[type="file"]', '/path/to/file.pdf')
// Upload multiple files
await page.setInputFiles('input[type="file"]', ['/path/to/file1.pdf', '/path/to/file2.pdf'])
// Clear file input
await page.setInputFiles('input[type="file"]', [])
// Hover over element
await page.hover('button.tooltip-trigger')
// Focus element
await page.focus('input[name="email"]')
// Blur element
await page.evaluate('document.activeElement.blur()')
import { expect } from '@playwright/test'
// Expect element to be visible
await expect(page.locator('button.submit')).toBeVisible()
// Expect element to be hidden
await expect(page.locator('.error-message')).toBeHidden()
// Expect exact text
await expect(page.locator('.title')).toHaveText('Welcome')
// Expect partial text
await expect(page.locator('.message')).toContainText('success')
// Expect text matching pattern
await expect(page.locator('.code')).toHaveText(/\d{6}/)
// Expect element to be enabled/disabled
await expect(page.locator('button.submit')).toBeEnabled()
await expect(page.locator('button.submit')).toBeDisabled()
// Expect checkbox to be checked
await expect(page.locator('input[type="checkbox"]')).toBeChecked()
// Expect element to be editable
await expect(page.locator('input[name="email"]')).toBeEditable()
// Expect attribute value
await expect(page.locator('img')).toHaveAttribute('src', '/logo.png')
// Expect CSS class
await expect(page.locator('button')).toHaveClass('btn-primary')
// Expect input value
await expect(page.locator('input[name="email"]')).toHaveValue('user@example.com')
// Expect specific count
await expect(page.locator('li')).toHaveCount(5)
// Get all elements and assert
const items = await page.locator('li').all()
expect(items.length).toBe(5)
import { test, expect } from '@playwright/test'
test('basic test example', async ({ page }) => {
// Test logic here
await page.goto('http://localhost:3000')
await page.waitForLoadState('networkidle')
})
import { test, expect } from '@playwright/test'
// Playwright Test provides built-in fixtures: page, browser, context
// No manual setup needed — test isolation is automatic
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()
})
import { test, expect } from '@playwright/test'
test.describe('Authentication', () => {
test('successful login', async ({ page }) => {
// Test successful login
})
test('failed login', async ({ page }) => {
// Test failed login
})
test('logout', async ({ page }) => {
// Test logout
})
})
import { test, expect } from '@playwright/test'
test.beforeEach(async ({ page }) => {
// Setup - runs before each test
await page.goto('http://localhost:3000')
await page.waitForLoadState('networkidle')
})
test.afterEach(async ({ page }) => {
// Teardown - runs after each test
await page.evaluate(() => localStorage.clear())
})
// Intercept and mock API response
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 images and stylesheets for faster tests
await page.route('**/*.{png,jpg,jpeg,gif,svg,css}', (route) => route.abort())
// Wait for specific API call
const [response] = await Promise.all([
page.waitForResponse('**/api/users'),
page.click('button.load-users'),
])
expect(response.status()).toBe(200)
// Full page screenshot
await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true })
// Element screenshot
await page.locator('.modal').screenshot({ path: '/tmp/modal.png' })
// Screenshot with custom dimensions
await page.setViewportSize({ width: 1920, height: 1080 })
await page.screenshot({ path: '/tmp/desktop.png' })
// In playwright.config.ts:
// use: {
// video: 'on-first-retry', // or 'on', 'retain-on-failure'
// }
// Or per test context:
const context = await browser.newContext({
recordVideo: { dir: '/tmp/videos/' },
})
const page = await context.newPage()
// Perform actions...
await context.close() // Video saved on close
await page.pause() // Opens Playwright Inspector
page.on('console', (msg) => {
console.log(`[${msg.type()}] ${msg.text()}`)
})
// In playwright.config.ts:
// use: {
// launchOptions: {
// slowMo: 1000,
// },
// }
// Or inline:
const browser = await chromium.launch({ headless: false, slowMo: 1000 })
# Set DEBUG environment variable
DEBUG=pw:api npx playwright test
// playwright.config.ts
import { defineConfig } from '@playwright/test'
export default defineConfig({
fullyParallel: true,
workers: process.env.CI ? 1 : undefined,
})
// Run tests in parallel (default)
// npx playwright test
// Run with specific number of workers
// npx playwright test --workers=4
// Each test gets an isolated context (cookies, localStorage, etc.)
// Playwright Test handles this automatically via the `page` fixture.
// For manual 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()
},
})