diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 00377d09a11d1..3902b92839156 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -109,7 +109,7 @@ stages: condition: eq(variables['isDocsOnly'], 'No') displayName: 'Run tests' - - job: test_integration_app_dir + - job: test_e2e_dev pool: vmImage: 'windows-2019' steps: @@ -139,6 +139,44 @@ stages: condition: eq(variables['isDocsOnly'], 'No') - script: | - node run-tests.js -c 1 test/integration/app-dir-basic/test/index.test.js + node run-tests.js -c 1 --debug test/e2e/app-dir/app/index.test.ts condition: eq(variables['isDocsOnly'], 'No') - displayName: 'Run tests' + displayName: 'Run tests (E2E Development)' + env: + NEXT_TEST_MODE: 'dev' + + - job: test_e2e_prod + pool: + vmImage: 'windows-2019' + steps: + - task: NodeTool@0 + inputs: + versionSpec: $(node_16_version) + displayName: 'Install Node.js' + + - bash: | + node scripts/run-for-change.js --not --type docs --exec echo "##vso[task.setvariable variable=isDocsOnly]No" + displayName: 'Check Docs Only Change' + + - script: npm i -g pnpm@$(PNPM_VERSION) + condition: eq(variables['isDocsOnly'], 'No') + + - script: pnpm config set store-dir $(PNPM_CACHE_FOLDER) + condition: eq(variables['isDocsOnly'], 'No') + + - script: pnpm store path + condition: eq(variables['isDocsOnly'], 'No') + + - script: pnpm install && pnpm run build + condition: eq(variables['isDocsOnly'], 'No') + displayName: 'Install and build' + + - script: npx playwright install chromium + condition: eq(variables['isDocsOnly'], 'No') + + - script: | + node run-tests.js -c 1 --debug test/e2e/app-dir/app/index.test.ts + condition: eq(variables['isDocsOnly'], 'No') + displayName: 'Run tests (E2E Production)' + env: + NEXT_TEST_MODE: 'start' diff --git a/run-tests.js b/run-tests.js index 687c9702e72f4..f43695e814d2a 100644 --- a/run-tests.js +++ b/run-tests.js @@ -49,11 +49,22 @@ const cleanUpAndExit = async (code) => { } async function getTestTimings() { - const timingsRes = await fetch(TIMINGS_API, { - headers: { - ...TIMINGS_API_HEADERS, - }, - }) + let timingsRes + + const doFetch = () => + fetch(TIMINGS_API, { + headers: { + ...TIMINGS_API_HEADERS, + }, + }) + timingsRes = await doFetch() + + if (timingsRes.status === 403) { + const delay = 15 + console.log(`Got 403 response waiting ${delay} seconds before retry`) + await new Promise((resolve) => setTimeout(resolve, delay * 1000)) + timingsRes = await doFetch() + } if (!timingsRes.ok) { throw new Error(`request status: ${timingsRes.status}`) @@ -219,6 +230,7 @@ async function main() { }) if ( + process.platform !== 'win32' && process.env.NEXT_TEST_MODE !== 'deploy' && ((testType && testType !== 'unit') || hasIsolatedTests) ) { diff --git a/test/integration/app-dir-basic/app/blog/page.js b/test/integration/app-dir-basic/app/blog/page.js deleted file mode 100644 index 1f2551bd40887..0000000000000 --- a/test/integration/app-dir-basic/app/blog/page.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function page() { - return
this is blog
-} diff --git a/test/integration/app-dir-basic/app/layout.js b/test/integration/app-dir-basic/app/layout.js deleted file mode 100644 index 747270b45987a..0000000000000 --- a/test/integration/app-dir-basic/app/layout.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function RootLayout({ children }) { - return ( - - - {children} - - ) -} diff --git a/test/integration/app-dir-basic/app/page.js b/test/integration/app-dir-basic/app/page.js deleted file mode 100644 index 25ec619b899db..0000000000000 --- a/test/integration/app-dir-basic/app/page.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function page() { - return
this is home
-} diff --git a/test/integration/app-dir-basic/next.config.js b/test/integration/app-dir-basic/next.config.js deleted file mode 100644 index cfa3ac3d7aa94..0000000000000 --- a/test/integration/app-dir-basic/next.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - experimental: { - appDir: true, - }, -} diff --git a/test/integration/app-dir-basic/test/index.test.js b/test/integration/app-dir-basic/test/index.test.js deleted file mode 100644 index efa7aeb5e707e..0000000000000 --- a/test/integration/app-dir-basic/test/index.test.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-env jest */ - -import { join } from 'path' -import cheerio from 'cheerio' -import { runDevSuite, runProdSuite, renderViaHTTP } from 'next-test-utils' - -import webdriver from 'next-webdriver' -const appDir = join(__dirname, '..') - -function runTests(context, env) { - describe('App Dir Basic', () => { - it('should render html properly', async () => { - const $index = cheerio.load(await renderViaHTTP(context.appPort, '/')) - const $blog = cheerio.load(await renderViaHTTP(context.appPort, '/blog')) - - expect($index('#home').text()).toBe('this is home') - expect($blog('#blog').text()).toBe('this is blog') - }) - - it('should hydrate pages properly', async () => { - const browser = await webdriver(context.appPort, '/') - const indexHtml = await browser.waitForElementByCss('#home').text() - const url = await browser.url() - await browser.loadPage(url + 'blog') - const blogHtml = await browser.waitForElementByCss('#blog').text() - - expect(indexHtml).toBe('this is home') - expect(blogHtml).toBe('this is blog') - }) - }) -} - -runDevSuite('App Dir Basic', appDir, { runTests }) -runProdSuite('App Dir Basic', appDir, { runTests }) diff --git a/test/lib/next-modes/next-dev.ts b/test/lib/next-modes/next-dev.ts index b2699bcd9b91f..41e510e586808 100644 --- a/test/lib/next-modes/next-dev.ts +++ b/test/lib/next-modes/next-dev.ts @@ -1,4 +1,4 @@ -import { spawn } from 'child_process' +import { spawn } from 'cross-spawn' import { Span } from 'next/trace' import { NextInstance } from './base' @@ -36,65 +36,70 @@ export class NextDevInstance extends NextInstance { } await new Promise((resolve, reject) => { - this.childProcess = spawn(startArgs[0], startArgs.slice(1), { - cwd: useDirArg ? process.cwd() : this.testDir, - stdio: ['ignore', 'pipe', 'pipe'], - shell: false, - env: { - ...process.env, - ...this.env, - NODE_ENV: '' as any, - PORT: this.forcedPort || '0', - __NEXT_TEST_MODE: '1', - __NEXT_TEST_WITH_DEVTOOL: '1', - }, - }) + try { + this.childProcess = spawn(startArgs[0], startArgs.slice(1), { + cwd: useDirArg ? process.cwd() : this.testDir, + stdio: ['ignore', 'pipe', 'pipe'], + shell: false, + env: { + ...process.env, + ...this.env, + NODE_ENV: '' as any, + PORT: this.forcedPort || '0', + __NEXT_TEST_MODE: '1', + __NEXT_TEST_WITH_DEVTOOL: '1', + }, + }) - this._cliOutput = '' + this._cliOutput = '' - this.childProcess.stdout.on('data', (chunk) => { - const msg = chunk.toString() - process.stdout.write(chunk) - this._cliOutput += msg - this.emit('stdout', [msg]) - }) - this.childProcess.stderr.on('data', (chunk) => { - const msg = chunk.toString() - process.stderr.write(chunk) - this._cliOutput += msg - this.emit('stderr', [msg]) - }) + this.childProcess.stdout.on('data', (chunk) => { + const msg = chunk.toString() + process.stdout.write(chunk) + this._cliOutput += msg + this.emit('stdout', [msg]) + }) + this.childProcess.stderr.on('data', (chunk) => { + const msg = chunk.toString() + process.stderr.write(chunk) + this._cliOutput += msg + this.emit('stderr', [msg]) + }) - this.childProcess.on('close', (code, signal) => { - if (this.isStopping) return - if (code || signal) { - throw new Error( - `next dev exited unexpectedly with code/signal ${code || signal}` - ) - } - }) - const readyCb = (msg) => { - if (msg.includes('started server on') && msg.includes('url:')) { - // turbo devserver emits stdout in rust directly, can contain unexpected chars with color codes - // strip out again for the safety - this._url = msg - .split('url: ') - .pop() - .trim() - .split(require('os').EOL)[0] - try { - this._parsedUrl = new URL(this._url) - } catch (err) { - reject({ - err, - msg, - }) + this.childProcess.on('close', (code, signal) => { + if (this.isStopping) return + if (code || signal) { + throw new Error( + `next dev exited unexpectedly with code/signal ${code || signal}` + ) + } + }) + const readyCb = (msg) => { + if (msg.includes('started server on') && msg.includes('url:')) { + // turbo devserver emits stdout in rust directly, can contain unexpected chars with color codes + // strip out again for the safety + this._url = msg + .split('url: ') + .pop() + .trim() + .split(require('os').EOL)[0] + try { + this._parsedUrl = new URL(this._url) + } catch (err) { + reject({ + err, + msg, + }) + } + this.off('stdout', readyCb) + resolve() } - this.off('stdout', readyCb) - resolve() } + this.on('stdout', readyCb) + } catch (err) { + require('console').error(`Failed to run ${startArgs.join(' ')}`, err) + setTimeout(() => process.exit(1), 0) } - this.on('stdout', readyCb) }) } } diff --git a/test/lib/next-modes/next-start.ts b/test/lib/next-modes/next-start.ts index f0b1484c0413e..d9dc6b4cdc533 100644 --- a/test/lib/next-modes/next-start.ts +++ b/test/lib/next-modes/next-start.ts @@ -1,7 +1,7 @@ import path from 'path' import fs from 'fs-extra' import { NextInstance } from './base' -import { spawn, SpawnOptions } from 'child_process' +import { spawn, SpawnOptions } from 'cross-spawn' import { Span } from 'next/trace' export class NextStartInstance extends NextInstance { @@ -65,20 +65,26 @@ export class NextStartInstance extends NextInstance { await new Promise((resolve, reject) => { console.log('running', buildArgs.join(' ')) - this.childProcess = spawn( - buildArgs[0], - buildArgs.slice(1), - this.spawnOpts - ) - this.handleStdio(this.childProcess) - this.childProcess.on('exit', (code, signal) => { - this.childProcess = null - if (code || signal) - reject( - new Error(`next build failed with code/signal ${code || signal}`) - ) - else resolve() - }) + + try { + this.childProcess = spawn( + buildArgs[0], + buildArgs.slice(1), + this.spawnOpts + ) + this.handleStdio(this.childProcess) + this.childProcess.on('exit', (code, signal) => { + this.childProcess = null + if (code || signal) + reject( + new Error(`next build failed with code/signal ${code || signal}`) + ) + else resolve() + }) + } catch (err) { + require('console').error(`Failed to run ${buildArgs.join(' ')}`, err) + setTimeout(() => process.exit(1), 0) + } }) this._buildId = ( @@ -95,31 +101,38 @@ export class NextStartInstance extends NextInstance { console.log('running', startArgs.join(' ')) await new Promise((resolve) => { - this.childProcess = spawn( - startArgs[0], - startArgs.slice(1), - this.spawnOpts - ) - this.handleStdio(this.childProcess) - - this.childProcess.on('close', (code, signal) => { - if (this.isStopping) return - if (code || signal) { - throw new Error( - `next start exited unexpectedly with code/signal ${code || signal}` - ) - } - }) + try { + this.childProcess = spawn( + startArgs[0], + startArgs.slice(1), + this.spawnOpts + ) + this.handleStdio(this.childProcess) + + this.childProcess.on('close', (code, signal) => { + if (this.isStopping) return + if (code || signal) { + throw new Error( + `next start exited unexpectedly with code/signal ${ + code || signal + }` + ) + } + }) - const readyCb = (msg) => { - if (msg.includes('started server on') && msg.includes('url:')) { - this._url = msg.split('url: ').pop().trim() - this._parsedUrl = new URL(this._url) - this.off('stdout', readyCb) - resolve() + const readyCb = (msg) => { + if (msg.includes('started server on') && msg.includes('url:')) { + this._url = msg.split('url: ').pop().trim() + this._parsedUrl = new URL(this._url) + this.off('stdout', readyCb) + resolve() + } } + this.on('stdout', readyCb) + } catch (err) { + require('console').error(`Failed to run ${startArgs.join(' ')}`, err) + setTimeout(() => process.exit(1), 0) } - this.on('stdout', readyCb) }) }