| name | e2e-test-builder |
| description | E2E测试构建器 - 使用Playwright或Cypress为关键用户流程构建端到端浏览器测试。包括选择器策略、测试数据管理、页面对象和视觉回归测试。适用于"E2E测试"、"浏览器测试"、"Playwright"或"Cypress测试"。 |
E2E 测试构建器
为关键用户流程构建可靠的端到端测试。
Playwright 测试设置
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,
},
});
关键流程测试
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 }) => {
await page.getByRole("link", { name: "Products" }).click();
(page).();
page.(, { : }).().();
(page.()).();
page.(, { : }).();
(page).();
(
page.(, { : })
).();
page.(, { : }).();
(page).();
page.().();
page.().();
page.().();
page.().();
page.().();
page.().();
page.().();
page.(, { : }).();
(page).();
(page.()).();
(page.()).();
});
(, ({ page }) => {
page.();
page.(, { : }).();
(page.()).();
(page.()).();
(page.()).();
});
(, ({ page }) => {
page.();
page.(, { : }).().();
page.();
page.().();
page.().();
page.().();
page.(, { : }).();
(page.()).();
(page.()).();
});
});
页面对象模式
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 () {
(..(message)).();
}
}
{
() {}
() {
..();
}
() {
product = ..();
product.(, { : }).();
}
() {
(
..(, { : productName })
).();
}
}
(, ({ page }) => {
loginPage = (page);
productPage = (page);
loginPage.();
loginPage.(, );
loginPage.();
productPage.();
productPage.();
});
选择器策略
await page.getByRole("button", { name: "Submit" });
await page.getByRole("link", { name: "Products" });
await page.getByRole("textbox", { name: "Email" });
await page.getByLabel("Email address");
await page.getByLabel("Password");
await page.getByTestId("user-menu");
await page.getByTestId("product-card-123");
await page.getByText("Welcome back!");
await page.getByText(/Order #\d+/);
测试数据管理
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",
},
};
export async function seedTestData() {
const prisma = new PrismaClient();
await prisma.user.upsert({
where: { email: testData.users.customer.email },
create: {
email: testData...,
: (testData...),
},
: {},
});
prisma..({
: { : testData... },
: testData..,
: {},
});
prisma.$disconnect();
}
视觉回归测试
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");
});
移动端测试
test.use({ viewport: { width: 375, height: 667 } });
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();
});
网络模拟
test("应处理支付 API 超时", async ({ page }) => {
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();
});
最佳实践
- 测试用户流程:而不是单个组件
- 使用基于角色的选择器:更有弹性
- 页面对象:可复用且可维护
- 等待元素:不要使用固定延迟
- 测试关键路径:登录、结账、注册
- 管理测试数据:每个测试隔离
- 视觉回归:仅关键页面
输出检查清单