Tool/everything-claude-code/docs/zh-CN/skills/e2e-testing/SKILL.md
Playwright E2E 测试模式、页面对象模型、配置、CI/CD 集成、工件管理和不稳定测试策略。
npx skillsauth add lyxjack/toolbox e2e-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
用于构建稳定、快速且可维护的 E2E 测试套件的全面 Playwright 模式。
tests/
├── e2e/
│ ├── auth/
│ │ ├── login.spec.ts
│ │ ├── logout.spec.ts
│ │ └── register.spec.ts
│ ├── features/
│ │ ├── browse.spec.ts
│ │ ├── search.spec.ts
│ │ └── create.spec.ts
│ └── api/
│ └── endpoints.spec.ts
├── fixtures/
│ ├── auth.ts
│ └── data.ts
└── playwright.config.ts
import { Page, Locator } from '@playwright/test'
export class ItemsPage {
readonly page: Page
readonly searchInput: Locator
readonly itemCards: Locator
readonly createButton: Locator
constructor(page: Page) {
this.page = page
this.searchInput = page.locator('[data-testid="search-input"]')
this.itemCards = page.locator('[data-testid="item-card"]')
this.createButton = page.locator('[data-testid="create-btn"]')
}
async goto() {
await this.page.goto('/items')
await this.page.waitForLoadState('networkidle')
}
async search(query: string) {
await this.searchInput.fill(query)
await this.page.waitForResponse(resp => resp.url().includes('/api/search'))
await this.page.waitForLoadState('networkidle')
}
async getItemCount() {
return await this.itemCards.count()
}
}
import { test, expect } from '@playwright/test'
import { ItemsPage } from '../../pages/ItemsPage'
test.describe('Item Search', () => {
let itemsPage: ItemsPage
test.beforeEach(async ({ page }) => {
itemsPage = new ItemsPage(page)
await itemsPage.goto()
})
test('should search by keyword', async ({ page }) => {
await itemsPage.search('test')
const count = await itemsPage.getItemCount()
expect(count).toBeGreaterThan(0)
await expect(itemsPage.itemCards.first()).toContainText(/test/i)
await page.screenshot({ path: 'artifacts/search-results.png' })
})
test('should handle no results', async ({ page }) => {
await itemsPage.search('xyznonexistent123')
await expect(page.locator('[data-testid="no-results"]')).toBeVisible()
expect(await itemsPage.getItemCount()).toBe(0)
})
})
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', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'playwright-results.xml' }],
['json', { outputFile: 'playwright-results.json' }]
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10000,
navigationTimeout: 30000,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
})
test('flaky: complex search', async ({ page }) => {
test.fixme(true, 'Flaky - Issue #123')
// test code...
})
test('conditional skip', async ({ page }) => {
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
// test code...
})
npx playwright test tests/search.spec.ts --repeat-each=10
npx playwright test tests/search.spec.ts --retries=3
竞态条件:
// Bad: assumes element is ready
await page.click('[data-testid="button"]')
// Good: auto-wait locator
await page.locator('[data-testid="button"]').click()
网络时序:
// Bad: arbitrary timeout
await page.waitForTimeout(5000)
// Good: wait for specific condition
await page.waitForResponse(resp => resp.url().includes('/api/data'))
动画时序:
// Bad: click during animation
await page.click('[data-testid="menu-item"]')
// Good: wait for stability
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
await page.waitForLoadState('networkidle')
await page.locator('[data-testid="menu-item"]').click()
await page.screenshot({ path: 'artifacts/after-login.png' })
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true })
await page.locator('[data-testid="chart"]').screenshot({ path: 'artifacts/chart.png' })
await browser.startTracing(page, {
path: 'artifacts/trace.json',
screenshots: true,
snapshots: true,
})
// ... test actions ...
await browser.stopTracing()
// In playwright.config.ts
use: {
video: 'retain-on-failure',
videosPath: 'artifacts/videos/'
}
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
env:
BASE_URL: ${{ vars.STAGING_URL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
# E2E 测试报告
**日期:** YYYY-MM-DD HH:MM
**持续时间:** Xm Ys
**状态:** 通过 / 失败
## 概要
- 总计:X | 通过:Y (Z%) | 失败:A | 不稳定:B | 跳过:C
## 失败的测试
### test-name
**文件:** `tests/e2e/feature.spec.ts:45`
**错误:** 期望元素可见
**截图:** artifacts/failed.png
**建议修复:** [description]
## 产物
- HTML 报告:playwright-report/index.html
- 截图:artifacts/*.png
- 视频:artifacts/videos/*.webm
- 追踪文件:artifacts/*.zip
test('wallet connection', async ({ page, context }) => {
// Mock wallet provider
await context.addInitScript(() => {
window.ethereum = {
isMetaMask: true,
request: async ({ method }) => {
if (method === 'eth_requestAccounts')
return ['0x1234567890123456789012345678901234567890']
if (method === 'eth_chainId') return '0x1'
}
}
})
await page.goto('/')
await page.locator('[data-testid="connect-wallet"]').click()
await expect(page.locator('[data-testid="wallet-address"]')).toContainText('0x1234')
})
test('trade execution', async ({ page }) => {
// Skip on production — real money
test.skip(process.env.NODE_ENV === 'production', 'Skip on production')
await page.goto('/markets/test-market')
await page.locator('[data-testid="position-yes"]').click()
await page.locator('[data-testid="trade-amount"]').fill('1.0')
// Verify preview
const preview = page.locator('[data-testid="trade-preview"]')
await expect(preview).toContainText('1.0')
// Confirm and wait for blockchain
await page.locator('[data-testid="confirm-trade"]').click()
await page.waitForResponse(
resp => resp.url().includes('/api/trade') && resp.status() === 200,
{ timeout: 30000 }
)
await expect(page.locator('[data-testid="trade-success"]')).toBeVisible()
})
development
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
development
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
data-ai
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
development
X/Twitter API integration for posting tweets, threads, reading timelines, search, and analytics. Covers OAuth auth patterns, rate limits, and platform-native content posting. Use when the user wants to interact with X programmatically.