playwright 最佳实践

发布于:2025-07-21 ⋅ 阅读:(14) ⋅ 点赞:(0)

playwright 最佳实践

Playwright 是由微软开发的现代化 E2E 自动化测试框架。

🎬 一、Playwright 简介

1. Playwright 是什么?

Playwright 是由微软开发的现代化 E2E 自动化测试框架,支持多浏览器(Chromium、Firefox、WebKit)和多语言(Node.js、Python、Java、C#),特别适合做 UI 自动化测试和爬虫。

示例代码

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

test('使用 Google 搜索', async ({ page }) => {
  // 进入 google 搜索页
  await page.goto('https://www.google.com');
  // 模拟手动输入 Playwright
  await page.fill('input[name="q"]', 'Playwright');
  // 模拟回车确认搜索
  await page.keyboard.press('Enter');
  // 校验
  await expect(page.locator('#search')).toContainText('Playwright');
});

2. Playwright 能做什么?

  • 端到端自动化测试
    • 登录、下单、支付、弹窗、表单交互等全流程模拟
  • 浏览器爬虫
    • 提取页面信息,模拟滚动、点击、登录等
  • 回归测试
    • CI/CD 中检测新版本是否破坏 UI 功能
  • 可视化回归测试
    • 截图、视频、trace,查看测试中实际发生了什么

3. Playwright 特性

特性 说明
✅ 多浏览器支持 支持 Chromium (Chrome/Edge)FirefoxWebKit (Safari)
✅ 多语言支持 支持 JavaScript/TypeScriptPythonJavaC#/.NET
✅ 自动等待机制 自动等待元素出现、可点击、导航完成等,不容易出 flaky(随机失败)测试
✅ 原生支持 iframe、多页签 轻松处理复杂页面中的 iframe、弹窗、多标签页
✅ 内置测试运行器 自带功能完整的 test runner(类似 Jest + Mocha + Chai + Puppeteer)
✅ 网络拦截与 Mock 可模拟后端接口,进行无网络依赖测试
✅ 跨平台运行 支持 Windows、macOS、Linux、本地/CI 环境运行
✅ 丰富调试工具 提供 UI trace viewer、codegen 工具、screenshot/video trace

🚀 二、Playwright 实践

1. Playwright 安装依赖

tests/
  ├── login.spec.ts
  ├── dashboard.spec.ts
  └── utils/
      └── auth.ts
playwright.config.ts

官网参考

$ npm init playwright@latest
# 或手动安装
$ npm install -D @playwright/test
$ npx playwright install

2. 测试用例基础结构

如何编写测试用例

import { test, expect } from '@playwright/test';
// test.describe 理解为测试分组
test.describe('用户模块', () => {
  // test 理解为定义了一个测试用例
  test('登录', () => {
    // 登录相关测试
  });

  test('注册', () => {
    // 注册相关测试
  });
});

3. 常见 API 用法

✅ 页面导航 & 交互

await page.goto('https://example.com');        // 打开页面
await page.click('text=Sign in');              // 点击按钮或链接
await page.fill('#username', 'myname');        // 填写输入框
await page.press('#input', 'Enter');           // 模拟按键
await page.selectOption('select#role', 'admin'); // 选择下拉框选项

✅ 元素等待

await page.waitForSelector('#submit-btn');        // 等待出现并可见
await page.waitForSelector('.modal', { state: 'hidden' });  // 等待消失
await expect(page.locator('h1')).toHaveText('Hello');       // 推荐写法

✅ 截图 / 视频 / Trace

await page.screenshot({ path: 'screenshot.png' });   // 截图
await page.context().tracing.start({ screenshots: true, snapshots: true });
await page.context().tracing.stop({ path: 'trace.zip' });  // 查看 UI 行为

✅ 请求拦截 / mock 接口

await page.route('**/api/**', route =>
  route.fulfill({ status: 200, body: JSON.stringify({ data: [] }) })
);

4. 模块封装

封装一个登录模块

// login.page.ts
export class LoginPage {
  constructor(private page: Page) {}

  async login(username: string, password: string) {
    await this.page.fill('#user', username);
    await this.page.fill('#pass', password);
    await this.page.click('text=Login');
  }
}

5. 配置项

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

export default defineConfig({
  use: {
    // Emulates `'prefers-colors-scheme'` media feature.
    colorScheme: 'dark',

    // Context geolocation.
    geolocation: { longitude: 12.492507, latitude: 41.889938 },

    // Emulates the user locale.
    locale: 'en-GB',

    // Grants specified permissions to the browser context.
    permissions: ['geolocation'],

    // Emulates the user timezone.
    timezoneId: 'Europe/Paris',

    // Viewport used for all pages in the context.
    viewport: { width: 1280, height: 720 },
  },
});

5. demo 演示

  • ✅ 示例 1:模拟登录
import { test, expect } from '@playwright/test';

test.describe('登录功能', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('https://yourapp.com/login');
  });

  test('登录成功跳转到首页', async ({ page }) => {
    await page.fill('#username', 'robbie');
    await page.fill('#password', 'correct_password');
    await page.click('button[type="submit"]');

    await expect(page).toHaveURL(/dashboard/);
    await expect(page.locator('text=欢迎回来')).toBeVisible();
  });

  test('登录失败提示错误', async ({ page }) => {
    await page.fill('#username', 'robbie');
    await page.fill('#password', 'wrong_password');
    await page.click('button[type="submit"]');

    await expect(page.locator('.error')).toHaveText('用户名或密码错误');
  });
});

  • ✅ 示例 2:等待 loading 消失再点击按钮
test('加载后点击按钮', async ({ page }) => {
  await page.goto('https://yourapp.com/profile');

  // 等待 loading 动画结束
  await page.waitForSelector('.loading-spinner', { state: 'hidden' });

  // 再点击保存按钮
  await page.click('button.save');
  await expect(page.locator('.toast')).toHaveText('保存成功');
});

  • ✅ 示例 3:表单验证(空值、错误格式)
test('表单校验错误提示', async ({ page }) => {
  await page.goto('https://yourapp.com/register');

  await page.click('button[type="submit"]');

  await expect(page.locator('#email-error')).toHaveText('邮箱不能为空');
  await expect(page.locator('#password-error')).toHaveText('密码不能为空');
});
  • ✅ 示例 4:打开弹窗并验证内容
test('打开弹窗并验证内容', async ({ page }) => {
  await page.goto('https://yourapp.com');

  await page.click('button.view-detail');
  await page.waitForSelector('.modal-content');

  await expect(page.locator('.modal-title')).toHaveText('详情信息');
});

  • ✅ 示例 5:拦截并 Mock 接口返回数据
test('拦截接口并返回自定义数据', async ({ page }) => {
  await page.route('**/api/user/info', route =>
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ name: '测试用户', id: 1 }),
    })
  );

  await page.goto('https://yourapp.com/user');
  await expect(page.locator('.username')).toHaveText('测试用户');
});

  • ✅ 示例 6:截图用于 UI 回归测试
test('页面截图', async ({ page }) => {
  await page.goto('https://yourapp.com/dashboard');
  await page.screenshot({ path: 'screenshots/dashboard.png', fullPage: true });
});
  • ✅ 示例 7:使用 test.step 添加步骤标记(增强 Trace 可读性)
test('使用步骤结构化流程', async ({ page }) => {
  await test.step('打开登录页', async () => {
    await page.goto('https://yourapp.com/login');
  });

  await test.step('填写登录表单', async () => {
    await page.fill('#username', 'robbie');
    await page.fill('#password', '123456');
  });

  await test.step('点击登录按钮并跳转', async () => {
    await page.click('button[type="submit"]');
    await expect(page).toHaveURL(/dashboard/);
  });
});

  • ✅ 示例 8:使用 test.use 指定不同登录状态

Playwright 支持通过 test.use() 给某些 describe 套件设置专属上下文(例如已登录用户):

test.describe.use({ storageState: 'logged-in-state.json' });

test('已登录用户访问主页', async ({ page }) => {
  await page.goto('https://yourapp.com');
  await expect(page.locator('.username')).toHaveText('robbie');
});

参考文档


网站公告

今日签到

点亮在社区的每一天
去签到