From 7ac7814d3dab851ca9036db8283d2b5a0adcf60b Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 30 Dec 2022 15:57:55 -0800 Subject: [PATCH 1/3] Update azure pipelines config --- azure-pipelines.yml | 44 ++++++- run-tests.js | 21 +++- .../app-dir-basic/app/blog/page.js | 3 - test/integration/app-dir-basic/app/layout.js | 8 -- test/integration/app-dir-basic/app/page.js | 3 - test/integration/app-dir-basic/next.config.js | 5 - .../app-dir-basic/test/index.test.js | 34 ------ test/lib/next-modes/next-dev.ts | 113 +++++++++--------- test/lib/next-modes/next-start.ts | 87 ++++++++------ 9 files changed, 166 insertions(+), 152 deletions(-) delete mode 100644 test/integration/app-dir-basic/app/blog/page.js delete mode 100644 test/integration/app-dir-basic/app/layout.js delete mode 100644 test/integration/app-dir-basic/app/page.js delete mode 100644 test/integration/app-dir-basic/next.config.js delete mode 100644 test/integration/app-dir-basic/test/index.test.js diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 00377d09a11d1..7fb8ef446bddb 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 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 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..03dcc140fc563 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}`) 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) }) } From 1e8f3b9ecc262d10584b7678be6faeff01293510 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 30 Dec 2022 16:22:14 -0800 Subject: [PATCH 2/3] add debug flag --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7fb8ef446bddb..3902b92839156 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -139,7 +139,7 @@ stages: condition: eq(variables['isDocsOnly'], 'No') - script: | - node run-tests.js -c 1 test/e2e/app-dir/app/index.test.ts + node run-tests.js -c 1 --debug test/e2e/app-dir/app/index.test.ts condition: eq(variables['isDocsOnly'], 'No') displayName: 'Run tests (E2E Development)' env: @@ -175,7 +175,7 @@ stages: condition: eq(variables['isDocsOnly'], 'No') - script: | - node run-tests.js -c 1 test/e2e/app-dir/app/index.test.ts + 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: From 120b0db77366dfe2692010d26ac221200156e31a Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 30 Dec 2022 16:36:53 -0800 Subject: [PATCH 3/3] skip test starter for windows --- run-tests.js | 1 + 1 file changed, 1 insertion(+) diff --git a/run-tests.js b/run-tests.js index 03dcc140fc563..f43695e814d2a 100644 --- a/run-tests.js +++ b/run-tests.js @@ -230,6 +230,7 @@ async function main() { }) if ( + process.platform !== 'win32' && process.env.NEXT_TEST_MODE !== 'deploy' && ((testType && testType !== 'unit') || hasIsolatedTests) ) {