Skip to main content

Mock APIs

Playwright 为大多数浏览器功能提供原生支持。但是,有些实验性 API 和并非所有浏览器都(尚未)完全支持的 API。在这种情况下,Playwright 通常不提供专用的自动化 API。您可以使用 mock 来测试应用程序在这种情况下的行为。本指南给出了几个示例。

简介

让我们考虑一个使用 电池 API 显示设备电池状态的 Web 应用程序。我们将 mock 电池 API 并检查页面是否正确显示电池状态。

创建 mock

由于页面可能会在加载时很早调用 API,因此在页面开始加载之前设置所有 mock 非常重要。实现此目的的最简单方法是调用 page.addInitScript(script[, arg])

await page.addInitScript(() => {
const mockBattery = {
level: 0.75,
charging: true,
chargingTime: 1800,
dischargingTime: Infinity,
addEventListener: () => { }
};
// 覆盖该方法以始终返回 mock 电池信息。
window.navigator.getBattery = async () => mockBattery;
});

完成后,您可以导航页面并检查其 UI 状态:

// 在每个测试之前配置 mock API。
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
const mockBattery = {
level: 0.90,
charging: true,
chargingTime: 1800, // 秒
dischargingTime: Infinity,
addEventListener: () => { }
};
// 覆盖该方法以始终返回 mock 电池信息。
window.navigator.getBattery = async () => mockBattery;
});
});

test('show battery status', async ({ page }) => {
await page.goto('/');
await expect(page.locator('.battery-percentage')).toHaveText('90%');
await expect(page.locator('.battery-status')).toHaveText('Adapter');
await expect(page.locator('.battery-fully')).toHaveText('00:30');
});

验证 API 调用

有时检查页面是否进行了所有预期的 API 调用很有用。您可以记录所有 API 方法调用,然后将它们与黄金结果进行比较。page.exposeFunction(name, callback) 对于将消息从页面传回测试代码可能会派上用场:

test('log battery calls', async ({ page }) => {
const log = [];
// 公开函数以将消息推送到 Node.js 脚本。
await page.exposeFunction('logCall', msg => log.push(msg));
await page.addInitScript(() => {
const mockBattery = {
level: 0.75,
charging: true,
chargingTime: 1800,
dischargingTime: Infinity,
// 记录 addEventListener 调用。
addEventListener: (name, cb) => logCall(`addEventListener:${name}`)
};
// 覆盖该方法以始终返回 mock 电池信息。
window.navigator.getBattery = async () => {
logCall('getBattery');
return mockBattery;
};
});

await page.goto('/');
await expect(page.locator('.battery-percentage')).toHaveText('75%');

// 将实际调用与黄金结果进行比较。
expect(log).toEqual([
'getBattery',
'addEventListener:chargingchange',
'addEventListener:levelchange'
]);
});

更新 mock

要测试应用程序是否正确反映电池状态更新,重要的是要确保 mock 电池对象触发与浏览器实现相同的事件。以下测试演示了如何实现这一点:

test('update battery status (no golden)', async ({ page }) => {
await page.addInitScript(() => {
// Mock 类,当电池状态更改时将通知相应的侦听器。
class BatteryMock {
level = 0.10;
charging = false;
chargingTime = 1800;
dischargingTime = Infinity;
_chargingListeners = [];
_levelListeners = [];
addEventListener(eventName, listener) {
if (eventName === 'chargingchange')
this._chargingListeners.push(listener);
if (eventName === 'levelchange')
this._levelListeners.push(listener);
}
// 将由测试调用。
_setLevel(value) {
this.level = value;
this._levelListeners.forEach(cb => cb());
}
_setCharging(value) {
this.charging = value;
this._chargingListeners.forEach(cb => cb());
}
};
const mockBattery = new BatteryMock();
// 覆盖该方法以始终返回 mock 电池信息。
window.navigator.getBattery = async () => mockBattery;
// 将 mock 对象保存在 window 上以便于访问。
window.mockBattery = mockBattery;
});

await page.goto('/');
await expect(page.locator('.battery-percentage')).toHaveText('10%');

// 将电量更新为 27.5%
await page.evaluate(() => window.mockBattery._setLevel(0.275));
await expect(page.locator('.battery-percentage')).toHaveText('27.5%');
await expect(page.locator('.battery-status')).toHaveText('Battery');

// 模拟连接适配器
await page.evaluate(() => window.mockBattery._setCharging(true));
await expect(page.locator('.battery-status')).toHaveText('Adapter');
await expect(page.locator('.battery-fully')).toHaveText('00:30');
});