2021-09-13 14:36:25 +02:00
|
|
|
import path from 'path'
|
|
|
|
import assert from 'assert'
|
|
|
|
import { NextConfig } from 'next'
|
2022-01-31 17:15:15 +01:00
|
|
|
import { InstallCommand, NextInstance, PackageJson } from './next-modes/base'
|
2021-09-13 14:36:25 +02:00
|
|
|
import { NextDevInstance } from './next-modes/next-dev'
|
|
|
|
import { NextStartInstance } from './next-modes/next-start'
|
|
|
|
|
|
|
|
const testsFolder = path.join(__dirname, '..')
|
|
|
|
|
2022-01-14 16:43:30 +01:00
|
|
|
let testFile
|
|
|
|
const testFileRegex = /\.test\.(js|tsx?)/
|
|
|
|
|
|
|
|
const visitedModules = new Set()
|
|
|
|
const checkParent = (mod) => {
|
|
|
|
if (!mod?.parent || visitedModules.has(mod)) return
|
|
|
|
testFile = mod.parent.filename || ''
|
|
|
|
visitedModules.add(mod)
|
|
|
|
|
|
|
|
if (!testFileRegex.test(testFile)) {
|
|
|
|
checkParent(mod.parent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
checkParent(module)
|
|
|
|
|
2021-10-25 08:21:57 +02:00
|
|
|
process.env.TEST_FILE_PATH = testFile
|
|
|
|
|
2021-09-13 14:36:25 +02:00
|
|
|
let testMode = process.env.NEXT_TEST_MODE
|
|
|
|
|
2022-01-14 16:43:30 +01:00
|
|
|
if (!testFileRegex.test(testFile)) {
|
2021-09-13 14:36:25 +02:00
|
|
|
throw new Error(
|
2022-01-14 16:43:30 +01:00
|
|
|
`e2e-utils imported from non-test file ${testFile} (must end with .test.(js,ts,tsx)`
|
2021-09-13 14:36:25 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const testFolderModes = ['e2e', 'development', 'production']
|
|
|
|
|
|
|
|
const testModeFromFile = testFolderModes.find((mode) =>
|
|
|
|
testFile.startsWith(path.join(testsFolder, mode))
|
|
|
|
)
|
|
|
|
|
|
|
|
if (testModeFromFile === 'e2e') {
|
|
|
|
const validE2EModes = ['dev', 'start', 'deploy']
|
2021-09-16 18:01:28 +02:00
|
|
|
|
2021-09-21 16:18:42 +02:00
|
|
|
if (!process.env.NEXT_TEST_JOB && !testMode) {
|
2021-09-16 18:01:28 +02:00
|
|
|
console.warn('Warn: no NEXT_TEST_MODE set, using default of start')
|
|
|
|
testMode = 'start'
|
|
|
|
}
|
2021-09-13 14:36:25 +02:00
|
|
|
assert(
|
|
|
|
validE2EModes.includes(testMode),
|
|
|
|
`NEXT_TEST_MODE must be one of ${validE2EModes.join(
|
|
|
|
', '
|
|
|
|
)} for e2e tests but received ${testMode}`
|
|
|
|
)
|
|
|
|
} else if (testModeFromFile === 'development') {
|
|
|
|
testMode = 'dev'
|
|
|
|
} else if (testModeFromFile === 'production') {
|
|
|
|
testMode = 'start'
|
|
|
|
}
|
|
|
|
|
|
|
|
if (testMode === 'dev') {
|
2021-09-21 16:21:05 +02:00
|
|
|
;(global as any).isNextDev = true
|
2021-09-13 14:36:25 +02:00
|
|
|
} else if (testMode === 'deploy') {
|
2021-09-21 16:21:05 +02:00
|
|
|
;(global as any).isNextDeploy = true
|
|
|
|
} else {
|
|
|
|
;(global as any).isNextStart = true
|
2021-09-13 14:36:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!testMode) {
|
|
|
|
throw new Error(
|
|
|
|
`No 'NEXT_TEST_MODE' set in environment, this is required for e2e-utils`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
console.log(`Using test mode: ${testMode} in test folder ${testModeFromFile}`)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* FileRef is wrapper around a file path that is meant be copied
|
|
|
|
* to the location where the next instance is being created
|
|
|
|
*/
|
|
|
|
export class FileRef {
|
|
|
|
public fsPath: string
|
|
|
|
|
|
|
|
constructor(path: string) {
|
|
|
|
this.fsPath = path
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let nextInstance: NextInstance | undefined = undefined
|
|
|
|
|
|
|
|
if (typeof afterAll === 'function') {
|
|
|
|
afterAll(async () => {
|
|
|
|
if (nextInstance) {
|
|
|
|
await nextInstance.destroy()
|
|
|
|
throw new Error(
|
|
|
|
`next instance not destroyed before exiting, make sure to call .destroy() after the tests after finished`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets up and manages a Next.js instance in the configured
|
|
|
|
* test mode. The next instance will be isolated from the monorepo
|
|
|
|
* to prevent relying on modules that shouldn't be
|
|
|
|
*/
|
|
|
|
export async function createNext(opts: {
|
|
|
|
files: {
|
|
|
|
[filename: string]: string | FileRef
|
|
|
|
}
|
|
|
|
dependencies?: {
|
|
|
|
[name: string]: string
|
|
|
|
}
|
|
|
|
nextConfig?: NextConfig
|
|
|
|
skipStart?: boolean
|
2022-01-14 16:43:30 +01:00
|
|
|
installCommand?: InstallCommand
|
|
|
|
buildCommand?: string
|
2022-01-31 17:15:15 +01:00
|
|
|
packageJson?: PackageJson
|
2022-01-14 16:43:30 +01:00
|
|
|
startCommand?: string
|
2021-09-13 14:36:25 +02:00
|
|
|
}): Promise<NextInstance> {
|
2021-09-29 19:38:21 +02:00
|
|
|
try {
|
|
|
|
if (nextInstance) {
|
|
|
|
throw new Error(`createNext called without destroying previous instance`)
|
|
|
|
}
|
2021-09-13 14:36:25 +02:00
|
|
|
|
2021-09-29 19:38:21 +02:00
|
|
|
if (testMode === 'dev') {
|
|
|
|
// next dev
|
|
|
|
nextInstance = new NextDevInstance(opts)
|
|
|
|
} else if (testMode === 'deploy') {
|
|
|
|
// Vercel
|
|
|
|
throw new Error('to-implement')
|
|
|
|
} else {
|
|
|
|
// next build + next start
|
|
|
|
nextInstance = new NextStartInstance(opts)
|
|
|
|
}
|
2021-09-13 14:36:25 +02:00
|
|
|
|
2021-09-29 19:38:21 +02:00
|
|
|
nextInstance.on('destroy', () => {
|
|
|
|
nextInstance = undefined
|
|
|
|
})
|
2021-09-13 14:36:25 +02:00
|
|
|
|
2021-09-29 19:38:21 +02:00
|
|
|
await nextInstance.setup()
|
2021-09-13 14:36:25 +02:00
|
|
|
|
2021-09-29 19:38:21 +02:00
|
|
|
if (!opts.skipStart) {
|
|
|
|
await nextInstance.start()
|
|
|
|
}
|
|
|
|
return nextInstance!
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Failed to create next instance', err)
|
|
|
|
try {
|
|
|
|
await nextInstance.destroy()
|
|
|
|
} catch (_) {}
|
|
|
|
process.exit(1)
|
2021-09-13 14:36:25 +02:00
|
|
|
}
|
|
|
|
}
|