Skip to main content 首页 创作者 diegosouzapw awesome-omni-skill qa-testing-playwright
qa-testing-playwright End-to-end web application testing with Playwright: scope control, stable selectors, parallelization/sharding, flake control, network mocking vs real services, visual testing tradeoffs, and CI/CD integration.
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/diegosouzapw/awesome-omni-skill --skill qa-testing-playwright命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... name qa-testing-playwright description End-to-end web application testing with Playwright: scope control, stable selectors, parallelization/sharding, flake control, network mocking vs real services, visual testing tradeoffs, and CI/CD integration.
QA Testing (Playwright, Dec 2025) — Quick Reference
This skill enables high-signal, cost-aware E2E testing of web applications using Playwright.
Core references: Playwright best practices (https://playwright.dev/docs/best-practices ), locators (https://playwright.dev/docs/locators ), retries (https://playwright.dev/docs/test-retries ), sharding (https://playwright.dev/docs/test-sharding ), and the trace viewer (https://playwright.dev/docs/trace-viewer ).
Core QA (Default)
Scope Control (What E2E Is For)
E2E exists to protect critical user journeys and high-risk integrations.
Keep E2E thin; push most coverage down to unit/integration/contract tests.
Test the contract and user intent, not CSS structure.
Selector Strategy (Stability First)
Prefer user-facing locators: getByRole, getByLabel, getByText (Playwright locators: https://playwright.dev/docs/locators ).
Use data-testid as a last resort for complex widgets or non-semantic UI.
Avoid XPath and brittle CSS selectors.
Flake Control (Make CI Reliable)
If clicks are intercepted by overlays, prefer keyboard activation or close the overlay rather than force: true.
CI Economics (Parallelize and Shard)
Do / Avoid
Make tests independent and deterministic (isolated state, stable data).
Use network mocking for third-party dependencies; test your integration contract, not their UI.
“Test everything E2E” as a default.
Weakening assertions to “fix” flakes (silent regressions).
Quick Reference Task Command When to Use Init Playwright npm init playwright@latestNew project setup Run all tests npx playwright testFull test suite Run with UI npx playwright test --uiDebugging, visual mode Run specific test npx playwright test login.spec.tsTargeted testing Generate code npx playwright codegenRecord interactions Show report npx playwright show-reportView test results
When to Use This Skill Claude should invoke this skill when a user requests:
Write E2E tests for web application
Test user authentication flows
Verify form submissions and validation
Test responsive designs across viewports
Automate browser interactions
Set up Playwright in CI/CD
Debug failing E2E tests
Quick Start
Installation
npm init playwright@latest
npm install -D @playwright/test
npx playwright install
Project Structure project/
├── playwright.config.ts
├── tests/
│ ├── auth.spec.ts
│ ├── checkout.spec.ts
│ └── fixtures/
│ └── auth.fixture.ts
├── pages/
│ ├── LoginPage.ts
│ └── DashboardPage.ts
└── .github/workflows/
└── playwright.yml
Core Testing Patterns
Browser Policy (Bundled vs Stable Channels)
Default: bundled browsers are designed for reliable automation.
When you must test against stable Chrome/Edge (policy/compliance, media codecs), use browser channels (see Playwright “Browsers”: https://playwright.dev/docs/browsers ).
Locator Priority (2025 Best Practice) Use role locators as the primary approach — they test from the user's perspective:
await page.getByRole ('textbox' , { name : 'Email' }).fill ('user@example.com' );
await page.getByRole ('button' , { name : 'Sign in' }).click ();
await expect (page.getByRole ('heading' , { name : 'Dashboard' })).toBeVisible ();
await page.getByLabel ('Email' ).fill ('user@example.com' );
await page.getByText ('Sign in' ).click ();
await page.getByTestId ('user-avatar' ).click ();
Common UI Flake Patterns
Overlay intercepts pointer events: use keyboard activation to follow the accessible path.
Menus: open via button role, select menuitem role to avoid hidden link clicks.
const menuButton = page.getByRole ('button' , { name : /open menu/i });
await menuButton.focus ();
await menuButton.press ('Enter' );
const menuItem = page.getByRole ('menuitem' , { name : /year ahead/i });
await menuItem.focus ();
await menuItem.press ('Enter' );
Basic Test Structure import { test, expect } from '@playwright/test' ;
test.describe ('User Authentication' , () => {
test ('should login with valid credentials' , async ({ page }) => {
await page.goto ('/login' );
await page.getByRole ('textbox' , { name : 'Email' }).fill ('user@example.com' );
await page.getByLabel ('Password' ).fill ('password123' );
await page.getByRole ('button' , { name : 'Sign in' }).click ();
await expect (page).toHaveURL ('/dashboard' );
await expect (page.getByRole ('heading' , { name : 'Dashboard' })).toBeVisible ();
});
test ('should show error for invalid credentials' , async ({ page }) => {
await page.goto ('/login' );
await page.getByRole ('textbox' , { name : 'Email' }).fill ('wrong@example.com' );
await page.getByLabel ('Password' ).fill ('wrongpassword' );
await page.getByRole ('button' , { name : 'Sign in' }).click ();
await expect (page.getByRole ('alert' )).toContainText ('Invalid' );
});
});
Page Object Model
import { Page , Locator , expect } from '@playwright/test' ;
export class LoginPage {
readonly page : Page ;
readonly emailInput : Locator ;
readonly passwordInput : Locator ;
readonly loginButton : Locator ;
readonly errorMessage : Locator ;
constructor (page : Page ) {
this .page = page;
this .emailInput = page.getByRole ('textbox' , { name : 'Email' });
this .passwordInput = page.getByLabel ('Password' );
this .loginButton = page.getByRole ('button' , { name : 'Sign in' });
this .errorMessage = page.getByRole ('alert' );
}
async goto ( ) {
await this .page .goto ('/login' );
}
async login (email : string , password : string ) {
await this .emailInput .fill (email);
await this .passwordInput .fill (password);
await this .loginButton .click ();
}
async expectError (message : string ) {
await expect (this .errorMessage ).toBeVisible ();
await expect (this .errorMessage ).toContainText (message);
}
}
import { test, expect } from '@playwright/test' ;
import { LoginPage } from '../pages/LoginPage' ;
test ('login with valid credentials' , async ({ page }) => {
const loginPage = new LoginPage (page);
await loginPage.goto ();
await loginPage.login ('user@example.com' , 'password123' );
await expect (page).toHaveURL ('/dashboard' );
});
Authentication Fixture
import { test as base, expect } from '@playwright/test' ;
type AuthFixtures = {
authenticatedPage : Page ;
};
export const test = base.extend <AuthFixtures >({
authenticatedPage : async ({ page }, use) => {
await page.goto ('/login' );
await page.getByRole ('textbox' , { name : 'Email' }).fill ('user@example.com' );
await page.getByLabel ('Password' ).fill ('password123' );
await page.getByRole ('button' , { name : 'Sign in' }).click ();
await expect (page).toHaveURL ('/dashboard' );
await use (page);
},
});
import { test } from '../fixtures/auth.fixture' ;
import { expect } from '@playwright/test' ;
test ('authenticated user can access dashboard' , async ({ authenticatedPage }) => {
await authenticatedPage.goto ('/dashboard' );
await expect (authenticatedPage.getByRole ('heading' , { name : 'Dashboard' })).toBeVisible ();
});
Common Scenarios
Form Testing test ('should validate form fields' , async ({ page }) => {
await page.goto ('/signup' );
await page.click ('[data-testid="submit"]' );
await expect (page.locator ('[data-testid="email-error"]' )).toHaveText ('Email is required' );
await expect (page.locator ('[data-testid="password-error"]' )).toHaveText ('Password is required' );
await page.fill ('[data-testid="email"]' , 'invalid-email' );
await page.click ('[data-testid="submit"]' );
await expect (page.locator ('[data-testid="email-error"]' )).toHaveText ('Invalid email format' );
});
API Mocking test ('should handle API errors gracefully' , async ({ page }) => {
await page.route ('**/api/users' , route => {
route.fulfill ({
status : 500 ,
body : JSON .stringify ({ error : 'Server error' }),
});
});
await page.goto ('/users' );
await expect (page.locator ('[data-testid="error-banner"]' )).toContainText ('Failed to load' );
});
test ('should display user data from API' , async ({ page }) => {
await page.route ('**/api/users' , route => {
route.fulfill ({
status : 200 ,
body : JSON .stringify ([
{ id : 1 , name : 'John Doe' },
{ id : 2 , name : 'Jane Smith' },
]),
});
});
await page.goto ('/users' );
await expect (page.locator ('[data-testid="user-list"] li' )).toHaveCount (2 );
});
Responsive Testing import { devices } from '@playwright/test' ;
test.describe ('Responsive Design' , () => {
test ('mobile navigation' , async ({ page }) => {
await page.setViewportSize ({ width : 375 , height : 667 });
await page.goto ('/' );
await expect (page.locator ('[data-testid="desktop-nav"]' )).toBeHidden ();
await expect (page.locator ('[data-testid="mobile-menu-button"]' )).toBeVisible ();
await page.click ('[data-testid="mobile-menu-button"]' );
await expect (page.locator ('[data-testid="mobile-nav"]' )).toBeVisible ();
});
});
export default defineConfig ({
projects : [
{ name : 'Desktop Chrome' , use : { ...devices['Desktop Chrome' ] } },
{ name : 'Mobile Safari' , use : { ...devices['iPhone 12' ] } },
{ name : 'Tablet' , use : { ...devices['iPad Pro' ] } },
],
});
Visual Regression test ('homepage visual regression' , async ({ page }) => {
await page.goto ('/' );
await expect (page).toHaveScreenshot ('homepage.png' , {
maxDiffPixels : 100 ,
});
});
test ('button states' , async ({ page }) => {
await page.goto ('/components' );
const button = page.locator ('[data-testid="primary-button"]' );
await expect (button).toHaveScreenshot ('button-default.png' );
await button.hover ();
await expect (button).toHaveScreenshot ('button-hover.png' );
await button.focus ();
await expect (button).toHaveScreenshot ('button-focus.png' );
});
Configuration
playwright.config.ts import { defineConfig, devices } from '@playwright/test' ;
export default defineConfig ({
testDir : './tests' ,
fullyParallel : true ,
forbidOnly : !!process.env .CI ,
retries : process.env .CI ? 2 : 0 ,
workers : process.env .CI ? 1 : undefined ,
reporter : [
['html' ],
['junit' , { outputFile : 'results.xml' }],
],
use : {
baseURL : 'http://localhost:3000' ,
trace : 'on-first-retry' ,
screenshot : 'only-on-failure' ,
video : 'retain-on-failure' ,
},
projects : [
{ name : 'chromium' , use : { ...devices['Desktop Chrome' ] } },
{ name : 'firefox' , use : { ...devices['Desktop Firefox' ] } },
{ name : 'webkit' , use : { ...devices['Desktop Safari' ] } },
],
webServer : {
command : 'npm run dev' ,
url : 'http://localhost:3000' ,
reuseExistingServer : !process.env .CI ,
},
});
CI/CD Integration
name: Playwright Tests
on:
push:
branches: [main ]
pull_request:
branches: [main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Debugging
npx playwright test --ui
npx playwright test --headed
npx playwright test --debug
npx playwright codegen http://localhost:3000
npx playwright show-trace trace.zip
Optional: AI / Automation
Use AI to scaffold tests and page objects, then enforce selector rules, remove sleeps, and add explicit oracles.
Use AI to summarize failing traces/logs, but base fixes on evidence and stable assertions.
Auto-healing by weakening assertions or switching to brittle selectors.
Generating tests that lock onto CSS structure instead of user-facing roles/labels.
Navigation