Skip to content

Commit

Permalink
Allow returning non-serializable values from waitFor (#732)
Browse files Browse the repository at this point in the history
  • Loading branch information
calebeby authored Aug 24, 2023
1 parent 2244265 commit 29b8671
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/proud-spies-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'pleasantest': minor
---

Allow returning non-serializable values from `waitFor`
4 changes: 4 additions & 0 deletions jest.config.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
module.exports = {
// See https://jestjs.io/docs/configuration/#prettierpath-string -> "Prettier version 3 is not supported!"
// Blocked by https://github.com/prettier/prettier-synchronized/issues/4#issuecomment-1649355749
// and https://github.com/jestjs/jest/pull/14311#issuecomment-1649358074
prettierPath: null,
testEnvironment: 'node',
moduleNameMapper: {
'^pleasantest$': '<rootDir>/dist/cjs/index.cjs',
Expand Down
42 changes: 17 additions & 25 deletions src/pptr-testing-library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,22 +247,12 @@ export interface WaitForOptions {
mutationObserverOptions?: MutationObserverInit;
}

interface WaitFor {
<T>(
page: Page,
asyncHookTracker: AsyncHookTracker,
cb: () => T | Promise<T>,
{ onTimeout, container, ...opts }: WaitForOptions,
wrappedFunction: (...args: any) => any,
): Promise<T>;
}

export const waitFor: WaitFor = async (
page,
asyncHookTracker,
cb,
{ onTimeout, container, ...opts },
wrappedFunction,
export const waitFor = async <T>(
page: Page,
asyncHookTracker: AsyncHookTracker,
cb: () => T | Promise<T>,
{ onTimeout, container, ...opts }: WaitForOptions,
wrappedFunction: (...args: any) => any,
) =>
asyncHookTracker.addHook(async () => {
const { port } = await createClientRuntimeServer();
Expand All @@ -272,7 +262,10 @@ export const waitFor: WaitFor = async (
// So we need a unique name for each variable
const browserFuncName = `pleasantest_waitFor_${waitForCounter}`;

await page.exposeFunction(browserFuncName, cb);
let returnValue: T | undefined;
await page.exposeFunction(browserFuncName, async () => {
returnValue = await cb();
});

const evalResult = await page.evaluateHandle(
// Using new Function to avoid babel transpiling the import
Expand All @@ -282,8 +275,8 @@ export const waitFor: WaitFor = async (
`return import("http://localhost:${port}/@pleasantest/dom-testing-library")
.then(async ({ waitFor }) => {
try {
const result = await waitFor(${browserFuncName}, { ...opts, container })
return { success: true, result }
await waitFor(${browserFuncName}, { ...opts, container })
return { success: true }
} catch (error) {
if (/timed out in waitFor/i.test(error.message)) {
// Leave out stack trace so the stack trace is given from Node
Expand All @@ -298,12 +291,11 @@ export const waitFor: WaitFor = async (
container,
);
const wasSuccessful = await evalResult.evaluate((r) => r.success);
const result = await evalResult.evaluate((r) =>
r.success
? r.result
: { message: r.result.message, stack: r.result.stack },
);
if (wasSuccessful) return result;
if (wasSuccessful) return returnValue as T;
const result = await evalResult.evaluate((r) => ({
message: r.result.message,
stack: r.result.stack,
}));
const err = new Error(result.message);
if (result.stack) err.stack = result.stack;
else removeFuncFromStackTrace(err, asyncHookTracker.addHook);
Expand Down
27 changes: 26 additions & 1 deletion tests/wait-for.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,29 @@ test(
}),
);

test(
'Returned non-serializable values',
withBrowser(async ({ utils, page, waitFor }) => {
await utils.runJS(`
setTimeout(() => {
document.title = 'hallo'
}, 100)
`);
// At first the title won't be set to hallo
// Because it waits 100ms before it sets it
expect(await page.title()).not.toEqual('hallo');
const imNotSerializable = Symbol('test');
const waitForCallback = jest.fn(async () => {
expect(await page.title()).toEqual('hallo');
return imNotSerializable;
});
const returnedValue = await waitFor(waitForCallback);
expect(returnedValue).toBe(imNotSerializable);
expect(await page.title()).toEqual('hallo');
expect(waitForCallback).toHaveBeenCalled();
}),
);

test(
'Throws error with timeout',
withBrowser(async ({ waitFor }) => {
Expand All @@ -40,7 +63,9 @@ test(
tests/wait-for.test.ts
throw new Error('something bad happened');
^"
^
-------------------------------------------------------
dist/cjs/index.cjs"
`);

// If the callback function never resolves (or takes too long to resolve),
Expand Down

0 comments on commit 29b8671

Please sign in to comment.