| | import { debugPrint, getFullUrl, waitFor } from 'next-test-utils' |
| | import os from 'os' |
| | import { |
| | Playwright, |
| | PlaywrightNavigationWaitUntil, |
| | } from './browsers/playwright' |
| | import { Page } from 'playwright' |
| |
|
| | export type { Playwright } |
| |
|
| | if (!process.env.TEST_FILE_PATH) { |
| | process.env.TEST_FILE_PATH = module.parent!.filename |
| | } |
| |
|
| | let deviceIP: string |
| | const isBrowserStack = !!process.env.BROWSERSTACK |
| | ;(global as any).browserName = process.env.BROWSER_NAME || 'chrome' |
| |
|
| | if (isBrowserStack) { |
| | const nets = os.networkInterfaces() |
| | for (const key of Object.keys(nets)) { |
| | let done = false |
| |
|
| | for (const item of nets[key]!) { |
| | if (item.family === 'IPv4' && !item.internal) { |
| | deviceIP = item.address |
| | done = true |
| | break |
| | } |
| | } |
| | if (done) break |
| | } |
| | } |
| |
|
| | let browserTeardown: (() => Promise<void>)[] = [] |
| | let browserQuit: (() => Promise<void>) | undefined |
| |
|
| | if (typeof afterAll === 'function') { |
| | afterAll(async () => { |
| | await Promise.all(browserTeardown.map((f) => f())).catch((e) => |
| | console.error('browser teardown', e) |
| | ) |
| |
|
| | if (browserQuit) { |
| | await browserQuit() |
| | } |
| | }) |
| | } |
| |
|
| | export interface WebdriverOptions { |
| | |
| | |
| | |
| | waitHydration?: boolean |
| | |
| | |
| | |
| | retryWaitHydration?: boolean |
| | |
| | |
| | |
| | waitUntil?: PlaywrightNavigationWaitUntil |
| | |
| | |
| | |
| | disableCache?: boolean |
| | |
| | |
| | |
| | |
| | |
| | beforePageLoad?: (page: Page) => void | Promise<void> |
| | |
| | |
| | |
| | extraHTTPHeaders?: Record<string, string> |
| | |
| | |
| | |
| | locale?: string |
| | |
| | |
| | |
| | disableJavaScript?: boolean |
| | headless?: boolean |
| | |
| | |
| | |
| | ignoreHTTPSErrors?: boolean |
| | cpuThrottleRate?: number |
| | pushErrorAsConsoleLog?: boolean |
| |
|
| | |
| | |
| | |
| | userAgent?: string |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | export default async function webdriver( |
| | appPortOrUrl: string | number, |
| | url: string, |
| | options?: WebdriverOptions |
| | ): Promise<Playwright> { |
| | const defaultOptions = { |
| | waitHydration: true, |
| | retryWaitHydration: false, |
| | disableCache: false, |
| | } |
| | options = Object.assign(defaultOptions, options) |
| | const { |
| | waitHydration, |
| | retryWaitHydration, |
| | disableCache, |
| | beforePageLoad, |
| | extraHTTPHeaders, |
| | locale, |
| | disableJavaScript, |
| | ignoreHTTPSErrors, |
| | headless, |
| | cpuThrottleRate, |
| | pushErrorAsConsoleLog, |
| | userAgent, |
| | waitUntil, |
| | } = options |
| |
|
| | const { Playwright, quit } = await import('./browsers/playwright') |
| | browserQuit = quit |
| |
|
| | const browser = new Playwright() |
| | const browserName = process.env.BROWSER_NAME || 'chrome' |
| | await browser.setup( |
| | browserName, |
| | locale!, |
| | !disableJavaScript, |
| | Boolean(ignoreHTTPSErrors), |
| | |
| | typeof headless !== 'undefined' ? headless : !!process.env.HEADLESS, |
| | userAgent |
| | ) |
| | ;(global as any).browserName = browserName |
| |
|
| | const fullUrl = getFullUrl( |
| | appPortOrUrl, |
| | url, |
| | isBrowserStack ? deviceIP : 'localhost' |
| | ) |
| |
|
| | debugPrint(`Loading browser with ${fullUrl}`) |
| |
|
| | await browser.loadPage(fullUrl, { |
| | disableCache, |
| | cpuThrottleRate, |
| | beforePageLoad, |
| | extraHTTPHeaders, |
| | pushErrorAsConsoleLog, |
| | waitUntil, |
| | }) |
| | debugPrint(`Loaded browser with ${fullUrl}`) |
| |
|
| | browserTeardown.push(browser.close.bind(browser)) |
| |
|
| | |
| | if (!disableJavaScript && waitHydration) { |
| | debugPrint(`Waiting hydration for ${fullUrl}`) |
| |
|
| | const checkHydrated = async () => { |
| | await browser.eval(() => { |
| | return new Promise<void>((callback) => { |
| | |
| | if ( |
| | !document.documentElement.innerHTML.includes('__NEXT_DATA__') && |
| | |
| | typeof ((window as any).next && (window as any).next.version) === |
| | 'undefined' |
| | ) { |
| | console.log('Not a next.js page, resolving hydrate check') |
| | callback() |
| | } |
| |
|
| | |
| | |
| | if ((window as any).__NEXT_HYDRATED) { |
| | console.log('Next.js page already hydrated') |
| | callback() |
| | } else { |
| | let timeout = setTimeout(callback, 10 * 1000) |
| | ;(window as any).__NEXT_HYDRATED_CB = function () { |
| | clearTimeout(timeout) |
| | console.log('Next.js hydrate callback fired') |
| | callback() |
| | } |
| | } |
| | }) |
| | }) |
| | } |
| |
|
| | try { |
| | await checkHydrated() |
| | } catch (err) { |
| | if (retryWaitHydration) { |
| | |
| | await new Promise((resolve) => setTimeout(resolve, 2000)) |
| | await checkHydrated() |
| | } else { |
| | console.error('failed to check hydration') |
| | throw err |
| | } |
| | } |
| |
|
| | debugPrint(`Hydration complete for ${fullUrl}`) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | if (process.env.IS_TURBOPACK_TEST && process.env.TURBOPACK_DEV) { |
| | debugPrint(`Waiting for for turbopack watcher to start`) |
| | await waitFor(1000) |
| | } |
| | return browser |
| | } |
| |
|