/
next-webdriver.ts
161 lines (141 loc) · 4.45 KB
/
next-webdriver.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import { getFullUrl } from 'next-test-utils'
import os from 'os'
import { BrowserInterface } from './browsers/base'
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 browserQuit: () => Promise<void>
if (typeof afterAll === 'function') {
afterAll(async () => {
if (browserQuit) {
await browserQuit()
}
})
}
export const USE_SELENIUM = Boolean(
process.env.LEGACY_SAFARI ||
process.env.BROWSER_NAME === 'internet explorer' ||
process.env.SKIP_LOCAL_SELENIUM_SERVER
)
/**
*
* @param appPortOrUrl can either be the port or the full URL
* @param url the path/query to append when using appPort
* @param options.waitHydration whether to wait for react hydration to finish
* @param options.retryWaitHydration allow retrying hydration wait if reload occurs
* @param options.disableCache disable cache for page load
* @param options.beforePageLoad the callback receiving page instance before loading page
* @returns thenable browser instance
*/
export default async function webdriver(
appPortOrUrl: string | number,
url: string,
options?: {
waitHydration?: boolean
retryWaitHydration?: boolean
disableCache?: boolean
beforePageLoad?: (page: any) => void
locale?: string
userAgent?: string
}
): Promise<BrowserInterface> {
let CurrentInterface: typeof BrowserInterface
const defaultOptions = {
waitHydration: true,
retryWaitHydration: false,
disableCache: false,
}
options = Object.assign(defaultOptions, options)
const {
waitHydration,
retryWaitHydration,
disableCache,
beforePageLoad,
locale,
} = options
// we import only the needed interface
if (USE_SELENIUM) {
const { Selenium, quit } = await import('./browsers/selenium')
CurrentInterface = Selenium
browserQuit = quit
} else {
const { Playwright, quit } = await import('./browsers/playwright')
CurrentInterface = Playwright
browserQuit = quit
}
const browser = new CurrentInterface()
const browserName = process.env.BROWSER_NAME || 'chrome'
await browser.setup(browserName, locale, userAgent)
;(global as any).browserName = browserName
const fullUrl = getFullUrl(
appPortOrUrl,
url,
isBrowserStack ? deviceIP : 'localhost'
)
console.log(`\n> Loading browser with ${fullUrl}\n`)
await browser.loadPage(fullUrl, { disableCache, beforePageLoad })
console.log(`\n> Loaded browser with ${fullUrl}\n`)
// Wait for application to hydrate
if (waitHydration) {
console.log(`\n> Waiting hydration for ${fullUrl}\n`)
const checkHydrated = async () => {
await browser.evalAsync(function () {
var callback = arguments[arguments.length - 1]
// if it's not a Next.js app return
if (
document.documentElement.innerHTML.indexOf('__NEXT_DATA__') === -1 &&
// @ts-ignore next exists on window if it's a Next.js page.
typeof ((window as any).next && (window as any).next.version) ===
'undefined'
) {
console.log('Not a next.js page, resolving hydrate check')
callback()
}
// TODO: should we also ensure router.isReady is true
// by default before resolving?
if ((window as any).__NEXT_HYDRATED) {
console.log('Next.js page already hydrated')
callback()
} else {
var 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) {
// re-try in case the page reloaded during check
await new Promise((resolve) => setTimeout(resolve, 2000))
await checkHydrated()
} else {
console.error('failed to check hydration')
throw err
}
}
console.log(`\n> Hydration complete for ${fullUrl}\n`)
}
return browser
}