From 2d2bbcc5d6997a5acfc2d9bab3730a4d4c60d7c3 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 20 Nov 2023 14:26:57 +0100 Subject: [PATCH] Fix crash on uncaught page errors --- playwright/jest-setup.js | 9 +- src/config/jest-playwright.ts | 1 + src/playwright/hooks.ts | 16 ++ src/playwright/transformPlaywright.test.ts | 140 +++++++----------- src/playwright/transformPlaywright.ts | 12 +- .../transformPlaywrightJson.test.ts | 140 +++++++----------- src/setup-page.ts | 23 +-- 7 files changed, 134 insertions(+), 207 deletions(-) diff --git a/playwright/jest-setup.js b/playwright/jest-setup.js index 7e319565..76c234e5 100644 --- a/playwright/jest-setup.js +++ b/playwright/jest-setup.js @@ -1,4 +1,10 @@ -const { getTestRunnerConfig, setPreVisit, setPostVisit, setupPage } = require('../dist'); +const { + getTestRunnerConfig, + setPreVisit, + setPostVisit, + setupPage, + throwUncaughtPageError, +} = require('../dist'); const testRunnerConfig = getTestRunnerConfig(process.env.STORYBOOK_CONFIG_DIR); if (testRunnerConfig) { @@ -21,4 +27,5 @@ if (testRunnerConfig) { // If the transformed tests need a dependency, it has to be globally available // in order to work both in default (file transformation) and stories/index.json mode. globalThis.__sbSetupPage = setupPage; +globalThis.__sbThrowUncaughtPageError = throwUncaughtPageError; globalThis.__sbCollectCoverage = process.env.STORYBOOK_COLLECT_COVERAGE === 'true'; diff --git a/src/config/jest-playwright.ts b/src/config/jest-playwright.ts index fc86105e..4f25b058 100644 --- a/src/config/jest-playwright.ts +++ b/src/config/jest-playwright.ts @@ -89,6 +89,7 @@ export const getJestConfig = () => { .map((p) => p.trim().toLowerCase()) .filter(Boolean), collectCoverage: STORYBOOK_COLLECT_COVERAGE === 'true', + exitOnPageError: false, }, }, watchPlugins: [ diff --git a/src/playwright/hooks.ts b/src/playwright/hooks.ts index f3c2bbda..72512c70 100644 --- a/src/playwright/hooks.ts +++ b/src/playwright/hooks.ts @@ -1,5 +1,6 @@ import type { BrowserContext, Page } from 'playwright'; import type { StoryContext } from '@storybook/csf'; +import dedent from 'ts-dedent'; export type TestContext = { id: string; @@ -80,3 +81,18 @@ export const waitForPageReady = async (page: Page) => { await page.waitForLoadState('networkidle'); await page.evaluate(() => document.fonts.ready); }; + +export const throwUncaughtPageError = (err: Error, context: TestContext) => { + const storybookUrl = process.env.REFERENCE_URL || process.env.TARGET_URL; + const storyUrl = `${storybookUrl}?path=/story/${context.id}`; + + const errorMessage = dedent` + An uncaught error occurred when visiting the following story. + Please access the link and check the logs in the browser: + ${storyUrl} + + Message: + ${err.stack || err.message}\n`; + + throw new Error(errorMessage); +}; diff --git a/src/playwright/transformPlaywright.test.ts b/src/playwright/transformPlaywright.test.ts index afecce62..5f0ffd29 100644 --- a/src/playwright/transformPlaywright.test.ts +++ b/src/playwright/transformPlaywright.test.ts @@ -66,15 +66,10 @@ describe('Playwright', () => { title: "Example/foo/bar", name: "A" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-foo-bar--a", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -96,6 +91,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -120,15 +116,10 @@ describe('Playwright', () => { title: "Example/foo/bar", name: "B" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-foo-bar--b", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -150,6 +141,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -192,15 +184,10 @@ describe('Playwright', () => { title: "Example/foo/bar", name: "B" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-foo-bar--b", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -222,6 +209,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -264,15 +252,10 @@ describe('Playwright', () => { title: "Example/foo/bar", name: "A" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-foo-bar--a", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -294,6 +277,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -318,15 +302,10 @@ describe('Playwright', () => { title: "Example/foo/bar", name: "B" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-foo-bar--b", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -348,6 +327,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -399,15 +379,10 @@ describe('Playwright', () => { title: "Example/foo/bar", name: "B" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-foo-bar--b", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -429,6 +404,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -453,15 +429,10 @@ describe('Playwright', () => { title: "Example/foo/bar", name: "C" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-foo-bar--c", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -483,6 +454,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -539,15 +511,10 @@ describe('Playwright', () => { title: "Example/foo/bar", name: "A" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-foo-bar--a", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -569,6 +536,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -609,15 +577,10 @@ describe('Playwright', () => { title: "Example/foo/bar", name: "A" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-foo-bar--a", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -639,6 +602,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -679,15 +643,10 @@ describe('Playwright', () => { title: "Example/Header", name: "A" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-header--a", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -709,6 +668,7 @@ describe('Playwright', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { diff --git a/src/playwright/transformPlaywright.ts b/src/playwright/transformPlaywright.ts index c3f9494c..11911bfb 100644 --- a/src/playwright/transformPlaywright.ts +++ b/src/playwright/transformPlaywright.ts @@ -19,10 +19,12 @@ export const testPrefixer = template( async () => { const testFn = async() => { const context = { id: %%id%%, title: %%title%%, name: %%name%% }; + + const onPageError = (err) => { + globalThis.__sbThrowUncaughtPageError(err, context); + } - page.on('pageerror', (err) => { - page.evaluate(({ id, err }) => __throwError(id, err), { id: %%id%%, err: err.message }); - }); + page.on('pageerror', onPageError); if(globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); @@ -37,7 +39,7 @@ export const testPrefixer = template( } if(globalThis.__sbCollectCoverage) { - const isCoverageSetupCorrectly = await page.evaluate(() => '__coverage__' in window); + const isCoverageSetupCorrectly = await page.evaluate(() => '__coverage__' in window); if (!isCoverageSetupCorrectly) { throw new Error(\`${coverageErrorMessage}\`); } @@ -45,6 +47,8 @@ export const testPrefixer = template( await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); + return result; }; diff --git a/src/playwright/transformPlaywrightJson.test.ts b/src/playwright/transformPlaywrightJson.test.ts index d644c330..e25b30f7 100644 --- a/src/playwright/transformPlaywrightJson.test.ts +++ b/src/playwright/transformPlaywrightJson.test.ts @@ -46,15 +46,10 @@ describe('Playwright Json', () => { title: "Example/Header", name: "Logged In" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-header--logged-in", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -76,6 +71,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -100,15 +96,10 @@ describe('Playwright Json', () => { title: "Example/Header", name: "Logged Out" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-header--logged-out", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -130,6 +121,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -156,15 +148,10 @@ describe('Playwright Json', () => { title: "Example/Page", name: "Logged In" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-page--logged-in", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -186,6 +173,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -259,15 +247,10 @@ describe('Playwright Json', () => { title: "Example/Header", name: "Logged Out" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-b", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -289,6 +272,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -315,15 +299,10 @@ describe('Playwright Json', () => { title: "Example/Page", name: "Logged In" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-c", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -345,6 +324,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -396,15 +376,10 @@ describe('Playwright Json', () => { title: "Example/Page", name: "Logged In" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-page--logged-in", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -426,6 +401,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -505,15 +481,10 @@ describe('Playwright Json', () => { title: "Example/Header", name: "Logged In" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-header--logged-in", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -535,6 +506,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -559,15 +531,10 @@ describe('Playwright Json', () => { title: "Example/Header", name: "Logged Out" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-header--logged-out", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -589,6 +556,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -615,15 +583,10 @@ describe('Playwright Json', () => { title: "Example/Page", name: "Logged In" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-page--logged-in", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -645,6 +608,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { @@ -709,15 +673,10 @@ describe('Playwright Json', () => { title: "Example/Page", name: "Logged In" }; - page.on('pageerror', err => { - page.evaluate(({ - id, - err - }) => __throwError(id, err), { - id: "example-page--logged-in", - err: err.message - }); - }); + const onPageError = err => { + globalThis.__sbThrowUncaughtPageError(err, context); + }; + page.on('pageerror', onPageError); if (globalThis.__sbPreVisit) { await globalThis.__sbPreVisit(page, context); } @@ -739,6 +698,7 @@ describe('Playwright Json', () => { } await jestPlaywright.saveCoverage(page); } + page.off('pageerror', onPageError); return result; }; try { diff --git a/src/setup-page.ts b/src/setup-page.ts index 5364d376..31d7c220 100644 --- a/src/setup-page.ts +++ b/src/setup-page.ts @@ -28,27 +28,6 @@ const defaultPrepare = async ({ page, browserContext, testRunnerConfig }: Prepar }); }; -const sanitizeURL = (url: string) => { - let finalURL = url; - // prepend URL protocol if not there - if (finalURL.indexOf('http://') === -1 && finalURL.indexOf('https://') === -1) { - finalURL = 'http://' + finalURL; - } - - // remove iframe.html if present - finalURL = finalURL.replace(/iframe.html\s*$/, ''); - - // remove index.html if present - finalURL = finalURL.replace(/index.html\s*$/, ''); - - // add forward slash at the end if not there - if (finalURL.slice(-1) !== '/') { - finalURL = finalURL + '/'; - } - - return finalURL; -}; - export const setupPage = async (page: Page, browserContext: BrowserContext) => { const targetURL = process.env.TARGET_URL; const failOnConsole = process.env.TEST_CHECK_CONSOLE; @@ -58,7 +37,7 @@ export const setupPage = async (page: Page, browserContext: BrowserContext) => { const { packageJson } = await readPackageUp(); const { version: testRunnerVersion } = packageJson; - const referenceURL = process.env.REFERENCE_URL && sanitizeURL(process.env.REFERENCE_URL); + const referenceURL = process.env.REFERENCE_URL; const debugPrintLimit = process.env.DEBUG_PRINT_LIMIT ? Number(process.env.DEBUG_PRINT_LIMIT) : 1000;