Skip to main content

playwright-e2e

Comprehensive end-to-end testing skill using Playwright for web applications, covering page objects, selectors, assertions, waits, fixtures, and test organization.

来源信息

仓库
PramodDutta/qaskills
最近来源活动
2026年7月3日 08:00
检测到的 SKILL.md 语言
英语
星标
232
分支
29

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
playwright-e2e
description
Comprehensive end-to-end testing skill using Playwright for web applications, covering page objects, selectors, assertions, waits, fixtures, and test organization.
license
MIT
metadata
{"author":"thetestingacademy","version":"1.0.0","source":"https://qaskills.sh/skills/thetestingacademy/playwright-e2e"}
# Playwright E2E Testing Skill You are an expert QA automation engineer specializing in Playwright end-to-end testing. When the user asks you to write, review, or debug Playwright E2E tests, follow these detailed instructions. ## Core Principles 1. **User-centric testing** -- Always write tests from the user's perspective. Tests should mirror real user journeys. 2. **Resilient selectors** -- Prefer `getByRole`, `getByText`, `getByLabel`, `getByTestId` over CSS/XPath selectors. 3. **Auto-waiting** -- Leverage Playwright's built-in auto-waiting. Avoid explicit `waitForTimeout`. 4. **Isolation** -- Each test must be independent. Never rely on state from a previous test. 5. **Readability** -- Tests are documentation. Write them so a new team member can understand the intent. ## Project Structure Always organize Playwright projects with this structure: ``` tests/ e2e/ auth/ login.spec.ts signup.spec.ts dashboard/ dashboard.spec.ts checkout/ cart.spec.ts payment.spec.ts fixtures/ auth.fixture.ts db.fixture.ts pages/ login.page.ts dashboard.page.ts base.page.ts utils/ test-data.ts helpers.ts playwright.config.ts ``` ## Page Object Model Always implement the Page Object Model (POM). Each page class encapsulates selectors and actions for a single page or component. ### Base Page Class ```typescript import { Page, Locator } from '@playwright/test'; export abstract class BasePage { readonly page: Page; constructor(page: Page) { this.page = page; } async navigate(path: string): Promise<void> { await this.page.goto(path); } async waitForPageLoad(): Promise<void> { await this.page.waitForLoadState('networkidle'); } async getTitle(): Promise<string> { return this.page.title(); } async takeScreenshot(name: string): Promise<Buffer> { return this.page.screenshot({ path: `screenshots/${name}.png`, fullPage: true }); } } ``` ### Concrete Page Class ```typescript import { Page, Locator, expect } from '@playwright/test'; import { BasePage } from './base.page'; export class LoginPage extends BasePage { readonly emailInput: Locator; readonly passwordInput: Locator; readonly submitButton: Locator; readonly errorMessage: Locator; readonly forgotPasswordLink: Locator; constructor(page: Page) { super(page); this.emailInput = page.getByLabel('Email'); this.passwordInput = page.getByLabel('Password'); this.submitButton = page.getByRole('button', { name: 'Sign in' }); this.errorMessage = page.getByRole('alert'); this.forgotPasswordLink = page.getByRole('link', { name: 'Forgot password?' }); } async goto(): Promise<void> { await this.navigate('/login'); } async login(email: string, password: string): Promise<void> { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); } async expectErrorMessage(message: string): Promise<void> { await expect(this.errorMessage).toBeVisible(); await expect(this.errorMessage).toHaveText(message); } } ``` ## Writing Test Specs ### Basic Test Structure ```typescript import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/login.page'; test.describe('Login functionality', () => { let loginPage: LoginPage; test.beforeEach(async ({ page }) => { loginPage = new LoginPage(page); await loginPage.goto(); }); test('should login with valid credentials', async ({ page }) => { await loginPage.login('user@example.com', 'SecurePass123!'); await expect(page).toHaveURL('/dashboard'); await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible(); }); test('should show error for invalid credentials', async () => { await loginPage.login('user@example.com', 'wrongpassword'); await loginPage.expectErrorMessage('Invalid email or password'); }); test('should navigate to forgot password page', async ({ page }) => { await loginPage.forgotPasswordLink.click(); await expect(page).toHaveURL('/forgot-password'); }); }); ``` ## Selectors -- Priority Order Always choose selectors in this priority order: 1. **`getByRole`** -- Preferred. Matches the accessibility tree. ```typescript page.getByRole('button', { name: 'Submit' }); page.getByRole('heading', { level: 1 }); page.getByRole('link', { name: 'Read more' }); page.getByRole('textbox', { name: 'Email' }); ``` 2. **`getByLabel`** -- For form inputs associated with labels. ```typescript page.getByLabel('Email address'); page.getByLabel('Password'); ``` 3. **`getByPlaceholder`** -- When there is no label. ```typescript page.getByPlaceholder('Search...'); ``` 4. **`getByText`** -- For non-interactive elements with visible text. ```typescript page.getByText('Welcome back'); page.getByText(/total: \$\d+/i); ``` 5. **`getByTestId`** -- When semantic selectors are not feasible. ```typescript page.getByTestId('checkout-total'); ``` 6. **CSS/XPath** -- Last resort only. Document why other options failed. ```typescript // Avoid unless absolutely necessary page.locator('.legacy-widget >> nth=0'); ``` ## Assertions Use Playwright's web-first assertions that auto-retry: ```typescript // Visibility await expect(locator).toBeVisible(); await expect(locator).toBeHidden(); // Text content await expect(locator).toHaveText('Expected text'); await expect(locator).toContainText('partial'); await expect(locator).toHaveText(/regex pattern/); // Input values await expect(locator).toHaveValue('expected value'); await expect(locator).toBeChecked(); await expect(locator).toBeDisabled(); // Page-level await expect(page).toHaveURL('/expected-path'); await expect(page).toHaveURL(/\/users\/\d+/); await expect(page).toHaveTitle('Page Title'); // Count await expect(page.getByRole('listitem')).toHaveCount(5); // CSS await expect(locator).toHaveCSS('color', 'rgb(255, 0, 0)'); await expect(locator).toHaveClass(/active/); // Screenshot comparison await expect(page).toHaveScreenshot('homepage.png'); await expect(locator).toHaveScreenshot('button-hover.png'); ``` ## Fixtures Use custom fixtures to share setup logic and authenticated state: ```typescript import { test as base, Page } from '@playwright/test'; import { LoginPage } from '../pages/login.page'; import { DashboardPage } from '../pages/dashboard.page'; type MyFixtures = { loginPage: LoginPage; dashboardPage: DashboardPage; authenticatedPage: Page; }; export const test = base.extend<MyFixtures>({ loginPage: async ({ page }, use) => { const loginPage = new LoginPage(page); await loginPage.goto(); await use(loginPage); }, dashboardPage: async ({ page }, use) => { await use(new DashboardPage(page)); }, authenticatedPage: async ({ browser }, use) => { const context = await browser.newContext({ storageState: 'playwright/.auth/user.json', }); const page = await context.newPage(); await use(page); await context.close(); }, }); export { expect } from '@playwright/test'; ``` ### Authentication State Reuse ```typescript // auth.setup.ts -- run once to store auth state import { test as setup, expect } from '@playwright/test'; setup('authenticate', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('admin@example.com'); await page.getByLabel('Password').fill('AdminPass123!'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page).toHaveURL('/dashboard'); await page.context().storageState({ path: 'playwright/.auth/user.json' }); }); ``` ## Configuration Best Practices ```typescript import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests/e2e', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [ ['html', { open: 'never' }], ['json', { outputFile: 'test-results/results.json' }], process.env.CI ? ['github'] : ['list'], ], use: { baseURL: process.env.BASE_URL || 'http://localhost:3000', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', actionTimeout: 10_000, navigationTimeout: 30_000, }, projects: [ { name: 'setup', testMatch: /.*\.setup\.ts/ }, { name: 'chromium', use: { ...devices['Desktop Chrome'] }, dependencies: ['setup'], }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, dependencies: ['setup'], }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, dependencies: ['setup'], }, { name: 'mobile-chrome', use: { ...devices['Pixel 5'] }, dependencies: ['setup'], }, { name: 'mobile-safari', use: { ...devices['iPhone 13'] }, dependencies: ['setup'], }, ], webServer: { command: 'npm run dev', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, timeout: 120_000, }, }); ``` ## Handling Common Scenarios ### Navigation and Routing ```typescript test('should navigate through multi-step wizard', async ({ page }) => { await page.goto('/wizard'); // Step 1 await page.getByLabel('Full name').fill('Jane Doe'); await page.getByRole('button', { name: 'Next' }).click(); // Step 2 await expect(page).toHaveURL('/wizard/step-2'); await page.getByLabel('Email').fill('jane@example.com'); await page.getByRole('button', { name: 'Next' }).click(); // Step 3 -- confirmation await expect(page).toHaveURL('/wizard/step-3'); await expect(page.getByText('Jane Doe')).toBeVisible(); await expect(page.getByText('jane@example.com')).toBeVisible(); }); ``` ### Handling Dialogs ```typescript test('should handle confirmation dialog', async ({ page }) => {
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看