2017-01-12 04:14:49 +00:00
|
|
|
import fetch from 'node-fetch'
|
|
|
|
import qs from 'querystring'
|
2017-02-09 13:40:09 +00:00
|
|
|
import http from 'http'
|
2017-05-09 23:24:34 +00:00
|
|
|
import express from 'express'
|
2017-07-20 16:00:45 +00:00
|
|
|
import path from 'path'
|
2018-04-12 07:47:42 +00:00
|
|
|
import getPort from 'get-port'
|
2018-12-15 21:55:59 +00:00
|
|
|
import spawn from 'cross-spawn'
|
2018-02-26 16:18:46 +00:00
|
|
|
import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs'
|
2017-07-20 16:00:45 +00:00
|
|
|
import fkill from 'fkill'
|
2017-01-12 04:14:49 +00:00
|
|
|
|
2018-07-24 09:24:40 +00:00
|
|
|
// `next` here is the symlink in `test/node_modules/next` which points to the root directory.
|
|
|
|
// This is done so that requiring from `next` works.
|
|
|
|
// The reason we don't import the relative path `../../dist/<etc>` is that it would lead to inconsistent module singletons
|
|
|
|
import server from 'next/dist/server/next'
|
|
|
|
import _pkg from 'next/package.json'
|
2017-01-12 04:14:49 +00:00
|
|
|
|
|
|
|
export const nextServer = server
|
|
|
|
export const pkg = _pkg
|
|
|
|
|
2018-02-02 14:43:36 +00:00
|
|
|
export function initNextServerScript (scriptPath, successRegexp, env) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const instance = spawn('node', [scriptPath], { env })
|
|
|
|
|
|
|
|
function handleStdout (data) {
|
|
|
|
const message = data.toString()
|
|
|
|
if (successRegexp.test(message)) {
|
|
|
|
resolve(instance)
|
|
|
|
}
|
|
|
|
process.stdout.write(message)
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleStderr (data) {
|
|
|
|
process.stderr.write(data.toString())
|
|
|
|
}
|
|
|
|
|
|
|
|
instance.stdout.on('data', handleStdout)
|
|
|
|
instance.stderr.on('data', handleStderr)
|
|
|
|
|
|
|
|
instance.on('close', () => {
|
|
|
|
instance.stdout.removeListener('data', handleStdout)
|
|
|
|
instance.stderr.removeListener('data', handleStderr)
|
|
|
|
})
|
|
|
|
|
|
|
|
instance.on('error', (err) => {
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-10-22 19:00:31 +00:00
|
|
|
export function renderViaAPI (app, pathname, query) {
|
|
|
|
const url = `${pathname}${query ? `?${qs.stringify(query)}` : ''}`
|
2017-05-03 16:40:09 +00:00
|
|
|
return app.renderToHTML({ url }, {}, pathname, query)
|
2017-01-12 04:14:49 +00:00
|
|
|
}
|
|
|
|
|
2017-10-22 19:00:31 +00:00
|
|
|
export function renderViaHTTP (appPort, pathname, query) {
|
2017-11-05 19:17:03 +00:00
|
|
|
return fetchViaHTTP(appPort, pathname, query).then((res) => res.text())
|
|
|
|
}
|
|
|
|
|
|
|
|
export function fetchViaHTTP (appPort, pathname, query) {
|
2017-10-22 19:00:31 +00:00
|
|
|
const url = `http://localhost:${appPort}${pathname}${query ? `?${qs.stringify(query)}` : ''}`
|
2017-11-05 19:17:03 +00:00
|
|
|
return fetch(url)
|
2017-01-12 04:14:49 +00:00
|
|
|
}
|
2017-02-09 13:40:09 +00:00
|
|
|
|
2017-07-20 16:00:45 +00:00
|
|
|
export function findPort () {
|
2018-04-12 07:47:42 +00:00
|
|
|
return getPort()
|
2017-07-20 16:00:45 +00:00
|
|
|
}
|
|
|
|
|
2018-12-15 21:55:59 +00:00
|
|
|
export function runNextCommand (argv, options = {}) {
|
|
|
|
const cwd = path.dirname(require.resolve('next/package'))
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
console.log(`Running command "next ${argv.join(' ')}"`)
|
|
|
|
const instance = spawn('node', ['dist/bin/next', ...argv], { cwd, stdio: options.stdout ? ['ignore', 'pipe', 'ignore'] : 'inherit' })
|
|
|
|
|
|
|
|
let stdoutOutput = ''
|
|
|
|
if (options.stdout) {
|
|
|
|
instance.stdout.on('data', function (chunk) {
|
|
|
|
stdoutOutput += chunk
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
instance.on('close', () => {
|
|
|
|
resolve({
|
|
|
|
stdout: stdoutOutput
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
instance.on('error', (err) => {
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
export function runNextCommandDev (argv, stdOut) {
|
2018-09-30 23:02:10 +00:00
|
|
|
const cwd = path.dirname(require.resolve('next/package'))
|
2017-07-20 16:00:45 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2018-12-15 21:55:59 +00:00
|
|
|
const instance = spawn('node', ['dist/bin/next', ...argv], { cwd })
|
2017-07-20 16:00:45 +00:00
|
|
|
|
|
|
|
function handleStdout (data) {
|
|
|
|
const message = data.toString()
|
|
|
|
if (/> Ready on/.test(message)) {
|
2018-12-15 21:55:59 +00:00
|
|
|
resolve(stdOut ? message : instance)
|
2017-07-20 16:00:45 +00:00
|
|
|
}
|
|
|
|
process.stdout.write(message)
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleStderr (data) {
|
|
|
|
process.stderr.write(data.toString())
|
|
|
|
}
|
|
|
|
|
|
|
|
instance.stdout.on('data', handleStdout)
|
|
|
|
instance.stderr.on('data', handleStderr)
|
|
|
|
|
|
|
|
instance.on('close', () => {
|
|
|
|
instance.stdout.removeListener('data', handleStdout)
|
|
|
|
instance.stderr.removeListener('data', handleStderr)
|
|
|
|
})
|
|
|
|
|
|
|
|
instance.on('error', (err) => {
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-12-15 21:55:59 +00:00
|
|
|
// Launch the app in dev mode.
|
|
|
|
export function launchApp (dir, port) {
|
|
|
|
return runNextCommandDev([dir, '-p', port])
|
|
|
|
}
|
|
|
|
|
|
|
|
export function nextBuild (dir) {
|
|
|
|
return runNextCommand(['build', dir])
|
|
|
|
}
|
|
|
|
|
|
|
|
export function nextExport (dir, {outdir}) {
|
|
|
|
return runNextCommand(['export', dir, '--outdir', outdir])
|
|
|
|
}
|
|
|
|
|
2017-07-20 16:00:45 +00:00
|
|
|
// Kill a launched app
|
|
|
|
export async function killApp (instance) {
|
|
|
|
await fkill(instance.pid)
|
|
|
|
}
|
|
|
|
|
2017-02-09 13:40:09 +00:00
|
|
|
export async function startApp (app) {
|
|
|
|
await app.prepare()
|
|
|
|
const handler = app.getRequestHandler()
|
|
|
|
const server = http.createServer(handler)
|
|
|
|
server.__app = app
|
|
|
|
|
|
|
|
await promiseCall(server, 'listen')
|
|
|
|
return server
|
|
|
|
}
|
|
|
|
|
2018-06-28 18:07:41 +00:00
|
|
|
export async function stopApp (server) {
|
2017-05-09 23:24:34 +00:00
|
|
|
if (server.__app) {
|
|
|
|
await server.__app.close()
|
|
|
|
}
|
2017-02-09 13:40:09 +00:00
|
|
|
await promiseCall(server, 'close')
|
|
|
|
}
|
|
|
|
|
|
|
|
function promiseCall (obj, method, ...args) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const newArgs = [
|
|
|
|
...args,
|
|
|
|
function (err, res) {
|
|
|
|
if (err) return reject(err)
|
|
|
|
resolve(res)
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
obj[method](...newArgs)
|
|
|
|
})
|
|
|
|
}
|
2017-02-26 19:45:16 +00:00
|
|
|
|
|
|
|
export function waitFor (millis) {
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, millis))
|
|
|
|
}
|
2017-05-09 23:24:34 +00:00
|
|
|
|
|
|
|
export async function startStaticServer (dir) {
|
|
|
|
const app = express()
|
|
|
|
const server = http.createServer(app)
|
|
|
|
app.use(express.static(dir))
|
|
|
|
|
|
|
|
await promiseCall(server, 'listen')
|
|
|
|
return server
|
|
|
|
}
|
2018-02-26 16:18:46 +00:00
|
|
|
|
|
|
|
export async function check (contentFn, regex) {
|
2018-07-24 09:24:40 +00:00
|
|
|
let found = false
|
2018-09-25 23:04:15 +00:00
|
|
|
const timeout = setTimeout(async () => {
|
2018-07-24 09:24:40 +00:00
|
|
|
if (found) {
|
|
|
|
return
|
|
|
|
}
|
2018-09-02 15:22:29 +00:00
|
|
|
let content
|
|
|
|
try {
|
|
|
|
content = await contentFn()
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Error while getting content', {regex})
|
|
|
|
}
|
|
|
|
console.error('TIMED OUT CHECK: ', {regex, content})
|
|
|
|
throw new Error('TIMED OUT: ' + regex + '\n\n' + content)
|
2018-07-24 09:24:40 +00:00
|
|
|
}, 1000 * 30)
|
|
|
|
while (!found) {
|
2018-02-26 16:18:46 +00:00
|
|
|
try {
|
|
|
|
const newContent = await contentFn()
|
2018-07-24 09:24:40 +00:00
|
|
|
if (regex.test(newContent)) {
|
|
|
|
found = true
|
2018-09-25 23:04:15 +00:00
|
|
|
clearTimeout(timeout)
|
2018-07-24 09:24:40 +00:00
|
|
|
break
|
|
|
|
}
|
2018-02-26 16:18:46 +00:00
|
|
|
await waitFor(1000)
|
|
|
|
} catch (ex) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class File {
|
|
|
|
constructor (path) {
|
|
|
|
this.path = path
|
|
|
|
this.originalContent = existsSync(this.path) ? readFileSync(this.path, 'utf8') : null
|
|
|
|
}
|
|
|
|
|
|
|
|
write (content) {
|
|
|
|
if (!this.originalContent) {
|
|
|
|
this.originalContent = content
|
|
|
|
}
|
|
|
|
writeFileSync(this.path, content, 'utf8')
|
|
|
|
}
|
|
|
|
|
|
|
|
replace (pattern, newValue) {
|
|
|
|
const newContent = this.originalContent.replace(pattern, newValue)
|
|
|
|
this.write(newContent)
|
|
|
|
}
|
|
|
|
|
|
|
|
delete () {
|
|
|
|
unlinkSync(this.path)
|
|
|
|
}
|
|
|
|
|
|
|
|
restore () {
|
|
|
|
this.write(this.originalContent)
|
|
|
|
}
|
|
|
|
}
|
2018-07-24 09:24:40 +00:00
|
|
|
|
|
|
|
// react-error-overlay uses an iframe so we have to read the contents from the frame
|
|
|
|
export async function getReactErrorOverlayContent (browser) {
|
|
|
|
let found = false
|
|
|
|
setTimeout(() => {
|
|
|
|
if (found) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
console.error('TIMED OUT CHECK FOR IFRAME')
|
|
|
|
throw new Error('TIMED OUT CHECK FOR IFRAME')
|
|
|
|
}, 1000 * 30)
|
|
|
|
while (!found) {
|
|
|
|
try {
|
2018-09-25 14:54:03 +00:00
|
|
|
await browser.waitForElementByCss('iframe', 10000)
|
|
|
|
|
2018-07-24 09:24:40 +00:00
|
|
|
const hasIframe = await browser.hasElementByCssSelector('iframe')
|
|
|
|
if (!hasIframe) {
|
|
|
|
throw new Error('Waiting for iframe')
|
|
|
|
}
|
|
|
|
|
|
|
|
found = true
|
|
|
|
return browser.eval(`document.querySelector('iframe').contentWindow.document.body.innerHTML`)
|
|
|
|
} catch (ex) {
|
|
|
|
await waitFor(1000)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return browser.eval(`document.querySelector('iframe').contentWindow.document.body.innerHTML`)
|
|
|
|
}
|
2018-09-25 23:04:15 +00:00
|
|
|
|
|
|
|
export function getBrowserBodyText (browser) {
|
|
|
|
return browser.eval('document.getElementsByTagName("body")[0].innerText')
|
|
|
|
}
|