From 30d8755f428b45ff28ad22847e47c6aef0bea323 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Wed, 30 Nov 2022 14:17:17 +0100 Subject: [PATCH 1/3] chore: add retries for navigation in ssr-tests-v9 --- apps/ssr-tests-v9/src/test.ts | 42 +----------- apps/ssr-tests-v9/src/utils/visitPage.test.ts | 32 +++++++++ apps/ssr-tests-v9/src/utils/visitPage.ts | 65 +++++++++++++++++++ 3 files changed, 99 insertions(+), 40 deletions(-) create mode 100644 apps/ssr-tests-v9/src/utils/visitPage.test.ts create mode 100644 apps/ssr-tests-v9/src/utils/visitPage.ts diff --git a/apps/ssr-tests-v9/src/test.ts b/apps/ssr-tests-v9/src/test.ts index c9cd097057853e..7630b40f4f8064 100644 --- a/apps/ssr-tests-v9/src/test.ts +++ b/apps/ssr-tests-v9/src/test.ts @@ -3,52 +3,14 @@ import * as fs from 'fs'; import * as path from 'path'; import type { Browser } from 'puppeteer'; -import { PROVIDER_ID } from './utils/constants'; import { hrToSeconds } from './utils/helpers'; import { launchBrowser } from './utils/launchBrowser'; +import { visitPage } from './utils/visitPage'; class RenderError extends Error { public name = 'RangeError'; } -export async function runTest(browser: Browser, url: string): Promise { - const page = await browser.newPage(); - await page.setRequestInterception(true); - - let error: Error | undefined; - - page.on('console', message => { - if (message.type() === 'error') { - // Ignoring network errors as we have an interceptor that prevents loading everything except our JS bundle - if (!message.text().includes('net::ERR_FAILED')) { - error = new RenderError(message.text()); - } - } - }); - - page.on('request', request => { - // Our interceptor allows only our HTML and JS output - if (request.url() === url || request.url().endsWith('/out-esm.js')) { - return request.continue(); - } - - return request.abort(); - }); - - page.on('pageerror', err => { - error = err; - }); - - await page.goto(url); - await page.waitForSelector(`#${PROVIDER_ID}`); - - await page.close(); - - if (error) { - throw error; - } -} - async function test(): Promise { const startTime = process.hrtime(); console.log('Starting a browser...'); @@ -68,7 +30,7 @@ async function test(): Promise { const url = `file://${htmlPath}`; console.log(`Using "${url}"`); - await runTest(browser, url); + await visitPage(browser, url); console.log(`Test finished successfully in ${hrToSeconds(process.hrtime(startTime))}`); } finally { if (browser) { diff --git a/apps/ssr-tests-v9/src/utils/visitPage.test.ts b/apps/ssr-tests-v9/src/utils/visitPage.test.ts new file mode 100644 index 00000000000000..634daace12839d --- /dev/null +++ b/apps/ssr-tests-v9/src/utils/visitPage.test.ts @@ -0,0 +1,32 @@ +import type { Page } from 'puppeteer'; +import { visitUrl } from './visitPage'; + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const noop = () => {}; + +describe('visitUrl', () => { + it('calls .goto() 5 times before a failure', async () => { + expect.assertions(2); + + jest.spyOn(console, 'warn').mockImplementation(noop); + jest.spyOn(console, 'error').mockImplementation(noop); + + const pageMock: Partial = { + goto: jest.fn().mockImplementation(() => Promise.reject()), + }; + + await expect(visitUrl(pageMock as Page, 'https://localhost:8080')).rejects.toMatchInlineSnapshot(`undefined`); + expect(pageMock.goto).toHaveBeenCalledTimes(5); + }); + + it('calls .goto once if successful', async () => { + expect.assertions(2); + + const pageMock: Partial = { + goto: jest.fn(), + }; + + await expect(visitUrl(pageMock as Page, 'https://localhost:8080')).resolves.toBeUndefined(); + expect(pageMock.goto).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/ssr-tests-v9/src/utils/visitPage.ts b/apps/ssr-tests-v9/src/utils/visitPage.ts new file mode 100644 index 00000000000000..0c25a2c24e6c5c --- /dev/null +++ b/apps/ssr-tests-v9/src/utils/visitPage.ts @@ -0,0 +1,65 @@ +import type { Browser, Page } from 'puppeteer'; +import { PROVIDER_ID } from './constants'; + +class RenderError extends Error { + public name = 'RangeError'; +} + +export async function visitUrl(page: Page, url: string) { + let attempt = 1; + + while (attempt <= 5) { + try { + await page.goto(url, { timeout: 10 * 1000 /* 10 seconds */ }); + break; + } catch (err) { + if (attempt === 5) { + console.error(`Failed to navigate to a page after 5 attempts...`); + throw err; + } + + console.warn('A browser failed to navigate to a page, retrying...'); + console.warn(err); + + attempt++; + } + } +} + +export async function visitPage(browser: Browser, url: string) { + const page = await browser.newPage(); + await page.setRequestInterception(true); + + let error: Error | undefined; + + page.on('console', message => { + if (message.type() === 'error') { + // Ignoring network errors as we have an interceptor that prevents loading everything except our JS bundle + if (!message.text().includes('net::ERR_FAILED')) { + error = new RenderError(message.text()); + } + } + }); + + page.on('request', request => { + // Our interceptor allows only our HTML and JS output + if (request.url() === url || request.url().endsWith('/out-esm.js')) { + return request.continue(); + } + + return request.abort(); + }); + + page.on('pageerror', err => { + error = err; + }); + + await visitUrl(page, url); + + await page.waitForSelector(`#${PROVIDER_ID}`); + await page.close(); + + if (error) { + throw error; + } +} From 9989c2e6ec05a0f14825cd2a7ac787877a0c69c1 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Wed, 30 Nov 2022 15:52:56 +0100 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Martin Hochel --- apps/ssr-tests-v9/src/utils/visitPage.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ssr-tests-v9/src/utils/visitPage.test.ts b/apps/ssr-tests-v9/src/utils/visitPage.test.ts index 634daace12839d..52d58395d62cd7 100644 --- a/apps/ssr-tests-v9/src/utils/visitPage.test.ts +++ b/apps/ssr-tests-v9/src/utils/visitPage.test.ts @@ -12,10 +12,10 @@ describe('visitUrl', () => { jest.spyOn(console, 'error').mockImplementation(noop); const pageMock: Partial = { - goto: jest.fn().mockImplementation(() => Promise.reject()), + goto: jest.fn().mockImplementation(() => Promise.reject('page wont open - mock')), }; - await expect(visitUrl(pageMock as Page, 'https://localhost:8080')).rejects.toMatchInlineSnapshot(`undefined`); + await expect(visitUrl(pageMock as Page, 'https://localhost:8080')).rejects.toMatchInlineSnapshot(`page wont open - mock`); expect(pageMock.goto).toHaveBeenCalledTimes(5); }); From 5ba92ee757a375a5a0bc5e46b546fba44bb34be3 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Wed, 30 Nov 2022 19:02:52 +0100 Subject: [PATCH 3/3] fix snapshot --- apps/ssr-tests-v9/src/utils/visitPage.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/ssr-tests-v9/src/utils/visitPage.test.ts b/apps/ssr-tests-v9/src/utils/visitPage.test.ts index 52d58395d62cd7..f598ae9d734670 100644 --- a/apps/ssr-tests-v9/src/utils/visitPage.test.ts +++ b/apps/ssr-tests-v9/src/utils/visitPage.test.ts @@ -12,10 +12,12 @@ describe('visitUrl', () => { jest.spyOn(console, 'error').mockImplementation(noop); const pageMock: Partial = { - goto: jest.fn().mockImplementation(() => Promise.reject('page wont open - mock')), + goto: jest.fn().mockImplementation(() => Promise.reject(new Error('page wont open - mock'))), }; - await expect(visitUrl(pageMock as Page, 'https://localhost:8080')).rejects.toMatchInlineSnapshot(`page wont open - mock`); + await expect(visitUrl(pageMock as Page, 'https://localhost:8080')).rejects.toMatchInlineSnapshot( + `[Error: page wont open - mock]`, + ); expect(pageMock.goto).toHaveBeenCalledTimes(5); });