模拟 API 响应与 Mock
本教程共 59 篇 · 第 31 篇 · 更新于 2026-08-04 · 约 11 分钟阅读
本节目标:学完能用一个假的接口响应替掉真实后端,让测试不依赖网络也能稳定通过。
上一章讲了怎么拦请求。拦下来之后呢?最常见的一招是:自己造一份假数据还回去,让页面以为接口真的返回了。这招在 Playwright 里叫 Mock(模拟)。
为什么要用 Mock
真实接口常常不靠谱。开发环境后端没写好,测试就卡住了;接口偶发超时,你的测试就误报红。Mock 把这些不确定性一刀切掉——接口返回什么,你说了算。
好处很直接:测试快、稳定、还能造出真实环境难触发的边界数据,比如一个超长的用户名。
NoteMock 的核心是
route.fulfill(),它直接给浏览器一个响应,请求压根不会发到服务器。
用 fulfill 返回假数据
下面这个例子拦截了水果列表接口,返回一条假数据。注意要写在 page.goto 之前,先布好陷阱。
import { test, expect } from '@playwright/test';
test('用假数据替代接口', async ({ page }) => {
// 先拦截接口,再打开页面
await page.route('*/**/api/v1/fruits', async route => {
const json = [{ name: 'Strawberry', id: 21 }];
await route.fulfill({ json });
});
await page.goto('https://demo.playwright.dev/api-mocking');
// 页面里应该出现我们造的 Strawberry
await expect(page.getByText('Strawberry')).toBeVisible();
});
fulfill 支持 json、body、status、headers、contentType、path 等字段。返回 JSON 时直接用 json 最省事,Playwright 会顺手把 Content-Type 设好。
Tip测试没通过,打开 Trace(追踪)看一眼回放。你会清楚看到接口根本没发出去,而是被
fulfill拦下喂了假数据——这比对着代码干瞪眼快多了。
先调真接口,再改返回
有时你不想完全断网,只想给真实数据加点料。那就先 route.fetch() 拿到真响应,改完再 fulfill。
test('调真接口再加一条数据', async ({ page }) => {
await page.route('*/**/api/v1/fruits', async route => {
const response = await route.fetch(); // 真的去请求一次
const json = await response.json();
json.push({ name: 'Loquat', id: 100 }); // 往里塞一条
await route.fulfill({ response, json }); // 用原响应,但换成新 json
});
await page.goto('https://demo.playwright.dev/api-mocking');
await expect(page.getByText('Loquat', { exact: true })).toBeVisible();
});
这种「真请求 + 改返回」的套路,特别适合测「新增后列表刷新」这类逻辑。
用 HAR 文件批量录制
接口一多,一个个写 fulfill 就累了。HAR(HTTP Archive)文件能把页面跑过的所有请求录下来,测试时按记录回放。
录制一次:
// update: true 让 Playwright 真实发请求,并把结果写进 HAR
await page.routeFromHAR('./hars/fruit.har', {
url: '*/**/api/v1/fruits',
update: true,
});
录好之后把 update 关掉(去掉这一行或改成 false),测试就完全照 HAR 回放,不再碰真实接口。
想改假数据也不用重录:打开 hars 目录,里面有个哈希命名的 .txt 文件,直接编辑它里面的 JSON 就行。
WarningHAR 回放对 URL 和 HTTP 方法卡得很严,POST 还会比对请求体。多个录制都能匹配时,挑请求头最像的那个。
Mock 浏览器自带 API
有些浏览器能力,比如电量 API(Battery API),Playwright 没法直接自动化。这时用 page.addInitScript() 在页面加载前偷偷换掉它。
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
const mockBattery = {
level: 0.90,
charging: true,
chargingTime: 1800,
dischargingTime: Infinity,
addEventListener: () => {},
};
// 让页面拿到的永远是这份假电量
window.navigator.getBattery = async () => mockBattery;
});
});
碰到只读属性直接赋值不生效,用 Object.defineProperty 强行覆盖:
await page.addInitScript(() => {
Object.defineProperty(navigator, 'onLine', { get: () => false });
});
想确认页面真的调用过某个 API,可以用 page.exposeFunction() 在页面里挂一个回调,把调用记录传回测试进程再断言。
TipMock 浏览器 API 要趁早。页面往往在加载那一瞬间就调用了,所以必须写在
addInitScript里,赶在页面脚本执行前生效。写在goto之后就晚了。
小结
- Mock 的核心是
route.fulfill(),请求根本不发出去,响应由你说了算。 - 拦截必须写在
page.goto()之前,先布好陷阱再开页面。 - 想在真数据上加料,用
route.fetch()取回真响应,改完再fulfill({ response, json })。 - 接口一多就上 HAR:
routeFromHAR配update: true录一次,之后关掉 update 回放。 - 改 HAR 数据不用重录,编辑
hars目录里那个哈希.txt文件即可。 - 浏览器自带 API(电量、在线状态等)用
addInitScript在加载前替换掉。 - Mock 是双刃剑:又快又稳,但用多了就离真实环境越远。我的习惯是联调阶段用真接口,冒烟和回归阶段再用 Mock 提速。
下一章讲怎么反过来——不拦请求,而是静静看着它,然后对返回做断言。