| name | e2e-bugfix |
| description | This skill should be used when the user asks to "debug E2E tests", "fix Playwright failures", "fix Cypress tests", "analyze timeout errors", or mentions keywords like "Playwright", "Cypress", "Timeout exceeded", "locator", "selector", "flaky test". It provides the complete bugfix workflow knowledge including error classification, confidence scoring, and E2E-specific debugging techniques.
|
| version | 2.1.0 |
E2E Bugfix Workflow Skill
本 skill 提供端到端测试 bugfix 的完整工作流知识,包括错误分类体系、置信度评分系统和 E2E 特有的调试技巧。
错误分类体系
E2E 测试失败主要分为以下类型(按频率排序):
1. 超时错误(35%)
症状:元素等待超时、操作超时
识别特征:
Timeout 30000ms exceeded
waiting for locator
waiting for element
TimeoutError
解决策略:使用显式等待和合理超时
await page.waitForTimeout(5000);
await page.click('.submit-button');
await page.waitForSelector('.submit-button', { state: 'visible' });
await page.click('.submit-button');
await page.getByRole('button', { name: 'Submit' }).click();
常见原因:
- 页面加载慢
- 动态内容未渲染
- 网络请求延迟
- 元素被遮挡或不可见
2. 选择器错误(25%)
症状:找不到元素、选择器匹配多个元素
识别特征:
strict mode violation
resolved to X elements
element not found
locator.click: Error
解决策略:使用更精确的选择器
await page.click('button');
await page.click('[data-testid="submit-button"]');
await page.getByRole('button', { name: 'Submit' }).click();
await page.locator('.form-container').getByRole('button').click();
Playwright 推荐选择器优先级:
getByRole() - 最语义化
getByTestId() - 最稳定
getByText() - 用户可见
- CSS/XPath - 最后手段
3. 断言错误(15%)
症状:期望值与实际值不匹配
识别特征:
expect(...).toHave*
Expected: vs Received:
AssertionError
解决策略:使用正确的断言和等待
expect(await page.textContent('.message')).toBe('Success');
await expect(page.locator('.message')).toHaveText('Success');
await expect(page.locator('.user-list')).toContainText('John');
await expect(page.locator('.modal')).toBeVisible();
4. 网络错误(12%)
症状:API 请求失败、网络拦截问题
识别特征:
Route handler 错误
net::ERR_*
request failed
- Mock 数据不生效
解决策略:正确配置网络拦截
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ users: [{ id: 1, name: 'Test' }] }),
});
});
const responsePromise = page.waitForResponse('**/api/users');
await page.click('.load-users');
const response = await responsePromise;
expect(response.status()).toBe(200);
5. 导航错误(8%)
症状:页面导航失败、URL 不匹配
识别特征:
page.goto: Error
ERR_NAME_NOT_RESOLVED
navigation timeout
- URL 重定向问题
解决策略:正确处理导航
await page.goto('http://localhost:3000/login');
await page.waitForURL('**/dashboard');
await Promise.all([
page.waitForNavigation(),
page.click('.login-button'),
]);
await expect(page).toHaveURL(/.*dashboard/);
6. 环境错误(3%)
症状:浏览器启动失败、测试环境问题
识别特征:
browser.launch 失败
Target closed
context 错误
- 端口冲突
解决策略:检查环境配置
export default defineConfig({
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
});
置信度评分系统
评分标准(0-100)
| 分数 | 级别 | 行为 |
|---|
| 80+ | 高 | 自动执行 |
| 60-79 | 中 | 标记验证后继续 |
| 40-59 | 低 | 暂停询问用户 |
| <40 | 不确定 | 停止收集信息 |
置信度计算
置信度 = 证据质量(40%) + 模式匹配(30%) + 上下文完整性(20%) + 可复现性(10%)
证据质量:
- 高:有截图、trace、完整堆栈
- 中:有错误信息但缺上下文
- 低:仅有失败描述
模式匹配:
- 高:完全匹配已知错误模式
- 中:部分匹配
- 低:未知错误类型
上下文完整性:
- 高:测试代码 + 页面代码 + trace + 截图
- 中:只有测试代码
- 低:只有错误信息
可复现性:
- 高:每次运行都复现
- 中:偶发(flaky test)
- 低:仅在特定环境失败
E2E 调试技巧
使用 Trace Viewer
npx playwright test --trace on
npx playwright show-trace trace.zip
使用 UI 模式调试
npx playwright test --ui
npx playwright test --debug
截图和录像
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'passed') {
await page.screenshot({ path: `screenshots/${testInfo.title}.png` });
}
});
use: {
video: 'on-first-retry',
}
处理 Flaky Tests
test.describe.configure({ retries: 2 });
export default defineConfig({
retries: process.env.CI ? 2 : 0,
});
TDD 流程
RED Phase(写失败测试)
import { test, expect } from '@playwright/test';
test('should display error message on invalid login', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'invalid@email');
await page.fill('[data-testid="password"]', 'wrong');
await page.click('[data-testid="submit"]');
await expect(page.locator('.error-message')).toHaveText('Invalid credentials');
});
GREEN Phase(最小实现)
REFACTOR Phase(重构)
Page Object 模式
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.page.fill('[data-testid="email"]', email);
await this.page.fill('[data-testid="password"]', password);
await this.page.click('[data-testid="submit"]');
}
async getErrorMessage() {
return this.page.locator('.error-message');
}
}
test('login with invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('invalid@email', 'wrong');
await expect(loginPage.getErrorMessage()).toHaveText('Invalid credentials');
});
质量门禁
| 检查项 | 标准 |
|---|
| 测试通过率 | 100% |
| 代码覆盖率 | >= 90%(如适用) |
| Lint | 无错误 |
| Flaky Rate | < 5% |
常用命令
make test TARGET=e2e
npx playwright test
npx playwright test tests/login.spec.ts
npx playwright test --grep @smoke
npx playwright test --ui
npx playwright codegen localhost:3000
npx playwright show-report
Playwright 配置示例
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',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-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 start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
相关文档
文档路径由配置指定(best_practices_dir),使用以下关键词搜索:
- 选择器策略:关键词 "selector", "locator", "data-testid"
- 等待策略:关键词 "wait", "timeout", "retry"
- 网络拦截:关键词 "intercept", "mock", "route"
- 问题诊断:关键词 "troubleshooting", "debugging", "flaky"