E2E测试构建器 - 使用Playwright或Cypress为关键用户流程构建端到端浏览器测试。包括选择器策略、测试数据管理、页面对象和视觉回归测试。适用于"E2E测试"、"浏览器测试"、"Playwright"或"Cypress测试"。
Scanned 9/12/2026
Install to Claude Code
npx -y skills add lza6/Claude-code-cli-config --skill e2e-test-builder --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of E2e Test Builder?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/lza6-e2e-test-builder)More formats (shields.io, HTML) on the badges page.
---
name: e2e-test-builder
description: E2E测试构建器 - 使用Playwright或Cypress为关键用户流程构建端到端浏览器测试。包括选择器策略、测试数据管理、页面对象和视觉回归测试。适用于"E2E测试"、"浏览器测试"、"Playwright"或"Cypress测试"。
---
# E2E 测试构建器
为关键用户流程构建可靠的端到端测试。
## Playwright 测试设置
```typescript
// playwright.config.ts
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: "./e2e",
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"] },
},
{
name: "Mobile Chrome",
use: { ...devices["Pixel 5"] },
},
],
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
```
## 关键流程测试
```typescript
// e2e/checkout-flow.spec.ts
import { test, expect } from "@playwright/test";
test.describe("结账流程", () => {
test.beforeEach(async ({ page }) => {
// 导航到首页
await page.goto("/");
// 登录
await page.getByRole("button", { name: "Login" }).click();
await page.getByLabel("Email").fill("test@example.com");
await page.getByLabel("Password").fill("password123");
await page.getByRole("button", { name: "Sign In" }).click();
// 等待仪表板
await expect(page).toHaveURL("/dashboard");
});
test("应成功完成结账", async ({ page }) => {
// 1. 浏览商品
await page.getByRole("link", { name: "Products" }).click();
await expect(page).toHaveURL("/products");
// 2. 添加商品到购物车
await page.getByRole("button", { name: "Add to Cart" }).first().click();
await expect(page.getByText("Added to cart")).toBeVisible();
// 3. 进入购物车
await page.getByRole("link", { name: "Cart" }).click();
await expect(page).toHaveURL("/cart");
await expect(
page.getByRole("heading", { name: "Shopping Cart" })
).toBeVisible();
// 4. 进入结账
await page.getByRole("button", { name: "Checkout" }).click();
await expect(page).toHaveURL("/checkout");
// 5. 填写配送信息
await page.getByLabel("Full Name").fill("张三");
await page.getByLabel("Address").fill("北京市朝阳区某某路123号");
await page.getByLabel("City").fill("北京");
await page.getByLabel("ZIP Code").fill("100001");
// 6. 填写支付信息
await page.getByLabel("Card Number").fill("4242424242424242");
await page.getByLabel("Expiry Date").fill("12/25");
await page.getByLabel("CVC").fill("123");
// 7. 提交订单
await page.getByRole("button", { name: "Place Order" }).click();
// 8. 验证成功
await expect(page).toHaveURL(/\/order\/\d+/);
await expect(page.getByText("Order confirmed!")).toBeVisible();
await expect(page.getByText(/Order #\d+/)).toBeVisible();
});
test("应显示空字段的验证错误", async ({ page }) => {
// 导航到结账页
await page.goto("/checkout");
// 尝试不填写字段就提交
await page.getByRole("button", { name: "Place Order" }).click();
// 验证错误提示
await expect(page.getByText("Name is required")).toBeVisible();
await expect(page.getByText("Address is required")).toBeVisible();
await expect(page.getByText("Card number is required")).toBeVisible();
});
test("应处理支付失败", async ({ page }) => {
// 添加商品并进入结账
await page.goto("/products");
await page.getByRole("button", { name: "Add to Cart" }).first().click();
await page.goto("/checkout");
// 填写会失败的卡号
await page.getByLabel("Card Number").fill("4000000000000002");
await page.getByLabel("Expiry Date").fill("12/25");
await page.getByLabel("CVC").fill("123");
// 提交
await page.getByRole("button", { name: "Place Order" }).click();
// 验证错误消息
await expect(page.getByText("Payment failed")).toBeVisible();
await expect(page.getByText("Please try a different card")).toBeVisible();
});
});
```
## 页面对象模式
```typescript
// e2e/pages/LoginPage.ts
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto("/login");
}
async login(email: string, password: string) {
await this.page.getByLabel("Email").fill(email);
await this.page.getByLabel("Password").fill(password);
await this.page.getByRole("button", { name: "Sign In" }).click();
}
async expectLoginSuccess() {
await expect(this.page).toHaveURL("/dashboard");
}
async expectLoginError(message: string) {
await expect(this.page.getByText(message)).toBeVisible();
}
}
// e2e/pages/ProductPage.ts
export class ProductPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto("/products");
}
async addToCart(productName: string) {
const product = this.page.locator(`[data-product="${productName}"]`);
await product.getByRole("button", { name: "Add to Cart" }).click();
}
async expectProductVisible(productName: string) {
await expect(
this.page.getByRole("heading", { name: productName })
).toBeVisible();
}
}
// 在测试中使用
test("应登录并添加商品", async ({ page }) => {
const loginPage = new LoginPage(page);
const productPage = new ProductPage(page);
await loginPage.goto();
await loginPage.login("test@example.com", "password123");
await loginPage.expectLoginSuccess();
await productPage.goto();
await productPage.addToCart("MacBook Pro");
});
```
## 选择器策略
```typescript
// 首选选择器优先级:
// 1. 基于角色(最有弹性)
await page.getByRole("button", { name: "Submit" });
await page.getByRole("link", { name: "Products" });
await page.getByRole("textbox", { name: "Email" });
// 2. 基于标签(语义化)
await page.getByLabel("Email address");
await page.getByLabel("Password");
// 3. 测试 ID(用于复杂情况)
await page.getByTestId("user-menu");
await page.getByTestId("product-card-123");
// 4. 文本内容(用于唯一文本)
await page.getByText("Welcome back!");
await page.getByText(/Order #\d+/);
// ❌ 避免:CSS 选择器(脆弱)
// await page.locator('.btn.btn-primary');
// await page.locator('#submit-button');
```
## 测试数据管理
```typescript
// e2e/fixtures/test-data.ts
export const testData = {
users: {
admin: {
email: "admin@example.com",
password: "admin123",
},
customer: {
email: "customer@example.com",
password: "customer123",
},
},
products: {
laptop: {
name: "MacBook Pro",
price: 2499.99,
},
phone: {
name: "iPhone 15",
price: 999.99,
},
},
cards: {
valid: "4242424242424242",
declined: "4000000000000002",
insufficientFunds: "4000000000009995",
},
};
// e2e/setup/seed-test-data.ts
export async function seedTestData() {
const prisma = new PrismaClient();
// 创建测试用户
await prisma.user.upsert({
where: { email: testData.users.customer.email },
create: {
email: testData.users.customer.email,
password: await hash(testData.users.customer.password),
},
update: {},
});
// 创建测试商品
await prisma.product.upsert({
where: { name: testData.products.laptop.name },
create: testData.products.laptop,
update: {},
});
await prisma.$disconnect();
}
```
## 视觉回归测试
```typescript
// e2e/visual/homepage.spec.ts
test("首页应与截图匹配", async ({ page }) => {
await page.goto("/");
// 截取全页面截图
await expect(page).toHaveScreenshot("homepage.png", {
fullPage: true,
maxDiffPixels: 100, // 允许微小差异
});
});
test("商品卡片应与截图匹配", async ({ page }) => {
await page.goto("/products");
const productCard = page.locator('[data-testid="product-card"]').first();
// 截取元素截图
await expect(productCard).toHaveScreenshot("product-card.png");
});
```
## 移动端测试
```typescript
// e2e/mobile/checkout-mobile.spec.ts
test.use({ viewport: { width: 375, height: 667 } }); // iPhone SE
test("应完成移动端结账", async ({ page }) => {
await page.goto("/");
// 打开移动菜单
await page.getByRole("button", { name: "Menu" }).click();
await page.getByRole("link", { name: "Products" }).click();
// 添加到购物车
await page.getByRole("button", { name: "Add to Cart" }).first().click();
// 继续结账
// ...
});
```
## 网络模拟
```typescript
// e2e/mocked/payment-api.spec.ts
test("应处理支付 API 超时", async ({ page }) => {
// 模拟慢速支付 API
await page.route("**/api/payment", async (route) => {
await new Promise((resolve) => setTimeout(resolve, 5000));
await route.fulfill({
status: 200,
body: JSON.stringify({ success: true }),
});
});
// 继续结账
await page.goto("/checkout");
// ... 填写表单 ...
await page.getByRole("button", { name: "Place Order" }).click();
// 应显示加载状态
await expect(page.getByText("Processing payment...")).toBeVisible();
});
```
## 最佳实践
1. **测试用户流程**:而不是单个组件
2. **使用基于角色的选择器**:更有弹性
3. **页面对象**:可复用且可维护
4. **等待元素**:不要使用固定延迟
5. **测试关键路径**:登录、结账、注册
6. **管理测试数据**:每个测试隔离
7. **视觉回归**:仅关键页面
## 输出检查清单
- [ ] Playwright/Cypress 已配置
- [ ] 关键流程已识别
- [ ] 页面对象已创建
- [ ] 选择器策略已定义(基于角色)
- [ ] 测试数据管理已设置
- [ ] 设置/清理钩子已配置
- [ ] 身份验证流程已测试
- [ ] 错误状态已测试
- [ ] 移动端视口测试已添加
- [ ] CI 集成已配置
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!