Playwright + AI:编写自动化E2E测试
利用Playwright与AI Codegen实现E2E测试自动化。基于TypeScript实战、GitHub Actions集成、视觉回归测试,可立即应用于实际工作的完整指南
概述
在现代Web应用开发中,E2E(End-to-End)测试是不可或缺的要素。但测试代码编写耗时、维护困难,且难以重现复杂的用户场景。Playwright与AI技术的结合以创新方式解决了这些问题。
为什么选择Playwright + AI?
传统E2E测试的问题:
- 耗费时间:需要手动编写所有测试用例
- 维护负担:UI变更时需要修改全部测试代码
- 需要专业知识:选择器编写、异步处理、等待时间调整等
Playwright + AI解决方案:
- ✅ AI Codegen:自动将浏览器操作转换为代码
- ✅ MCP集成:连接Claude与Playwright,用自然语言生成测试
- ✅ 自动等待:通过Smart Auto-waiting实现稳定测试
- ✅ 多浏览器支持:同时支持Chromium、Firefox、WebKit
本指南将构建从Playwright MCP、AI Codegen、GitHub Actions集成到视觉回归测试的完整自动化测试系统,可立即应用于实际工作。
Playwright核心概念
1. 什么是Playwright?
由Microsoft开发的跨浏览器E2E测试框架。作为Puppeteer的后继者,提供更强大、更稳定的功能。
核心特性:
// 多浏览器支持
import { chromium, firefox, webkit } from "playwright";
// Chromium (Chrome, Edge)
const browser1 = await chromium.launch();
// Firefox
const browser2 = await firefox.launch();
// WebKit (Safari)
const browser3 = await webkit.launch();
2. Auto-waiting机制
Playwright最强大的功能是自动等待(Auto-waiting)。无需手动编写sleep()或waitFor()。
// ❌ 传统方式(Selenium)
await driver.sleep(5000); // 无条件等待5秒
const button = await driver.findElement(By.id("submit"));
await button.click();
// ✅ Playwright方式
await page.click("#submit"); // 自动等待元素准备就绪
自动等待条件:
- 元素存在于DOM中
- 元素在屏幕上可见
- 元素处于启用状态(非disabled)
- 元素未被其他元素遮挡
3. 强大的选择器引擎
Playwright提供多种选择器策略:
// CSS选择器
await page.click("button.submit");
// 文本内容
await page.click("text=登录");
// ARIA角色(基于可访问性)
await page.click('role=button[name="提交"]');
// XPath
await page.click('xpath=//button[contains(text(), "确认")]');
// 可组合使用
await page.click('article:has-text("新闻") >> button');
4. 架构比较
graph TB
subgraph "Selenium架构"
S1[测试代码] -->|WebDriver Protocol| S2[浏览器驱动]
S2 -->|HTTP| S3[浏览器]
end
subgraph "Playwright架构"
P1[测试代码] -->|WebSocket| P2[Playwright Server]
P2 -->|CDP/DevTools| P3[浏览器]
end
style P1 fill:#4CAF50
style P2 fill:#4CAF50
style P3 fill:#4CAF50
Playwright的优势:
- 基于WebSocket:比HTTP更快、更稳定
- CDP(Chrome DevTools Protocol):直接访问浏览器内部API
- 单一进程:无需驱动程序即可直接控制
基于AI的测试生成
1. Playwright Codegen
Playwright内置的代码生成器,自动将浏览器操作转换为TypeScript代码。
基本用法
# 运行Codegen
npx playwright codegen https://example.com
# 指定特定浏览器
npx playwright codegen --browser=firefox https://example.com
# 移动设备模拟
npx playwright codegen --device="iPhone 13" https://example.com
# 深色模式
npx playwright codegen --color-scheme=dark https://example.com
Codegen实战:登录测试
- 启动Codegen:
npx playwright codegen https://github.com/login
-
在浏览器中操作:
- 输入用户名
- 输入密码
- 点击”Sign in”按钮
-
自动生成的代码:
import { test, expect } from "@playwright/test";
test("GitHub登录测试", async ({ page }) => {
await page.goto("https://github.com/login");
// Codegen自动生成的代码
await page.getByLabel("Username or email address").fill("myusername");
await page.getByLabel("Password").fill("mypassword");
await page.getByRole("button", { name: "Sign in" }).click();
// 确认登录成功
await expect(page).toHaveURL(/.*github\.com$/);
});
2. Playwright MCP(Model Context Protocol)
连接Claude与Playwright,用自然语言生成测试的创新方法。
MCP安装与配置
# 1. 安装MCP服务器
npm install -g @automatalabs/mcp-server-playwright
# 2. 修改Claude配置文件(~/.claude/config.json)
{
"mcpServers": {
"playwright": {
"command": "mcp-server-playwright"
}
}
}
MCP实战:用自然语言编写测试
自然语言命令:
"访问GitHub仓库页面并点击Star按钮的测试"
Claude生成的代码:
import { test, expect } from "@playwright/test";
test("GitHub仓库Star测试", async ({ page }) => {
// 1. 访问仓库页面
await page.goto("https://github.com/microsoft/playwright");
// 2. 查找并点击Star按钮
const starButton = page.getByRole("button", { name: /star/i });
await starButton.click();
// 3. 确认Star成功
await expect(starButton).toHaveAttribute("aria-pressed", "true");
// 4. 确认Star数量增加
const starCount = page.locator(".Counter");
await expect(starCount).toBeVisible();
});
3. MCP高级功能
基于快照的测试
test("页面可访问性快照", async ({ page }) => {
await page.goto("https://example.com");
// 利用MCP的browser_snapshot功能
const snapshot = await page.accessibility.snapshot();
// 验证快照
expect(snapshot).toMatchSnapshot("homepage-a11y.json");
});
多页面操作
test("在新标签页中操作", async ({ context }) => {
// 第一个页面
const page1 = await context.newPage();
await page1.goto("https://example.com");
// 点击链接打开新标签页
const [page2] = await Promise.all([
context.waitForEvent("page"),
page1.click('a[target="_blank"]'),
]);
// 在第二个页面中操作
await page2.waitForLoadState();
await expect(page2).toHaveTitle(/新页面/);
});
实战实现指南
Step 1:项目初始化
# 1. 创建Playwright项目
npm init playwright@latest
# 配置选择
# ✓ 选择TypeScript
# ✓ 使用tests文件夹
# ✓ 添加GitHub Actions workflow
# ✓ 安装Playwright浏览器
# 2. 确认项目结构
playwright-demo/
├── tests/
│ └── example.spec.ts
├── playwright.config.ts
├── package.json
└── .github/
└── workflows/
└── playwright.yml
Step 2:优化配置文件
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
// 测试文件位置
testDir: "./tests",
// 并行执行
fullyParallel: true,
// CI环境配置
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
// 报告器配置
reporter: [
["html"], // HTML报告
["json", { outputFile: "test-results.json" }],
["junit", { outputFile: "junit.xml" }],
],
// 全局配置
use: {
// 基础URL
baseURL: "http://localhost:3000",
// 追踪收集(失败时)
trace: "on-first-retry",
// 截图(失败时)
screenshot: "only-on-failure",
// 视频录制
video: "retain-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"] },
},
{
name: "Mobile Safari",
use: { ...devices["iPhone 13"] },
},
],
// 自动运行开发服务器
webServer: {
command: "npm run dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
Step 3:编写第一个测试
// tests/login.spec.ts
import { test, expect } from "@playwright/test";
test.describe("登录功能", () => {
// 测试前执行
test.beforeEach(async ({ page }) => {
await page.goto("/login");
});
test("正常登录", async ({ page }) => {
// 1. 填写登录表单
await page.fill('[name="email"]', "user@example.com");
await page.fill('[name="password"]', "SecurePassword123!");
// 2. 点击登录按钮
await page.click('button[type="submit"]');
// 3. 确认重定向到仪表板
await expect(page).toHaveURL("/dashboard");
// 4. 确认欢迎消息
await expect(page.locator("h1")).toContainText("欢迎");
});
test("错误密码", async ({ page }) => {
await page.fill('[name="email"]', "user@example.com");
await page.fill('[name="password"]', "WrongPassword");
await page.click('button[type="submit"]');
// 确认错误消息
const errorMsg = page.locator('[role="alert"]');
await expect(errorMsg).toBeVisible();
await expect(errorMsg).toContainText("密码不正确");
});
test("邮箱格式验证", async ({ page }) => {
await page.fill('[name="email"]', "invalid-email");
await page.fill('[name="password"]', "password123");
// Submit按钮应该被禁用
await expect(page.locator('button[type="submit"]')).toBeDisabled();
});
});
Step 4:Page Object Model (POM)模式
// pages/LoginPage.ts
import { Page, Locator } from "@playwright/test";
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.locator('[name="email"]');
this.passwordInput = page.locator('[name="password"]');
this.submitButton = page.locator('button[type="submit"]');
this.errorMessage = page.locator('[role="alert"]');
}
async goto() {
await this.page.goto("/login");
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async getErrorMessage() {
return this.errorMessage.textContent();
}
}
// 在测试中使用
test("POM模式登录测试", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login("user@example.com", "password123");
await expect(page).toHaveURL("/dashboard");
});
Step 5:API Mocking
// tests/api-mocking.spec.ts
import { test, expect } from "@playwright/test";
test("API响应模拟", async ({ page }) => {
// 拦截API请求
await page.route("**/api/user", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
id: 1,
name: "Test User",
email: "test@example.com",
}),
});
});
await page.goto("/profile");
// 确认模拟数据
await expect(page.locator(".user-name")).toHaveText("Test User");
await expect(page.locator(".user-email")).toHaveText("test@example.com");
});
test("网络错误模拟", async ({ page }) => {
// 产生网络错误
await page.route("**/api/data", (route) => route.abort("failed"));
await page.goto("/data-page");
// 确认错误状态
await expect(page.locator(".error-message")).toBeVisible();
});
高级代码示例
1. 文件上传测试
test("文件上传", async ({ page }) => {
await page.goto("/upload");
// 选择文件
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles("path/to/test-file.pdf");
// 点击上传按钮
await page.click('button:has-text("上传")');
// 确认上传成功
await expect(page.locator(".upload-success")).toBeVisible();
// 多文件上传
await fileInput.setInputFiles(["file1.pdf", "file2.jpg", "file3.png"]);
});
2. 拖放操作
test("拖放", async ({ page }) => {
await page.goto("/kanban-board");
// 源和目标元素
const task = page.locator(".task").first();
const column = page.locator('.column[data-status="done"]');
// 执行拖放
await task.dragTo(column);
// 确认移动
await expect(column.locator(".task")).toHaveCount(1);
});
3. 无限滚动测试
test("无限滚动加载", async ({ page }) => {
await page.goto("/infinite-scroll");
// 初始项目数
let itemCount = await page.locator(".item").count();
expect(itemCount).toBe(20);
// 滚动加载更多
for (let i = 0; i < 3; i++) {
// 滚动到页面底部
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
// 等待新项目加载
await page.waitForFunction(
(prevCount) => document.querySelectorAll(".item").length > prevCount,
itemCount
);
itemCount = await page.locator(".item").count();
}
// 总共加载80个项目
expect(itemCount).toBe(80);
});
4. WebSocket测试
test("实时聊天(WebSocket)", async ({ page }) => {
await page.goto("/chat");
// 等待WebSocket连接
await page.waitForEvent("websocket");
// 发送消息
await page.fill("#message-input", "Hello WebSocket!");
await page.click("#send-button");
// 确认接收消息
const lastMessage = page.locator(".message").last();
await expect(lastMessage).toHaveText("Hello WebSocket!");
// 监控WebSocket事件
page.on("websocket", (ws) => {
ws.on("framesent", (event) => console.log("Sent:", event.payload));
ws.on("framereceived", (event) => console.log("Received:", event.payload));
});
});
5. 认证状态重用
// auth.setup.ts(只登录一次)
import { test as setup } from "@playwright/test";
const authFile = "playwright/.auth/user.json";
setup("认证", async ({ page }) => {
await page.goto("/login");
await page.fill('[name="email"]', "user@example.com");
await page.fill('[name="password"]', "password123");
await page.click('button[type="submit"]');
// 保存Cookie
await page.context().storageState({ path: authFile });
});
// playwright.config.ts
export default defineConfig({
projects: [
{ name: "setup", testMatch: /.*\.setup\.ts/ },
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
storageState: authFile, // 重用认证状态
},
dependencies: ["setup"],
},
],
});
// 现在所有测试都以登录状态开始
test("访问仪表板", async ({ page }) => {
await page.goto("/dashboard"); // 无需登录
await expect(page).toHaveURL("/dashboard");
});
GitHub Actions CI/CD集成
1. 基本工作流
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: 安装依赖
run: npm ci
- name: 安装Playwright浏览器
run: npx playwright install --with-deps
- name: 运行测试
run: npx playwright test
- name: 上传测试报告
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
2. 多浏览器矩阵
jobs:
test:
strategy:
matrix:
browser: [chromium, firefox, webkit]
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps ${{ matrix.browser }}
- run: npx playwright test --project=${{ matrix.browser }}
3. 视觉回归测试集成
jobs:
visual-regression:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install --with-deps
# 截图比较
- name: 视觉回归测试
run: npx playwright test --update-snapshots
if: github.event_name == 'push'
# 差异报告
- name: 上传快照差异
uses: actions/upload-artifact@v4
if: failure()
with:
name: snapshot-diff
path: test-results/**/*-diff.png
4. Slack通知集成
- name: Slack通知
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "❌ Playwright测试失败!",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Repository:* ${{ github.repository }}\n*Branch:* ${{ github.ref }}\n*Author:* ${{ github.actor }}"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Logs"
},
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
视觉回归测试
1. 截图比较
// tests/visual.spec.ts
import { test, expect } from "@playwright/test";
test("首页视觉验证", async ({ page }) => {
await page.goto("/");
// 整页截图
await expect(page).toHaveScreenshot("homepage.png");
// 仅特定元素
const header = page.locator("header");
await expect(header).toHaveScreenshot("header.png");
// 设置阈值(允许像素差异)
await expect(page).toHaveScreenshot("homepage-flexible.png", {
maxDiffPixels: 100,
});
});
2. 响应式设计测试
const viewports = [
{ width: 375, height: 667, name: "mobile" },
{ width: 768, height: 1024, name: "tablet" },
{ width: 1920, height: 1080, name: "desktop" },
];
for (const viewport of viewports) {
test(`${viewport.name}视口测试`, async ({ page }) => {
await page.setViewportSize(viewport);
await page.goto("/");
await expect(page).toHaveScreenshot(`${viewport.name}-homepage.png`);
});
}
3. 深浅色模式比较
test("浅色/深色模式比较", async ({ page }) => {
await page.goto("/");
// 浅色模式
await expect(page).toHaveScreenshot("light-mode.png");
// 切换到深色模式
await page.emulateMedia({ colorScheme: "dark" });
await expect(page).toHaveScreenshot("dark-mode.png");
});
4. 禁用动画
test("无动画截图", async ({ page }) => {
await page.goto("/");
// 禁用CSS动画
await page.addStyleTag({
content: `
*, *::before, *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
`,
});
await expect(page).toHaveScreenshot("no-animation.png");
});
最佳实践
1. 选择器编写原则
// ❌ 不好的例子:脆弱的选择器
await page.click(".btn-primary.submit-form");
await page.click("div > div > button:nth-child(3)");
// ✅ 好的例子:有意义的选择器
await page.click('[data-testid="submit-button"]');
await page.getByRole("button", { name: "提交" });
await page.getByLabel("邮箱地址");
// 在HTML中添加test-id
<button data-testid="submit-button">提交</button>;
2. 等待策略
// ❌ 硬编码等待(反模式)
await page.waitForTimeout(5000);
// ✅ 基于条件的等待
await page.waitForSelector(".loading-spinner", { state: "hidden" });
await page.waitForLoadState("networkidle");
await page.waitForResponse((resp) => resp.url().includes("/api/data"));
// ✅ 利用Auto-waiting
await page.click("button"); // 自动等待直到可点击
3. 测试隔离
// ✅ 每个测试都有独立的上下文
test.describe("用户管理", () => {
test.beforeEach(async ({ page }) => {
// 每个测试前初始化
await page.goto("/users");
});
test("添加用户", async ({ page }) => {
// 独立的测试
});
test("删除用户", async ({ page }) => {
// 不受前一个测试影响
});
});
4. 错误处理
test("网络错误处理", async ({ page }) => {
// 捕获控制台错误
const errors: string[] = [];
page.on("console", (msg) => {
if (msg.type() === "error") {
errors.push(msg.text());
}
});
await page.goto("/");
// 确认无错误
expect(errors).toHaveLength(0);
});
test("404页面处理", async ({ page }) => {
const response = await page.goto("/non-existent-page");
expect(response?.status()).toBe(404);
await expect(page.locator("h1")).toHaveText("找不到页面");
});
5. 测试数据管理
// fixtures/testData.ts
export const testUsers = {
admin: {
email: "admin@example.com",
password: "AdminPass123!",
},
user: {
email: "user@example.com",
password: "UserPass123!",
},
};
// 在测试中使用
import { testUsers } from "./fixtures/testData";
test("管理员登录", async ({ page }) => {
await loginPage.login(testUsers.admin.email, testUsers.admin.password);
});
6. 并行执行优化
// playwright.config.ts
export default defineConfig({
// 根据CPU核心数自动调整
workers: process.env.CI ? 2 : undefined,
// 测试间完全隔离
fullyParallel: true,
// 失败时重试
retries: 2,
// 超时设置
timeout: 30000,
expect: {
timeout: 5000,
},
});
实际应用场景
1. 电子商务结账流程
test("完整购买流程", async ({ page }) => {
// 1. 搜索商品
await page.goto("/");
await page.fill('[name="search"]', "Laptop");
await page.press('[name="search"]', "Enter");
// 2. 选择商品
await page.click('.product-card:has-text("MacBook Pro")');
await expect(page).toHaveURL(/\/product\/\d+/);
// 3. 加入购物车
await page.click('button:has-text("加入购物车")');
await expect(page.locator(".cart-badge")).toHaveText("1");
// 4. 确认购物车
await page.click('[aria-label="购物车"]');
await expect(page.locator(".cart-item")).toHaveCount(1);
// 5. 进行结账
await page.click('button:has-text("结账")');
// 6. 输入配送地址
await page.fill('[name="address"]', "北京市朝阳区");
await page.fill('[name="phone"]', "010-1234-5678");
// 7. 选择支付方式
await page.click('[value="credit-card"]');
// 8. 完成订单
await page.click('button:has-text("下单")');
// 9. 确认完成页面
await expect(page).toHaveURL("/order/success");
await expect(page.locator("h1")).toContainText("订单已完成");
});
2. 表单验证测试
test.describe("注册表单验证", () => {
test("邮箱重复检查", async ({ page }) => {
await page.goto("/signup");
await page.fill('[name="email"]', "existing@example.com");
// 实时验证
await page.waitForResponse((resp) =>
resp.url().includes("/api/check-email")
);
const errorMsg = page.locator(".email-error");
await expect(errorMsg).toHaveText("该邮箱已被使用");
});
test("密码强度检查", async ({ page }) => {
await page.goto("/signup");
const passwordInput = page.locator('[name="password"]');
const strengthIndicator = page.locator(".password-strength");
// 弱密码
await passwordInput.fill("123");
await expect(strengthIndicator).toHaveClass(/weak/);
// 强密码
await passwordInput.fill("MySecure@Pass123");
await expect(strengthIndicator).toHaveClass(/strong/);
});
});
3. 性能监控
test("页面加载性能", async ({ page }) => {
// 使用Performance API
await page.goto("/");
const metrics = await page.evaluate(() => {
const navigation = performance.getEntriesByType(
"navigation"
)[0] as PerformanceNavigationTiming;
return {
domContentLoaded:
navigation.domContentLoadedEventEnd - navigation.fetchStart,
loadComplete: navigation.loadEventEnd - navigation.fetchStart,
firstPaint: performance.getEntriesByType("paint")[0]?.startTime,
};
});
// 验证性能标准
expect(metrics.domContentLoaded).toBeLessThan(2000); // 2秒内
expect(metrics.loadComplete).toBeLessThan(5000); // 5秒内
console.log("性能指标:", metrics);
});
注意事项与故障排除
1. 常见问题
超时错误
// ❌ 问题:超过默认30秒超时
test("慢速API测试", async ({ page }) => {
await page.goto("/slow-page"); // TimeoutError!
});
// ✅ 解决:增加超时时间
test("慢速API测试", async ({ page }) => {
test.setTimeout(60000); // 增加到60秒
await page.goto("/slow-page", { timeout: 60000 });
});
不稳定的测试
// ❌ 不稳定的测试
test("不稳定测试", async ({ page }) => {
await page.click("button"); // 有时失败
await expect(page.locator(".result")).toBeVisible();
});
// ✅ 稳定的测试
test("稳定测试", async ({ page }) => {
await page.click("button");
// 等待网络请求
await page.waitForResponse((resp) => resp.url().includes("/api"));
await expect(page.locator(".result")).toBeVisible();
});
2. 调试技巧
# UI模式运行测试(可视化调试)
npx playwright test --ui
# 有头模式(显示浏览器)
npx playwright test --headed
# 只运行特定测试
npx playwright test login.spec.ts
# 调试模式
npx playwright test --debug
# 追踪查看器
npx playwright show-trace trace.zip
3. CI环境优化
// playwright.config.ts
export default defineConfig({
use: {
// CI中始终收集追踪
trace: process.env.CI ? "on" : "on-first-retry",
// CI中禁用视频录制(节省磁盘空间)
video: process.env.CI ? "retain-on-failure" : "on",
// CI中使用更长的超时
actionTimeout: process.env.CI ? 10000 : 5000,
},
});
结论
Playwright + AI改变了E2E测试自动化的范式。总结本指南涵盖的内容:
核心要点
- 基于AI的代码生成:使用Codegen和MCP将测试代码编写时间减少80%
- 稳定的测试:通过Auto-waiting和智能选择器消除不稳定测试
- CI/CD集成:使用GitHub Actions在每次提交时自动测试
- 视觉回归测试:通过截图比较预防UI错误
- 跨浏览器测试:同时测试Chromium、Firefox、WebKit
立即开始
# 1. 初始化项目
npm init playwright@latest
# 2. 运行第一个测试
npx playwright test
# 3. 查看HTML报告
npx playwright show-report
# 4. 用AI生成测试
npx playwright codegen https://your-app.com
下一步
- 高级模式:自定义Fixtures、全局设置、测试分片
- 性能测试:集成Lighthouse CI
- 可访问性测试:集成axe-core
- 移动测试:连接真实设备云
Playwright + AI不仅是测试工具,更是革新开发生产力的必备工具。现在就开始吧!
参考资料
官方文档
教程与指南
社区与工具
相关技术
编写日期:2025年10月25日 分类:E2E测试、自动化、TypeScript 难度:中级〜高级
阅读其他语言版本
- 🇰🇷 한국어
- 🇯🇵 日本語
- 🇺🇸 English
- 🇨🇳 中文(当前页面)
这篇文章有帮助吗?
您的支持能帮助我创作更好的内容。请我喝杯咖啡吧!☕