@v4fire/client
Version:
V4Fire client core library
261 lines (219 loc) • 6.03 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, JSHandle, BrowserContext, ElementHandle } from 'playwright';
import BOM, { WaitForIdleOptions } from 'tests/helpers/bom';
import type { ExtractFromJSHandle } from 'tests/helpers/mock';
import type { SetupOptions } from 'tests/helpers/utils/interface';
const
logsMap = new WeakMap<Page, string[]>();
export default class Utils {
/**
* Waits for the specified function returns true (`Boolean(result) === true`).
* Similar to the `Playwright.Page.waitForFunction`, but it executes with the provided context.
*
* @param ctx – context that will be available as the first argument of the provided function
* @param fn
* @param args
*
* @example
* ```typescript
* // `ctx` refers to `imgNode`
* h.utils.waitForFunction(imgNode, (ctx, imgUrl) => ctx.src === imgUrl, imgUrl)
* ```
*/
static waitForFunction<ARGS extends any[] = any[], CTX extends JSHandle = JSHandle>(
ctx: CTX,
fn: (this: any, ctx: ExtractFromJSHandle<CTX>, ...args: ARGS) => unknown,
...args: ARGS
): Promise<void> {
const
strFn = fn.toString();
return ctx.evaluate((ctx, [strFn, ...args]) => {
const
timeout = 4e3,
// eslint-disable-next-line no-new-func
newFn = Function(`return (${strFn}).apply(this, [this, ...${JSON.stringify(args)}])`);
let
isTimeout = false;
return new Promise<void>((res, rej) => {
const
timeoutTimer = setTimeout(() => isTimeout = true, timeout);
const interval = setInterval(() => {
try {
const
fnRes = Boolean(newFn.call(ctx));
if (fnRes) {
clearTimeout(timeoutTimer);
clearInterval(interval);
res();
}
if (isTimeout) {
clearInterval(interval);
rej(`The given function\n${newFn.toString()}\nreturns a negative result`);
}
} catch (err) {
clearInterval(interval);
rej(err);
}
}, 15);
});
}, [strFn, ...args]);
}
/**
* Imports the specified module into page and returns `JSHandle` for this module
*
* @param page
* @param moduleName
*/
static import<T>(page: Page, moduleName: string): Promise<JSHandle<T>> {
if (!moduleName.startsWith('./')) {
moduleName = `./src/${moduleName}`;
}
if (!moduleName.endsWith('.ts')) {
moduleName = `${moduleName}/index.ts`;
}
return <Promise<JSHandle<T>>>page.evaluateHandle(
([{moduleName}]) => globalThis.importModule(moduleName), [{moduleName}]
);
}
/**
* Reloads the page and waits until `requestIdleCallback`
*
* @param page
* @param [idleOptions]
*
* @deprecated
*/
static async reloadAndWaitForIdle(page: Page, idleOptions?: WaitForIdleOptions): Promise<void> {
await page.reload({waitUntil: 'networkidle'});
await BOM.waitForIdleCallback(page, idleOptions);
}
/**
* Performs the pre-setting environment
*
* @deprecated
* @param page
* @param context
* @param [opts]
*/
static async setup(page: Page, context: BrowserContext, opts?: SetupOptions): Promise<void> {
opts = {
// eslint-disable-next-line quotes
mocks: '[\'.*\']',
permissions: ['geolocation'],
location: {latitude: 59.95, longitude: 30.31667},
sleepAfter: 2000,
reload: true,
waitForEl: undefined,
...opts
};
if (Object.size(opts.permissions) > 0) {
await context.grantPermissions(opts.permissions!);
}
if (opts.location) {
await context.setGeolocation(opts.location);
}
await page.waitForLoadState('networkidle');
if (opts.mocks != null) {
await page.evaluate(`setEnv('mock', {patterns: ${opts.mocks}});`);
}
await page.waitForLoadState('networkidle');
if (opts.reload) {
await this.reloadAndWaitForIdle(page);
}
if (opts.waitForEl != null) {
await page.waitForSelector(opts.waitForEl);
}
if (opts.sleepAfter != null) {
await delay(opts.sleepAfter);
}
await page.waitForSelector('#root-component', {timeout: (60).seconds(), state: 'attached'});
}
/**
* Intercepts and collects all invoking of `console` methods on the specified page.
* Mind, the intercepted callings aren't be shown a console till you invoke the `printPageLogs` method.
*
* @param page
*/
static collectPageLogs(page: Page): void {
const logs = logsMap.get(page);
if (logs === undefined) {
const logsArr = <string[]>[];
logsMap.set(page, logsArr);
page.on('console', (message) => {
logsArr.push(message.text());
});
}
}
/**
* Prints all of intercepted page console invokes to the console
* @param page
*/
static printPageLogs(page: Page): void {
const
logs = logsMap.get(page);
if (logs) {
console.log(logs.join('\n'));
logsMap.delete(page);
}
}
/**
* Performs a pre-setting environment
*
* @param page
* @param context
* @param [opts]
*
* @deprecated
*/
async setup(page: Page, context: BrowserContext, opts?: SetupOptions): Promise<void> {
return Utils.setup(page, context, opts);
}
/**
* @param page
* @deprecated
* @see [[Utils.collectPageLogs]]
*/
collectPageLogs(page: Page): void {
return Utils.collectPageLogs(page);
}
/**
* @param page
* @deprecated
* @see [[Utils.printPageLogs]]
*/
printPageLogs(page: Page): void {
return Utils.printPageLogs(page);
}
/**
* @param page
* @param [idleOpts]
*
* @deprecated
* @see [[Utils.reloadAndWaitForIdle]]
*/
async reloadAndWaitForIdle(page: Page, idleOpts?: WaitForIdleOptions): Promise<void> {
return Utils.reloadAndWaitForIdle(page, idleOpts);
}
/**
* @deprecated
* @see [[Utils.waitForFunction]]
*
* @param ctx
* @param fn
* @param args
*/
waitForFunction<ARGS extends any[] = any[], CTX extends JSHandle = JSHandle>(
ctx: CTX,
fn: (this: any, ctx: ExtractFromJSHandle<CTX>, ...args: ARGS) => unknown,
...args: ARGS
): Promise<void> {
return Utils.waitForFunction(ctx, fn, ...args);
}
}