playwright-fixtures
Version:
Wrap your tests with Playwright-like test fixtures in node & browsers
84 lines (83 loc) • 3.46 kB
JavaScript
/**
* 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;