UNPKG

playwright-fixtures

Version:

Wrap your tests with Playwright-like test fixtures in node & browsers

84 lines (83 loc) 3.46 kB
/** * Resolve fixture values, and returns the resolved values, * a callback to start cleaning jobs, and the promises of the cleaning jobs. */ const prepareFixtures = async (base, init) => { const extend = {}; // The cleaning starter, called after the inner test and all sub-level fixtures are finished. let useResolve; let usePromise; await new Promise((construct) => { usePromise = new Promise((resolve) => { useResolve = resolve; construct(); }); }); // The promises of the cleaning jobs. const finishJobs = []; // Resolve fixture values. const prepareJobs = Object.entries(init) .map(([key, fixtureValue]) => (new Promise((prepareValueResolve) => { /** * Check if it is callable. * Hard to be reliable and fast at the same time. * E.g., classes are functions, too. */ if (typeof fixtureValue === 'function') { const useValue = async (value) => { extend[key] = value; prepareValueResolve(); await usePromise; }; finishJobs.push( /** * Package to promise, chain with another resolve in case of * the fixture function finishes without using `useValue`. * * Specify the type of `extend` as `T` to allow users to use sibling fixtures * as in Playwright's official docs. * @TODO filter out constants before handling these fixture functions. * @see [Test fixtures - Advanced: fixtures | Playwright]{@link https://playwright.dev/docs/test-fixtures/#overriding-fixtures} */ Promise .resolve(fixtureValue({ ...base, ...extend }, useValue)) .then(prepareValueResolve)); } else { extend[key] = fixtureValue; prepareValueResolve(); } }))); await Promise.all(prepareJobs); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return [{ ...base, ...extend }, useResolve, finishJobs]; }; const wrapTest = (baseTest, fixturesList) => new Proxy(baseTest, { // The call signature. apply: (target, thisArg, [name, inner]) => (target.call(thisArg, name, async (...baseTestArgs) => { const finishList = []; const fixtures = await fixturesList.reduce(async (initializing, init) => { const [initialized, finishFunc, finishJobs,] = await prepareFixtures(await initializing, init); finishList.push([finishFunc, finishJobs]); return initialized; }, Promise.resolve({})); // A try block to avoid inner errors blocking the cleaning jobs. try { await inner.call(thisArg, fixtures, ...baseTestArgs); } finally { // Start the cleaning jobs, from sub-level fixtures to parent fixtures. await finishList.reduceRight(async (finishing, [finishFunc, finishJobs]) => { await finishing; finishFunc(); await Promise.all(finishJobs); }, Promise.resolve()); } })), get(target, key) { if (key === 'extend') { // The `extend` method. return (fixtures) => wrapTest(baseTest, [...fixturesList, fixtures]); } return target[key]; }, }); const wrap = (baseTest) => wrapTest(baseTest, []); export default wrap;