Advanced (更新: 2026/6/3)

用 Claude Code 和 Playwright 落地 E2E 测试

用 Claude Code 设计 Playwright E2E:移动截图、认证状态、Trace Viewer、选择器和 CI 重试。

用 Claude Code 和 Playwright 落地 E2E 测试

只对 Claude Code 说“帮我写 Playwright 测试”,通常得不到能支撑发布的 E2E 套件。它可能生成能跑一次的代码,但选择器依赖 CSS 类名,登录每个用例都走 UI,手机截图没有留下,CI 失败后也没有 Trace Viewer 可以排查。

更有效的做法,是把 Claude Code 当成测试设计伙伴。你负责说明业务风险、页面路径、选择器规则和验证命令;Claude Code 负责阅读代码库、补齐 Playwright 配置和测试文件。本文会把 Claude Code + Playwright 的实用流程整理成可复制的示例,覆盖移动端截图、代码块布局 QA、Trace Viewer、认证状态、CI retry,以及如何减少 flaky test。

官方资料建议以 Claude Code overviewClaude Code common workflows、Playwright 的 LocatorsAuthenticationScreenshotsTrace ViewerRetriesCI 为准。ClaudeCodeLab 内部可继续阅读测试策略CI/CD 设置响应式设计

先选值得保护的流程

E2E 测试会启动真实浏览器,比单元测试慢,所以不要试图覆盖所有细节。优先选择只有浏览器才能证明的关键路径。

用例保护什么Playwright 检查点
文章到产品页读者能从内容进入 /products/CTA 链接、URL、手机可点击性
登录后的后台已认证用户能进入受保护页面storageState、重定向、权限
技术文章布局代码块和表格不会撑破手机宽度移动截图、无横向溢出、Trace
flowchart LR
  A["收入或注册路径"] --> B["Playwright E2E"]
  C["移动布局风险"] --> B
  D["纯计算和校验"] --> E["单元测试"]
  F["API 或组件边界"] --> G["集成测试"]

这三个用例适合内容站、SaaS 和电商。CTA 在手机上被遮住、代码块让页面变宽、付费用户页面没有进 CI,看起来都是小问题,但会直接影响转化和信任。

给 Claude Code 明确边界

提示词要写清目标路由、允许的选择器、验证命令和可修改文件。这样可以避免 Claude Code 为了补测试而顺手重构 UI。

请读取现有 Astro 网站,并添加 Playwright E2E 测试。

目标:
- 验证 `/zh/blog/claude-code-playwright-testing/` 可以进入 `/products/` 和 `/training/`
- 在 390px 手机宽度检查文章、表格、代码块是否横向溢出
- 需要登录的测试使用 `storageState`,不要每个测试都从 UI 登录
- CI 中 retry 设为 2,trace 使用 `on-first-retry`

限制:
- 不使用 `page.waitForTimeout()`
- 优先使用 role、label、text、test id,避免 CSS 类名链
- 只修改 `playwright.config.ts` 和 `tests/e2e/**`
- 执行 `npx playwright test`,失败时用 Trace Viewer 说明原因

评审 Claude Code 的输出时,不要只看文件是否生成,而要看失败时能不能解释、选择器是否稳定、测试数据是否可控。

可复制的最小配置

下面的配置可直接放进应用。不同框架只需要调整 BASE_URL 和 preview 命令。

cd site
npm i -D @playwright/test
npx playwright install
mkdir tests/e2e
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

const baseURL = process.env.BASE_URL ?? 'http://127.0.0.1:4321';
const hasAuth = Boolean(process.env.TEST_EMAIL && process.env.TEST_PASSWORD);
const authFile = 'playwright/.auth/user.json';

export default defineConfig({
  testDir: './tests/e2e',
  timeout: 30_000,
  expect: { timeout: 5_000 },
  fullyParallel: true,
  forbidOnly: Boolean(process.env.CI),
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 2 : undefined,
  reporter: process.env.CI ? [['html'], ['github']] : 'html',
  use: {
    baseURL,
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  ...(process.env.PLAYWRIGHT_WEB_SERVER === '1'
    ? {
        webServer: {
          command: 'npm run preview -- --host 127.0.0.1 --port 4321',
          url: baseURL,
          reuseExistingServer: !process.env.CI,
          timeout: 120_000,
        },
      }
    : {}),
  projects: [
    ...(hasAuth
      ? [
          {
            name: 'setup',
            testMatch: /.*\.setup\.ts/,
          },
        ]
      : []),
    {
      name: 'desktop-chrome',
      use: {
        ...devices['Desktop Chrome'],
        storageState: hasAuth ? authFile : undefined,
      },
      dependencies: hasAuth ? ['setup'] : [],
    },
    {
      name: 'mobile-safari',
      use: {
        ...devices['iPhone 13'],
        storageState: hasAuth ? authFile : undefined,
      },
      dependencies: hasAuth ? ['setup'] : [],
    },
  ],
});

本地不启用 retry,便于尽快暴露问题;CI 才启用 retry,因为共享 runner 会有偶发波动。关键是 retry 必须和 trace、截图、HTML report 一起保存。

用 storageState 保存登录状态

认证相关测试如果每次都从登录页开始,会慢,而且容易受二次验证、限流、UI 改版影响。storageState 可以把登录后的浏览器状态保存到文件,后续项目直接复用。注意:playwright/.auth 可能包含 Cookie 和 header,必须加入 .gitignore

// tests/e2e/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
import fs from 'node:fs';
import path from 'node:path';

const authFile = path.resolve('playwright/.auth/user.json');
const email = process.env.TEST_EMAIL;
const password = process.env.TEST_PASSWORD;

setup('save signed-in browser state', async ({ page }) => {
  setup.skip(!email || !password, 'Set TEST_EMAIL and TEST_PASSWORD to record auth state.');

  await page.goto('/login');
  await page.getByLabel(/email|メール|e-mail/i).fill(email!);
  await page.getByLabel(/password|パスワード/i).fill(password!);
  await page.getByRole('button', { name: /log in|sign in|ログイン/i }).click();

  await expect(page).toHaveURL(/dashboard|account|admin/);
  await expect(page.locator('body')).toBeVisible();

  fs.mkdirSync(path.dirname(authFile), { recursive: true });
  await page.context().storageState({ path: authFile });
});

请让 Claude Code 把“登录流程本身的测试”和“登录后功能的测试”分开。否则登录按钮文案一变,整套 E2E 都会红。

检查移动截图和代码块布局

技术文章最常见的问题不是页面完全打不开,而是长代码行、表格或图片把手机页面撑宽。下面的测试会检查 CTA、保存移动截图,并用数字断言横向溢出。

// tests/e2e/article-quality.spec.ts
import { test, expect } from '@playwright/test';

const articlePath = process.env.ARTICLE_PATH ?? '/zh/blog/claude-code-playwright-testing/';

test.describe('article quality checks', () => {
  test('article has monetization CTAs', async ({ page }) => {
    await page.goto(articlePath);

    await expect(page.getByRole('heading', { level: 1 })).toContainText(/Playwright|E2E|Claude Code/i);
    await expect(page.locator('a[href="/products/"], a[href="/products"]').first()).toBeVisible();
    await expect(page.locator('a[href="/training/"], a[href="/training"]').first()).toBeVisible();
  });

  test('mobile layout has no horizontal overflow', async ({ page }, testInfo) => {
    await page.setViewportSize({ width: 390, height: 844 });
    await page.goto(articlePath);
    await expect(page.locator('main, article').first()).toBeVisible();

    const overflow = await page.evaluate(() => ({
      viewport: window.innerWidth,
      documentWidth: document.documentElement.scrollWidth,
      offenders: Array.from(document.querySelectorAll('pre, table, img, iframe, .prose'))
        .filter((node) => {
          const rect = node.getBoundingClientRect();
          return rect.left < -1 || rect.right > window.innerWidth + 1;
        })
        .map((node) => {
          const rect = node.getBoundingClientRect();
          return `${node.tagName.toLowerCase()} ${Math.round(rect.left)}-${Math.round(rect.right)}`;
        }),
    }));

    expect(overflow.documentWidth, JSON.stringify(overflow)).toBeLessThanOrEqual(overflow.viewport + 2);
    expect(overflow.offenders).toEqual([]);
    await page.screenshot({ path: testInfo.outputPath('article-mobile.png'), fullPage: true });
  });

  test('code examples are present and copyable', async ({ page }) => {
    await page.goto(articlePath);

    const blocks = page.locator('pre code');
    await expect(blocks.first()).toBeVisible();
    expect(await blocks.count()).toBeGreaterThanOrEqual(3);
    await expect(blocks.nth(0)).toContainText(/playwright|defineConfig|test/i);
  });
});

截图用于人工 review,横向溢出断言用于 CI。两者组合,比单纯依赖视觉快照更稳定。

把变现路径放进 E2E

内容站不只要文章能读,还要读者能继续进入产品、培训或咨询页面。下面的测试保护 /products//training/ 两个关键 CTA。

// tests/e2e/revenue-flows.spec.ts
import { test, expect } from '@playwright/test';

const articlePath = process.env.ARTICLE_PATH ?? '/zh/blog/claude-code-playwright-testing/';

test.describe('revenue and learning flows', () => {
  test('reader can move from article to products', async ({ page }) => {
    await page.goto(articlePath);

    await page.locator('a[href="/products/"], a[href="/products"]').first().click();
    await expect(page).toHaveURL(/\/products\/?$/);
    await expect(page.locator('main').first()).toBeVisible();
  });

  test('training CTA is reachable on mobile', async ({ page }) => {
    await page.setViewportSize({ width: 390, height: 844 });
    await page.goto(articlePath);

    await page.locator('a[href="/training/"], a[href="/training"]').first().click();
    await expect(page).toHaveURL(/\/training\/?$/);
    await expect(page.locator('main').first()).toBeVisible();
  });

  test('main navigation can open the blog index', async ({ page }) => {
    await page.goto('/');

    await expect(page.getByRole('navigation').first()).toBeVisible();
    await page.getByRole('link', { name: /blog|記事|articles/i }).first().click();
    await expect(page).toHaveURL(/blog/);
  });
});

如果页面是多语言的,CTA 文案可能变,但 href 不该乱变。对于 checkout、logout、拖拽操作等关键动作,可以让 Claude Code 添加稳定的 data-testid

减少 flaky test

选择器优先级建议如下:

优先级选择器示例原因
role + namepage.getByRole('button', { name: /save/i })接近真实用户和辅助技术
labelpage.getByLabel(/email/i)同时检查表单语义
textpage.getByText(/Start trial/)清楚,但会受文案影响
test idpage.getByTestId('checkout-submit')适合稳定业务动作
CSS 结构.card:nth-child(3)布局一变就坏

也不要用 page.waitForTimeout() 掩盖问题。优先使用 toBeVisible()toHaveURL()toContainText() 等 web-first assertion,让 Playwright 自动等待真实条件。

用 Trace Viewer 排查

CI 失败时,Trace Viewer 能看到每一步的 DOM、截图、网络和 console。配置 trace: 'on-first-retry',可以只在失败重试时留下证据。

npx playwright test --trace on
npx playwright show-report
npx playwright show-trace test-results/path-to-trace/trace.zip

把失败交给 Claude Code 时,请附上测试名、trace 中看到的画面状态、以及期望的用户行为。这样它更可能修选择器、补数据准备或指出 UI 缺陷,而不是简单加等待时间。

CI 配置

# .github/workflows/playwright.yml
name: Playwright E2E

on:
  pull_request:
  push:
    branches: [main]

jobs:
  e2e:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    defaults:
      run:
        working-directory: site
    env:
      BASE_URL: http://127.0.0.1:4321
      PLAYWRIGHT_WEB_SERVER: "1"
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: site/package-lock.json
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run build
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: site/playwright-report
          retention-days: 7

retry 不是治好 flaky test,而是把它分类并留下证据。HTML report、trace zip、截图 artifact 都要保留。

常见坑

第一个坑是把所有测试都写成 E2E。价格计算、权限边界、字段校验应该优先放在单元或集成测试,Playwright 只负责浏览器层面的关键路径。

第二个坑是把认证状态提交到仓库。storageState 方便,但包含敏感信息,必须使用专门测试账号并排除出 git。

第三个坑是用 retry 盖住不稳定。只要测试经常靠 retry 才通过,就要看 Trace Viewer,排查网络等待、随机数据、动画、时间依赖或弱选择器。

第四个坑是让 Claude Code 修改范围过大。更可靠的流程是:先补会失败的测试,再看 trace,最后做最小产品修复。

如果你想自助落地,可从 ClaudeCodeLab 的 products 模板开始;如果团队需要统一 review 规则、CI 策略和培训流程,可以看 training

我用这套流程检查了一个本地 ClaudeCodeLab 风格文章页:390px 截图、代码块横向溢出、/products//training/ CTA、以及 CI retry 配置。最先暴露的问题不是 Playwright,而是过长代码行和不够明确的链接名称。修好这些之后,Trace Viewer 才真正变成可以交给 Claude Code 继续修复的证据。

#Claude Code #Playwright #E2E 测试 #测试自动化 #质量保障
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。