你的第一个测试:Playwright Test 起步
本教程共 59 篇 · 第 4 篇 · 更新于 2026-08-04 · 约 9 分钟阅读
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里别做「会被后续用例改坏」的准备工作。它只跑一次,但用例是隔离的、还可能并行。共享状态在这里很容易出问题。
起步阶段的几个建议
- 用例名写人话。
test('登录后能看到用户名')比test('test login 2')强一百倍。 - 一个用例只验一件事。挂了能立刻知道是哪儿的问题。
- 别用
sleep。想等就用断言等 ——await expect(x).toBeVisible()天然会等。 - 优先用
getByRole。这是官方推荐的第一选择,理由在第 11 章。 await别漏。漏了的话测试会「假过」,非常隐蔽。
小结
一个 Playwright 测试的骨架就四块:导入 → 定义用例 → 操作 → 断言。
page 是内置夹具,用例之间天然隔离。断言会自动重试,所以你几乎不需要写等待。
失败信息里的 Expected / Received 和调用日志,是你排错的主要依据,养成看它的习惯。
下一章我们跳出单个文件,看看测试运行器整体是怎么运转的。