Skip to main content

模擬瀏覽器 API

簡介

Playwright 對大部分瀏覽器功能提供原生支援。然而,有些實驗性 API 和尚未被所有瀏覽器完全支援的 API。在這些情況下,Playwright 通常不提供專門的自動化 API。您可以使用模擬來測試應用程式在這些情況下的行為。本指南提供一些範例。

讓我們考慮一個使用 battery API 來顯示裝置電池狀態的網頁應用程式。我們將模擬電池 API 並檢查頁面是否正確顯示電池狀態。

建立模擬

由於頁面可能在載入時很早就呼叫 API,因此在頁面開始載入之前設定所有模擬是很重要的。最簡單的方法是呼叫 page.addInitScript()

await page.addInitScript(() => {
const mockBattery = {
level: 0.75,
charging: true,
chargingTime: 1800,
dischargingTime: Infinity,
addEventListener: () => { }
};
// 覆寫方法,始終回傳模擬電池資訊。
window.navigator.getBattery = async () => mockBattery;
});

完成此設定後,您就可以導航頁面並檢查其 UI 狀態:

// 在每個測試之前設定模擬 API。
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
const mockBattery = {
level: 0.90,
charging: true,
chargingTime: 1800, // 秒
dischargingTime: Infinity,
addEventListener: () => { }
};
// 覆寫方法,始終回傳模擬電池資訊。
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 是唯讀的,因此您無法指派給 navigator 屬性。例如,

// 以下程式碼沒有效果。
navigator.cookieEnabled = true;

然而,如果屬性是 configurable,您仍然可以使用純 JavaScript 來覆寫它:

await page.addInitScript(() => {
Object.defineProperty(Object.getPrototypeOf(navigator), 'cookieEnabled', { value: false });
});

驗證 API 呼叫

有時檢查頁面是否進行了所有預期的 API 呼叫是很有用的。您可以記錄所有 API 方法呼叫,然後與黃金結果進行比較。page.exposeFunction() 可能有助於將訊息從頁面傳回到測試程式碼:

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}`)
};
// 覆寫方法,始終回傳模擬電池資訊。
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'
]);
});

更新模擬

為了測試應用程式是否正確反映電池狀態更新,確保模擬電池物件觸發與瀏覽器實作相同的事件是很重要的。以下測試展示如何達成這個目標:

test('update battery status (no golden)', async ({ page }) => {
await page.addInitScript(() => {
// 當電池狀態變更時通知對應監聽器的模擬類別。
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();
// 覆寫方法,始終回傳模擬電池資訊。
window.navigator.getBattery = async () => mockBattery;
// 將模擬物件儲存在 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');
});