首页 / Playwright 入门教程 / 你的第一个测试:Playwright Test 起步

Playwright 入门教程

你的第一个测试:Playwright Test 起步

本教程共 59 篇 · 第 4 篇 · 更新于 2026-08-04 · 约 9 分钟阅读

Playwright第一个测试expect断言测试隔离钩子

4. 你的第一个测试:Playwright Test 起步

本节目标:亲手写出并跑通一个 Playwright 测试,看懂它的每一行,也看懂它失败时在说什么。

测试就干两件事

先给个心智模型,后面所有内容都挂在这上面:

执行操作(Actions) + 断言状态(Assertions)

打开页面、点按钮、填表单,这是操作。检查标题对不对、元素在不在、文字是什么,这是断言。

就这么简单。复杂度都来自「怎么找到元素」和「什么时候检查」,而这两件事 Playwright 帮你处理了大半。

最小可跑示例

tests/ 下新建 first.spec.ts

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

test('页面标题包含 Playwright', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await expect(page).toHaveTitle(/Playwright/);
});

跑起来:

npx playwright test tests/first.spec.ts

四行代码,逐行拆。

第一行:导入

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

test 用来定义用例,expect 用来写断言。两个都从 @playwright/test 来。

Note

用 JavaScript 而不是 TypeScript 的话,建议在文件顶部加一行 // @ts-check。这样 VS Code 也能给你类型检查和补全。

第二行:定义用例

test('页面标题包含 Playwright', async ({ page }) => {

第一个参数是用例名,会显示在终端和报告里。名字要写清楚,失败时你只看得到这行字。

第二个参数是异步函数。Playwright 的 API 基本都返回 Promise,所以必须 async

参数解构出来的 page,是 Playwright 的内置 Fixture(夹具)。你在参数里写了它,测试运行器就给你造一个;不写就不造。这叫惰性初始化,能省下不必要的开销。

page 代表一个浏览器页面标签。

第三行:导航

await page.goto('https://playwright.dev/');

大部分测试都从跳转开始。

goto 会等页面到达加载状态才往下走,不用你手动等。

第四行:断言

await expect(page).toHaveTitle(/Playwright/);

这里传的是正则,意思是标题里包含 Playwright 就算过。想精确匹配就传字符串。

注意前面的 await。Playwright 的这类断言是会重试的 —— 条件暂时不满足就等一会儿再看,直到超时(默认 5 秒)才判失败。这就是所谓的 Web 优先断言(Web-First Assertions)。

加上交互

只看标题太单薄,加个点击:

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

test('点击 Get started 跳到安装页', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // 按角色 + 名称定位链接,然后点击
  await page.getByRole('link', { name: 'Get started' }).click();

  // 断言新页面上有对应标题
  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

getByRole('link', { name: 'Get started' }) 创建了一个 Locator(定位器)

定位器不是「立刻找元素」,而是「描述怎么找元素」。真正查找发生在你调用 .click() 的那一刻。

这个设计很关键 —— 页面重新渲染了也没关系,定位器每次用都会重新找一遍。第 9 章会展开讲。

常用操作和断言

先混个眼熟,具体用法后面章节展开。

操作

方法作用
locator.click()点击
locator.fill()填写输入框
locator.check() / uncheck()勾选 / 取消勾选
locator.hover()鼠标悬停
locator.focus()聚焦
locator.press()按键
locator.selectOption()下拉框选择
locator.setInputFiles()选择上传文件

断言

断言含义
expect(locator).toBeVisible()元素可见
expect(locator).toBeEnabled()控件可用
expect(locator).toBeChecked()复选框被勾选
expect(locator).toHaveText()文本完全匹配
expect(locator).toContainText()文本包含
expect(locator).toHaveValue()输入框的值
expect(locator).toHaveCount()匹配元素的个数
expect(page).toHaveTitle()页面标题
expect(page).toHaveURL()页面地址
Tip

上面这些都要加 await,因为它们会自动重试。

还有一类通用断言比如 toBeTruthy()toEqual()toContain(),检查的是已经拿到手的普通值,是同步的,不用 await

expect(success).toBeTruthy();

忘了写 await 是新手最常见的错误。断言看起来「过了」,其实根本没执行完。

故意让它失败一次

我强烈建议你现在就做这个实验。把断言改成一个肯定不成立的值:

await expect(page).toHaveTitle(/这个标题肯定不存在/);

再跑一次,终端会给你:

  • 用例名和文件位置(哪一行)
  • Expected(期望值)和 Received(实际值)
  • 完整的调用日志 —— Playwright 重试了多少次、每次拿到什么
Error: Timed out 5000ms waiting for expect(locator).toHaveTitle(expected)

Expected pattern: /这个标题肯定不存在/
Received string:  "Fast and reliable end-to-end testing for modern web apps | Playwright"

这段信息的价值在于:它告诉你实际是什么。很多时候一眼就看出是自己写错了选择器或者拼错了文字。

失败之后 HTML 报告会自动打开。没开就手动:

npx playwright show-report

测试之间是隔离的

这是 Playwright 的一个重要保证。

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

test('第一个测试', async ({ page }) => {
  // 这个 page 属于一个专门为它创建的独立浏览器上下文
});

test('第二个测试', async ({ page }) => {
  // 这个 page 和上面那个完全隔离
});

每个用例拿到的 page 都来自独立的 Browser Context(浏览器上下文),相当于一个全新的浏览器配置文件。Cookie、localStorage、缓存互不干扰。

好处很直接:

  • 用例之间没有隐式依赖,谁先跑谁后跑都一样
  • 可以放心并行执行
  • 一个用例挂了不会连累其他用例

代价是每个用例都得从头来(比如重新登录)。后面讲 storageState 时会给出优化方案。

分组和钩子

用例多了就要组织。test.describe 用来分组,test.beforeEach 用来抽公共前置步骤:

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

test.describe('导航相关', () => {
  test.beforeEach(async ({ page }) => {
    // 每个用例开始前都先跳到首页
    await page.goto('https://playwright.dev/');
  });

  test('停在首页地址', async ({ page }) => {
    await expect(page).toHaveURL('https://playwright.dev/');
  });

  test('能打开文档页', async ({ page }) => {
    await page.getByRole('link', { name: 'Docs' }).click();
    await expect(page).toHaveURL(/.*docs/);
  });
});

四个钩子的区别:

  • beforeEach / afterEach每个用例前后各跑一次
  • beforeAll / afterAll:每个 worker(工作进程)里跑一次,在所有用例之前 / 之后
Warning

beforeAll 里别做「会被后续用例改坏」的准备工作。它只跑一次,但用例是隔离的、还可能并行。共享状态在这里很容易出问题。

起步阶段的几个建议

  1. 用例名写人话test('登录后能看到用户名')test('test login 2') 强一百倍。
  2. 一个用例只验一件事。挂了能立刻知道是哪儿的问题。
  3. 别用 sleep。想等就用断言等 —— await expect(x).toBeVisible() 天然会等。
  4. 优先用 getByRole。这是官方推荐的第一选择,理由在第 11 章。
  5. await 别漏。漏了的话测试会「假过」,非常隐蔽。

小结

一个 Playwright 测试的骨架就四块:导入 → 定义用例 → 操作 → 断言。

page 是内置夹具,用例之间天然隔离。断言会自动重试,所以你几乎不需要写等待。

失败信息里的 Expected / Received 和调用日志,是你排错的主要依据,养成看它的习惯。

下一章我们跳出单个文件,看看测试运行器整体是怎么运转的。