web-snaps
Version:
Browser automation with automatic snapshotting.
158 lines (157 loc) • 5.25 kB
JavaScript
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;
}