TypeScript API

API 教程与代码示例

掌握 Playwright Test 最常用的 TypeScript API,配合可运行的代码示例快速学习。

页面导航

navigation.spec.ts
import { test, expect } from '@playwright/test';

test('navigation examples', async ({ page }) => {
  // 基本导航
  await page.goto('https://example.com');
  await page.goto('https://example.com/login', {
    waitUntil: 'networkidle',
  });

  // waitUntil 选项:
  //   'load'             — load 事件(默认)
  //   'domcontentloaded' — DOMContentLoaded
  //   'networkidle'      — 500ms 无网络请求
  //   'commit'           — 收到响应

  // 前进后退与刷新
  await page.goBack();
  await page.goForward();
  await page.reload();

  // 获取页面信息
  console.log(page.url);
  console.log(await page.title());
  console.log(await page.content());

  // baseURL 配合 — 在 config 中设置后可用相对路径
  await page.goto('/');          // → baseURL + '/'
  await page.goto('/dashboard'); // → baseURL + '/dashboard'
});

元素定位

Playwright 推荐优先使用语义化定位方法(getByRole 等),它们更贴近用户视角。

locators.spec.ts — 推荐方式
// ===== 推荐:语义化定位 =====

// 按 ARIA 角色定位 — 最推荐
page.getByRole('button', { name: '提交' });
page.getByRole('heading', { name: '欢迎', level: 1 });
page.getByRole('link', { name: '首页' });
page.getByRole('textbox', { name: '用户名' });
page.getByRole('checkbox', { name: '记住我' });

// 按文本
page.getByText('立即注册');
page.getByText('注册', { exact: true });

// 按 label / placeholder / alt / test-id
page.getByLabel('邮箱地址');
page.getByPlaceholder('请输入搜索内容');
page.getByAltText('公司 Logo');
page.getByTestId('submit-button');
locators.spec.ts — CSS / 链式过滤
// CSS 选择器
page.locator('#login-form');
page.locator('.btn.btn-primary');
page.locator('input[type="email"]');

// 链式过滤
page.locator('.product-card')
  .filter({ hasText: 'TypeScript' })
  .first();

// 包含特定子元素
page.locator('.card').filter({
  has: page.getByRole('button', { name: '购买' }),
});

// 第 N 个 / 计数
page.locator('li.item').nth(2);
page.locator('li.item').first();
const count = await page.locator('li').count();

页面操作

actions.spec.ts
// ===== 点击 =====
await page.getByRole('button', { name: '提交' }).click();
await page.locator('#item').dblclick();
await page.locator('#menu').click({ button: 'right' });
await page.locator('#link').click({ modifiers: ['Shift'] });

// ===== 输入 =====
await page.getByLabel('用户名').fill('admin');        // 清空再填
await page.getByLabel('搜索').pressSequentially('Playwright', {
  delay: 80,  // 逐字输入
});
await page.keyboard.press('Enter');

// ===== 选择 =====
await page.locator('#city').selectOption('shanghai');
await page.locator('#city').selectOption({ label: '上海' });

// ===== 复选框 =====
await page.getByRole('checkbox', { name: '同意' }).check();
await page.getByRole('checkbox', { name: '同意' }).uncheck();
await page.getByRole('checkbox', { name: '同意' }).setChecked(true);

// ===== 文件上传 =====
await page.locator('input[type="file"]').setInputFiles('report.pdf');
await page.locator('input[type="file"]').setInputFiles([]);

// ===== 拖放 / 悬停 =====
await page.locator('#source').dragTo(page.locator('#target'));
await page.locator('.dropdown').hover();

// ===== 截图 =====
await page.screenshot({ path: 'full.png', fullPage: true });
await page.locator('.chart').screenshot({ path: 'chart.png' });

断言(Assertions)

Playwright Test 提供自带自动重试的 expect 断言。

assertions.spec.ts
import { test, expect } from '@playwright/test';

test('assertions', async ({ page }) => {
  // ===== 页面断言 =====
  await expect(page).toHaveTitle(/Dashboard/);
  await expect(page).toHaveURL('**/dashboard');

  // ===== 元素断言(自动重试) =====
  const msg = page.locator('.message');
  await expect(msg).toBeVisible();
  await expect(msg).toHaveText('操作成功');
  await expect(msg).toContainText('成功');
  await expect(msg).toHaveClass(/success/);
  await expect(msg).toHaveAttribute('role', 'alert');
  await expect(msg).toBeEnabled();

  // 否定
  await expect(msg).not.toBeVisible();

  // 列表
  const items = page.locator('ul > li');
  await expect(items).toHaveCount(5);
  await expect(items).toHaveText(['A', 'B', 'C', 'D', 'E']);

  // ===== 截图对比(Visual) =====
  await expect(page).toHaveScreenshot('home.png');
  await expect(page.locator('.hero')).toHaveScreenshot('hero.png', {
    maxDiffPixelRatio: 0.01,
  });

  // ===== 软断言(不立即失败) =====
  await expect.soft(msg).toHaveText('OK');
  await expect.soft(page).toHaveTitle('Home');
  // 所有软断言结果在测试结束时汇总
});

网络处理

network.spec.ts
import { test, expect } from '@playwright/test';

test('network interception', async ({ page }) => {
  // ===== 拦截并修改 =====
  await page.route('**/*.{png,jpg,gif}', route => route.abort());

  // ===== Mock API =====
  await page.route('**/api/users', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        users: [{ name: 'Alice' }, { name: 'Bob' }],
      }),
    });
  });

  // ===== 修改请求头 =====
  await page.route('**/api/**', async route => {
    const headers = route.request().headers();
    await route.continue_({
      headers: { ...headers, 'X-Custom': 'value' },
    });
  });

  // ===== 等待特定响应 =====
  const responsePromise = page.waitForResponse('**/api/submit');
  await page.getByRole('button', { name: '提交' }).click();
  const response = await responsePromise;
  expect(response.status()).toBe(200);
});

// ===== API 测试(使用 request fixture) =====
test('API testing', async ({ request }) => {
  const res = await request.post('/api/login', {
    data: { username: 'admin', password: 'secret' },
  });
  expect(res.ok()).toBeTruthy();

  const body = await res.json();
  expect(body.token).toBeDefined();
});

Test Fixtures

Playwright Test 的核心特性——依赖注入系统。

fixtures.ts — 自定义 Fixture
import { test as base, expect, Page } from '@playwright/test';

// 定义自定义 fixture 类型
type MyFixtures = {
  adminPage: Page;
  todoPage: TodoPage;
};

// Page Object
class TodoPage {
  constructor(private page: Page) {}

  async goto() {
    await this.page.goto('/todos');
  }

  async addTodo(text: string) {
    await this.page.getByPlaceholder('What needs to be done?').fill(text);
    await this.page.keyboard.press('Enter');
  }

  get items() {
    return this.page.getByTestId('todo-item');
  }
}

// 扩展 test
export const test = base.extend<MyFixtures>({
  // 每个测试获得已登录的管理员页面
  adminPage: async ({ browser }, use) => {
    const ctx = await browser.newContext({
      storageState: 'admin-auth.json',
    });
    const page = await ctx.newPage();
    await use(page);
    await ctx.close();
  },

  // Page Object fixture
  todoPage: async ({ page }, use) => {
    const todoPage = new TodoPage(page);
    await todoPage.goto();
    await use(todoPage);
  },
});
todo.spec.ts — 使用自定义 Fixture
import { test } from './fixtures';
import { expect } from '@playwright/test';

test('can add todos', async ({ todoPage }) => {
  await todoPage.addTodo('Learn Playwright');
  await todoPage.addTodo('Write tests');
  await expect(todoPage.items).toHaveCount(2);
});

test('admin can manage', async ({ adminPage }) => {
  await adminPage.goto('/admin');
  await expect(adminPage.getByText('Admin Panel')).toBeVisible();
});

高级功能

auth-setup.ts
import { test as setup } from '@playwright/test';

// 全局 setup:登录并保存状态
setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('[email protected]');
  await page.getByLabel('Password').fill('password');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.waitForURL('/dashboard');

  // 保存状态供后续测试使用
  await page.context().storageState({
    path: '.auth/user.json',
  });
});
multi-tab.spec.ts
test('multi tab', async ({ page, context }) => {
  // 新标签页
  const [newPage] = await Promise.all([
    context.waitForEvent('page'),
    page.getByText('Open tab').click(),
  ]);
  await expect(newPage).toHaveURL(/new-tab/);

  // 弹窗
  page.on('dialog', d => d.accept());

  // 下载
  const [download] = await Promise.all([
    page.waitForEvent('download'),
    page.getByText('Download').click(),
  ]);
  await download.saveAs('report.xlsx');
});
device.spec.ts
import { devices } from '@playwright/test';

// 在 config 中配置设备
// projects: [
//   { name: 'Mobile', use: devices['iPhone 14'] }
// ]

// 或在测试中动态创建
test('mobile view', async ({ browser }) => {
  const ctx = await browser.newContext({
    ...devices['iPhone 14'],
    locale: 'zh-CN',
    geolocation: { latitude: 31.23, longitude: 121.47 },
    permissions: ['geolocation'],
    colorScheme: 'dark',
  });
  const page = await ctx.newPage();
  // ...
});
trace.spec.ts
// 在 config 中启用 trace
// use: { trace: 'on-first-retry' }

// 或手动控制
test('with trace', async ({ page, context }) => {
  await context.tracing.start({
    screenshots: true,
    snapshots: true,
    sources: true,
  });

  await page.goto('/');
  // ... test steps ...

  await context.tracing.stop({
    path: 'trace.zip',
  });
  // npx playwright show-trace trace.zip
});