@v4fire/client
Version:
V4Fire client core library
158 lines (125 loc) • 3.72 kB
text/typescript
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
import delay from 'delay';
import type { Page } from 'playwright';
import type { WaitForIdleOptions, WaitForRAFOptions } from 'tests/helpers/bom/interface';
export * from 'tests/helpers/bom/interface';
/**
* Class provides API to work with BOM (browser object model)
*/
export default class BOM {
/**
* Creates a {@link PerformanceObserver} that monitors the CLS metric while fn is executed,
* and returns the sum of the value of all {@link PerformanceEntry PerformanceEntries}.
*
* @param page
* @param fn
* @param [waitForIdle]
*/
static async clsScore(page: Page, fn: Function, waitForIdle: boolean = true): Promise<number> {
interface ObserverData {
score: number;
observer: PerformanceObserver;
}
const uniqId = Math.random().toString();
await page.evaluate(([uniqId]) => {
const data: ObserverData = {
score: 0,
observer: new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
data.score += Object.cast<{value: number}>(entry).value;
}
})
};
globalThis[uniqId] = data;
data.observer.observe({type: 'layout-shift'});
}, [uniqId]);
await fn();
if (waitForIdle) {
await this.waitForIdleCallback(page, {sleepAfterIdles: 0});
}
return page.evaluate(([uniqId]) => {
const data: ObserverData = globalThis[uniqId];
data.observer.takeRecords();
data.observer.disconnect();
return data.score;
}, [uniqId]);
}
/**
* Returns a promise that will be resolved when the passed page process is switched to idle
*
* @param page
* @param [idleOpts]
*/
static async waitForIdleCallback(page: Page, idleOpts: WaitForIdleOptions = {}): Promise<void> {
const normalizedIdleOptions = <Required<WaitForIdleOptions>>{
waitForIdleTimes: 1,
sleepAfterIdles: 100,
...idleOpts
};
try {
await page.evaluate((normalizedIdleOptions) => new Promise<void>(async (res) => {
const waitForIdle = () => new Promise<void>((res) => {
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(() => res());
} else {
setTimeout(res, 50);
}
});
while (normalizedIdleOptions.waitForIdleTimes > 0) {
await waitForIdle();
normalizedIdleOptions.waitForIdleTimes--;
}
res();
}), normalizedIdleOptions);
} catch {}
await delay(normalizedIdleOptions.sleepAfterIdles);
}
/**
* Waits until `requestAnimationFrame` fires on the page
*
* @param page
* @param [rafOpts]
*/
static async waitForRAF(page: Page, rafOpts: WaitForRAFOptions = {}): Promise<void> {
const normalizedRafOptions = <Required<WaitForRAFOptions>>{
waitForRafTimes: 1,
sleepAfterRAF: 100,
...rafOpts
};
try {
await page.evaluate((normalizedRafOptions) => new Promise<void>(async (res) => {
const waitForRAF = () => new Promise((res) => {
requestAnimationFrame(res);
});
while (normalizedRafOptions.waitForRafTimes > 0) {
await waitForRAF();
normalizedRafOptions.waitForRafTimes--;
}
res();
}), normalizedRafOptions);
} catch {}
await delay(normalizedRafOptions.sleepAfterRAF);
}
/**
* @deprecated
* @see [[BOM.waitForIdleCallback]]
*/
waitForIdleCallback(page: Page, idleOptions: WaitForIdleOptions = {}): Promise<void> {
return BOM.waitForIdleCallback(page, idleOptions);
}
/**
* @param page
* @param [rafOptions]
* @deprecated
* @see [[BOM.waitForRAF]]
*/
async waitForRAF(page: Page, rafOptions: WaitForRAFOptions = {}): Promise<void> {
return BOM.waitForRAF(page, rafOptions);
}
}