UNPKG

web-snaps

Version:

Browser automation with automatic snapshotting.

158 lines (157 loc) 5.25 kB
import { awaitedForEach, chunkArray, ensureError, } from '@augment-vir/common'; import { join } from 'node:path'; import { setupBrowser, withBrowserContext } from '../browser/run-browser.js'; import { runWebFlow, } from '../web-flow/run-web-flow.js'; import { createPhaseNamesEnum } from '../web-flow/web-flow.js'; /** * Define a suite of WebFlow functions with already set type parameters. * * @category Main */ export function defineSnapSuite( /** Output directory for saved snapshots. Setting this to `undefined` disables snapshots. */ webSnapDirPath) { return { /** * Executes a single {@link WebFlow} with the suite's `Context` and `Output` type parameters * and `webSnapDirPath` option already set. */ runWebFlow(params) { return runWebFlow({ ...params, options: { webSnapDirPath, ...params.options, }, }); }, /** * Defines a {@link WebFlow} with the suite's `Context` and `Output` type parameters already * set. */ defineWebFlow(init) { return defineWebFlow(init, webSnapDirPath); }, /** * Executes multiple {@link WebFlow} instances with the suite's `Context` and `Output` type * parameters already set. */ async runWebFlows(params) { return runWebFlows({ ...params, options: { webSnapDirPath, ...params.options, }, }); }, /** Runs {@link withBrowserContext} with the suite's `Context` type parameter already set. */ async withBrowserContext(params, /** Calls this callback and then automatically closes the browser afterwards. */ callback) { return await withBrowserContext(params, callback); }, setupBrowser(params) { return setupBrowser(params); }, }; } /** * Runs an array of {@link WebFlow} instances. Use {@link defineSnapSuite} instead of calling this * function directly for cleaner Type Parameter inference. * * @category Internal */ export async function runWebFlows({ context, userDataDirPath, webFlows, options, }) { const duplicateFlowKeys = webFlows.reduce((accum, webFlow) => { if (webFlow.flowKey in accum.allKeys) { accum.duplicateKeys.add(webFlow.flowKey); } else { accum.allKeys.add(webFlow.flowKey); } return accum; }, { allKeys: new Set(), duplicateKeys: new Set(), }).duplicateKeys; if (duplicateFlowKeys.size) { throw new Error(`Duplicate WebFlow keys given: ${Array.from(duplicateFlowKeys).join(',')}`); } return await withBrowserContext({ context, userDataDirPath, options: options?.browserOptions, }, async (browserParams) => { await options?.preHook?.({ browserContext: browserParams.browserContext, }); let error; const allWebFlowPhaseOutputs = []; try { const chunks = chunkArray(webFlows, { chunkSize: options?.serial ? 1 : options?.batchSize || 10, }); await awaitedForEach(chunks, async (chunk) => { const chunkOutputs = await Promise.all(chunk.map(async (webFlow) => { return await runWebFlow({ browserParams, webFlow, options, }); })); allWebFlowPhaseOutputs.push(...chunkOutputs); }); } catch (caught) { error = ensureError(caught); } await options?.postHook?.({ browserContext: browserParams.browserContext, error, }); if (error) { throw error; } return allWebFlowPhaseOutputs; }); } /** * Define a full {@link WebFlow}. Use {@link defineSnapSuite} instead of calling this function * directly for cleaner Type Parameter inference. * * @category Internal */ export function defineWebFlow(init, /** Directory path for saved snapshots. */ webSnapDirPath) { const webFlow = { flowKey: init.flowKey, phases: init.phases, startUrl: init.startUrl, phaseNames: createPhaseNamesEnum(init), webSnapPaths: webSnapDirPath ? { ts: join(webSnapDirPath, init.flowKey + '.mock.ts'), js: join(webSnapDirPath, init.flowKey + '.mock.js'), } : undefined, }; Object.defineProperties(webFlow, { ContextType: { configurable: false, enumerable: false, get() { throw new Error('Cannot read ContextType as a runtime value: it is a type only.'); }, }, OutputType: { configurable: false, enumerable: false, get() { throw new Error('Cannot read OutputType as a runtime value: it is a type only.'); }, }, }); return webFlow; }