rsnext/test/development/acceptance-app/helpers.ts
JJ Kasper 19013a5e3f
Ensure next.url is used instead of next.appPort (#44163)
`appPort` shouldn't be used unless absolutely necessary as `next.url` is
more specific and allows more control.

x-ref:
https://github.com/vercel/next.js/actions/runs/3734223762/jobs/6336069606
2022-12-19 13:29:50 -08:00

134 lines
4 KiB
TypeScript

import {
getRedboxDescription,
getRedboxHeader,
getRedboxSource,
hasRedbox,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { NextInstance } from 'test/lib/next-modes/base'
export async function sandbox(
next: NextInstance,
initialFiles?: Map<string, string>
) {
await next.stop()
await next.clean()
if (initialFiles) {
for (const [k, v] of initialFiles.entries()) {
await next.patchFile(k, v)
}
}
await next.start()
const browser = await webdriver(next.url, '/')
return {
browser,
session: {
async write(filename, content) {
// Update the file on filesystem
await next.patchFile(filename, content)
},
async patch(filename, content) {
// Register an event for HMR completion
await browser.eval(function () {
;(window as any).__HMR_STATE = 'pending'
var timeout = setTimeout(() => {
;(window as any).__HMR_STATE = 'timeout'
}, 30 * 1000)
;(window as any).__NEXT_HMR_CB = function () {
clearTimeout(timeout)
;(window as any).__HMR_STATE = 'success'
}
})
await this.write(filename, content)
for (;;) {
const status = await browser.eval(() => (window as any).__HMR_STATE)
if (!status) {
await new Promise((resolve) => setTimeout(resolve, 750))
// Wait for application to re-hydrate:
await browser.evalAsync(function () {
var callback = arguments[arguments.length - 1]
if ((window as any).__NEXT_HYDRATED) {
callback()
} else {
var timeout = setTimeout(callback, 30 * 1000)
;(window as any).__NEXT_HYDRATED_CB = function () {
clearTimeout(timeout)
callback()
}
}
})
console.log('Application re-loaded.')
// Slow down tests a bit:
await new Promise((resolve) => setTimeout(resolve, 750))
return false
}
if (status === 'success') {
console.log('Hot update complete.')
break
}
if (status !== 'pending') {
throw new Error(`Application is in inconsistent state: ${status}.`)
}
await new Promise((resolve) => setTimeout(resolve, 30))
}
// Slow down tests a bit (we don't know how long re-rendering takes):
await new Promise((resolve) => setTimeout(resolve, 750))
return true
},
async remove(filename) {
await next.deleteFile(filename)
},
async evaluate(snippet: () => any) {
if (typeof snippet === 'function') {
const result = await browser.eval(snippet)
await new Promise((resolve) => setTimeout(resolve, 30))
return result
} else {
throw new Error(
`You must pass a function to be evaluated in the browser.`
)
}
},
async hasRedbox(expected = false) {
return hasRedbox(browser, expected)
},
async hasErrorToast() {
return browser.eval(() => {
return Boolean(
Array.from(document.querySelectorAll('nextjs-portal')).find((p) =>
p.shadowRoot.querySelector('[data-nextjs-toast]')
)
)
})
},
async getRedboxDescription() {
return getRedboxDescription(browser)
},
async getRedboxSource(includeHeader = false) {
const header = includeHeader ? await getRedboxHeader(browser) : ''
const source = await getRedboxSource(browser)
if (includeHeader) {
return `${header}\n\n${source}`
}
return source
},
async waitForAndOpenRuntimeError() {
return browser.waitForElementByCss('[data-nextjs-toast]').click()
},
},
async cleanup() {
await browser.close()
await next.stop()
await next.clean()
},
}
}