From 89619fbeec1f8ca621b7a28214ac8a938469675d Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 2 Jul 2025 09:20:18 +0100 Subject: [PATCH 1/4] test: add additional e2e command tests --- packages/nuxt-cli/test/e2e/commands.spec.ts | 102 ++++++++++++++++++-- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/commands.spec.ts b/packages/nuxt-cli/test/e2e/commands.spec.ts index 846e888f5..378604d77 100644 --- a/packages/nuxt-cli/test/e2e/commands.spec.ts +++ b/packages/nuxt-cli/test/e2e/commands.spec.ts @@ -28,18 +28,87 @@ describe('commands', () => { await rm(file, { force: true }) }, analyze: 'todo', - build: 'todo', - cleanup: 'todo', + build: async () => { + const res = await x(nuxi, ['build'], { + throwOnError: true, + nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, + }) + expect(res.exitCode).toBe(0) + expect(existsSync(join(fixtureDir, '.output'))).toBeTruthy() + expect(existsSync(join(fixtureDir, '.output/server'))).toBeTruthy() + expect(existsSync(join(fixtureDir, '.output/public'))).toBeTruthy() + }, + cleanup: async () => { + const res = await x(nuxi, ['cleanup'], { + throwOnError: true, + nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, + }) + expect(res.exitCode).toBe(0) + }, devtools: 'todo', module: 'todo', - prepare: 'todo', - preview: 'todo', + prepare: async () => { + const res = await x(nuxi, ['prepare'], { + throwOnError: true, + nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, + }) + expect(res.exitCode).toBe(0) + expect(existsSync(join(fixtureDir, '.nuxt'))).toBeTruthy() + expect(existsSync(join(fixtureDir, '.nuxt/types'))).toBeTruthy() + }, + preview: async () => { + await x(nuxi, ['build'], { + throwOnError: true, + nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, + }) + + const previewProcess = x(nuxi, ['preview', '--port=3002'], { + throwOnError: true, + nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, + }) + + // Test that server responds + const response = await fetchWithPolling('http://localhost:3002').catch(() => null) + expect.soft(response?.status).toBe(200) + + previewProcess.kill() + }, start: 'todo', test: 'todo', - typecheck: 'todo', + typecheck: async () => { + const res = await x(nuxi, ['typecheck'], { + throwOnError: true, + nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, + }) + expect(res.exitCode).toBe(0) + }, upgrade: 'todo', - dev: 'todo', - generate: 'todo', + dev: async () => { + const controller = new AbortController() + const devProcess = x(nuxi, ['dev', '--port=3001'], { + nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, + signal: controller.signal, + }) + + // Test that server responds + const response = await fetchWithPolling('http://localhost:3001', {}, 30, 300) + expect.soft(response.status).toBe(200) + + controller.abort() + try { + await devProcess + } + catch {} + }, + generate: async () => { + const res = await x(nuxi, ['generate'], { + throwOnError: true, + nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, + }) + expect(res.exitCode).toBe(0) + expect(existsSync(join(fixtureDir, 'dist'))).toBeTruthy() + expect(existsSync(join(fixtureDir, 'dist/index.html'))).toBeTruthy() + }, init: async () => { const dir = tmpdir() const pm = 'pnpm' @@ -87,3 +156,22 @@ describe('commands', () => { } } }) + +async function fetchWithPolling(url: string, options: RequestInit = {}, maxAttempts = 10, interval = 100): Promise { + let response: Response | null = null + let attempts = 0 + while (attempts < maxAttempts) { + try { + response = await fetch(url, options) + if (response.ok) { + return response + } + } + catch { + // Ignore errors and retry + } + attempts++ + await new Promise(resolve => setTimeout(resolve, interval)) + } + return response as Response +} From 6dc09f0ae355958339371c395abf3483331f5aac Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 2 Jul 2025 09:26:43 +0100 Subject: [PATCH 2/4] chore: use random port --- packages/nuxt-cli/package.json | 1 + packages/nuxt-cli/test/e2e/commands.spec.ts | 13 ++++++++----- pnpm-lock.yaml | 3 +++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/nuxt-cli/package.json b/packages/nuxt-cli/package.json index e539bc501..ab39e0eee 100644 --- a/packages/nuxt-cli/package.json +++ b/packages/nuxt-cli/package.json @@ -62,6 +62,7 @@ "devDependencies": { "@nuxt/schema": "^3.17.6", "@types/node": "^22.15.32", + "get-port-please": "^3.1.2", "rollup": "^4.44.0", "rollup-plugin-visualizer": "^6.0.3", "typescript": "^5.8.3", diff --git a/packages/nuxt-cli/test/e2e/commands.spec.ts b/packages/nuxt-cli/test/e2e/commands.spec.ts index 378604d77..b8b560869 100644 --- a/packages/nuxt-cli/test/e2e/commands.spec.ts +++ b/packages/nuxt-cli/test/e2e/commands.spec.ts @@ -7,6 +7,7 @@ import { readdir, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { fileURLToPath } from 'node:url' +import { getPort } from 'get-port-please' import { isWindows } from 'std-env' import { x } from 'tinyexec' import { describe, expect, it } from 'vitest' @@ -62,14 +63,15 @@ describe('commands', () => { nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, }) - const previewProcess = x(nuxi, ['preview', '--port=3002'], { + const port = await getPort({ host: 'localhost', port: 3002 }) + const previewProcess = x(nuxi, ['preview', `--port=${port}`], { throwOnError: true, nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, }) // Test that server responds - const response = await fetchWithPolling('http://localhost:3002').catch(() => null) - expect.soft(response?.status).toBe(200) + const response = await fetchWithPolling(`http://localhost:${port}`) + expect.soft(response.status).toBe(200) previewProcess.kill() }, @@ -85,13 +87,14 @@ describe('commands', () => { upgrade: 'todo', dev: async () => { const controller = new AbortController() - const devProcess = x(nuxi, ['dev', '--port=3001'], { + const port = await getPort({ host: 'localhost', port: 3001 }) + const devProcess = x(nuxi, ['dev', `--port=${port}`], { nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, signal: controller.signal, }) // Test that server responds - const response = await fetchWithPolling('http://localhost:3001', {}, 30, 300) + const response = await fetchWithPolling(`http://localhost:${port}`, {}, 30, 300) expect.soft(response.status).toBe(200) controller.abort() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af6b294ce..3b57673c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -299,6 +299,9 @@ importers: '@types/node': specifier: ^22.15.32 version: 22.15.32 + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 rollup: specifier: ^4.44.0 version: 4.44.0 From 560f3ababc7331b30744f36cb3330b5e14a4d1ac Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 2 Jul 2025 09:40:37 +0100 Subject: [PATCH 3/4] chore: try 127.0.0.1 --- packages/nuxt-cli/test/e2e/commands.spec.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/commands.spec.ts b/packages/nuxt-cli/test/e2e/commands.spec.ts index b8b560869..4b0dbaf92 100644 --- a/packages/nuxt-cli/test/e2e/commands.spec.ts +++ b/packages/nuxt-cli/test/e2e/commands.spec.ts @@ -63,15 +63,15 @@ describe('commands', () => { nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, }) - const port = await getPort({ host: 'localhost', port: 3002 }) - const previewProcess = x(nuxi, ['preview', `--port=${port}`], { + const port = await getPort({ host: '127.0.0.1', port: 3002 }) + const previewProcess = x(nuxi, ['preview', `--host=127.0.0.1`, `--port=${port}`], { throwOnError: true, nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, }) // Test that server responds - const response = await fetchWithPolling(`http://localhost:${port}`) - expect.soft(response.status).toBe(200) + const response = await fetchWithPolling(`http://127.0.0.1:${port}`) + expect.soft(response?.status).toBe(200) previewProcess.kill() }, @@ -87,15 +87,15 @@ describe('commands', () => { upgrade: 'todo', dev: async () => { const controller = new AbortController() - const port = await getPort({ host: 'localhost', port: 3001 }) - const devProcess = x(nuxi, ['dev', `--port=${port}`], { + const port = await getPort({ host: '127.0.0.1', port: 3001 }) + const devProcess = x(nuxi, ['dev', `--host=127.0.0.1`, `--port=${port}`], { nodeOptions: { stdio: 'pipe', cwd: fixtureDir }, signal: controller.signal, }) // Test that server responds - const response = await fetchWithPolling(`http://localhost:${port}`, {}, 30, 300) - expect.soft(response.status).toBe(200) + const response = await fetchWithPolling(`http://127.0.0.1:${port}`, {}, 30, 300) + expect.soft(response?.status).toBe(200) controller.abort() try { @@ -160,7 +160,7 @@ describe('commands', () => { } }) -async function fetchWithPolling(url: string, options: RequestInit = {}, maxAttempts = 10, interval = 100): Promise { +async function fetchWithPolling(url: string, options: RequestInit = {}, maxAttempts = 10, interval = 100): Promise { let response: Response | null = null let attempts = 0 while (attempts < maxAttempts) { @@ -176,5 +176,5 @@ async function fetchWithPolling(url: string, options: RequestInit = {}, maxAttem attempts++ await new Promise(resolve => setTimeout(resolve, interval)) } - return response as Response + return response } From b7a6ad0fb85ed63c1497ceb52a143f2e744b90ae Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 2 Jul 2025 11:22:14 +0100 Subject: [PATCH 4/4] test: multiply by 10 in ci --- packages/nuxt-cli/test/e2e/commands.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/commands.spec.ts b/packages/nuxt-cli/test/e2e/commands.spec.ts index 4b0dbaf92..47bd33bb3 100644 --- a/packages/nuxt-cli/test/e2e/commands.spec.ts +++ b/packages/nuxt-cli/test/e2e/commands.spec.ts @@ -8,7 +8,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import { getPort } from 'get-port-please' -import { isWindows } from 'std-env' +import { isCI, isWindows } from 'std-env' import { x } from 'tinyexec' import { describe, expect, it } from 'vitest' @@ -174,7 +174,7 @@ async function fetchWithPolling(url: string, options: RequestInit = {}, maxAttem // Ignore errors and retry } attempts++ - await new Promise(resolve => setTimeout(resolve, interval)) + await new Promise(resolve => setTimeout(resolve, isCI ? interval * 10 : interval)) } return response }