Học Playwright tiếng Việt, Cộng đồng Playwright cho người Việt

Vọc Vạch Playwright

[Vọc Playwright] Mock browser APIs

https://playwright.dev/docs/mock-browser-apis

  • Thường thì Playwright sẽ cung cấp native support cho hầu hết các tính năng của browser.
  • Đối với các tính năng chỉ có trên một số browser, hoặc tính năng đang thử nghiệm, thì Playwright sẽ không support.
  • Với trường hợp này thì bạn nên dùng mock để xử lý.
  • Bài này cho bạn một số ví dụ.

Introduction

  • Cùng xét ví dụ: 1 web app sử dụng battery API để hiển thị trạng thái pin thiết bị của bạn.
  • Chúng ta sẽ mock battery API và check xem page có thể hiển thị chính xác trạng thái pin hay không.

Creating mocks

  • Vì web này có thể gọi API từ rất sớm trong khi loading nên điều quan trọng là phải setup tất cả các mocks trước khi page bắt đầu loading. Cách dễ nhất để làm điều đó là gọi hàm: page.addInitScript()
await page.addInitScript(() => {
  const mockBattery = {
    level: 0.75,
    charging: true,
    chargingTime: 1800,
    dischargingTime: Infinity,
    addEventListener: () => { }
  };
  // Override the method to always return mock battery info.
  window.navigator.getBattery = async () => mockBattery;
});
  • Sau khi setup xong, bạn có thể navigate page và kiểm tra trạng thái giao diện (UI) của nó:
// Configure mock API before each test.
test.beforeEach(async ({ page }) => {
  await page.addInitScript(() => {
    const mockBattery = {
      level: 0.90,
      charging: true,
      chargingTime: 1800, // seconds
      dischargingTime: Infinity,
      addEventListener: () => { }
    };
    // Override the method to always return mock battery info.
    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');
});

Mocking read-only APIs

  • Có một số APIs ở dạng read-only nên bạn không thể gán cho nó thuộc tính navigator được. Ví dụ:
// Following line will have no effect.
navigator.cookieEnabled = true;
  • Tuy nhiên, nếu thuộc tính đó là configurable, bạn có thể ghi đè nó bằng Javascript đơn giản:
await page.addInitScript(() => {
  Object.defineProperty(Object.getPrototypeOf(navigator), 'cookieEnabled', { value: false });
});

Verifying API calls

  • Bạn có thể record lại tất cả các phương thức API đã được gọi và sau đó so sánh chúng với expect. page.exposeFunction() có thể hữu ích để truyền messages từ page trở lại test code."
test('log battery calls', async ({ page }) => {
  const log = [];
  // Expose function for pushing messages to the Node.js script.
  await page.exposeFunction('logCall', msg => log.push(msg));
  await page.addInitScript(() => {
    const mockBattery = {
      level: 0.75,
      charging: true,
      chargingTime: 1800,
      dischargingTime: Infinity,
      // Log addEventListener calls.
      addEventListener: (name, cb) => logCall(`addEventListener:${name}`)
    };
    // Override the method to always return mock battery info.
    window.navigator.getBattery = async () => {
      logCall('getBattery');
      return mockBattery;
    };
  });

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

  // Compare actual calls with golden.
  expect(log).toEqual([
    'getBattery',
    'addEventListener:chargingchange',
    'addEventListener:levelchange'
  ]);
});

Update mock

  • Để test xem app có phản ánh được chính xác các updates của trạng thái pin hay không, điều quan trọng là phải đảm bảo được rằng các object mock battery sẽ kích hoạt được các events giống như quá trình browser chạy. Phần test sau đây sẽ minh hoạ cách để đạt được điều đó:
test('update battery status (no golden)', async ({ page }) => {
  await page.addInitScript(() => {
    // Mock class that will notify corresponding listeners when battery status changes.
    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);
      }
      // Will be called by the test.
      _setLevel(value) {
        this.level = value;
        this._levelListeners.forEach(cb => cb());
      }
      _setCharging(value) {
        this.charging = value;
        this._chargingListeners.forEach(cb => cb());
      }
    }
    const mockBattery = new BatteryMock();
    // Override the method to always return mock battery info.
    window.navigator.getBattery = async () => mockBattery;
    // Save the mock object on window for easier access.
    window.mockBattery = mockBattery;
  });

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

  // Update level to 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');

  // Emulate connected adapter
  await page.evaluate(() => window.mockBattery._setCharging(true));
  await expect(page.locator('.battery-status')).toHaveText('Adapter');
  await expect(page.locator('.battery-fully')).toHaveText('00:30');
});

Trả lời