Playwright-浏览器自动化与端到端测试技能 playwright

这个技能是基于Playwright框架的浏览器自动化和端到端测试工具。它能够自动检测开发服务器,编写和执行测试脚本,支持多种测试场景如页面测试、表单填充、截图、响应式设计检查、用户体验验证、登录流程测试、链接检查等。适用于跨浏览器测试、视觉回归测试、API测试和组件测试,帮助提高测试效率和软件质量。关键词:Playwright, 浏览器自动化, 端到端测试, 测试框架, 跨浏览器测试, 视觉回归测试, API测试, 自动化测试, 软件测试。

测试 0 次安装 0 次浏览 更新于 3/7/2026

名称: playwright 描述: 使用Playwright进行浏览器自动化和端到端测试。自动检测开发服务器,编写干净的测试脚本。测试页面、填写表单、截图、检查响应式设计、验证用户体验、测试登录流程、检查链接、自动化任何浏览器任务。用于跨浏览器测试、视觉回归测试、API测试、TypeScript/JavaScript和Python项目中的组件测试。 允许工具: Bash, Read, Edit, Write, Grep, Glob, TodoWrite

Playwright - 浏览器自动化与端到端测试

精通Playwright的浏览器自动化和端到端测试知识——一个现代的跨浏览器测试框架。

重要 - 路径解析: 此技能可以安装在不同位置。在执行命令之前,根据加载此SKILL.md文件的位置确定技能目录,并在所有命令中使用该路径。用实际发现的路径替换$SKILL_DIR

常见安装路径:

  • 插件系统: ~/.claude/plugins/*/playwright/skills/playwright
  • 手动全局: ~/.claude/skills/playwright
  • 项目特定: <project>/.claude/skills/playwright

关键工作流程 - 遵循这些步骤

当自动化浏览器任务时:

  1. 自动检测开发服务器 - 对于本地主机测试,始终先运行服务器检测:

    cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"
    
    • 如果发现1个服务器:自动使用它,通知用户
    • 如果发现多个服务器:询问用户要测试哪一个
    • 如果未发现服务器:询问URL或帮助启动开发服务器
  2. 将脚本写入/tmp - 切勿将测试文件写入技能目录;始终使用/tmp/playwright-test-*.js

  3. 默认使用可见浏览器 - 除非用户特别请求无头模式,始终使用headless: false

  4. 参数化URL - 始终通过脚本顶部的常量使URL可配置

  5. 通过run.js执行 - 始终运行:cd $SKILL_DIR && node run.js /tmp/playwright-test-*.js

快速开始

首次设置

# 导航到技能目录
cd $SKILL_DIR

# 使用bun安装(首选)
bun run setup

# 或使用npm
npm run setup:npm

这将安装Playwright和Chromium浏览器。只需执行一次。

安装(用于端到端测试项目)

# 使用Bun(首选)
bun add -d @playwright/test
bunx playwright install

# 使用npm
npm init playwright@latest

配置

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  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: 'bun run dev',
    url: 'http://localhost:3000',
  },
})

浏览器自动化模式

工作原理

  1. 您描述要测试/自动化的内容
  2. 我自动检测运行中的开发服务器(或询问URL)
  3. 我在/tmp/playwright-test-*.js中编写自定义Playwright代码
  4. 我通过执行:cd $SKILL_DIR && node run.js /tmp/playwright-test-*.js
  5. 结果实时显示,浏览器窗口可见

测试页面(多视口)

// /tmp/playwright-test-responsive.js
const { chromium } = require('playwright');

const TARGET_URL = 'http://localhost:3001'; // 自动检测

(async () => {
  const browser = await chromium.launch({ headless: false, slowMo: 100 });
  const page = await browser.newPage();

  // 桌面测试
  await page.setViewportSize({ width: 1920, height: 1080 });
  await page.goto(TARGET_URL);
  console.log('桌面 - 标题:', await page.title());
  await page.screenshot({ path: '/tmp/desktop.png', fullPage: true });

  // 移动测试
  await page.setViewportSize({ width: 375, height: 667 });
  await page.screenshot({ path: '/tmp/mobile.png', fullPage: true });

  await browser.close();
})();

执行:cd $SKILL_DIR && node run.js /tmp/playwright-test-responsive.js

测试登录流程

// /tmp/playwright-test-login.js
const { chromium } = require('playwright');

const TARGET_URL = 'http://localhost:3001'; // 自动检测

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  await page.goto(`${TARGET_URL}/login`);
  await page.fill('input[name="email"]', 'test@example.com');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  await page.waitForURL('**/dashboard');
  console.log('✅ 登录成功,重定向到仪表板');

  await browser.close();
})();

检查损坏链接

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  await page.goto('http://localhost:3000');

  const links = await page.locator('a[href^="http"]').all();
  const results = { working: 0, broken: [] };

  for (const link of links) {
    const href = await link.getAttribute('href');
    try {
      const response = await page.request.head(href);
      if (response.ok()) {
        results.working++;
      } else {
        results.broken.push({ url: href, status: response.status() });
      }
    } catch (e) {
      results.broken.push({ url: href, error: e.message });
    }
  }

  console.log(`✅ 工作链接: ${results.working}`);
  console.log(`❌ 损坏链接:`, results.broken);

  await browser.close();
})();

端到端测试模式

运行测试

# 运行所有测试
bunx playwright test

# 有头模式(看到浏览器)
bunx playwright test --headed

# 特定文件
bunx playwright test tests/login.spec.ts

# 调试模式
bunx playwright test --debug

# UI模式(交互式)
bunx playwright test --ui

# 特定浏览器
bunx playwright test --project=chromium

# 生成报告
bunx playwright show-report

编写测试

import { test, expect } from '@playwright/test'

test.describe('登录流程', () => {
  test('成功登录', async ({ page }) => {
    await page.goto('/')
    await page.getByRole('link', { name: '登录' }).click()
    await page.getByLabel('电子邮件').fill('user@example.com')
    await page.getByLabel('密码').fill('password123')
    await page.getByRole('button', { name: '登录' }).click()

    await expect(page.getByRole('heading', { name: '仪表板' })).toBeVisible()
  })

  test('显示无效凭据错误', async ({ page }) => {
    await page.goto('/login')
    await page.getByLabel('电子邮件').fill('wrong@example.com')
    await page.getByLabel('密码').fill('wrongpassword')
    await page.getByRole('button', { name: '登录' }).click()

    await expect(page.getByText('无效凭据')).toBeVisible()
  })
})

选择器(最佳实践)

// ✅ 基于角色(推荐)
await page.getByRole('button', { name: '提交' })
await page.getByRole('link', { name: '首页' })

// ✅ 文本/标签
await page.getByText('你好世界')
await page.getByLabel('电子邮件')

// ✅ 测试ID(后备)
await page.getByTestId('提交按钮')

// ❌ 避免CSS选择器(脆弱)
await page.locator('.btn-primary')

断言

// 可见性
await expect(page.getByText('成功')).toBeVisible()
await expect(page.getByRole('button')).toBeEnabled()

// 文本
await expect(page.getByRole('heading')).toHaveText('欢迎')
await expect(page.getByRole('alert')).toContainText('错误')

// 属性
await expect(page.getByRole('link')).toHaveAttribute('href', '/home')

// URL/标题
await expect(page).toHaveURL('/dashboard')
await expect(page).toHaveTitle('仪表板')

// 计数
await expect(page.getByRole('listitem')).toHaveCount(5)

操作

// 点击
await page.getByRole('button').click()
await page.getByText('文件').dblclick()

// 输入
await page.getByLabel('电子邮件').fill('user@example.com')
await page.getByLabel('搜索').press('Enter')

// 选择
await page.getByLabel('国家').selectOption('us')

// 文件上传
await page.getByLabel('上传').setInputFiles('path/to/file.pdf')

网络模拟

test('模拟API响应', async ({ page }) => {
  await page.route('**/api/users', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([{ id: 1, name: '测试用户' }]),
    })
  })

  await page.goto('/users')
  await expect(page.getByText('测试用户')).toBeVisible()
})

视觉测试

test('捕获截图', async ({ page }) => {
  await page.goto('/')
  await page.screenshot({ path: 'screenshot.png', fullPage: true })
  await expect(page).toHaveScreenshot('homepage.png')
})

认证状态

// 登录后保存状态
setup('认证', async ({ page }) => {
  await page.goto('/login')
  await page.getByLabel('电子邮件').fill('user@example.com')
  await page.getByLabel('密码').fill('password123')
  await page.getByRole('button', { name: '登录' }).click()
  await page.context().storageState({ path: 'auth.json' })
})

// 在配置中重用
use: { storageState: 'auth.json' }

页面对象模型

// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test'

export class LoginPage {
  readonly emailInput: Locator
  readonly passwordInput: Locator
  readonly submitButton: Locator

  constructor(page: Page) {
    this.emailInput = page.getByLabel('电子邮件')
    this.passwordInput = page.getByLabel('密码')
    this.submitButton = page.getByRole('button', { name: '登录' })
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email)
    await this.passwordInput.fill(password)
    await this.submitButton.click()
  }
}

// 使用
const loginPage = new LoginPage(page)
await loginPage.login('user@example.com', 'password123')

可用帮助函数

可选实用函数在lib/helpers.js中:

const helpers = require('./lib/helpers');

// 检测运行中的开发服务器(关键 - 首先使用这个!)
const servers = await helpers.detectDevServers();
console.log('发现服务器:', servers);

// 带重试的安全点击
await helpers.safeClick(page, 'button.submit', { retries: 3 });

// 带清除的安全输入
await helpers.safeType(page, '#username', 'testuser');

// 带时间戳的截图
await helpers.takeScreenshot(page, 'test-result');

// 处理cookie横幅
await helpers.handleCookieBanner(page);

// 提取表格数据
const data = await helpers.extractTableData(page, 'table.results');

// 创建带有自定义头部的上下文
const context = await helpers.createContext(browser);

自定义HTTP头部

通过环境变量为所有HTTP请求配置自定义头部:

# 单个头部(常见情况)
PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill \
  cd $SKILL_DIR && node run.js /tmp/my-script.js

# 多个头部(JSON格式)
PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Debug":"true"}' \
  cd $SKILL_DIR && node run.js /tmp/my-script.js

使用helpers.createContext()时自动应用头部。

内联执行(简单任务)

对于快速一次性任务:

cd $SKILL_DIR && node run.js "
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('http://localhost:3001');
await page.screenshot({ path: '/tmp/quick-screenshot.png', fullPage: true });
console.log('截图保存');
await browser.close();
"

何时使用:

  • 内联:快速一次性任务(截图、元素检查)
  • 文件:复杂测试、可重用自动化

最佳实践

  • 关键:首先检测服务器 - 在编写测试代码之前始终运行detectDevServers()
  • 使用/tmp存储测试文件 - 写入/tmp/playwright-test-*.js,绝不写入技能目录
  • 参数化URL - 将检测/提供的URL放入TARGET_URL常量
  • 默认:可见浏览器 - 除非明确请求,始终使用headless: false
  • 优先基于角色的选择器 - 比CSS选择器更稳定
  • 信任自动等待 - 不需要手动睡眠
  • 每个测试获取新上下文 - 自动隔离
  • 并行运行测试 - 默认行为
  • 模拟外部依赖 - 使用page.route()
  • 使用跟踪查看器 - 时间旅行调试

提示

  • 放慢速度:使用slowMo: 100使动作可见
  • 等待策略:使用waitForURLwaitForSelectorwaitForLoadState而不是固定超时
  • 错误处理:始终使用try-catch进行健壮自动化
  • 控制台输出:使用console.log()跟踪进度

故障排除

Playwright未安装:

cd $SKILL_DIR && bun run setup

模块未找到: 确保通过run.js包装器从技能目录运行

浏览器未打开: 检查headless: false并确保显示可用

元素未找到: 添加等待:await page.waitForSelector('.element', { timeout: 10000 })

另请参阅

  • vitest-testing - 单元和集成测试
  • api-testing - HTTP API测试
  • test-quality-analysis - 测试质量模式

何时加载参考

当需要时加载references/API_REFERENCE.md

  • 高级选择器模式和定位器策略
  • 网络拦截和请求/响应模拟
  • 认证模式和会话管理
  • 视觉回归测试设置
  • 移动设备模拟配置
  • 性能测试和指标
  • 调试技术(跟踪查看器、检查器)
  • CI/CD管道集成
  • 使用axe-core进行可访问性测试
  • 数据驱动和参数化测试
  • 页面对象模型高级模式
  • 并行执行策略