diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 7782b24942b30..e82ae0d68ad73 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -655,7 +655,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ env.NODE_LTS_VERSION }} | FORCE=1 bash && npm i -g pnpm@${PNPM_VERSION} > /dev/null && NEXT_TEST_JOB=1 NEXT_TEST_CNA=1 xvfb-run node run-tests.js test/integration/create-next-app/index.test.ts test/integration/create-next-app/templates.test.ts >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_LTS_VERSION }} ./scripts/setup-node.sh && npm i -g pnpm@${PNPM_VERSION} > /dev/null && NEXT_TEST_JOB=1 NEXT_TEST_CNA=1 xvfb-run node run-tests.js test/integration/create-next-app/index.test.ts test/integration/create-next-app/templates.test.ts >> /proc/1/fd/1" if: ${{ needs.build.outputs.docsChange == 'nope' }} - name: Upload test trace @@ -693,7 +693,7 @@ jobs: key: ${{ github.sha }}-${{ github.run_number }} - name: Run tests - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ env.NODE_LTS_VERSION }} | FORCE=1 bash && npm i -g pnpm@${PNPM_VERSION} > /dev/null && cd ./packages/next-codemod && pnpm build && pnpm test >> /proc/1/fd/1" + run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_LTS_VERSION }} ./scripts/setup-node.sh && npm i -g pnpm@${PNPM_VERSION} > /dev/null && cd ./packages/next-codemod && pnpm build && pnpm test >> /proc/1/fd/1" testIntegration: name: Test Integration @@ -758,7 +758,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ env.NODE_LTS_VERSION }} | FORCE=1 bash && npm i -g pnpm@${PNPM_VERSION} > /dev/null && NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/28 >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_LTS_VERSION }} ./scripts/setup-node.sh && npm i -g pnpm@${PNPM_VERSION} > /dev/null && NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/28 >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} - name: Upload test trace @@ -855,7 +855,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ env.NODE_LTS_VERSION }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} > /dev/null && __INTERNAL_NEXT_DEV_TEST_TURBO_DEV=TRUE __INTERNAL_CUSTOM_TURBOPACK_BINDINGS=${NEXT_BINDINGS_BIN} __INTERNAL_NEXT_DEV_TEST_TURBO_GLOB_MATCH=${NEXT_DEV_TEST_GLOB} NEXT_E2E_TEST_TIMEOUT=240000 NEXT_TEST_JOB=1 NEXT_TEST_MODE=dev TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type development --timings -c 1 $TEST_FILES_LIST >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_LTS_VERSION }} ./scripts/setup-node.sh && node -v && npm i -g pnpm@${PNPM_VERSION} > /dev/null && __INTERNAL_NEXT_DEV_TEST_TURBO_DEV=TRUE __INTERNAL_CUSTOM_TURBOPACK_BINDINGS=${NEXT_BINDINGS_BIN} __INTERNAL_NEXT_DEV_TEST_TURBO_GLOB_MATCH=${NEXT_DEV_TEST_GLOB} NEXT_E2E_TEST_TIMEOUT=240000 NEXT_TEST_JOB=1 NEXT_TEST_MODE=dev TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type development --timings -c 1 $TEST_FILES_LIST >> /proc/1/fd/1" name: Run test/development if: ${{needs.build.outputs.docsChange == 'nope'}} @@ -901,7 +901,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ env.NODE_MAINTENANCE_VERSION }} | FORCE=1 bash && npm i -g pnpm@${PNPM_VERSION} > /dev/null && BROWSERNAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_MAINTENANCE_VERSION }} ./scripts/setup-node.sh && npm i -g pnpm@${PNPM_VERSION} > /dev/null && BROWSERNAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} testSafari: @@ -955,7 +955,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ env.NODE_LTS_VERSION }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} > /dev/null && BROWSER_NAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_LTS_VERSION }} ./scripts/setup-node.sh && node -v && npm i -g pnpm@${PNPM_VERSION} > /dev/null && BROWSER_NAME=firefox NEXT_TEST_JOB=1 TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js test/integration/production/test/index.test.js >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} publishRelease: @@ -1052,7 +1052,7 @@ jobs: - run: RESET_VC_PROJECT=true node scripts/reset-vercel-project.mjs name: Reset test project - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ env.NODE_LTS_VERSION }} | FORCE=1 bash && npm i -g pnpm@${PNPM_VERSION} > /dev/null && VERCEL_TEST_TOKEN=${{ secrets.VERCEL_TEST_TOKEN }} VERCEL_TEST_TEAM=vtest314-next-e2e-tests NEXT_TEST_JOB=1 NEXT_TEST_MODE=deploy TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type e2e >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_LTS_VERSION }} ./scripts/setup-node.sh && npm i -g pnpm@${PNPM_VERSION} > /dev/null && VERCEL_TEST_TOKEN=${{ secrets.VERCEL_TEST_TOKEN }} VERCEL_TEST_TEAM=vtest314-next-e2e-tests NEXT_TEST_JOB=1 NEXT_TEST_MODE=deploy TEST_TIMINGS_TOKEN=${{ secrets.TEST_TIMINGS_TOKEN }} xvfb-run node run-tests.js --type e2e >> /proc/1/fd/1" name: Run test/e2e (deploy) - name: Upload test trace @@ -1274,7 +1274,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && curl -s https://install-node.vercel.app/v${{ env.NODE_LTS_VERSION }} | FORCE=1 bash && node -v && node ./scripts/setup-wasm.mjs && npm i -g pnpm@${PNPM_VERSION} > /dev/null && TEST_WASM=true xvfb-run node run-tests.js test/integration/production/test/index.test.js test/e2e/streaming-ssr/index.test.ts >> /proc/1/fd/1" + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.28.1-jammy /bin/bash -c "cd /work && NODE_VERSION=${{ env.NODE_LTS_VERSION }} ./scripts/setup-node.sh && node -v && node ./scripts/setup-wasm.mjs && npm i -g pnpm@${PNPM_VERSION} > /dev/null && TEST_WASM=true xvfb-run node run-tests.js test/integration/production/test/index.test.js test/e2e/streaming-ssr/index.test.ts >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} # Build binaries for publishing diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index e7ab97e1e9a75..76ba7f9f9b6e2 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -1247,13 +1247,16 @@ export default async function build( // each worker will consume ~1GB of memory in a production build. // For example, if the system has 10 CPU cores and 8GB of remaining memory // we will use 8 workers. - const numWorkers = + const numWorkers = Math.max( config.experimental.cpus !== defaultConfig.experimental!.cpus - ? config.experimental.cpus + ? (config.experimental.cpus as number) : Math.min( config.experimental.cpus || 1, Math.floor(os.freemem() / 1e9) - ) + ), + // enforce a minimum of 4 workers + 4 + ) const staticWorkers = new Worker(staticWorker, { timeout: timeout * 1000, diff --git a/scripts/setup-node.sh b/scripts/setup-node.sh new file mode 100755 index 0000000000000..72fe4e01ab601 --- /dev/null +++ b/scripts/setup-node.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# retry setting up Node.js version 5 times waiting 15 seconds between +for i in 1 2 3 4 5; + do curl -s https://install-node.vercel.app/v${NODE_VERSION} | FORCE=1 bash && break || sleep 15; +done diff --git a/test/integration/cli/test/index.test.js b/test/integration/cli/test/index.test.js index b04828b1bfa59..aca40b06e6716 100644 --- a/test/integration/cli/test/index.test.js +++ b/test/integration/cli/test/index.test.js @@ -9,6 +9,7 @@ import { runNextCommand, runNextCommandDev, } from 'next-test-utils' +import fs from 'fs-extra' import { join } from 'path' import pkg from 'next/package' import http from 'http' @@ -17,7 +18,191 @@ import stripAnsi from 'strip-ansi' const dir = join(__dirname, '..') const dirDuplicateSass = join(__dirname, '../duplicate-sass') +const testExitSignal = async ( + killSignal = '', + args = [], + readyRegex = /Creating an optimized production/ +) => { + let instance + const killSigint = (inst) => { + instance = inst + } + let output = '' + + let cmdPromise = runNextCommand(args, { + ignoreFail: true, + instance: killSigint, + onStdout: (msg) => { + output += stripAnsi(msg) + }, + }).catch((err) => expect.fail(err.message)) + + await check(() => output, readyRegex) + instance.kill(killSignal) + + const { code, signal } = await cmdPromise + // Node can only partially emulate signals on Windows. Our signal handlers won't affect the exit code. + // See: https://nodejs.org/api/process.html#process_signal_events + const expectedExitSignal = process.platform === `win32` ? killSignal : null + expect(signal).toBe(expectedExitSignal) + expect(code).toBe(0) +} + describe('CLI Usage', () => { + describe('start', () => { + test('should exit when SIGINT is signalled', async () => { + require('console').log('before build') + await fs.remove(join(dir, '.next')) + await nextBuild(dir, undefined, { + onStdout(msg) { + console.log(msg) + }, + onStderr(msg) { + console.log(msg) + }, + }) + require('console').log('build finished') + + const port = await findPort() + await testExitSignal( + 'SIGINT', + ['start', dir, '-p', port], + /started server on/ + ) + }) + test('should exit when SIGTERM is signalled', async () => { + await fs.remove(join(dir, '.next')) + await nextBuild(dir, undefined, { + onStdout(msg) { + console.log(msg) + }, + onStderr(msg) { + console.log(msg) + }, + }) + const port = await findPort() + await testExitSignal( + 'SIGTERM', + ['start', dir, '-p', port], + /started server on/ + ) + }) + + test('--help', async () => { + const help = await runNextCommand(['start', '--help'], { + stdout: true, + }) + expect(help.stdout).toMatch(/Starts the application in production mode/) + }) + + test('-h', async () => { + const help = await runNextCommand(['start', '-h'], { + stdout: true, + }) + expect(help.stdout).toMatch(/Starts the application in production mode/) + }) + + test('should format IPv6 addresses correctly', async () => { + const port = await findPort() + const output = await runNextCommand( + ['start', '--hostname', '::', '--port', port], + { + stdout: true, + } + ) + expect(output.stdout).toMatch(new RegExp(`on \\[::\\]:${port}`)) + expect(output.stdout).toMatch(new RegExp(`http://\\[::1\\]:${port}`)) + }) + + test('should warn when unknown argument provided', async () => { + const { stderr } = await runNextCommand(['start', '--random'], { + stderr: true, + }) + expect(stderr).toEqual('Unknown or unexpected option: --random\n') + }) + test('should not throw UnhandledPromiseRejectionWarning', async () => { + const { stderr } = await runNextCommand(['start', '--random'], { + stderr: true, + }) + expect(stderr).not.toContain('UnhandledPromiseRejectionWarning') + }) + + test('duplicate sass deps', async () => { + const port = await findPort() + + let stderr = '' + let instance = await launchApp(dirDuplicateSass, port, { + stderr: true, + onStderr(msg) { + stderr += msg + }, + }) + + try { + await check(() => stderr, /both `sass` and `node-sass` installed/) + } finally { + await killApp(instance) + } + }) + + test('invalid directory', async () => { + const output = await runNextCommand(['start', 'non-existent'], { + stderr: true, + }) + expect(output.stderr).toContain( + 'Invalid project directory provided, no such directory' + ) + }) + + test('--keepAliveTimeout string arg', async () => { + const { stderr } = await runNextCommand( + ['start', '--keepAliveTimeout', 'string'], + { + stderr: true, + } + ) + expect(stderr).toContain( + 'Invalid --keepAliveTimeout, expected a non negative number but received "NaN"' + ) + }) + + test('--keepAliveTimeout negative number', async () => { + const { stderr } = await runNextCommand( + ['start', '--keepAliveTimeout=-100'], + { + stderr: true, + } + ) + expect(stderr).toContain( + 'Invalid --keepAliveTimeout, expected a non negative number but received "-100"' + ) + }) + + test('--keepAliveTimeout Infinity', async () => { + const { stderr } = await runNextCommand( + ['start', '--keepAliveTimeout', 'Infinity'], + { + stderr: true, + } + ) + expect(stderr).toContain( + 'Invalid --keepAliveTimeout, expected a non negative number but received "Infinity"' + ) + }) + + test('--keepAliveTimeout happy path', async () => { + const { stderr } = await runNextCommand( + ['start', '--keepAliveTimeout', '100'], + { + stderr: true, + } + ) + expect(stderr).not.toContain( + 'Invalid keep alive timeout provided, expected a non negative number' + ) + }) + }) + describe('no command', () => { test('--help', async () => { const help = await runNextCommand(['--help'], { @@ -114,32 +299,11 @@ describe('CLI Usage', () => { }) test('should exit when SIGINT is signalled', async () => { - const killSigint = (instance) => - setTimeout(() => instance.kill('SIGINT'), 1000) - const { code, signal } = await runNextCommand(['build', dir], { - ignoreFail: true, - instance: killSigint, - }) - // Node can only partially emulate signals on Windows. Our signal handlers won't affect the exit code. - // See: https://nodejs.org/api/process.html#process_signal_events - const expectedExitCode = process.platform === `win32` ? null : 0 - const expectedExitSignal = process.platform === `win32` ? 'SIGINT' : null - expect(code).toBe(expectedExitCode) - expect(signal).toBe(expectedExitSignal) + await testExitSignal('SIGINT', ['build', dir]) }) + test('should exit when SIGTERM is signalled', async () => { - const killSigterm = (instance) => - setTimeout(() => instance.kill('SIGTERM'), 1000) - const { code, signal } = await runNextCommand(['build', dir], { - ignoreFail: true, - instance: killSigterm, - }) - // Node can only partially emulate signals on Windows. Our signal handlers won't affect the exit code. - // See: https://nodejs.org/api/process.html#process_signal_events - const expectedExitCode = process.platform === `win32` ? null : 0 - const expectedExitSignal = process.platform === `win32` ? 'SIGTERM' : null - expect(code).toBe(expectedExitCode) - expect(signal).toBe(expectedExitSignal) + await testExitSignal('SIGTERM', ['build', dir]) }) test('invalid directory', async () => { @@ -169,61 +333,113 @@ describe('CLI Usage', () => { test('custom directory', async () => { const port = await findPort() - const output = await runNextCommandDev([dir, '--port', port], true) - expect(output).toMatch(/started server/i) + let output = '' + const app = await runNextCommandDev([dir, '--port', port], undefined, { + onStdout(msg) { + output += stripAnsi(msg) + }, + }) + try { + await check(() => output, /started server/i) + } finally { + await killApp(app) + } }) test('--port', async () => { const port = await findPort() - const output = await runNextCommandDev([dir, '--port', port], true) - expect(output).toMatch(new RegExp(`on 0.0.0.0:${port}`)) - expect(output).toMatch(new RegExp(`http://localhost:${port}`)) + let output = '' + const app = await runNextCommandDev([dir, '--port', port], undefined, { + onStdout(msg) { + output += stripAnsi(msg) + }, + }) + try { + await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`http://localhost:${port}`)) + } finally { + await killApp(app) + } }) test('--port 0', async () => { - const output = await runNextCommandDev([dir, '--port', '0'], true) + const port = await findPort() + let output = '' + const app = await runNextCommandDev([dir, '--port', port], undefined, { + onStdout(msg) { + output += stripAnsi(msg) + }, + }) + try { + await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`http://localhost:${port}`)) + } finally { + await killApp(app) + } const matches = /on 0.0.0.0:(\d+)/.exec(output) expect(matches).not.toBe(null) - const port = parseInt(matches[1]) + const _port = parseInt(matches[1]) // Regression test: port 0 was interpreted as if no port had been // provided, falling back to 3000. - expect(port).not.toBe(3000) - - expect(output).toMatch(new RegExp(`http://localhost:${port}`)) + expect(_port).not.toBe(3000) }) test('PORT=0', async () => { - const output = await runNextCommandDev([dir], true, { - env: { PORT: 0 }, + let output = '' + const app = await runNextCommandDev([dir], undefined, { + env: { + PORT: 0, + }, + onStdout(msg) { + output += stripAnsi(msg) + }, }) - const matches = /on 0.0.0.0:(\d+)/.exec(output) - expect(matches).not.toBe(null) - - const port = parseInt(matches[1]) - // Regression test: port 0 was interpreted as if no port had been - // provided, falling back to 3000. - expect(port).not.toBe(3000) - - expect(output).toMatch(new RegExp(`http://localhost:${port}`)) + try { + await check(() => output, /on 0.0.0.0:(\d+)/) + const matches = /on 0.0.0.0:(\d+)/.exec(output) + const _port = parseInt(matches[1]) + expect(matches).not.toBe(null) + // Regression test: port 0 was interpreted as if no port had been + // provided, falling back to 3000. + expect(_port).not.toBe(3000) + } finally { + await killApp(app) + } }) test("NODE_OPTIONS='--inspect'", async () => { - // this test checks that --inspect works by launching a single debugger for the main Next.js process, - // not for its subprocesses const port = await findPort() - const output = await runNextCommandDev([dir, '--port', port], true, { + let output = '' + const app = await runNextCommandDev([dir, '--port', port], undefined, { + onStdout(msg) { + output += stripAnsi(msg) + }, env: { NODE_OPTIONS: '--inspect' }, }) - expect(output).toMatch(new RegExp(`on 0.0.0.0:${port}`)) - expect(output).toMatch(new RegExp(`http://localhost:${port}`)) + try { + await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`http://localhost:${port}`)) + } finally { + await killApp(app) + } }) test('-p', async () => { const port = await findPort() - const output = await runNextCommandDev([dir, '-p', port], true) - expect(output).toMatch(new RegExp(`on 0.0.0.0:${port}`)) - expect(output).toMatch(new RegExp(`http://localhost:${port}`)) + let output = '' + const app = await runNextCommandDev([dir, '-p', port], undefined, { + onStdout(msg) { + output += stripAnsi(msg) + }, + env: { NODE_OPTIONS: '--inspect' }, + }) + try { + await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`http://localhost:${port}`)) + } finally { + await killApp(app) + } }) test('-p conflict', async () => { @@ -261,241 +477,102 @@ describe('CLI Usage', () => { test('--hostname', async () => { const port = await findPort() - const output = await runNextCommandDev( + let output = '' + const app = await runNextCommandDev( [dir, '--hostname', '0.0.0.0', '--port', port], - true + undefined, + { + onStdout(msg) { + output += stripAnsi(msg) + }, + } ) - expect(output).toMatch(new RegExp(`on 0.0.0.0:${port}`)) - expect(output).toMatch(new RegExp(`http://localhost:${port}`)) + try { + await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`http://localhost:${port}`)) + } finally { + await killApp(app) + } }) test('-H', async () => { const port = await findPort() - const output = await runNextCommandDev( + let output = '' + const app = await runNextCommandDev( [dir, '-H', '0.0.0.0', '--port', port], - true + undefined, + { + onStdout(msg) { + output += stripAnsi(msg) + }, + } ) - expect(output).toMatch(new RegExp(`on 0.0.0.0:${port}`)) - expect(output).toMatch(new RegExp(`http://localhost:${port}`)) + try { + await check(() => output, new RegExp(`on 0.0.0.0:${port}`)) + await check(() => output, new RegExp(`http://localhost:${port}`)) + } finally { + await killApp(app) + } }) test('should format IPv6 addresses correctly', async () => { const port = await findPort() - const output = await runNextCommandDev( + let output = '' + const app = await runNextCommandDev( [dir, '--hostname', '::', '--port', port], - true - ) - expect(output).toMatch(new RegExp(`on \\[::\\]:${port}`)) - expect(output).toMatch(new RegExp(`http://\\[::1\\]:${port}`)) - }) - - test('should warn when unknown argument provided', async () => { - const { stderr } = await runNextCommand(['dev', '--random'], { - stderr: true, - }) - expect(stderr).toEqual('Unknown or unexpected option: --random\n') - }) - test('should not throw UnhandledPromiseRejectionWarning', async () => { - const { stderr } = await runNextCommand(['dev', '--random'], { - stderr: true, - }) - expect(stderr).not.toContain('UnhandledPromiseRejectionWarning') - }) - - test('should exit when SIGINT is signalled', async () => { - const killSigint = (instance) => - setTimeout(() => instance.kill('SIGINT'), 2000) - const port = await findPort() - const { code, signal } = await runNextCommand(['dev', dir, '-p', port], { - ignoreFail: true, - instance: killSigint, - }) - // Node can only partially emulate signals on Windows. Our signal handlers won't affect the exit code. - // See: https://nodejs.org/api/process.html#process_signal_events - const expectedExitCode = process.platform === `win32` ? null : 0 - const expectedExitSignal = process.platform === `win32` ? 'SIGINT' : null - expect(code).toBe(expectedExitCode) - expect(signal).toBe(expectedExitSignal) - }) - test('should exit when SIGTERM is signalled', async () => { - const killSigterm = (instance) => - setTimeout(() => instance.kill('SIGTERM'), 2000) - const port = await findPort() - const { code, signal } = await runNextCommand(['dev', dir, '-p', port], { - ignoreFail: true, - instance: killSigterm, - }) - // Node can only partially emulate signals on Windows. Our signal handlers won't affect the exit code. - // See: https://nodejs.org/api/process.html#process_signal_events - const expectedExitCode = process.platform === `win32` ? null : 0 - const expectedExitSignal = process.platform === `win32` ? 'SIGTERM' : null - expect(code).toBe(expectedExitCode) - expect(signal).toBe(expectedExitSignal) - }) - - test('invalid directory', async () => { - const output = await runNextCommand(['dev', 'non-existent'], { - stderr: true, - }) - expect(output.stderr).toContain( - 'Invalid project directory provided, no such directory' - ) - }) - }) - - describe('start', () => { - test('--help', async () => { - const help = await runNextCommand(['start', '--help'], { - stdout: true, - }) - expect(help.stdout).toMatch(/Starts the application in production mode/) - }) - - test('-h', async () => { - const help = await runNextCommand(['start', '-h'], { - stdout: true, - }) - expect(help.stdout).toMatch(/Starts the application in production mode/) - }) - - test('should format IPv6 addresses correctly', async () => { - const port = await findPort() - const output = await runNextCommand( - ['start', '--hostname', '::', '--port', port], + undefined, { - stdout: true, + onStdout(msg) { + output += stripAnsi(msg) + }, } ) - expect(output.stdout).toMatch(new RegExp(`on \\[::\\]:${port}`)) - expect(output.stdout).toMatch(new RegExp(`http://\\[::1\\]:${port}`)) + try { + await check(() => output, new RegExp(`on \\[::\\]:${port}`)) + await check(() => output, new RegExp(`http://\\[::1\\]:${port}`)) + } finally { + await killApp(app) + } }) test('should warn when unknown argument provided', async () => { - const { stderr } = await runNextCommand(['start', '--random'], { + const { stderr } = await runNextCommand(['dev', '--random'], { stderr: true, }) expect(stderr).toEqual('Unknown or unexpected option: --random\n') }) test('should not throw UnhandledPromiseRejectionWarning', async () => { - const { stderr } = await runNextCommand(['start', '--random'], { + const { stderr } = await runNextCommand(['dev', '--random'], { stderr: true, }) expect(stderr).not.toContain('UnhandledPromiseRejectionWarning') }) - test('duplicate sass deps', async () => { - const port = await findPort() - - let stderr = '' - let instance = await launchApp(dirDuplicateSass, port, { - stderr: true, - onStderr(msg) { - stderr += msg - }, - }) - - try { - await check(() => stderr, /both `sass` and `node-sass` installed/) - } finally { - await killApp(instance) - } - }) - test('should exit when SIGINT is signalled', async () => { - const killSigint = (instance) => - setTimeout(() => instance.kill('SIGINT'), 1000) - await nextBuild(dir) const port = await findPort() - const { code, signal } = await runNextCommand( - ['start', dir, '-p', port], - { - ignoreFail: true, - instance: killSigint, - } + await testExitSignal( + 'SIGINT', + ['dev', dir, '-p', port], + /started server on/ ) - // Node can only partially emulate signals on Windows. Our signal handlers won't affect the exit code. - // See: https://nodejs.org/api/process.html#process_signal_events - const expectedExitCode = process.platform === `win32` ? null : 0 - const expectedExitSignal = process.platform === `win32` ? 'SIGINT' : null - expect(code).toBe(expectedExitCode) - expect(signal).toBe(expectedExitSignal) }) test('should exit when SIGTERM is signalled', async () => { - const killSigterm = (instance) => - setTimeout(() => instance.kill('SIGTERM'), 1000) - await nextBuild(dir) const port = await findPort() - const { code, signal } = await runNextCommand( - ['start', dir, '-p', port], - { - ignoreFail: true, - instance: killSigterm, - } + await testExitSignal( + 'SIGTERM', + ['dev', dir, '-p', port], + /started server on/ ) - // Node can only partially emulate signals on Windows. Our signal handlers won't affect the exit code. - // See: https://nodejs.org/api/process.html#process_signal_events - const expectedExitCode = process.platform === `win32` ? null : 0 - const expectedExitSignal = process.platform === `win32` ? 'SIGTERM' : null - expect(code).toBe(expectedExitCode) - expect(signal).toBe(expectedExitSignal) }) test('invalid directory', async () => { - const output = await runNextCommand(['start', 'non-existent'], { + const output = await runNextCommand(['dev', 'non-existent'], { stderr: true, }) expect(output.stderr).toContain( 'Invalid project directory provided, no such directory' ) }) - - test('--keepAliveTimeout string arg', async () => { - const { stderr } = await runNextCommand( - ['start', '--keepAliveTimeout', 'string'], - { - stderr: true, - } - ) - expect(stderr).toContain( - 'Invalid --keepAliveTimeout, expected a non negative number but received "NaN"' - ) - }) - - test('--keepAliveTimeout negative number', async () => { - const { stderr } = await runNextCommand( - ['start', '--keepAliveTimeout=-100'], - { - stderr: true, - } - ) - expect(stderr).toContain( - 'Invalid --keepAliveTimeout, expected a non negative number but received "-100"' - ) - }) - - test('--keepAliveTimeout Infinity', async () => { - const { stderr } = await runNextCommand( - ['start', '--keepAliveTimeout', 'Infinity'], - { - stderr: true, - } - ) - expect(stderr).toContain( - 'Invalid --keepAliveTimeout, expected a non negative number but received "Infinity"' - ) - }) - - test('--keepAliveTimeout happy path', async () => { - const { stderr } = await runNextCommand( - ['start', '--keepAliveTimeout', '100'], - { - stderr: true, - } - ) - expect(stderr).not.toContain( - 'Invalid keep alive timeout provided, expected a non negative number' - ) - }) }) describe('export', () => { diff --git a/test/lib/browsers/playwright.ts b/test/lib/browsers/playwright.ts index a322d42784245..ab638cc3a697b 100644 --- a/test/lib/browsers/playwright.ts +++ b/test/lib/browsers/playwright.ts @@ -110,6 +110,12 @@ export class Playwright extends BrowserInterface { await oldPage.close() } page = await context.newPage() + + // in development compilation can take longer due to + // lower CPU availability in GH actions + page.setDefaultTimeout(60 * 1000) + page.setDefaultNavigationTimeout(60 * 1000) + pageLogs = [] websocketFrames = []