UNPKG

@v4fire/client

Version:

V4Fire client core library

261 lines (219 loc) 6.03 kB
/*! * 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); } }