From 0eae7ee07232d4209322e55e35a5c734e44d2343 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Thu, 16 Apr 2026 23:12:24 +0200 Subject: [PATCH 1/9] chore: unify react-start basic e2e mode projects --- e2e/react-start/basic-prerender/.gitignore | 5 - e2e/react-start/basic-prerender/package.json | 24 -- .../basic-prerender/playwright.config.ts | 48 --- e2e/react-start/basic-preview/.gitignore | 5 - e2e/react-start/basic-preview/package.json | 24 -- .../basic-preview/playwright.config.ts | 47 --- e2e/react-start/basic-spa/.gitignore | 5 - e2e/react-start/basic-spa/package.json | 24 -- .../basic-spa/playwright.config.ts | 48 --- e2e/react-start/basic-test-suite/.gitignore | 5 - e2e/react-start/basic-test-suite/package.json | 12 - .../src/utils/getBasicAppRoot.ts | 8 - .../basic-test-suite/src/utils/isPrerender.ts | 1 - .../basic-test-suite/src/utils/isPreview.ts | 1 - .../basic-test-suite/src/utils/isSpaMode.ts | 1 - .../basic-test-suite/tsconfig.json | 18 - e2e/react-start/basic/.gitignore | 1 + e2e/react-start/basic/package.json | 34 +- e2e/react-start/basic/playwright.config.ts | 36 +- e2e/react-start/basic/server.js | 11 +- .../src => basic/tests}/client-only.spec.ts | 0 .../src => basic/tests}/navigation.spec.ts | 0 .../src => basic/tests}/not-found.spec.ts | 0 .../tests}/open-redirect-prevention.spec.ts | 0 .../src => basic/tests}/prerendering.spec.ts | 10 +- .../src => basic/tests}/raw-stream.spec.ts | 0 .../src => basic/tests}/redirect.spec.ts | 8 +- .../src => basic/tests}/root-scripts.spec.ts | 0 .../tests}/script-duplication.spec.ts | 0 .../src => basic/tests}/search-params.spec.ts | 0 .../src => basic/tests}/setup/global.setup.ts | 4 +- .../tests}/setup/global.teardown.ts | 4 +- .../tests}/setup/waitForDummyServer.ts | 4 +- .../tests}/special-characters.spec.ts | 0 .../src => basic/tests}/streaming.spec.ts | 0 .../tests}/type-only-reexport.spec.ts | 0 .../basic/tests/utils/getE2EPortKey.ts | 5 + .../tests}/utils/getPackageName.ts | 0 e2e/react-start/basic/tsconfig.json | 2 + package.json | 2 +- pnpm-lock.yaml | 63 ---- scripts/nx/playwright-plugin.md | 175 ++++++---- scripts/nx/playwright-plugin.ts | 329 +++++++++++++++++- 43 files changed, 514 insertions(+), 450 deletions(-) delete mode 100644 e2e/react-start/basic-prerender/.gitignore delete mode 100644 e2e/react-start/basic-prerender/package.json delete mode 100644 e2e/react-start/basic-prerender/playwright.config.ts delete mode 100644 e2e/react-start/basic-preview/.gitignore delete mode 100644 e2e/react-start/basic-preview/package.json delete mode 100644 e2e/react-start/basic-preview/playwright.config.ts delete mode 100644 e2e/react-start/basic-spa/.gitignore delete mode 100644 e2e/react-start/basic-spa/package.json delete mode 100644 e2e/react-start/basic-spa/playwright.config.ts delete mode 100644 e2e/react-start/basic-test-suite/.gitignore delete mode 100644 e2e/react-start/basic-test-suite/package.json delete mode 100644 e2e/react-start/basic-test-suite/src/utils/getBasicAppRoot.ts delete mode 100644 e2e/react-start/basic-test-suite/src/utils/isPrerender.ts delete mode 100644 e2e/react-start/basic-test-suite/src/utils/isPreview.ts delete mode 100644 e2e/react-start/basic-test-suite/src/utils/isSpaMode.ts delete mode 100644 e2e/react-start/basic-test-suite/tsconfig.json rename e2e/react-start/{basic-test-suite/src => basic/tests}/client-only.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/navigation.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/not-found.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/open-redirect-prevention.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/prerendering.spec.ts (90%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/raw-stream.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/redirect.spec.ts (97%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/root-scripts.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/script-duplication.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/search-params.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/setup/global.setup.ts (50%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/setup/global.teardown.ts (55%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/setup/waitForDummyServer.ts (82%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/special-characters.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/streaming.spec.ts (100%) rename e2e/react-start/{basic-test-suite/src => basic/tests}/type-only-reexport.spec.ts (100%) create mode 100644 e2e/react-start/basic/tests/utils/getE2EPortKey.ts rename e2e/react-start/{basic-test-suite/src => basic/tests}/utils/getPackageName.ts (100%) diff --git a/e2e/react-start/basic-prerender/.gitignore b/e2e/react-start/basic-prerender/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/react-start/basic-prerender/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/react-start/basic-prerender/package.json b/e2e/react-start/basic-prerender/package.json deleted file mode 100644 index 415e0f4739..0000000000 --- a/e2e/react-start/basic-prerender/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-react-start-e2e-basic-prerender", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf port*.txt; MODE=prerender playwright test --project=chromium" - }, - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-react-start-e2e-basic": "workspace:*", - "tanstack-react-start-e2e-basic-test-suite": "workspace:*" - }, - "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } - } - } -} diff --git a/e2e/react-start/basic-prerender/playwright.config.ts b/e2e/react-start/basic-prerender/playwright.config.ts deleted file mode 100644 index cf2cf5d396..0000000000 --- a/e2e/react-start/basic-prerender/playwright.config.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' -import { - getDummyServerPort, - getTestServerPort, -} from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } - -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - testDir: '../basic-test-suite/src', - workers: 1, - reporter: [['line']], - - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', - - use: { - baseURL, - }, - - webServer: { - command: - 'pnpm run test:e2e:startDummyServer && pnpm --dir ../basic build:prerender && pnpm --dir ../basic start', - url: baseURL, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - env: { - MODE: 'prerender', - VITE_NODE_ENV: 'test', - VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), - VITE_SERVER_PORT: String(PORT), - START_PORT: String(START_PORT), - PORT: String(PORT), - }, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - ], -}) diff --git a/e2e/react-start/basic-preview/.gitignore b/e2e/react-start/basic-preview/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/react-start/basic-preview/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/react-start/basic-preview/package.json b/e2e/react-start/basic-preview/package.json deleted file mode 100644 index ba24e8a41a..0000000000 --- a/e2e/react-start/basic-preview/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-react-start-e2e-basic-preview", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf port*.txt; MODE=preview playwright test --project=chromium" - }, - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-react-start-e2e-basic": "workspace:*", - "tanstack-react-start-e2e-basic-test-suite": "workspace:*" - }, - "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } - } - } -} diff --git a/e2e/react-start/basic-preview/playwright.config.ts b/e2e/react-start/basic-preview/playwright.config.ts deleted file mode 100644 index 071f336f9f..0000000000 --- a/e2e/react-start/basic-preview/playwright.config.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' -import { - getDummyServerPort, - getTestServerPort, -} from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } - -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - testDir: '../basic-test-suite/src', - workers: 1, - reporter: [['line']], - - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', - - use: { - baseURL, - }, - - webServer: { - command: `pnpm run test:e2e:startDummyServer && pnpm --dir ../basic build && pnpm --dir ../basic preview --port ${PORT}`, - url: baseURL, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - env: { - MODE: 'preview', - VITE_NODE_ENV: 'test', - VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), - VITE_SERVER_PORT: String(PORT), - START_PORT: String(START_PORT), - PORT: String(PORT), - }, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - ], -}) diff --git a/e2e/react-start/basic-spa/.gitignore b/e2e/react-start/basic-spa/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/react-start/basic-spa/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/react-start/basic-spa/package.json b/e2e/react-start/basic-spa/package.json deleted file mode 100644 index 709ce8a0d6..0000000000 --- a/e2e/react-start/basic-spa/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-react-start-e2e-basic-spa", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf port*.txt; MODE=spa playwright test --project=chromium" - }, - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-react-start-e2e-basic": "workspace:*", - "tanstack-react-start-e2e-basic-test-suite": "workspace:*" - }, - "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } - } - } -} diff --git a/e2e/react-start/basic-spa/playwright.config.ts b/e2e/react-start/basic-spa/playwright.config.ts deleted file mode 100644 index 27e85e1f1a..0000000000 --- a/e2e/react-start/basic-spa/playwright.config.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' -import { - getDummyServerPort, - getTestServerPort, -} from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } - -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - testDir: '../basic-test-suite/src', - workers: 1, - reporter: [['line']], - - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', - - use: { - baseURL, - }, - - webServer: { - command: - 'pnpm run test:e2e:startDummyServer && pnpm --dir ../basic build:spa && pnpm --dir ../basic start', - url: baseURL, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - env: { - MODE: 'spa', - VITE_NODE_ENV: 'test', - VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), - VITE_SERVER_PORT: String(PORT), - START_PORT: String(START_PORT), - PORT: String(PORT), - }, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - ], -}) diff --git a/e2e/react-start/basic-test-suite/.gitignore b/e2e/react-start/basic-test-suite/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/react-start/basic-test-suite/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/react-start/basic-test-suite/package.json b/e2e/react-start/basic-test-suite/package.json deleted file mode 100644 index aaf431e09d..0000000000 --- a/e2e/react-start/basic-test-suite/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "tanstack-react-start-e2e-basic-test-suite", - "private": true, - "sideEffects": false, - "type": "module", - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:*", - "@types/node": "^22.10.2", - "combinate": "^1.1.11" - } -} diff --git a/e2e/react-start/basic-test-suite/src/utils/getBasicAppRoot.ts b/e2e/react-start/basic-test-suite/src/utils/getBasicAppRoot.ts deleted file mode 100644 index 4725f532ed..0000000000 --- a/e2e/react-start/basic-test-suite/src/utils/getBasicAppRoot.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { join } from 'node:path' -import { getPackageName } from './getPackageName.ts' - -export function getBasicAppRoot() { - return getPackageName() === 'tanstack-react-start-e2e-basic' - ? process.cwd() - : join(process.cwd(), '../basic') -} diff --git a/e2e/react-start/basic-test-suite/src/utils/isPrerender.ts b/e2e/react-start/basic-test-suite/src/utils/isPrerender.ts deleted file mode 100644 index d5d991d454..0000000000 --- a/e2e/react-start/basic-test-suite/src/utils/isPrerender.ts +++ /dev/null @@ -1 +0,0 @@ -export const isPrerender: boolean = process.env.MODE === 'prerender' diff --git a/e2e/react-start/basic-test-suite/src/utils/isPreview.ts b/e2e/react-start/basic-test-suite/src/utils/isPreview.ts deleted file mode 100644 index 7ea362a83e..0000000000 --- a/e2e/react-start/basic-test-suite/src/utils/isPreview.ts +++ /dev/null @@ -1 +0,0 @@ -export const isPreview: boolean = process.env.MODE === 'preview' diff --git a/e2e/react-start/basic-test-suite/src/utils/isSpaMode.ts b/e2e/react-start/basic-test-suite/src/utils/isSpaMode.ts deleted file mode 100644 index b4edb829a8..0000000000 --- a/e2e/react-start/basic-test-suite/src/utils/isSpaMode.ts +++ /dev/null @@ -1 +0,0 @@ -export const isSpaMode: boolean = process.env.MODE === 'spa' diff --git a/e2e/react-start/basic-test-suite/tsconfig.json b/e2e/react-start/basic-test-suite/tsconfig.json deleted file mode 100644 index 3f3dbe6bcd..0000000000 --- a/e2e/react-start/basic-test-suite/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "include": ["src/**/*.ts"], - "compilerOptions": { - "strict": true, - "esModuleInterop": true, - "module": "ESNext", - "moduleResolution": "Bundler", - "lib": ["DOM", "DOM.Iterable", "ES2022"], - "isolatedModules": true, - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "target": "ES2022", - "types": ["node"], - "forceConsistentCasingInFileNames": true, - "noEmit": true - } -} diff --git a/e2e/react-start/basic/.gitignore b/e2e/react-start/basic/.gitignore index a79d5cf129..b9c2c7e5f6 100644 --- a/e2e/react-start/basic/.gitignore +++ b/e2e/react-start/basic/.gitignore @@ -18,3 +18,4 @@ yarn.lock /playwright-report/ /blob-report/ /playwright/.cache/ +/dist-vite-*/ diff --git a/e2e/react-start/basic/package.json b/e2e/react-start/basic/package.json index 957372b08e..f689617694 100644 --- a/e2e/react-start/basic/package.json +++ b/e2e/react-start/basic/package.json @@ -6,14 +6,12 @@ "scripts": { "dev": "vite dev --port 3000", "dev:e2e": "vite dev", - "build": "vite build && tsc --noEmit", - "build:spa": "MODE=spa vite build && tsc --noEmit", - "build:prerender": "MODE=prerender vite build && tsc --noEmit", + "build": "vite build --outDir ${E2E_DIST_DIR:-dist} && tsc --noEmit", "preview": "vite preview", "start": "node server.js", - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf dist; rm -rf port*.txt; playwright test --project=chromium" + "test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"./tests/setup/waitForDummyServer.ts\").then(m => m.default())'", + "test:e2e:stopDummyServer": "node -e 'import(\"./tests/setup/global.teardown.ts\").then(m => m.default())'", + "test:e2e:local": "playwright test --project=chromium" }, "dependencies": { "@tanstack/react-router": "workspace:^", @@ -31,7 +29,6 @@ "@playwright/test": "^1.50.1", "@tailwindcss/vite": "^4.2.2", "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-react-start-e2e-basic-test-suite": "workspace:*", "@types/js-cookie": "^3.0.6", "@types/node": "^22.10.2", "@types/react": "^19.0.8", @@ -45,10 +42,25 @@ "zod": "^3.24.2" }, "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } + "metadata": { + "playwrightModes": [ + { + "bundler": "vite", + "mode": "ssr" + }, + { + "bundler": "vite", + "mode": "spa" + }, + { + "bundler": "vite", + "mode": "prerender" + }, + { + "bundler": "vite", + "mode": "preview" + } + ] } } } diff --git a/e2e/react-start/basic/playwright.config.ts b/e2e/react-start/basic/playwright.config.ts index bf22363c94..5e46a2b1c7 100644 --- a/e2e/react-start/basic/playwright.config.ts +++ b/e2e/react-start/basic/playwright.config.ts @@ -1,3 +1,4 @@ +import fs from 'node:fs' import { defineConfig, devices } from '@playwright/test' import { getDummyServerPort, @@ -5,20 +6,37 @@ import { } from '@tanstack/router-e2e-utils' import packageJson from './package.json' with { type: 'json' } -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) +const mode = process.env.MODE ?? 'ssr' +const e2ePortKey = process.env.E2E_PORT_KEY ?? packageJson.name +const distDir = process.env.E2E_DIST_DIR ?? 'dist' + +if (process.env.TEST_WORKER_INDEX === undefined) { + for (const portFile of [ + `port-${e2ePortKey}.txt`, + `port-${e2ePortKey}_start.txt`, + `port-${e2ePortKey}-external.txt`, + ]) { + fs.rmSync(portFile, { force: true }) + } +} + +const PORT = await getTestServerPort(e2ePortKey) +const START_PORT = await getTestServerPort(`${e2ePortKey}_start`) +const EXTERNAL_PORT = await getDummyServerPort(e2ePortKey) const baseURL = `http://localhost:${PORT}` -const ssrModeCommand = `pnpm build && pnpm start` +const commandByMode = + mode === 'preview' + ? `pnpm run test:e2e:startDummyServer && pnpm preview --outDir ${distDir} --port ${PORT}` + : `pnpm run test:e2e:startDummyServer && pnpm start` /** * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: '../basic-test-suite/src', + testDir: './tests', workers: 1, reporter: [['line']], - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', + globalTeardown: './tests/setup/global.teardown.ts', use: { /* Base URL to use in actions like `await page.goto('/')`. */ @@ -26,17 +44,19 @@ export default defineConfig({ }, webServer: { - command: `pnpm run test:e2e:startDummyServer && ${ssrModeCommand}`, + command: commandByMode, url: baseURL, reuseExistingServer: !process.env.CI, stdout: 'pipe', env: { - MODE: '', + MODE: mode, VITE_NODE_ENV: 'test', VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), VITE_SERVER_PORT: String(PORT), START_PORT: String(START_PORT), PORT: String(PORT), + E2E_DIST_DIR: distDir, + E2E_PORT_KEY: e2ePortKey, }, }, diff --git a/e2e/react-start/basic/server.js b/e2e/react-start/basic/server.js index 83f5ff0079..9db7619ae1 100644 --- a/e2e/react-start/basic/server.js +++ b/e2e/react-start/basic/server.js @@ -9,16 +9,19 @@ const startPort = process.env.START_PORT || 3001 const isSpaMode = process.env.MODE === 'spa' const isPrerender = process.env.MODE === 'prerender' +const distDir = process.env.E2E_DIST_DIR || 'dist' +const distClientDir = path.resolve(distDir, 'client') +const distServerEntryPath = path.resolve(distDir, 'server', 'server.js') export async function createStartServer() { - const server = (await import('./dist/server/server.js')).default + const server = (await import(distServerEntryPath)).default const nodeHandler = toNodeHandler(server.fetch) const app = express() // to keep testing uniform stop express from redirecting /posts to /posts/ // when serving pre-rendered pages - app.use(express.static('./dist/client', { redirect: !isPrerender })) + app.use(express.static(distClientDir, { redirect: !isPrerender })) app.use(async (req, res, next) => { try { @@ -50,10 +53,10 @@ export async function createSpaServer() { }), ) - app.use(express.static('./dist/client')) + app.use(express.static(distClientDir)) app.get('/{*splat}', (req, res) => { - res.sendFile(path.resolve('./dist/client/index.html')) + res.sendFile(path.resolve(distClientDir, 'index.html')) }) return { app } diff --git a/e2e/react-start/basic-test-suite/src/client-only.spec.ts b/e2e/react-start/basic/tests/client-only.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/client-only.spec.ts rename to e2e/react-start/basic/tests/client-only.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/navigation.spec.ts b/e2e/react-start/basic/tests/navigation.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/navigation.spec.ts rename to e2e/react-start/basic/tests/navigation.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/not-found.spec.ts b/e2e/react-start/basic/tests/not-found.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/not-found.spec.ts rename to e2e/react-start/basic/tests/not-found.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/open-redirect-prevention.spec.ts b/e2e/react-start/basic/tests/open-redirect-prevention.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/open-redirect-prevention.spec.ts rename to e2e/react-start/basic/tests/open-redirect-prevention.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/prerendering.spec.ts b/e2e/react-start/basic/tests/prerendering.spec.ts similarity index 90% rename from e2e/react-start/basic-test-suite/src/prerendering.spec.ts rename to e2e/react-start/basic/tests/prerendering.spec.ts index 66cc1f68ff..ddcf720a4c 100644 --- a/e2e/react-start/basic-test-suite/src/prerendering.spec.ts +++ b/e2e/react-start/basic/tests/prerendering.spec.ts @@ -3,15 +3,18 @@ import { join } from 'node:path' import { expect } from '@playwright/test' import { test } from '@tanstack/router-e2e-utils' import { isPrerender } from './utils/isPrerender' -import { getBasicAppRoot } from './utils/getBasicAppRoot' + +const distDir = join( + process.cwd(), + process.env.E2E_DIST_DIR ?? 'dist', + 'client', +) test.describe('Prerender Static Path Discovery', () => { test.skip(!isPrerender, 'Skipping since not in prerender mode') test.describe('Build Output Verification', () => { test('should automatically discover and prerender static routes', () => { // Check that static routes were automatically discovered and prerendered - const distDir = join(getBasicAppRoot(), 'dist', 'client') - // These static routes should be automatically discovered and prerendered expect(existsSync(join(distDir, 'index.html'))).toBe(true) expect(existsSync(join(distDir, 'posts/index.html'))).toBe(true) @@ -35,7 +38,6 @@ test.describe('Prerender Static Path Discovery', () => { test.describe('Static Files Verification', () => { test('should contain prerendered content in posts.html', () => { - const distDir = join(getBasicAppRoot(), 'dist', 'client') expect(existsSync(join(distDir, 'posts/index.html'))).toBe(true) // "Select a post." should be in the prerendered HTML diff --git a/e2e/react-start/basic-test-suite/src/raw-stream.spec.ts b/e2e/react-start/basic/tests/raw-stream.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/raw-stream.spec.ts rename to e2e/react-start/basic/tests/raw-stream.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/redirect.spec.ts b/e2e/react-start/basic/tests/redirect.spec.ts similarity index 97% rename from e2e/react-start/basic-test-suite/src/redirect.spec.ts rename to e2e/react-start/basic/tests/redirect.spec.ts index 0763a901a2..1915388b0d 100644 --- a/e2e/react-start/basic-test-suite/src/redirect.spec.ts +++ b/e2e/react-start/basic/tests/redirect.spec.ts @@ -6,15 +6,15 @@ import { getTestServerPort, test, } from '@tanstack/router-e2e-utils' -import { getPackageName } from './utils/getPackageName.ts' +import { getE2EPortKey } from './utils/getE2EPortKey.ts' // somehow playwright does not correctly import default exports const combinate = (combinateImport as any).default as typeof combinateImport -const packageName = getPackageName() +const e2ePortKey = getE2EPortKey() -const PORT = await getTestServerPort(packageName) +const PORT = await getTestServerPort(e2ePortKey) -const EXTERNAL_HOST_PORT = await getDummyServerPort(packageName) +const EXTERNAL_HOST_PORT = await getDummyServerPort(e2ePortKey) test.describe('redirects', () => { test.describe('internal', () => { diff --git a/e2e/react-start/basic-test-suite/src/root-scripts.spec.ts b/e2e/react-start/basic/tests/root-scripts.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/root-scripts.spec.ts rename to e2e/react-start/basic/tests/root-scripts.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/script-duplication.spec.ts b/e2e/react-start/basic/tests/script-duplication.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/script-duplication.spec.ts rename to e2e/react-start/basic/tests/script-duplication.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/search-params.spec.ts b/e2e/react-start/basic/tests/search-params.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/search-params.spec.ts rename to e2e/react-start/basic/tests/search-params.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/setup/global.setup.ts b/e2e/react-start/basic/tests/setup/global.setup.ts similarity index 50% rename from e2e/react-start/basic-test-suite/src/setup/global.setup.ts rename to e2e/react-start/basic/tests/setup/global.setup.ts index fc06a65591..cd38c35885 100644 --- a/e2e/react-start/basic-test-suite/src/setup/global.setup.ts +++ b/e2e/react-start/basic/tests/setup/global.setup.ts @@ -1,6 +1,6 @@ import { e2eStartDummyServer } from '@tanstack/router-e2e-utils' -import { getPackageName } from '../utils/getPackageName.ts' +import { getE2EPortKey } from '../utils/getE2EPortKey.ts' export default async function setup() { - await e2eStartDummyServer(getPackageName()) + await e2eStartDummyServer(getE2EPortKey()) } diff --git a/e2e/react-start/basic-test-suite/src/setup/global.teardown.ts b/e2e/react-start/basic/tests/setup/global.teardown.ts similarity index 55% rename from e2e/react-start/basic-test-suite/src/setup/global.teardown.ts rename to e2e/react-start/basic/tests/setup/global.teardown.ts index a8a6899700..36e5c92858 100644 --- a/e2e/react-start/basic-test-suite/src/setup/global.teardown.ts +++ b/e2e/react-start/basic/tests/setup/global.teardown.ts @@ -1,8 +1,8 @@ import { e2eStopDummyServer } from '@tanstack/router-e2e-utils' -import { getPackageName } from '../utils/getPackageName.ts' +import { getE2EPortKey } from '../utils/getE2EPortKey.ts' export default async function teardown() { try { - await e2eStopDummyServer(getPackageName()) + await e2eStopDummyServer(getE2EPortKey()) } catch {} } diff --git a/e2e/react-start/basic-test-suite/src/setup/waitForDummyServer.ts b/e2e/react-start/basic/tests/setup/waitForDummyServer.ts similarity index 82% rename from e2e/react-start/basic-test-suite/src/setup/waitForDummyServer.ts rename to e2e/react-start/basic/tests/setup/waitForDummyServer.ts index f27dc86d5b..88c18fd79e 100644 --- a/e2e/react-start/basic-test-suite/src/setup/waitForDummyServer.ts +++ b/e2e/react-start/basic/tests/setup/waitForDummyServer.ts @@ -1,11 +1,11 @@ import { getDummyServerPort } from '@tanstack/router-e2e-utils' -import { getPackageName } from '../utils/getPackageName.ts' +import { getE2EPortKey } from '../utils/getE2EPortKey.ts' const timeoutMs = 10_000 const retryIntervalMs = 100 export default async function waitForDummyServer() { - const port = await getDummyServerPort(getPackageName()) + const port = await getDummyServerPort(getE2EPortKey()) const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { diff --git a/e2e/react-start/basic-test-suite/src/special-characters.spec.ts b/e2e/react-start/basic/tests/special-characters.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/special-characters.spec.ts rename to e2e/react-start/basic/tests/special-characters.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/streaming.spec.ts b/e2e/react-start/basic/tests/streaming.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/streaming.spec.ts rename to e2e/react-start/basic/tests/streaming.spec.ts diff --git a/e2e/react-start/basic-test-suite/src/type-only-reexport.spec.ts b/e2e/react-start/basic/tests/type-only-reexport.spec.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/type-only-reexport.spec.ts rename to e2e/react-start/basic/tests/type-only-reexport.spec.ts diff --git a/e2e/react-start/basic/tests/utils/getE2EPortKey.ts b/e2e/react-start/basic/tests/utils/getE2EPortKey.ts new file mode 100644 index 0000000000..48be6a9027 --- /dev/null +++ b/e2e/react-start/basic/tests/utils/getE2EPortKey.ts @@ -0,0 +1,5 @@ +import { getPackageName } from './getPackageName.ts' + +export function getE2EPortKey() { + return process.env.E2E_PORT_KEY ?? getPackageName() +} diff --git a/e2e/react-start/basic-test-suite/src/utils/getPackageName.ts b/e2e/react-start/basic/tests/utils/getPackageName.ts similarity index 100% rename from e2e/react-start/basic-test-suite/src/utils/getPackageName.ts rename to e2e/react-start/basic/tests/utils/getPackageName.ts diff --git a/e2e/react-start/basic/tsconfig.json b/e2e/react-start/basic/tsconfig.json index af81492efb..e486543b96 100644 --- a/e2e/react-start/basic/tsconfig.json +++ b/e2e/react-start/basic/tsconfig.json @@ -8,9 +8,11 @@ "moduleResolution": "Bundler", "lib": ["DOM", "DOM.Iterable", "ES2022"], "isolatedModules": true, + "allowImportingTsExtensions": true, "resolveJsonModule": true, "skipLibCheck": true, "target": "ES2022", + "types": ["node"], "allowJs": true, "forceConsistentCasingInFileNames": true, "paths": { diff --git a/package.json b/package.json index 8c007f88fe..bf189380f2 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "test:docs": "node scripts/verify-links.ts", "vite-ecosystem-ci:build": "nx run-many --targets=build --projects=@tanstack/router-plugin,@tanstack/start-plugin-core,@tanstack/react-start,@tanstack/react-start-client,@tanstack/react-start-server --skipRemoteCache", "vite-ecosystem-ci:before-test": "pnpm exec playwright install chromium", - "vite-ecosystem-ci:test": "nx run-many --targets=test:unit --projects=@tanstack/router-plugin,@tanstack/start-plugin-core,@tanstack/react-start-client --skipRemoteCache && nx run-many --target=test:e2e --projects=tanstack-router-e2e-react-basic-file-based,tanstack-router-e2e-react-basic-file-based-code-splitting,tanstack-react-start-e2e-basic,tanstack-react-start-e2e-basic-spa,tanstack-react-start-e2e-basic-prerender,tanstack-react-start-e2e-basic-preview,tanstack-vue-start-e2e-basic,tanstack-vue-start-e2e-basic-spa,tanstack-vue-start-e2e-basic-prerender,tanstack-vue-start-e2e-basic-preview,tanstack-solid-start-e2e-basic,tanstack-solid-start-e2e-basic-spa,tanstack-solid-start-e2e-basic-prerender,tanstack-solid-start-e2e-basic-preview --skipRemoteCache" + "vite-ecosystem-ci:test": "nx run-many --targets=test:unit --projects=@tanstack/router-plugin,@tanstack/start-plugin-core,@tanstack/react-start-client --skipRemoteCache && nx run-many --target=test:e2e --projects=tanstack-router-e2e-react-basic-file-based,tanstack-router-e2e-react-basic-file-based-code-splitting,tanstack-react-start-e2e-basic,tanstack-vue-start-e2e-basic,tanstack-vue-start-e2e-basic-spa,tanstack-vue-start-e2e-basic-prerender,tanstack-vue-start-e2e-basic-preview,tanstack-solid-start-e2e-basic,tanstack-solid-start-e2e-basic-spa,tanstack-solid-start-e2e-basic-prerender,tanstack-solid-start-e2e-basic-preview --skipRemoteCache" }, "nx": { "includedScripts": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4cd1c6b677..675be44b29 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1389,9 +1389,6 @@ importers: tailwindcss: specifier: ^4.2.2 version: 4.2.2 - tanstack-react-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite typescript: specifier: ^6.0.2 version: 6.0.2 @@ -1527,36 +1524,6 @@ importers: specifier: ^4.74.0 version: 4.75.0 - e2e/react-start/basic-prerender: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - tanstack-react-start-e2e-basic: - specifier: workspace:* - version: link:../basic - tanstack-react-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite - - e2e/react-start/basic-preview: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - tanstack-react-start-e2e-basic: - specifier: workspace:* - version: link:../basic - tanstack-react-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite - e2e/react-start/basic-react-query: dependencies: '@tanstack/react-query': @@ -1627,36 +1594,6 @@ importers: specifier: ^6.0.2 version: 6.0.2 - e2e/react-start/basic-spa: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - tanstack-react-start-e2e-basic: - specifier: workspace:* - version: link:../basic - tanstack-react-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite - - e2e/react-start/basic-test-suite: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:* - version: link:../../e2e-utils - '@types/node': - specifier: 25.0.9 - version: 25.0.9 - combinate: - specifier: ^1.1.11 - version: 1.1.11 - e2e/react-start/basic-tsr-config: dependencies: '@tanstack/react-router': diff --git a/scripts/nx/playwright-plugin.md b/scripts/nx/playwright-plugin.md index 8fdf391141..47ceb642df 100644 --- a/scripts/nx/playwright-plugin.md +++ b/scripts/nx/playwright-plugin.md @@ -1,40 +1,102 @@ -# Playwright E2E Sharding +# Playwright E2E Inference (Modes + Shards) -Use `scripts/nx/playwright-plugin.ts` to turn a Playwright e2e project into Nx shard targets. +Use `scripts/nx/playwright-plugin.ts` to infer Playwright e2e targets from +`package.json` metadata. -The plugin looks for `nx.metadata.playwrightShards` in a project's `package.json`. When present, it generates: +The plugin supports two metadata styles: -- `test:e2e--shard-1-of-N` through `test:e2e--shard-N-of-N` -- a parent `test:e2e` target that depends on all shard targets +- `nx.metadata.playwrightModes` (recommended) +- `nx.metadata.playwrightShards` (legacy, still supported) -Each shard target runs Playwright with: +When `playwrightModes` is present, it takes precedence. -- `--shard=/` -- a shard-specific `E2E_PORT_KEY` -- a dependency on `build` and `^build` - -## 1. Enable sharding in `package.json` +## 1. Configure `playwrightModes` in `package.json` ```json { - "name": "tanstack-react-start-e2e-rsc", + "name": "tanstack-react-start-e2e-basic", "nx": { "metadata": { - "playwrightShards": 6 + "playwrightModes": [ + { "bundler": "vite", "mode": "ssr" }, + { "bundler": "vite", "mode": "spa", "shards": 4 }, + { "bundler": "vite", "mode": "prerender", "shards": 4 }, + { "bundler": "vite", "mode": "preview" } + ] } } } ``` -The package name is used to build the shard port key: +Supported modes: + +- `ssr` +- `spa` +- `prerender` +- `preview` + +`shards` is optional and defaults to `1`. + +Supported bundlers: + +- `vite` +- `webpack` +- `rspack` +- `esbuild` + +## 2. What targets are generated + +For each `{ bundler, mode }` entry, the plugin generates: + +- `build:e2e---` +- `test:e2e---` + +If `shards > 1`, it also generates: + +- `test:e2e-----shard-1-of-N` ... `--shard-N-of-N` +- `test:e2e---` as a parent noop target depending on all shards -```txt --shard--of- +Finally, it generates: + +- `test:e2e` as a parent noop target depending on every mode target + +## 3. Environment variables injected by inferred targets + +Each inferred `build:e2e:*` and `test:e2e:*` target sets: + +- `MODE=` +- `BUNDLER=` +- `E2E_BUNDLER=` +- `E2E_DIST=dist--` +- `E2E_DIST_DIR=dist--` + +Each inferred `test:e2e:*` target also sets: + +- `E2E_PORT_KEY=--` +- For shard targets: `E2E_PORT_KEY=---shard--of-` + +## 4. Build behavior and webServer command + +Each inferred e2e target depends on the inferred `build:e2e---` +target, which runs: + +```sh +pnpm build ``` -## 2. Do not build inside `playwright.config.ts` +with the mode/bundler env above. + +The inferred build target also hashes these env vars as target inputs: -The plugin already makes each shard depend on `build`, so the Playwright `webServer` command should only start the built app. +- `MODE` +- `BUNDLER` +- `E2E_BUNDLER` +- `E2E_DIST` +- `E2E_DIST_DIR` + +This keeps Nx cache entries distinct when dist output folders differ by mode. + +Because of this, Playwright `webServer.command` should only start the app. Good: @@ -49,18 +111,16 @@ Avoid: ```ts webServer: { - command: `PORT=${PORT} pnpm build && PORT=${PORT} pnpm start`, + command: `MODE=spa pnpm build && PORT=${PORT} pnpm start`, url: baseURL, } ``` -Running the build inside every shard causes unnecessary work and can create races when multiple shards share the same project directory. - -## 3. Use `E2E_PORT_KEY` for every derived port +## 5. Use `E2E_PORT_KEY` for all server ports -If the project uses `getTestServerPort`, `getDummyServerPort`, `e2eStartDummyServer`, or `e2eStopDummyServer`, key them off `process.env.E2E_PORT_KEY` first. - -Example: +If your setup uses `getTestServerPort`, `getDummyServerPort`, +`e2eStartDummyServer`, or `e2eStopDummyServer`, use +`process.env.E2E_PORT_KEY` first. ```ts import { getTestServerPort } from '@tanstack/router-e2e-utils' @@ -77,15 +137,14 @@ await e2eStartDummyServer(process.env.E2E_PORT_KEY ?? packageJson.name) await e2eStopDummyServer(process.env.E2E_PORT_KEY ?? packageJson.name) ``` -Without this, multiple shards on the same runner will reuse the same `port-*.txt` files and tear each other down. - -## 4. Clean stale port files once per Playwright run - -If the project allocates ports through `@tanstack/router-e2e-utils`, clean stale `port-*.txt` files before resolving the port. +## 6. Clean stale port files once per Playwright run -Important: do this only in the main Playwright config load. Playwright loads the config more than once, and unconditional cleanup can remap the port after the web server has already started. +If the project allocates ports through `@tanstack/router-e2e-utils`, clean stale +`port-*.txt` files before resolving the port. -Example: +Important: do this only in the main Playwright config load. Playwright loads the +config more than once, and unconditional cleanup can remap the port after the +web server has already started. ```ts import fs from 'node:fs' @@ -105,47 +164,29 @@ if (process.env.TEST_WORKER_INDEX === undefined) { Avoid broad cleanup such as `rm -rf port*.txt` in shared shard runs. -## 5. Keep the base URL derived from the shard key - -```ts -const PORT = await getTestServerPort(e2ePortKey) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - use: { - baseURL, - }, - webServer: { - command: `PORT=${PORT} pnpm start`, - url: baseURL, - }, -}) -``` - -## 6. Run the generated targets +## 7. Run inferred targets Examples: ```sh -pnpm nx run tanstack-react-start-e2e-rsc:test:e2e--shard-1-of-6 -pnpm nx run tanstack-react-start-e2e-rsc:test:e2e +pnpm nx run tanstack-react-start-e2e-basic:test:e2e--vite-ssr +pnpm nx run tanstack-react-start-e2e-basic:test:e2e--vite-spa--shard-1-of-4 +pnpm nx run tanstack-react-start-e2e-basic:test:e2e ``` -## Checklist +## Legacy fallback: `playwrightShards` -- add `nx.metadata.playwrightShards` to `package.json` -- make sure the project has a working `build` target -- remove `build` from `webServer.command` -- read ports from `process.env.E2E_PORT_KEY ?? packageJson.name` -- use the same key for helper servers in setup and teardown -- clean shard-specific `port-*.txt` files once, guarded by `TEST_WORKER_INDEX === undefined` -- avoid wildcard port cleanup in shared shard runs +If `playwrightModes` is not configured, the plugin still supports: -## Reference - -Working example: +```json +{ + "nx": { + "metadata": { + "playwrightShards": 6 + } + } +} +``` -- `e2e/react-start/rsc/package.json` -- `e2e/react-start/rsc/playwright.config.ts` -- `e2e/react-start/rsc/tests/setup/global.setup.ts` -- `e2e/react-start/rsc/tests/setup/global.teardown.ts` +This generates legacy shard targets under `test:e2e--shard-...` plus a parent +`test:e2e` target. diff --git a/scripts/nx/playwright-plugin.ts b/scripts/nx/playwright-plugin.ts index 9e8c9618ad..c804d78a62 100644 --- a/scripts/nx/playwright-plugin.ts +++ b/scripts/nx/playwright-plugin.ts @@ -10,7 +10,7 @@ export const createNodesV2: CreateNodesV2 = [ '**/package.json', async (configFiles, options, context) => { return await createNodesFromFiles( - (configFile, options, context) => + (configFile, _options, context) => createNodesInternal(configFile, context), configFiles, options, @@ -19,15 +19,47 @@ export const createNodesV2: CreateNodesV2 = [ }, ] -async function createNodesInternal( +function createNodesInternal( configFilePath: string, - context: CreateNodesContextV2, + _context: CreateNodesContextV2, ) { const projectConfiguration = readJsonFile<{ name?: string - nx?: { metadata?: { playwrightShards?: number } } + nx?: { + metadata?: { + playwrightShards?: number + playwrightModes?: Array + } + } }>(configFilePath) const root = dirname(configFilePath) + const packageName = projectConfiguration.name ?? root + const playwrightModes = + projectConfiguration.nx?.metadata?.playwrightModes ?? [] + + if (playwrightModes.length > 0) { + const { targets, targetGroupEntries } = buildModeTargets( + root, + packageName, + playwrightModes, + ['default', '^production'], + ['production', '^production'], + ) + + return { + projects: { + [root]: { + targets, + metadata: { + targetGroups: { + playwright: targetGroupEntries, + }, + }, + tags: ['playwright:sharded'], + }, + }, + } + } if (!projectConfiguration.nx?.metadata?.playwrightShards) { return { @@ -39,7 +71,7 @@ async function createNodesInternal( const { targets, targetGroupEntries } = buildShardedTargets( root, - projectConfiguration.name ?? root, + packageName, projectConfiguration.nx.metadata.playwrightShards, ['default', '^production'], ) @@ -53,13 +85,298 @@ async function createNodesInternal( playwright: targetGroupEntries, }, }, - tags: ['playwright:shareded'], + tags: ['playwright:sharded'], }, }, } } const CI_TARGET_NAME = 'test:e2e' +const MODE_TARGET_SEPARATOR = '--' + +const PLAYWRIGHT_BUNDLERS = ['vite', 'webpack', 'rspack', 'esbuild'] as const +const PLAYWRIGHT_MODES = ['ssr', 'spa', 'prerender', 'preview'] as const + +type PlaywrightBundler = (typeof PLAYWRIGHT_BUNDLERS)[number] +type PlaywrightMode = (typeof PLAYWRIGHT_MODES)[number] + +type PlaywrightModeMetadata = { + bundler: PlaywrightBundler + mode: PlaywrightMode + shards?: number +} + +type RawPlaywrightModeMetadata = { + bundler?: string + mode?: string + shards?: number +} + +function isPlaywrightBundler(bundler: string): bundler is PlaywrightBundler { + return PLAYWRIGHT_BUNDLERS.includes(bundler as PlaywrightBundler) +} + +function isPlaywrightMode(mode: string): mode is PlaywrightMode { + return PLAYWRIGHT_MODES.includes(mode as PlaywrightMode) +} + +function withEnvInputs( + inputs: TargetConfiguration['inputs'], + envNames: Array, +): TargetConfiguration['inputs'] { + const baseInputs = inputs ?? [] + + return [...baseInputs, ...envNames.map((env) => ({ env }))] +} + +function getDistDirName({ bundler, mode }: PlaywrightModeMetadata): string { + return `dist-${bundler}-${mode}` +} + +function getBuildTargetName({ bundler, mode }: PlaywrightModeMetadata): string { + return `build:e2e--${bundler}-${mode}` +} + +function getModeTargetName({ bundler, mode }: PlaywrightModeMetadata): string { + return `${CI_TARGET_NAME}--${bundler}-${mode}` +} + +function getModeEnv( + modeMetadata: PlaywrightModeMetadata, +): Record { + const distDir = getDistDirName(modeMetadata) + + return { + MODE: modeMetadata.mode, + BUNDLER: modeMetadata.bundler, + E2E_BUNDLER: modeMetadata.bundler, + E2E_DIST: distDir, + E2E_DIST_DIR: distDir, + } +} + +function getModePortKey( + packageName: string, + modeMetadata: PlaywrightModeMetadata, + shardIndex?: number, + shardCount?: number, +): string { + const modePortKey = `${packageName}-${modeMetadata.bundler}-${modeMetadata.mode}` + + if (shardIndex === undefined || shardCount === undefined) { + return modePortKey + } + + return `${modePortKey}-shard-${shardIndex}-of-${shardCount}` +} + +function normalizeShardCount( + shards: number | undefined, + targetName: string, +): number { + if (shards === undefined) { + return 1 + } + + const shardCount = Number(shards) + + if (!Number.isInteger(shardCount) || shardCount < 1) { + throw new Error( + `[Playwright Sharding Plugin] Invalid shard count for ${targetName}: ${shards}. ` + + `Expected a positive integer.`, + ) + } + + return shardCount +} + +function validateModeMetadata( + modeMetadata: RawPlaywrightModeMetadata, + index: number, +): PlaywrightModeMetadata { + const targetName = `${modeMetadata.bundler ?? ''}:${modeMetadata.mode ?? ''}` + + if (!modeMetadata.bundler || !modeMetadata.mode) { + throw new Error( + `[Playwright Sharding Plugin] Invalid playwrightModes[${index}] entry: ` + + `${JSON.stringify(modeMetadata)}. Both "bundler" and "mode" are required.`, + ) + } + + if (!isPlaywrightBundler(modeMetadata.bundler)) { + throw new Error( + `[Playwright Sharding Plugin] Invalid bundler for playwrightModes[${index}]: ` + + `${modeMetadata.bundler}. Supported bundlers: ${PLAYWRIGHT_BUNDLERS.join(', ')}.`, + ) + } + + if (!isPlaywrightMode(modeMetadata.mode)) { + throw new Error( + `[Playwright Sharding Plugin] Invalid mode for playwrightModes[${index}]: ` + + `${modeMetadata.mode}. Supported modes: ${PLAYWRIGHT_MODES.join(', ')}.`, + ) + } + + normalizeShardCount(modeMetadata.shards, targetName) + + return { + bundler: modeMetadata.bundler, + mode: modeMetadata.mode, + shards: modeMetadata.shards, + } +} + +function buildModeTargets( + projectRoot: string, + packageName: string, + modes: Array, + testInputs: TargetConfiguration['inputs'], + buildInputs: TargetConfiguration['inputs'], +): { + targets: Record + targetGroupEntries: Array +} { + const targets: Record = {} + const targetGroup: Array = [] + const ciDependsOnTargets: Array<{ + target: string + projects: 'self' + params: 'forward' + }> = [] + + for (const [index, rawModeMetadata] of modes.entries()) { + const modeMetadata = validateModeMetadata(rawModeMetadata, index) + const modeTargetName = getModeTargetName(modeMetadata) + const buildTargetName = getBuildTargetName(modeMetadata) + const shardCount = normalizeShardCount(modeMetadata.shards, modeTargetName) + const modeEnv = getModeEnv(modeMetadata) + const modePortKey = getModePortKey(packageName, modeMetadata) + const modeDescription = `${modeMetadata.bundler}/${modeMetadata.mode}` + const modeShardTargets: Array = [] + + targets[buildTargetName] = { + executor: 'nx:run-commands', + options: { + command: 'pnpm build', + cwd: projectRoot, + env: modeEnv, + }, + parallelism: false, + cache: true, + inputs: withEnvInputs(buildInputs, [ + 'MODE', + 'BUNDLER', + 'E2E_BUNDLER', + 'E2E_DIST', + 'E2E_DIST_DIR', + ]), + dependsOn: ['^build'], + outputs: [`{projectRoot}/${getDistDirName(modeMetadata)}`], + metadata: { + technologies: ['playwright'], + description: `Build artifacts for ${modeDescription} e2e tests`, + }, + } + + if (shardCount === 1) { + targets[modeTargetName] = { + executor: 'nx:run-commands', + options: { + command: 'playwright test --project=chromium', + cwd: projectRoot, + env: { + ...modeEnv, + E2E_PORT_KEY: modePortKey, + }, + }, + cache: true, + inputs: testInputs, + dependsOn: [buildTargetName], + metadata: { + technologies: ['playwright'], + description: `Run Playwright tests for ${modeDescription}`, + }, + } + } else { + for (let shardIndex = 1; shardIndex <= shardCount; shardIndex++) { + const shardTargetName = `${modeTargetName}${MODE_TARGET_SEPARATOR}shard-${shardIndex}-of-${shardCount}` + const shardPortKey = getModePortKey( + packageName, + modeMetadata, + shardIndex, + shardCount, + ) + + targets[shardTargetName] = { + executor: 'nx:run-commands', + options: { + command: `playwright test --project=chromium --shard=${shardIndex}/${shardCount}`, + cwd: projectRoot, + env: { + ...modeEnv, + E2E_PORT_KEY: shardPortKey, + }, + }, + cache: true, + inputs: testInputs, + dependsOn: [buildTargetName], + metadata: { + technologies: ['playwright'], + description: `Run Playwright ${modeDescription} shard ${shardIndex} of ${shardCount}`, + help: { + command: `npx playwright run --help`, + example: { + options: { + shard: `${shardIndex}/${shardCount}`, + }, + }, + }, + }, + } + + modeShardTargets.push(shardTargetName) + } + + targets[modeTargetName] = { + executor: 'nx:noop', + cache: true, + inputs: testInputs, + dependsOn: modeShardTargets.map((shardTargetName) => ({ + target: shardTargetName, + projects: 'self' as const, + params: 'forward' as const, + })), + metadata: { + technologies: ['playwright'], + description: `Run all Playwright ${modeDescription} shards (${shardCount} shards)`, + }, + } + } + + ciDependsOnTargets.push({ + target: modeTargetName, + projects: 'self', + params: 'forward', + }) + targetGroup.push(modeTargetName) + } + + targets[CI_TARGET_NAME] = { + executor: 'nx:noop', + cache: true, + inputs: testInputs, + dependsOn: ciDependsOnTargets, + metadata: { + technologies: ['playwright'], + description: `Run all Playwright modes (${modes.length} modes)`, + }, + } + + return { + targets, + targetGroupEntries: [...targetGroup, CI_TARGET_NAME], + } +} function buildShardedTargets( projectRoot: string, From 0d3fccc4f7e2da7bd455a3017cc23a86af87f601 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Thu, 16 Apr 2026 23:32:37 +0200 Subject: [PATCH 2/9] fix(e2e): align react-start basic build outDir with mode env --- e2e/react-start/basic/package.json | 2 +- e2e/react-start/basic/vite.config.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/e2e/react-start/basic/package.json b/e2e/react-start/basic/package.json index f689617694..ffc3f84897 100644 --- a/e2e/react-start/basic/package.json +++ b/e2e/react-start/basic/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "vite dev --port 3000", "dev:e2e": "vite dev", - "build": "vite build --outDir ${E2E_DIST_DIR:-dist} && tsc --noEmit", + "build": "vite build && tsc --noEmit", "preview": "vite preview", "start": "node server.js", "test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"./tests/setup/waitForDummyServer.ts\").then(m => m.default())'", diff --git a/e2e/react-start/basic/vite.config.ts b/e2e/react-start/basic/vite.config.ts index 8ac18ef504..f88efe304f 100644 --- a/e2e/react-start/basic/vite.config.ts +++ b/e2e/react-start/basic/vite.config.ts @@ -28,8 +28,13 @@ const prerenderConfiguration = { maxRedirects: 100, } +const outDir = process.env.E2E_DIST_DIR ?? 'dist' + export default defineConfig({ resolve: { tsconfigPaths: true }, + build: { + outDir, + }, server: { port: 3000, }, From 8db72e825d005bd6583d133e0a21acd1da2a4134 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Thu, 16 Apr 2026 23:58:02 +0200 Subject: [PATCH 3/9] fix(e2e): scope playwright mode toolchain to vite Rename playwright mode metadata from bundler to toolchain, hardcode inferred build commands to 'vite build && tsc --noEmit', and drop the explicit rename-check error path while keeping only vite supported for now. --- e2e/react-start/basic/package.json | 11 +++--- scripts/nx/playwright-plugin.md | 45 +++++++++++----------- scripts/nx/playwright-plugin.ts | 60 +++++++++++++++++------------- 3 files changed, 61 insertions(+), 55 deletions(-) diff --git a/e2e/react-start/basic/package.json b/e2e/react-start/basic/package.json index ffc3f84897..ddb9d962bc 100644 --- a/e2e/react-start/basic/package.json +++ b/e2e/react-start/basic/package.json @@ -45,19 +45,20 @@ "metadata": { "playwrightModes": [ { - "bundler": "vite", - "mode": "ssr" + "toolchain": "vite", + "mode": "ssr", + "shards": 4 }, { - "bundler": "vite", + "toolchain": "vite", "mode": "spa" }, { - "bundler": "vite", + "toolchain": "vite", "mode": "prerender" }, { - "bundler": "vite", + "toolchain": "vite", "mode": "preview" } ] diff --git a/scripts/nx/playwright-plugin.md b/scripts/nx/playwright-plugin.md index 47ceb642df..f4f2ea54d2 100644 --- a/scripts/nx/playwright-plugin.md +++ b/scripts/nx/playwright-plugin.md @@ -18,10 +18,10 @@ When `playwrightModes` is present, it takes precedence. "nx": { "metadata": { "playwrightModes": [ - { "bundler": "vite", "mode": "ssr" }, - { "bundler": "vite", "mode": "spa", "shards": 4 }, - { "bundler": "vite", "mode": "prerender", "shards": 4 }, - { "bundler": "vite", "mode": "preview" } + { "toolchain": "vite", "mode": "ssr" }, + { "toolchain": "vite", "mode": "spa", "shards": 4 }, + { "toolchain": "vite", "mode": "prerender", "shards": 4 }, + { "toolchain": "vite", "mode": "preview" } ] } } @@ -37,24 +37,21 @@ Supported modes: `shards` is optional and defaults to `1`. -Supported bundlers: +Supported toolchains: - `vite` -- `webpack` -- `rspack` -- `esbuild` ## 2. What targets are generated -For each `{ bundler, mode }` entry, the plugin generates: +For each `{ toolchain, mode }` entry, the plugin generates: -- `build:e2e---` -- `test:e2e---` +- `build:e2e---` +- `test:e2e---` If `shards > 1`, it also generates: -- `test:e2e-----shard-1-of-N` ... `--shard-N-of-N` -- `test:e2e---` as a parent noop target depending on all shards +- `test:e2e-----shard-1-of-N` ... `--shard-N-of-N` +- `test:e2e---` as a parent noop target depending on all shards Finally, it generates: @@ -65,32 +62,32 @@ Finally, it generates: Each inferred `build:e2e:*` and `test:e2e:*` target sets: - `MODE=` -- `BUNDLER=` -- `E2E_BUNDLER=` -- `E2E_DIST=dist--` -- `E2E_DIST_DIR=dist--` +- `TOOLCHAIN=` +- `E2E_TOOLCHAIN=` +- `E2E_DIST=dist--` +- `E2E_DIST_DIR=dist--` Each inferred `test:e2e:*` target also sets: -- `E2E_PORT_KEY=--` -- For shard targets: `E2E_PORT_KEY=---shard--of-` +- `E2E_PORT_KEY=--` +- For shard targets: `E2E_PORT_KEY=---shard--of-` ## 4. Build behavior and webServer command -Each inferred e2e target depends on the inferred `build:e2e---` +Each inferred e2e target depends on the inferred `build:e2e---` target, which runs: ```sh -pnpm build +vite build && tsc --noEmit ``` -with the mode/bundler env above. +with the mode/toolchain env above. The inferred build target also hashes these env vars as target inputs: - `MODE` -- `BUNDLER` -- `E2E_BUNDLER` +- `TOOLCHAIN` +- `E2E_TOOLCHAIN` - `E2E_DIST` - `E2E_DIST_DIR` diff --git a/scripts/nx/playwright-plugin.ts b/scripts/nx/playwright-plugin.ts index c804d78a62..dd6d9a979c 100644 --- a/scripts/nx/playwright-plugin.ts +++ b/scripts/nx/playwright-plugin.ts @@ -94,26 +94,28 @@ function createNodesInternal( const CI_TARGET_NAME = 'test:e2e' const MODE_TARGET_SEPARATOR = '--' -const PLAYWRIGHT_BUNDLERS = ['vite', 'webpack', 'rspack', 'esbuild'] as const +const PLAYWRIGHT_TOOLCHAINS = ['vite'] as const const PLAYWRIGHT_MODES = ['ssr', 'spa', 'prerender', 'preview'] as const -type PlaywrightBundler = (typeof PLAYWRIGHT_BUNDLERS)[number] +type PlaywrightToolchain = (typeof PLAYWRIGHT_TOOLCHAINS)[number] type PlaywrightMode = (typeof PLAYWRIGHT_MODES)[number] type PlaywrightModeMetadata = { - bundler: PlaywrightBundler + toolchain: PlaywrightToolchain mode: PlaywrightMode shards?: number } type RawPlaywrightModeMetadata = { - bundler?: string + toolchain?: string mode?: string shards?: number } -function isPlaywrightBundler(bundler: string): bundler is PlaywrightBundler { - return PLAYWRIGHT_BUNDLERS.includes(bundler as PlaywrightBundler) +function isPlaywrightToolchain( + toolchain: string, +): toolchain is PlaywrightToolchain { + return PLAYWRIGHT_TOOLCHAINS.includes(toolchain as PlaywrightToolchain) } function isPlaywrightMode(mode: string): mode is PlaywrightMode { @@ -129,16 +131,22 @@ function withEnvInputs( return [...baseInputs, ...envNames.map((env) => ({ env }))] } -function getDistDirName({ bundler, mode }: PlaywrightModeMetadata): string { - return `dist-${bundler}-${mode}` +function getDistDirName({ toolchain, mode }: PlaywrightModeMetadata): string { + return `dist-${toolchain}-${mode}` } -function getBuildTargetName({ bundler, mode }: PlaywrightModeMetadata): string { - return `build:e2e--${bundler}-${mode}` +function getBuildTargetName({ + toolchain, + mode, +}: PlaywrightModeMetadata): string { + return `build:e2e--${toolchain}-${mode}` } -function getModeTargetName({ bundler, mode }: PlaywrightModeMetadata): string { - return `${CI_TARGET_NAME}--${bundler}-${mode}` +function getModeTargetName({ + toolchain, + mode, +}: PlaywrightModeMetadata): string { + return `${CI_TARGET_NAME}--${toolchain}-${mode}` } function getModeEnv( @@ -148,8 +156,8 @@ function getModeEnv( return { MODE: modeMetadata.mode, - BUNDLER: modeMetadata.bundler, - E2E_BUNDLER: modeMetadata.bundler, + TOOLCHAIN: modeMetadata.toolchain, + E2E_TOOLCHAIN: modeMetadata.toolchain, E2E_DIST: distDir, E2E_DIST_DIR: distDir, } @@ -161,7 +169,7 @@ function getModePortKey( shardIndex?: number, shardCount?: number, ): string { - const modePortKey = `${packageName}-${modeMetadata.bundler}-${modeMetadata.mode}` + const modePortKey = `${packageName}-${modeMetadata.toolchain}-${modeMetadata.mode}` if (shardIndex === undefined || shardCount === undefined) { return modePortKey @@ -194,19 +202,19 @@ function validateModeMetadata( modeMetadata: RawPlaywrightModeMetadata, index: number, ): PlaywrightModeMetadata { - const targetName = `${modeMetadata.bundler ?? ''}:${modeMetadata.mode ?? ''}` + const targetName = `${modeMetadata.toolchain ?? ''}:${modeMetadata.mode ?? ''}` - if (!modeMetadata.bundler || !modeMetadata.mode) { + if (!modeMetadata.toolchain || !modeMetadata.mode) { throw new Error( `[Playwright Sharding Plugin] Invalid playwrightModes[${index}] entry: ` + - `${JSON.stringify(modeMetadata)}. Both "bundler" and "mode" are required.`, + `${JSON.stringify(modeMetadata)}. Both "toolchain" and "mode" are required.`, ) } - if (!isPlaywrightBundler(modeMetadata.bundler)) { + if (!isPlaywrightToolchain(modeMetadata.toolchain)) { throw new Error( - `[Playwright Sharding Plugin] Invalid bundler for playwrightModes[${index}]: ` + - `${modeMetadata.bundler}. Supported bundlers: ${PLAYWRIGHT_BUNDLERS.join(', ')}.`, + `[Playwright Sharding Plugin] Invalid toolchain for playwrightModes[${index}]: ` + + `${modeMetadata.toolchain}. Supported toolchains: ${PLAYWRIGHT_TOOLCHAINS.join(', ')}.`, ) } @@ -220,7 +228,7 @@ function validateModeMetadata( normalizeShardCount(modeMetadata.shards, targetName) return { - bundler: modeMetadata.bundler, + toolchain: modeMetadata.toolchain, mode: modeMetadata.mode, shards: modeMetadata.shards, } @@ -251,13 +259,13 @@ function buildModeTargets( const shardCount = normalizeShardCount(modeMetadata.shards, modeTargetName) const modeEnv = getModeEnv(modeMetadata) const modePortKey = getModePortKey(packageName, modeMetadata) - const modeDescription = `${modeMetadata.bundler}/${modeMetadata.mode}` + const modeDescription = `${modeMetadata.toolchain}/${modeMetadata.mode}` const modeShardTargets: Array = [] targets[buildTargetName] = { executor: 'nx:run-commands', options: { - command: 'pnpm build', + command: 'vite build && tsc --noEmit', cwd: projectRoot, env: modeEnv, }, @@ -265,8 +273,8 @@ function buildModeTargets( cache: true, inputs: withEnvInputs(buildInputs, [ 'MODE', - 'BUNDLER', - 'E2E_BUNDLER', + 'TOOLCHAIN', + 'E2E_TOOLCHAIN', 'E2E_DIST', 'E2E_DIST_DIR', ]), From 3e06cf04e95484eb1df8dbf50248196aebcfd5a4 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Fri, 17 Apr 2026 00:35:03 +0200 Subject: [PATCH 4/9] chore(e2e): align solid and vue start basic mode targets --- .../basic-test-suite/src/redirect.spec.ts | 8 ++--- .../src/setup/global.setup.ts | 4 +-- .../src/setup/global.teardown.ts | 4 +-- .../src/setup/waitForDummyServer.ts | 4 +-- .../src/utils/getE2EPortKey.ts | 5 +++ e2e/solid-start/basic/.gitignore | 1 + e2e/solid-start/basic/package.json | 28 ++++++++++++---- e2e/solid-start/basic/playwright.config.ts | 32 +++++++++++++++---- e2e/solid-start/basic/server.js | 11 ++++--- e2e/solid-start/basic/vite.config.ts | 5 +++ .../basic-test-suite/src/redirect.spec.ts | 8 ++--- .../src/setup/global.setup.ts | 4 +-- .../src/setup/global.teardown.ts | 4 +-- .../src/setup/waitForDummyServer.ts | 4 +-- .../src/utils/getE2EPortKey.ts | 5 +++ e2e/vue-start/basic/.gitignore | 1 + e2e/vue-start/basic/package.json | 28 ++++++++++++---- e2e/vue-start/basic/playwright.config.ts | 32 +++++++++++++++---- e2e/vue-start/basic/server.js | 11 ++++--- e2e/vue-start/basic/vite.config.ts | 5 +++ package.json | 2 +- 21 files changed, 151 insertions(+), 55 deletions(-) create mode 100644 e2e/solid-start/basic-test-suite/src/utils/getE2EPortKey.ts create mode 100644 e2e/vue-start/basic-test-suite/src/utils/getE2EPortKey.ts diff --git a/e2e/solid-start/basic-test-suite/src/redirect.spec.ts b/e2e/solid-start/basic-test-suite/src/redirect.spec.ts index c25a6b0dbf..2cad7d8f9d 100644 --- a/e2e/solid-start/basic-test-suite/src/redirect.spec.ts +++ b/e2e/solid-start/basic-test-suite/src/redirect.spec.ts @@ -6,14 +6,14 @@ import { getTestServerPort, test, } from '@tanstack/router-e2e-utils' -import { getPackageName } from './utils/getPackageName.ts' +import { getE2EPortKey } from './utils/getE2EPortKey.ts' // somehow playwright does not correctly import default exports const combinate = (combinateImport as any).default as typeof combinateImport -const packageName = getPackageName() -const PORT = await getTestServerPort(packageName) -const EXTERNAL_HOST_PORT = await getDummyServerPort(packageName) +const e2ePortKey = getE2EPortKey() +const PORT = await getTestServerPort(e2ePortKey) +const EXTERNAL_HOST_PORT = await getDummyServerPort(e2ePortKey) test.describe('redirects', () => { const internalNavigationTestMatrix = combinate({ diff --git a/e2e/solid-start/basic-test-suite/src/setup/global.setup.ts b/e2e/solid-start/basic-test-suite/src/setup/global.setup.ts index fc06a65591..cd38c35885 100644 --- a/e2e/solid-start/basic-test-suite/src/setup/global.setup.ts +++ b/e2e/solid-start/basic-test-suite/src/setup/global.setup.ts @@ -1,6 +1,6 @@ import { e2eStartDummyServer } from '@tanstack/router-e2e-utils' -import { getPackageName } from '../utils/getPackageName.ts' +import { getE2EPortKey } from '../utils/getE2EPortKey.ts' export default async function setup() { - await e2eStartDummyServer(getPackageName()) + await e2eStartDummyServer(getE2EPortKey()) } diff --git a/e2e/solid-start/basic-test-suite/src/setup/global.teardown.ts b/e2e/solid-start/basic-test-suite/src/setup/global.teardown.ts index a8a6899700..36e5c92858 100644 --- a/e2e/solid-start/basic-test-suite/src/setup/global.teardown.ts +++ b/e2e/solid-start/basic-test-suite/src/setup/global.teardown.ts @@ -1,8 +1,8 @@ import { e2eStopDummyServer } from '@tanstack/router-e2e-utils' -import { getPackageName } from '../utils/getPackageName.ts' +import { getE2EPortKey } from '../utils/getE2EPortKey.ts' export default async function teardown() { try { - await e2eStopDummyServer(getPackageName()) + await e2eStopDummyServer(getE2EPortKey()) } catch {} } diff --git a/e2e/solid-start/basic-test-suite/src/setup/waitForDummyServer.ts b/e2e/solid-start/basic-test-suite/src/setup/waitForDummyServer.ts index f27dc86d5b..88c18fd79e 100644 --- a/e2e/solid-start/basic-test-suite/src/setup/waitForDummyServer.ts +++ b/e2e/solid-start/basic-test-suite/src/setup/waitForDummyServer.ts @@ -1,11 +1,11 @@ import { getDummyServerPort } from '@tanstack/router-e2e-utils' -import { getPackageName } from '../utils/getPackageName.ts' +import { getE2EPortKey } from '../utils/getE2EPortKey.ts' const timeoutMs = 10_000 const retryIntervalMs = 100 export default async function waitForDummyServer() { - const port = await getDummyServerPort(getPackageName()) + const port = await getDummyServerPort(getE2EPortKey()) const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { diff --git a/e2e/solid-start/basic-test-suite/src/utils/getE2EPortKey.ts b/e2e/solid-start/basic-test-suite/src/utils/getE2EPortKey.ts new file mode 100644 index 0000000000..48be6a9027 --- /dev/null +++ b/e2e/solid-start/basic-test-suite/src/utils/getE2EPortKey.ts @@ -0,0 +1,5 @@ +import { getPackageName } from './getPackageName.ts' + +export function getE2EPortKey() { + return process.env.E2E_PORT_KEY ?? getPackageName() +} diff --git a/e2e/solid-start/basic/.gitignore b/e2e/solid-start/basic/.gitignore index a79d5cf129..b9c2c7e5f6 100644 --- a/e2e/solid-start/basic/.gitignore +++ b/e2e/solid-start/basic/.gitignore @@ -18,3 +18,4 @@ yarn.lock /playwright-report/ /blob-report/ /playwright/.cache/ +/dist-vite-*/ diff --git a/e2e/solid-start/basic/package.json b/e2e/solid-start/basic/package.json index 09e4b11f6b..fc01ed5635 100644 --- a/e2e/solid-start/basic/package.json +++ b/e2e/solid-start/basic/package.json @@ -7,13 +7,11 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "build:spa": "MODE=spa vite build && tsc --noEmit", - "build:prerender": "MODE=prerender vite build && tsc --noEmit", "preview": "vite preview", "start": "node server.js", "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf dist; rm -rf port*.txt; playwright test --project=chromium" + "test:e2e:local": "playwright test --project=chromium" }, "dependencies": { "@tanstack/solid-router": "workspace:^", @@ -42,10 +40,26 @@ "vite-plugin-solid": "^2.11.11" }, "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } + "metadata": { + "playwrightModes": [ + { + "toolchain": "vite", + "mode": "ssr", + "shards": 4 + }, + { + "toolchain": "vite", + "mode": "spa" + }, + { + "toolchain": "vite", + "mode": "prerender" + }, + { + "toolchain": "vite", + "mode": "preview" + } + ] } } } diff --git a/e2e/solid-start/basic/playwright.config.ts b/e2e/solid-start/basic/playwright.config.ts index 0bc99a1ec4..1387437ee9 100644 --- a/e2e/solid-start/basic/playwright.config.ts +++ b/e2e/solid-start/basic/playwright.config.ts @@ -1,3 +1,4 @@ +import fs from 'node:fs' import { defineConfig, devices } from '@playwright/test' import { getDummyServerPort, @@ -5,11 +6,28 @@ import { } from '@tanstack/router-e2e-utils' import packageJson from './package.json' with { type: 'json' } -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) +const mode = process.env.MODE ?? 'ssr' +const e2ePortKey = process.env.E2E_PORT_KEY ?? packageJson.name +const distDir = process.env.E2E_DIST_DIR ?? 'dist' + +if (process.env.TEST_WORKER_INDEX === undefined) { + for (const portFile of [ + `port-${e2ePortKey}.txt`, + `port-${e2ePortKey}_start.txt`, + `port-${e2ePortKey}-external.txt`, + ]) { + fs.rmSync(portFile, { force: true }) + } +} + +const PORT = await getTestServerPort(e2ePortKey) +const START_PORT = await getTestServerPort(`${e2ePortKey}_start`) +const EXTERNAL_PORT = await getDummyServerPort(e2ePortKey) const baseURL = `http://localhost:${PORT}` -const ssrModeCommand = `pnpm build && pnpm start` +const commandByMode = + mode === 'preview' + ? `pnpm run test:e2e:startDummyServer && pnpm preview --outDir ${distDir} --port ${PORT}` + : `pnpm run test:e2e:startDummyServer && pnpm start` export default defineConfig({ testDir: '../basic-test-suite/src', @@ -23,17 +41,19 @@ export default defineConfig({ }, webServer: { - command: `pnpm run test:e2e:startDummyServer && ${ssrModeCommand}`, + command: commandByMode, url: baseURL, reuseExistingServer: !process.env.CI, stdout: 'pipe', env: { - MODE: '', + MODE: mode, VITE_NODE_ENV: 'test', VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), VITE_SERVER_PORT: String(PORT), START_PORT: String(START_PORT), PORT: String(PORT), + E2E_DIST_DIR: distDir, + E2E_PORT_KEY: e2ePortKey, }, }, diff --git a/e2e/solid-start/basic/server.js b/e2e/solid-start/basic/server.js index 83f5ff0079..9db7619ae1 100644 --- a/e2e/solid-start/basic/server.js +++ b/e2e/solid-start/basic/server.js @@ -9,16 +9,19 @@ const startPort = process.env.START_PORT || 3001 const isSpaMode = process.env.MODE === 'spa' const isPrerender = process.env.MODE === 'prerender' +const distDir = process.env.E2E_DIST_DIR || 'dist' +const distClientDir = path.resolve(distDir, 'client') +const distServerEntryPath = path.resolve(distDir, 'server', 'server.js') export async function createStartServer() { - const server = (await import('./dist/server/server.js')).default + const server = (await import(distServerEntryPath)).default const nodeHandler = toNodeHandler(server.fetch) const app = express() // to keep testing uniform stop express from redirecting /posts to /posts/ // when serving pre-rendered pages - app.use(express.static('./dist/client', { redirect: !isPrerender })) + app.use(express.static(distClientDir, { redirect: !isPrerender })) app.use(async (req, res, next) => { try { @@ -50,10 +53,10 @@ export async function createSpaServer() { }), ) - app.use(express.static('./dist/client')) + app.use(express.static(distClientDir)) app.get('/{*splat}', (req, res) => { - res.sendFile(path.resolve('./dist/client/index.html')) + res.sendFile(path.resolve(distClientDir, 'index.html')) }) return { app } diff --git a/e2e/solid-start/basic/vite.config.ts b/e2e/solid-start/basic/vite.config.ts index 3cca892d57..9e26483d04 100644 --- a/e2e/solid-start/basic/vite.config.ts +++ b/e2e/solid-start/basic/vite.config.ts @@ -30,8 +30,13 @@ const prerenderConfiguration = { maxRedirects: 100, } +const outDir = process.env.E2E_DIST_DIR ?? 'dist' + export default defineConfig({ resolve: { tsconfigPaths: true }, + build: { + outDir, + }, server: { port: 3000, }, diff --git a/e2e/vue-start/basic-test-suite/src/redirect.spec.ts b/e2e/vue-start/basic-test-suite/src/redirect.spec.ts index c25a6b0dbf..2cad7d8f9d 100644 --- a/e2e/vue-start/basic-test-suite/src/redirect.spec.ts +++ b/e2e/vue-start/basic-test-suite/src/redirect.spec.ts @@ -6,14 +6,14 @@ import { getTestServerPort, test, } from '@tanstack/router-e2e-utils' -import { getPackageName } from './utils/getPackageName.ts' +import { getE2EPortKey } from './utils/getE2EPortKey.ts' // somehow playwright does not correctly import default exports const combinate = (combinateImport as any).default as typeof combinateImport -const packageName = getPackageName() -const PORT = await getTestServerPort(packageName) -const EXTERNAL_HOST_PORT = await getDummyServerPort(packageName) +const e2ePortKey = getE2EPortKey() +const PORT = await getTestServerPort(e2ePortKey) +const EXTERNAL_HOST_PORT = await getDummyServerPort(e2ePortKey) test.describe('redirects', () => { const internalNavigationTestMatrix = combinate({ diff --git a/e2e/vue-start/basic-test-suite/src/setup/global.setup.ts b/e2e/vue-start/basic-test-suite/src/setup/global.setup.ts index fc06a65591..cd38c35885 100644 --- a/e2e/vue-start/basic-test-suite/src/setup/global.setup.ts +++ b/e2e/vue-start/basic-test-suite/src/setup/global.setup.ts @@ -1,6 +1,6 @@ import { e2eStartDummyServer } from '@tanstack/router-e2e-utils' -import { getPackageName } from '../utils/getPackageName.ts' +import { getE2EPortKey } from '../utils/getE2EPortKey.ts' export default async function setup() { - await e2eStartDummyServer(getPackageName()) + await e2eStartDummyServer(getE2EPortKey()) } diff --git a/e2e/vue-start/basic-test-suite/src/setup/global.teardown.ts b/e2e/vue-start/basic-test-suite/src/setup/global.teardown.ts index a8a6899700..36e5c92858 100644 --- a/e2e/vue-start/basic-test-suite/src/setup/global.teardown.ts +++ b/e2e/vue-start/basic-test-suite/src/setup/global.teardown.ts @@ -1,8 +1,8 @@ import { e2eStopDummyServer } from '@tanstack/router-e2e-utils' -import { getPackageName } from '../utils/getPackageName.ts' +import { getE2EPortKey } from '../utils/getE2EPortKey.ts' export default async function teardown() { try { - await e2eStopDummyServer(getPackageName()) + await e2eStopDummyServer(getE2EPortKey()) } catch {} } diff --git a/e2e/vue-start/basic-test-suite/src/setup/waitForDummyServer.ts b/e2e/vue-start/basic-test-suite/src/setup/waitForDummyServer.ts index f27dc86d5b..88c18fd79e 100644 --- a/e2e/vue-start/basic-test-suite/src/setup/waitForDummyServer.ts +++ b/e2e/vue-start/basic-test-suite/src/setup/waitForDummyServer.ts @@ -1,11 +1,11 @@ import { getDummyServerPort } from '@tanstack/router-e2e-utils' -import { getPackageName } from '../utils/getPackageName.ts' +import { getE2EPortKey } from '../utils/getE2EPortKey.ts' const timeoutMs = 10_000 const retryIntervalMs = 100 export default async function waitForDummyServer() { - const port = await getDummyServerPort(getPackageName()) + const port = await getDummyServerPort(getE2EPortKey()) const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { diff --git a/e2e/vue-start/basic-test-suite/src/utils/getE2EPortKey.ts b/e2e/vue-start/basic-test-suite/src/utils/getE2EPortKey.ts new file mode 100644 index 0000000000..48be6a9027 --- /dev/null +++ b/e2e/vue-start/basic-test-suite/src/utils/getE2EPortKey.ts @@ -0,0 +1,5 @@ +import { getPackageName } from './getPackageName.ts' + +export function getE2EPortKey() { + return process.env.E2E_PORT_KEY ?? getPackageName() +} diff --git a/e2e/vue-start/basic/.gitignore b/e2e/vue-start/basic/.gitignore index a79d5cf129..b9c2c7e5f6 100644 --- a/e2e/vue-start/basic/.gitignore +++ b/e2e/vue-start/basic/.gitignore @@ -18,3 +18,4 @@ yarn.lock /playwright-report/ /blob-report/ /playwright/.cache/ +/dist-vite-*/ diff --git a/e2e/vue-start/basic/package.json b/e2e/vue-start/basic/package.json index ab56c475ef..eafce66615 100644 --- a/e2e/vue-start/basic/package.json +++ b/e2e/vue-start/basic/package.json @@ -7,13 +7,11 @@ "dev": "vite dev --port 3000", "dev:e2e": "vite dev", "build": "vite build && tsc --noEmit", - "build:spa": "MODE=spa vite build && tsc --noEmit", - "build:prerender": "MODE=prerender vite build && tsc --noEmit", "preview": "vite preview", "start": "node server.js", "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf dist; rm -rf port*.txt; playwright test --project=chromium" + "test:e2e:local": "playwright test --project=chromium" }, "dependencies": { "@tanstack/vue-router": "workspace:^", @@ -43,10 +41,26 @@ "typescript": "^6.0.2" }, "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } + "metadata": { + "playwrightModes": [ + { + "toolchain": "vite", + "mode": "ssr", + "shards": 4 + }, + { + "toolchain": "vite", + "mode": "spa" + }, + { + "toolchain": "vite", + "mode": "prerender" + }, + { + "toolchain": "vite", + "mode": "preview" + } + ] } } } diff --git a/e2e/vue-start/basic/playwright.config.ts b/e2e/vue-start/basic/playwright.config.ts index 0bc99a1ec4..1387437ee9 100644 --- a/e2e/vue-start/basic/playwright.config.ts +++ b/e2e/vue-start/basic/playwright.config.ts @@ -1,3 +1,4 @@ +import fs from 'node:fs' import { defineConfig, devices } from '@playwright/test' import { getDummyServerPort, @@ -5,11 +6,28 @@ import { } from '@tanstack/router-e2e-utils' import packageJson from './package.json' with { type: 'json' } -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) +const mode = process.env.MODE ?? 'ssr' +const e2ePortKey = process.env.E2E_PORT_KEY ?? packageJson.name +const distDir = process.env.E2E_DIST_DIR ?? 'dist' + +if (process.env.TEST_WORKER_INDEX === undefined) { + for (const portFile of [ + `port-${e2ePortKey}.txt`, + `port-${e2ePortKey}_start.txt`, + `port-${e2ePortKey}-external.txt`, + ]) { + fs.rmSync(portFile, { force: true }) + } +} + +const PORT = await getTestServerPort(e2ePortKey) +const START_PORT = await getTestServerPort(`${e2ePortKey}_start`) +const EXTERNAL_PORT = await getDummyServerPort(e2ePortKey) const baseURL = `http://localhost:${PORT}` -const ssrModeCommand = `pnpm build && pnpm start` +const commandByMode = + mode === 'preview' + ? `pnpm run test:e2e:startDummyServer && pnpm preview --outDir ${distDir} --port ${PORT}` + : `pnpm run test:e2e:startDummyServer && pnpm start` export default defineConfig({ testDir: '../basic-test-suite/src', @@ -23,17 +41,19 @@ export default defineConfig({ }, webServer: { - command: `pnpm run test:e2e:startDummyServer && ${ssrModeCommand}`, + command: commandByMode, url: baseURL, reuseExistingServer: !process.env.CI, stdout: 'pipe', env: { - MODE: '', + MODE: mode, VITE_NODE_ENV: 'test', VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), VITE_SERVER_PORT: String(PORT), START_PORT: String(START_PORT), PORT: String(PORT), + E2E_DIST_DIR: distDir, + E2E_PORT_KEY: e2ePortKey, }, }, diff --git a/e2e/vue-start/basic/server.js b/e2e/vue-start/basic/server.js index 83f5ff0079..9db7619ae1 100644 --- a/e2e/vue-start/basic/server.js +++ b/e2e/vue-start/basic/server.js @@ -9,16 +9,19 @@ const startPort = process.env.START_PORT || 3001 const isSpaMode = process.env.MODE === 'spa' const isPrerender = process.env.MODE === 'prerender' +const distDir = process.env.E2E_DIST_DIR || 'dist' +const distClientDir = path.resolve(distDir, 'client') +const distServerEntryPath = path.resolve(distDir, 'server', 'server.js') export async function createStartServer() { - const server = (await import('./dist/server/server.js')).default + const server = (await import(distServerEntryPath)).default const nodeHandler = toNodeHandler(server.fetch) const app = express() // to keep testing uniform stop express from redirecting /posts to /posts/ // when serving pre-rendered pages - app.use(express.static('./dist/client', { redirect: !isPrerender })) + app.use(express.static(distClientDir, { redirect: !isPrerender })) app.use(async (req, res, next) => { try { @@ -50,10 +53,10 @@ export async function createSpaServer() { }), ) - app.use(express.static('./dist/client')) + app.use(express.static(distClientDir)) app.get('/{*splat}', (req, res) => { - res.sendFile(path.resolve('./dist/client/index.html')) + res.sendFile(path.resolve(distClientDir, 'index.html')) }) return { app } diff --git a/e2e/vue-start/basic/vite.config.ts b/e2e/vue-start/basic/vite.config.ts index e638e37f36..6fe8d5d194 100644 --- a/e2e/vue-start/basic/vite.config.ts +++ b/e2e/vue-start/basic/vite.config.ts @@ -30,8 +30,13 @@ const prerenderConfiguration = { maxRedirects: 100, } +const outDir = process.env.E2E_DIST_DIR ?? 'dist' + export default defineConfig({ resolve: { tsconfigPaths: true }, + build: { + outDir, + }, server: { port: 3000, }, diff --git a/package.json b/package.json index bf189380f2..a9ce36b8cb 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "test:docs": "node scripts/verify-links.ts", "vite-ecosystem-ci:build": "nx run-many --targets=build --projects=@tanstack/router-plugin,@tanstack/start-plugin-core,@tanstack/react-start,@tanstack/react-start-client,@tanstack/react-start-server --skipRemoteCache", "vite-ecosystem-ci:before-test": "pnpm exec playwright install chromium", - "vite-ecosystem-ci:test": "nx run-many --targets=test:unit --projects=@tanstack/router-plugin,@tanstack/start-plugin-core,@tanstack/react-start-client --skipRemoteCache && nx run-many --target=test:e2e --projects=tanstack-router-e2e-react-basic-file-based,tanstack-router-e2e-react-basic-file-based-code-splitting,tanstack-react-start-e2e-basic,tanstack-vue-start-e2e-basic,tanstack-vue-start-e2e-basic-spa,tanstack-vue-start-e2e-basic-prerender,tanstack-vue-start-e2e-basic-preview,tanstack-solid-start-e2e-basic,tanstack-solid-start-e2e-basic-spa,tanstack-solid-start-e2e-basic-prerender,tanstack-solid-start-e2e-basic-preview --skipRemoteCache" + "vite-ecosystem-ci:test": "nx run-many --targets=test:unit --projects=@tanstack/router-plugin,@tanstack/start-plugin-core,@tanstack/react-start-client --skipRemoteCache && nx run-many --target=test:e2e --projects=tanstack-router-e2e-react-basic-file-based,tanstack-router-e2e-react-basic-file-based-code-splitting,tanstack-react-start-e2e-basic,tanstack-vue-start-e2e-basic,tanstack-solid-start-e2e-basic --skipRemoteCache" }, "nx": { "includedScripts": [ From e963b8fdb38ca91d4b8be2fa86886f8adbb3e836 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Fri, 17 Apr 2026 10:21:11 +0200 Subject: [PATCH 5/9] chore: unify solid-start basic e2e mode projects --- e2e/solid-start/basic-prerender/.gitignore | 5 -- e2e/solid-start/basic-prerender/package.json | 24 ------- .../basic-prerender/playwright.config.ts | 48 -------------- e2e/solid-start/basic-preview/.gitignore | 5 -- e2e/solid-start/basic-preview/package.json | 24 ------- .../basic-preview/playwright.config.ts | 47 -------------- e2e/solid-start/basic-spa/.gitignore | 5 -- e2e/solid-start/basic-spa/package.json | 24 ------- .../basic-spa/playwright.config.ts | 48 -------------- e2e/solid-start/basic-test-suite/.gitignore | 5 -- e2e/solid-start/basic-test-suite/package.json | 12 ---- .../src/utils/getBasicAppRoot.ts | 8 --- .../basic-test-suite/src/utils/isPrerender.ts | 1 - .../basic-test-suite/src/utils/isPreview.ts | 1 - .../basic-test-suite/src/utils/isSpaMode.ts | 1 - .../basic-test-suite/tsconfig.json | 18 ------ e2e/solid-start/basic/package.json | 5 +- e2e/solid-start/basic/playwright.config.ts | 4 +- .../src => basic/tests}/navigation.spec.ts | 0 .../src => basic/tests}/not-found.spec.ts | 0 .../src => basic/tests}/prerendering.spec.ts | 10 +-- .../src => basic/tests}/redirect.spec.ts | 0 .../tests}/script-duplication.spec.ts | 0 .../src => basic/tests}/search-params.spec.ts | 0 .../src => basic/tests}/setup/global.setup.ts | 0 .../tests}/setup/global.teardown.ts | 0 .../tests}/setup/waitForDummyServer.ts | 0 .../tests}/special-characters.spec.ts | 0 .../src => basic/tests}/streaming.spec.ts | 0 .../src => basic/tests}/transition.spec.ts | 0 .../tests}/utils/getE2EPortKey.ts | 0 .../tests}/utils/getPackageName.ts | 0 e2e/solid-start/basic/tsconfig.json | 1 + pnpm-lock.yaml | 63 ------------------- 34 files changed, 11 insertions(+), 348 deletions(-) delete mode 100644 e2e/solid-start/basic-prerender/.gitignore delete mode 100644 e2e/solid-start/basic-prerender/package.json delete mode 100644 e2e/solid-start/basic-prerender/playwright.config.ts delete mode 100644 e2e/solid-start/basic-preview/.gitignore delete mode 100644 e2e/solid-start/basic-preview/package.json delete mode 100644 e2e/solid-start/basic-preview/playwright.config.ts delete mode 100644 e2e/solid-start/basic-spa/.gitignore delete mode 100644 e2e/solid-start/basic-spa/package.json delete mode 100644 e2e/solid-start/basic-spa/playwright.config.ts delete mode 100644 e2e/solid-start/basic-test-suite/.gitignore delete mode 100644 e2e/solid-start/basic-test-suite/package.json delete mode 100644 e2e/solid-start/basic-test-suite/src/utils/getBasicAppRoot.ts delete mode 100644 e2e/solid-start/basic-test-suite/src/utils/isPrerender.ts delete mode 100644 e2e/solid-start/basic-test-suite/src/utils/isPreview.ts delete mode 100644 e2e/solid-start/basic-test-suite/src/utils/isSpaMode.ts delete mode 100644 e2e/solid-start/basic-test-suite/tsconfig.json rename e2e/solid-start/{basic-test-suite/src => basic/tests}/navigation.spec.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/not-found.spec.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/prerendering.spec.ts (90%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/redirect.spec.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/script-duplication.spec.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/search-params.spec.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/setup/global.setup.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/setup/global.teardown.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/setup/waitForDummyServer.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/special-characters.spec.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/streaming.spec.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/transition.spec.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/utils/getE2EPortKey.ts (100%) rename e2e/solid-start/{basic-test-suite/src => basic/tests}/utils/getPackageName.ts (100%) diff --git a/e2e/solid-start/basic-prerender/.gitignore b/e2e/solid-start/basic-prerender/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/solid-start/basic-prerender/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/solid-start/basic-prerender/package.json b/e2e/solid-start/basic-prerender/package.json deleted file mode 100644 index eda156f8e3..0000000000 --- a/e2e/solid-start/basic-prerender/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-solid-start-e2e-basic-prerender", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf port*.txt; MODE=prerender playwright test --project=chromium" - }, - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-solid-start-e2e-basic": "workspace:*", - "tanstack-solid-start-e2e-basic-test-suite": "workspace:*" - }, - "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } - } - } -} diff --git a/e2e/solid-start/basic-prerender/playwright.config.ts b/e2e/solid-start/basic-prerender/playwright.config.ts deleted file mode 100644 index cf2cf5d396..0000000000 --- a/e2e/solid-start/basic-prerender/playwright.config.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' -import { - getDummyServerPort, - getTestServerPort, -} from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } - -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - testDir: '../basic-test-suite/src', - workers: 1, - reporter: [['line']], - - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', - - use: { - baseURL, - }, - - webServer: { - command: - 'pnpm run test:e2e:startDummyServer && pnpm --dir ../basic build:prerender && pnpm --dir ../basic start', - url: baseURL, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - env: { - MODE: 'prerender', - VITE_NODE_ENV: 'test', - VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), - VITE_SERVER_PORT: String(PORT), - START_PORT: String(START_PORT), - PORT: String(PORT), - }, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - ], -}) diff --git a/e2e/solid-start/basic-preview/.gitignore b/e2e/solid-start/basic-preview/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/solid-start/basic-preview/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/solid-start/basic-preview/package.json b/e2e/solid-start/basic-preview/package.json deleted file mode 100644 index 233d6af609..0000000000 --- a/e2e/solid-start/basic-preview/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-solid-start-e2e-basic-preview", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf port*.txt; MODE=preview playwright test --project=chromium" - }, - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-solid-start-e2e-basic": "workspace:*", - "tanstack-solid-start-e2e-basic-test-suite": "workspace:*" - }, - "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } - } - } -} diff --git a/e2e/solid-start/basic-preview/playwright.config.ts b/e2e/solid-start/basic-preview/playwright.config.ts deleted file mode 100644 index 071f336f9f..0000000000 --- a/e2e/solid-start/basic-preview/playwright.config.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' -import { - getDummyServerPort, - getTestServerPort, -} from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } - -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - testDir: '../basic-test-suite/src', - workers: 1, - reporter: [['line']], - - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', - - use: { - baseURL, - }, - - webServer: { - command: `pnpm run test:e2e:startDummyServer && pnpm --dir ../basic build && pnpm --dir ../basic preview --port ${PORT}`, - url: baseURL, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - env: { - MODE: 'preview', - VITE_NODE_ENV: 'test', - VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), - VITE_SERVER_PORT: String(PORT), - START_PORT: String(START_PORT), - PORT: String(PORT), - }, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - ], -}) diff --git a/e2e/solid-start/basic-spa/.gitignore b/e2e/solid-start/basic-spa/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/solid-start/basic-spa/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/solid-start/basic-spa/package.json b/e2e/solid-start/basic-spa/package.json deleted file mode 100644 index c9f0283736..0000000000 --- a/e2e/solid-start/basic-spa/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-solid-start-e2e-basic-spa", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf port*.txt; MODE=spa playwright test --project=chromium" - }, - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-solid-start-e2e-basic": "workspace:*", - "tanstack-solid-start-e2e-basic-test-suite": "workspace:*" - }, - "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } - } - } -} diff --git a/e2e/solid-start/basic-spa/playwright.config.ts b/e2e/solid-start/basic-spa/playwright.config.ts deleted file mode 100644 index 27e85e1f1a..0000000000 --- a/e2e/solid-start/basic-spa/playwright.config.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' -import { - getDummyServerPort, - getTestServerPort, -} from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } - -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - testDir: '../basic-test-suite/src', - workers: 1, - reporter: [['line']], - - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', - - use: { - baseURL, - }, - - webServer: { - command: - 'pnpm run test:e2e:startDummyServer && pnpm --dir ../basic build:spa && pnpm --dir ../basic start', - url: baseURL, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - env: { - MODE: 'spa', - VITE_NODE_ENV: 'test', - VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), - VITE_SERVER_PORT: String(PORT), - START_PORT: String(START_PORT), - PORT: String(PORT), - }, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - ], -}) diff --git a/e2e/solid-start/basic-test-suite/.gitignore b/e2e/solid-start/basic-test-suite/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/solid-start/basic-test-suite/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/solid-start/basic-test-suite/package.json b/e2e/solid-start/basic-test-suite/package.json deleted file mode 100644 index 775f9fc598..0000000000 --- a/e2e/solid-start/basic-test-suite/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "tanstack-solid-start-e2e-basic-test-suite", - "private": true, - "sideEffects": false, - "type": "module", - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "@types/node": "^22.10.2", - "combinate": "^1.1.11" - } -} diff --git a/e2e/solid-start/basic-test-suite/src/utils/getBasicAppRoot.ts b/e2e/solid-start/basic-test-suite/src/utils/getBasicAppRoot.ts deleted file mode 100644 index 12a411989a..0000000000 --- a/e2e/solid-start/basic-test-suite/src/utils/getBasicAppRoot.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { join } from 'node:path' -import { getPackageName } from './getPackageName.ts' - -export function getBasicAppRoot() { - return getPackageName().endsWith('-basic') - ? process.cwd() - : join(process.cwd(), '../basic') -} diff --git a/e2e/solid-start/basic-test-suite/src/utils/isPrerender.ts b/e2e/solid-start/basic-test-suite/src/utils/isPrerender.ts deleted file mode 100644 index d5d991d454..0000000000 --- a/e2e/solid-start/basic-test-suite/src/utils/isPrerender.ts +++ /dev/null @@ -1 +0,0 @@ -export const isPrerender: boolean = process.env.MODE === 'prerender' diff --git a/e2e/solid-start/basic-test-suite/src/utils/isPreview.ts b/e2e/solid-start/basic-test-suite/src/utils/isPreview.ts deleted file mode 100644 index 7ea362a83e..0000000000 --- a/e2e/solid-start/basic-test-suite/src/utils/isPreview.ts +++ /dev/null @@ -1 +0,0 @@ -export const isPreview: boolean = process.env.MODE === 'preview' diff --git a/e2e/solid-start/basic-test-suite/src/utils/isSpaMode.ts b/e2e/solid-start/basic-test-suite/src/utils/isSpaMode.ts deleted file mode 100644 index b4edb829a8..0000000000 --- a/e2e/solid-start/basic-test-suite/src/utils/isSpaMode.ts +++ /dev/null @@ -1 +0,0 @@ -export const isSpaMode: boolean = process.env.MODE === 'spa' diff --git a/e2e/solid-start/basic-test-suite/tsconfig.json b/e2e/solid-start/basic-test-suite/tsconfig.json deleted file mode 100644 index 3f3dbe6bcd..0000000000 --- a/e2e/solid-start/basic-test-suite/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "include": ["src/**/*.ts"], - "compilerOptions": { - "strict": true, - "esModuleInterop": true, - "module": "ESNext", - "moduleResolution": "Bundler", - "lib": ["DOM", "DOM.Iterable", "ES2022"], - "isolatedModules": true, - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "target": "ES2022", - "types": ["node"], - "forceConsistentCasingInFileNames": true, - "noEmit": true - } -} diff --git a/e2e/solid-start/basic/package.json b/e2e/solid-start/basic/package.json index fc01ed5635..e166a886ba 100644 --- a/e2e/solid-start/basic/package.json +++ b/e2e/solid-start/basic/package.json @@ -9,8 +9,8 @@ "build": "vite build && tsc --noEmit", "preview": "vite preview", "start": "node server.js", - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", + "test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"./tests/setup/waitForDummyServer.ts\").then(m => m.default())'", + "test:e2e:stopDummyServer": "node -e 'import(\"./tests/setup/global.teardown.ts\").then(m => m.default())'", "test:e2e:local": "playwright test --project=chromium" }, "dependencies": { @@ -35,7 +35,6 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "tanstack-solid-start-e2e-basic-test-suite": "workspace:*", "typescript": "^6.0.2", "vite-plugin-solid": "^2.11.11" }, diff --git a/e2e/solid-start/basic/playwright.config.ts b/e2e/solid-start/basic/playwright.config.ts index 1387437ee9..6924134cd1 100644 --- a/e2e/solid-start/basic/playwright.config.ts +++ b/e2e/solid-start/basic/playwright.config.ts @@ -30,11 +30,11 @@ const commandByMode = : `pnpm run test:e2e:startDummyServer && pnpm start` export default defineConfig({ - testDir: '../basic-test-suite/src', + testDir: './tests', workers: 1, reporter: [['line']], - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', + globalTeardown: './tests/setup/global.teardown.ts', use: { baseURL, diff --git a/e2e/solid-start/basic-test-suite/src/navigation.spec.ts b/e2e/solid-start/basic/tests/navigation.spec.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/navigation.spec.ts rename to e2e/solid-start/basic/tests/navigation.spec.ts diff --git a/e2e/solid-start/basic-test-suite/src/not-found.spec.ts b/e2e/solid-start/basic/tests/not-found.spec.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/not-found.spec.ts rename to e2e/solid-start/basic/tests/not-found.spec.ts diff --git a/e2e/solid-start/basic-test-suite/src/prerendering.spec.ts b/e2e/solid-start/basic/tests/prerendering.spec.ts similarity index 90% rename from e2e/solid-start/basic-test-suite/src/prerendering.spec.ts rename to e2e/solid-start/basic/tests/prerendering.spec.ts index 66cc1f68ff..ddcf720a4c 100644 --- a/e2e/solid-start/basic-test-suite/src/prerendering.spec.ts +++ b/e2e/solid-start/basic/tests/prerendering.spec.ts @@ -3,15 +3,18 @@ import { join } from 'node:path' import { expect } from '@playwright/test' import { test } from '@tanstack/router-e2e-utils' import { isPrerender } from './utils/isPrerender' -import { getBasicAppRoot } from './utils/getBasicAppRoot' + +const distDir = join( + process.cwd(), + process.env.E2E_DIST_DIR ?? 'dist', + 'client', +) test.describe('Prerender Static Path Discovery', () => { test.skip(!isPrerender, 'Skipping since not in prerender mode') test.describe('Build Output Verification', () => { test('should automatically discover and prerender static routes', () => { // Check that static routes were automatically discovered and prerendered - const distDir = join(getBasicAppRoot(), 'dist', 'client') - // These static routes should be automatically discovered and prerendered expect(existsSync(join(distDir, 'index.html'))).toBe(true) expect(existsSync(join(distDir, 'posts/index.html'))).toBe(true) @@ -35,7 +38,6 @@ test.describe('Prerender Static Path Discovery', () => { test.describe('Static Files Verification', () => { test('should contain prerendered content in posts.html', () => { - const distDir = join(getBasicAppRoot(), 'dist', 'client') expect(existsSync(join(distDir, 'posts/index.html'))).toBe(true) // "Select a post." should be in the prerendered HTML diff --git a/e2e/solid-start/basic-test-suite/src/redirect.spec.ts b/e2e/solid-start/basic/tests/redirect.spec.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/redirect.spec.ts rename to e2e/solid-start/basic/tests/redirect.spec.ts diff --git a/e2e/solid-start/basic-test-suite/src/script-duplication.spec.ts b/e2e/solid-start/basic/tests/script-duplication.spec.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/script-duplication.spec.ts rename to e2e/solid-start/basic/tests/script-duplication.spec.ts diff --git a/e2e/solid-start/basic-test-suite/src/search-params.spec.ts b/e2e/solid-start/basic/tests/search-params.spec.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/search-params.spec.ts rename to e2e/solid-start/basic/tests/search-params.spec.ts diff --git a/e2e/solid-start/basic-test-suite/src/setup/global.setup.ts b/e2e/solid-start/basic/tests/setup/global.setup.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/setup/global.setup.ts rename to e2e/solid-start/basic/tests/setup/global.setup.ts diff --git a/e2e/solid-start/basic-test-suite/src/setup/global.teardown.ts b/e2e/solid-start/basic/tests/setup/global.teardown.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/setup/global.teardown.ts rename to e2e/solid-start/basic/tests/setup/global.teardown.ts diff --git a/e2e/solid-start/basic-test-suite/src/setup/waitForDummyServer.ts b/e2e/solid-start/basic/tests/setup/waitForDummyServer.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/setup/waitForDummyServer.ts rename to e2e/solid-start/basic/tests/setup/waitForDummyServer.ts diff --git a/e2e/solid-start/basic-test-suite/src/special-characters.spec.ts b/e2e/solid-start/basic/tests/special-characters.spec.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/special-characters.spec.ts rename to e2e/solid-start/basic/tests/special-characters.spec.ts diff --git a/e2e/solid-start/basic-test-suite/src/streaming.spec.ts b/e2e/solid-start/basic/tests/streaming.spec.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/streaming.spec.ts rename to e2e/solid-start/basic/tests/streaming.spec.ts diff --git a/e2e/solid-start/basic-test-suite/src/transition.spec.ts b/e2e/solid-start/basic/tests/transition.spec.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/transition.spec.ts rename to e2e/solid-start/basic/tests/transition.spec.ts diff --git a/e2e/solid-start/basic-test-suite/src/utils/getE2EPortKey.ts b/e2e/solid-start/basic/tests/utils/getE2EPortKey.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/utils/getE2EPortKey.ts rename to e2e/solid-start/basic/tests/utils/getE2EPortKey.ts diff --git a/e2e/solid-start/basic-test-suite/src/utils/getPackageName.ts b/e2e/solid-start/basic/tests/utils/getPackageName.ts similarity index 100% rename from e2e/solid-start/basic-test-suite/src/utils/getPackageName.ts rename to e2e/solid-start/basic/tests/utils/getPackageName.ts diff --git a/e2e/solid-start/basic/tsconfig.json b/e2e/solid-start/basic/tsconfig.json index cb1a0b80dd..0543683c33 100644 --- a/e2e/solid-start/basic/tsconfig.json +++ b/e2e/solid-start/basic/tsconfig.json @@ -9,6 +9,7 @@ "moduleResolution": "Bundler", "lib": ["DOM", "DOM.Iterable", "ES2022"], "isolatedModules": true, + "allowImportingTsExtensions": true, "resolveJsonModule": true, "skipLibCheck": true, "target": "ES2022", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 675be44b29..1a1cf6a433 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3872,9 +3872,6 @@ importers: tailwindcss: specifier: ^4.2.2 version: 4.2.2 - tanstack-solid-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite typescript: specifier: ^6.0.2 version: 6.0.2 @@ -3992,36 +3989,6 @@ importers: specifier: ^4.74.0 version: 4.75.0 - e2e/solid-start/basic-prerender: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - tanstack-solid-start-e2e-basic: - specifier: workspace:* - version: link:../basic - tanstack-solid-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite - - e2e/solid-start/basic-preview: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - tanstack-solid-start-e2e-basic: - specifier: workspace:* - version: link:../basic - tanstack-solid-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite - e2e/solid-start/basic-solid-query: dependencies: '@tanstack/solid-query': @@ -4083,36 +4050,6 @@ importers: specifier: ^2.11.11 version: 2.11.11(@testing-library/jest-dom@6.6.3)(solid-js@1.9.12)(vite@8.0.0(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.6.1)(sass-embedded@1.97.2)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) - e2e/solid-start/basic-spa: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - tanstack-solid-start-e2e-basic: - specifier: workspace:* - version: link:../basic - tanstack-solid-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite - - e2e/solid-start/basic-test-suite: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - '@types/node': - specifier: 25.0.9 - version: 25.0.9 - combinate: - specifier: ^1.1.11 - version: 1.1.11 - e2e/solid-start/basic-tsr-config: dependencies: '@tanstack/solid-router': From f99483193de77aeedfc670c7b125c6cd542462e6 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Fri, 17 Apr 2026 10:56:55 +0200 Subject: [PATCH 6/9] chore: unify vue-start basic e2e mode projects --- e2e/vue-start/basic-prerender/.gitignore | 5 -- e2e/vue-start/basic-prerender/package.json | 24 ------- .../basic-prerender/playwright.config.ts | 48 -------------- e2e/vue-start/basic-preview/.gitignore | 5 -- e2e/vue-start/basic-preview/package.json | 24 ------- .../basic-preview/playwright.config.ts | 47 -------------- e2e/vue-start/basic-spa/.gitignore | 5 -- e2e/vue-start/basic-spa/package.json | 24 ------- e2e/vue-start/basic-spa/playwright.config.ts | 48 -------------- e2e/vue-start/basic-test-suite/.gitignore | 5 -- e2e/vue-start/basic-test-suite/package.json | 12 ---- .../src/utils/getBasicAppRoot.ts | 8 --- .../basic-test-suite/src/utils/isPrerender.ts | 1 - .../basic-test-suite/src/utils/isPreview.ts | 1 - .../basic-test-suite/src/utils/isSpaMode.ts | 1 - e2e/vue-start/basic-test-suite/tsconfig.json | 18 ------ e2e/vue-start/basic/package.json | 5 +- e2e/vue-start/basic/playwright.config.ts | 4 +- .../src => basic/tests}/navigation.spec.ts | 0 .../src => basic/tests}/not-found.spec.ts | 0 .../src => basic/tests}/prerendering.spec.ts | 10 +-- .../src => basic/tests}/redirect.spec.ts | 0 .../tests}/script-duplication.spec.ts | 0 .../src => basic/tests}/search-params.spec.ts | 0 .../src => basic/tests}/setup/global.setup.ts | 0 .../tests}/setup/global.teardown.ts | 0 .../tests}/setup/waitForDummyServer.ts | 0 .../tests}/special-characters.spec.ts | 0 .../src => basic/tests}/streaming.spec.ts | 0 .../tests}/utils/getE2EPortKey.ts | 0 .../tests}/utils/getPackageName.ts | 0 e2e/vue-start/basic/tsconfig.json | 1 + pnpm-lock.yaml | 63 ------------------- 33 files changed, 11 insertions(+), 348 deletions(-) delete mode 100644 e2e/vue-start/basic-prerender/.gitignore delete mode 100644 e2e/vue-start/basic-prerender/package.json delete mode 100644 e2e/vue-start/basic-prerender/playwright.config.ts delete mode 100644 e2e/vue-start/basic-preview/.gitignore delete mode 100644 e2e/vue-start/basic-preview/package.json delete mode 100644 e2e/vue-start/basic-preview/playwright.config.ts delete mode 100644 e2e/vue-start/basic-spa/.gitignore delete mode 100644 e2e/vue-start/basic-spa/package.json delete mode 100644 e2e/vue-start/basic-spa/playwright.config.ts delete mode 100644 e2e/vue-start/basic-test-suite/.gitignore delete mode 100644 e2e/vue-start/basic-test-suite/package.json delete mode 100644 e2e/vue-start/basic-test-suite/src/utils/getBasicAppRoot.ts delete mode 100644 e2e/vue-start/basic-test-suite/src/utils/isPrerender.ts delete mode 100644 e2e/vue-start/basic-test-suite/src/utils/isPreview.ts delete mode 100644 e2e/vue-start/basic-test-suite/src/utils/isSpaMode.ts delete mode 100644 e2e/vue-start/basic-test-suite/tsconfig.json rename e2e/vue-start/{basic-test-suite/src => basic/tests}/navigation.spec.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/not-found.spec.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/prerendering.spec.ts (90%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/redirect.spec.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/script-duplication.spec.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/search-params.spec.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/setup/global.setup.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/setup/global.teardown.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/setup/waitForDummyServer.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/special-characters.spec.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/streaming.spec.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/utils/getE2EPortKey.ts (100%) rename e2e/vue-start/{basic-test-suite/src => basic/tests}/utils/getPackageName.ts (100%) diff --git a/e2e/vue-start/basic-prerender/.gitignore b/e2e/vue-start/basic-prerender/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/vue-start/basic-prerender/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/vue-start/basic-prerender/package.json b/e2e/vue-start/basic-prerender/package.json deleted file mode 100644 index 5b05d8f771..0000000000 --- a/e2e/vue-start/basic-prerender/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-vue-start-e2e-basic-prerender", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf port*.txt; MODE=prerender playwright test --project=chromium" - }, - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-vue-start-e2e-basic": "workspace:*", - "tanstack-vue-start-e2e-basic-test-suite": "workspace:*" - }, - "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } - } - } -} diff --git a/e2e/vue-start/basic-prerender/playwright.config.ts b/e2e/vue-start/basic-prerender/playwright.config.ts deleted file mode 100644 index cf2cf5d396..0000000000 --- a/e2e/vue-start/basic-prerender/playwright.config.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' -import { - getDummyServerPort, - getTestServerPort, -} from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } - -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - testDir: '../basic-test-suite/src', - workers: 1, - reporter: [['line']], - - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', - - use: { - baseURL, - }, - - webServer: { - command: - 'pnpm run test:e2e:startDummyServer && pnpm --dir ../basic build:prerender && pnpm --dir ../basic start', - url: baseURL, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - env: { - MODE: 'prerender', - VITE_NODE_ENV: 'test', - VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), - VITE_SERVER_PORT: String(PORT), - START_PORT: String(START_PORT), - PORT: String(PORT), - }, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - ], -}) diff --git a/e2e/vue-start/basic-preview/.gitignore b/e2e/vue-start/basic-preview/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/vue-start/basic-preview/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/vue-start/basic-preview/package.json b/e2e/vue-start/basic-preview/package.json deleted file mode 100644 index 663b1365f1..0000000000 --- a/e2e/vue-start/basic-preview/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-vue-start-e2e-basic-preview", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf port*.txt; MODE=preview playwright test --project=chromium" - }, - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-vue-start-e2e-basic": "workspace:*", - "tanstack-vue-start-e2e-basic-test-suite": "workspace:*" - }, - "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } - } - } -} diff --git a/e2e/vue-start/basic-preview/playwright.config.ts b/e2e/vue-start/basic-preview/playwright.config.ts deleted file mode 100644 index 071f336f9f..0000000000 --- a/e2e/vue-start/basic-preview/playwright.config.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' -import { - getDummyServerPort, - getTestServerPort, -} from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } - -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - testDir: '../basic-test-suite/src', - workers: 1, - reporter: [['line']], - - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', - - use: { - baseURL, - }, - - webServer: { - command: `pnpm run test:e2e:startDummyServer && pnpm --dir ../basic build && pnpm --dir ../basic preview --port ${PORT}`, - url: baseURL, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - env: { - MODE: 'preview', - VITE_NODE_ENV: 'test', - VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), - VITE_SERVER_PORT: String(PORT), - START_PORT: String(START_PORT), - PORT: String(PORT), - }, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - ], -}) diff --git a/e2e/vue-start/basic-spa/.gitignore b/e2e/vue-start/basic-spa/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/vue-start/basic-spa/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/vue-start/basic-spa/package.json b/e2e/vue-start/basic-spa/package.json deleted file mode 100644 index 2186636549..0000000000 --- a/e2e/vue-start/basic-spa/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "tanstack-vue-start-e2e-basic-spa", - "private": true, - "sideEffects": false, - "type": "module", - "scripts": { - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", - "test:e2e": "rm -rf port*.txt; MODE=spa playwright test --project=chromium" - }, - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "tanstack-vue-start-e2e-basic": "workspace:*", - "tanstack-vue-start-e2e-basic-test-suite": "workspace:*" - }, - "nx": { - "targets": { - "test:e2e": { - "parallelism": false - } - } - } -} diff --git a/e2e/vue-start/basic-spa/playwright.config.ts b/e2e/vue-start/basic-spa/playwright.config.ts deleted file mode 100644 index 27e85e1f1a..0000000000 --- a/e2e/vue-start/basic-spa/playwright.config.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' -import { - getDummyServerPort, - getTestServerPort, -} from '@tanstack/router-e2e-utils' -import packageJson from './package.json' with { type: 'json' } - -const PORT = await getTestServerPort(packageJson.name) -const START_PORT = await getTestServerPort(`${packageJson.name}_start`) -const EXTERNAL_PORT = await getDummyServerPort(packageJson.name) -const baseURL = `http://localhost:${PORT}` - -export default defineConfig({ - testDir: '../basic-test-suite/src', - workers: 1, - reporter: [['line']], - - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', - - use: { - baseURL, - }, - - webServer: { - command: - 'pnpm run test:e2e:startDummyServer && pnpm --dir ../basic build:spa && pnpm --dir ../basic start', - url: baseURL, - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - env: { - MODE: 'spa', - VITE_NODE_ENV: 'test', - VITE_EXTERNAL_PORT: String(EXTERNAL_PORT), - VITE_SERVER_PORT: String(PORT), - START_PORT: String(START_PORT), - PORT: String(PORT), - }, - }, - - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - ], -}) diff --git a/e2e/vue-start/basic-test-suite/.gitignore b/e2e/vue-start/basic-test-suite/.gitignore deleted file mode 100644 index 6381bf3860..0000000000 --- a/e2e/vue-start/basic-test-suite/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules - -/test-results/ -/playwright-report/ -/blob-report/ diff --git a/e2e/vue-start/basic-test-suite/package.json b/e2e/vue-start/basic-test-suite/package.json deleted file mode 100644 index 7d5ea7c002..0000000000 --- a/e2e/vue-start/basic-test-suite/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "tanstack-vue-start-e2e-basic-test-suite", - "private": true, - "sideEffects": false, - "type": "module", - "devDependencies": { - "@playwright/test": "^1.50.1", - "@tanstack/router-e2e-utils": "workspace:^", - "@types/node": "^22.10.2", - "combinate": "^1.1.11" - } -} diff --git a/e2e/vue-start/basic-test-suite/src/utils/getBasicAppRoot.ts b/e2e/vue-start/basic-test-suite/src/utils/getBasicAppRoot.ts deleted file mode 100644 index 12a411989a..0000000000 --- a/e2e/vue-start/basic-test-suite/src/utils/getBasicAppRoot.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { join } from 'node:path' -import { getPackageName } from './getPackageName.ts' - -export function getBasicAppRoot() { - return getPackageName().endsWith('-basic') - ? process.cwd() - : join(process.cwd(), '../basic') -} diff --git a/e2e/vue-start/basic-test-suite/src/utils/isPrerender.ts b/e2e/vue-start/basic-test-suite/src/utils/isPrerender.ts deleted file mode 100644 index d5d991d454..0000000000 --- a/e2e/vue-start/basic-test-suite/src/utils/isPrerender.ts +++ /dev/null @@ -1 +0,0 @@ -export const isPrerender: boolean = process.env.MODE === 'prerender' diff --git a/e2e/vue-start/basic-test-suite/src/utils/isPreview.ts b/e2e/vue-start/basic-test-suite/src/utils/isPreview.ts deleted file mode 100644 index 7ea362a83e..0000000000 --- a/e2e/vue-start/basic-test-suite/src/utils/isPreview.ts +++ /dev/null @@ -1 +0,0 @@ -export const isPreview: boolean = process.env.MODE === 'preview' diff --git a/e2e/vue-start/basic-test-suite/src/utils/isSpaMode.ts b/e2e/vue-start/basic-test-suite/src/utils/isSpaMode.ts deleted file mode 100644 index b4edb829a8..0000000000 --- a/e2e/vue-start/basic-test-suite/src/utils/isSpaMode.ts +++ /dev/null @@ -1 +0,0 @@ -export const isSpaMode: boolean = process.env.MODE === 'spa' diff --git a/e2e/vue-start/basic-test-suite/tsconfig.json b/e2e/vue-start/basic-test-suite/tsconfig.json deleted file mode 100644 index 3f3dbe6bcd..0000000000 --- a/e2e/vue-start/basic-test-suite/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "include": ["src/**/*.ts"], - "compilerOptions": { - "strict": true, - "esModuleInterop": true, - "module": "ESNext", - "moduleResolution": "Bundler", - "lib": ["DOM", "DOM.Iterable", "ES2022"], - "isolatedModules": true, - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "target": "ES2022", - "types": ["node"], - "forceConsistentCasingInFileNames": true, - "noEmit": true - } -} diff --git a/e2e/vue-start/basic/package.json b/e2e/vue-start/basic/package.json index eafce66615..6950d738ea 100644 --- a/e2e/vue-start/basic/package.json +++ b/e2e/vue-start/basic/package.json @@ -9,8 +9,8 @@ "build": "vite build && tsc --noEmit", "preview": "vite preview", "start": "node server.js", - "test:e2e:startDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"../basic-test-suite/src/setup/waitForDummyServer.ts\").then(m => m.default())'", - "test:e2e:stopDummyServer": "node -e 'import(\"../basic-test-suite/src/setup/global.teardown.ts\").then(m => m.default())'", + "test:e2e:startDummyServer": "node -e 'import(\"./tests/setup/global.setup.ts\").then(m => m.default())' & node -e 'import(\"./tests/setup/waitForDummyServer.ts\").then(m => m.default())'", + "test:e2e:stopDummyServer": "node -e 'import(\"./tests/setup/global.teardown.ts\").then(m => m.default())'", "test:e2e:local": "playwright test --project=chromium" }, "dependencies": { @@ -37,7 +37,6 @@ "combinate": "^1.1.11", "srvx": "^0.11.9", "tailwindcss": "^4.2.2", - "tanstack-vue-start-e2e-basic-test-suite": "workspace:*", "typescript": "^6.0.2" }, "nx": { diff --git a/e2e/vue-start/basic/playwright.config.ts b/e2e/vue-start/basic/playwright.config.ts index 1387437ee9..6924134cd1 100644 --- a/e2e/vue-start/basic/playwright.config.ts +++ b/e2e/vue-start/basic/playwright.config.ts @@ -30,11 +30,11 @@ const commandByMode = : `pnpm run test:e2e:startDummyServer && pnpm start` export default defineConfig({ - testDir: '../basic-test-suite/src', + testDir: './tests', workers: 1, reporter: [['line']], - globalTeardown: '../basic-test-suite/src/setup/global.teardown.ts', + globalTeardown: './tests/setup/global.teardown.ts', use: { baseURL, diff --git a/e2e/vue-start/basic-test-suite/src/navigation.spec.ts b/e2e/vue-start/basic/tests/navigation.spec.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/navigation.spec.ts rename to e2e/vue-start/basic/tests/navigation.spec.ts diff --git a/e2e/vue-start/basic-test-suite/src/not-found.spec.ts b/e2e/vue-start/basic/tests/not-found.spec.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/not-found.spec.ts rename to e2e/vue-start/basic/tests/not-found.spec.ts diff --git a/e2e/vue-start/basic-test-suite/src/prerendering.spec.ts b/e2e/vue-start/basic/tests/prerendering.spec.ts similarity index 90% rename from e2e/vue-start/basic-test-suite/src/prerendering.spec.ts rename to e2e/vue-start/basic/tests/prerendering.spec.ts index 66cc1f68ff..ddcf720a4c 100644 --- a/e2e/vue-start/basic-test-suite/src/prerendering.spec.ts +++ b/e2e/vue-start/basic/tests/prerendering.spec.ts @@ -3,15 +3,18 @@ import { join } from 'node:path' import { expect } from '@playwright/test' import { test } from '@tanstack/router-e2e-utils' import { isPrerender } from './utils/isPrerender' -import { getBasicAppRoot } from './utils/getBasicAppRoot' + +const distDir = join( + process.cwd(), + process.env.E2E_DIST_DIR ?? 'dist', + 'client', +) test.describe('Prerender Static Path Discovery', () => { test.skip(!isPrerender, 'Skipping since not in prerender mode') test.describe('Build Output Verification', () => { test('should automatically discover and prerender static routes', () => { // Check that static routes were automatically discovered and prerendered - const distDir = join(getBasicAppRoot(), 'dist', 'client') - // These static routes should be automatically discovered and prerendered expect(existsSync(join(distDir, 'index.html'))).toBe(true) expect(existsSync(join(distDir, 'posts/index.html'))).toBe(true) @@ -35,7 +38,6 @@ test.describe('Prerender Static Path Discovery', () => { test.describe('Static Files Verification', () => { test('should contain prerendered content in posts.html', () => { - const distDir = join(getBasicAppRoot(), 'dist', 'client') expect(existsSync(join(distDir, 'posts/index.html'))).toBe(true) // "Select a post." should be in the prerendered HTML diff --git a/e2e/vue-start/basic-test-suite/src/redirect.spec.ts b/e2e/vue-start/basic/tests/redirect.spec.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/redirect.spec.ts rename to e2e/vue-start/basic/tests/redirect.spec.ts diff --git a/e2e/vue-start/basic-test-suite/src/script-duplication.spec.ts b/e2e/vue-start/basic/tests/script-duplication.spec.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/script-duplication.spec.ts rename to e2e/vue-start/basic/tests/script-duplication.spec.ts diff --git a/e2e/vue-start/basic-test-suite/src/search-params.spec.ts b/e2e/vue-start/basic/tests/search-params.spec.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/search-params.spec.ts rename to e2e/vue-start/basic/tests/search-params.spec.ts diff --git a/e2e/vue-start/basic-test-suite/src/setup/global.setup.ts b/e2e/vue-start/basic/tests/setup/global.setup.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/setup/global.setup.ts rename to e2e/vue-start/basic/tests/setup/global.setup.ts diff --git a/e2e/vue-start/basic-test-suite/src/setup/global.teardown.ts b/e2e/vue-start/basic/tests/setup/global.teardown.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/setup/global.teardown.ts rename to e2e/vue-start/basic/tests/setup/global.teardown.ts diff --git a/e2e/vue-start/basic-test-suite/src/setup/waitForDummyServer.ts b/e2e/vue-start/basic/tests/setup/waitForDummyServer.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/setup/waitForDummyServer.ts rename to e2e/vue-start/basic/tests/setup/waitForDummyServer.ts diff --git a/e2e/vue-start/basic-test-suite/src/special-characters.spec.ts b/e2e/vue-start/basic/tests/special-characters.spec.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/special-characters.spec.ts rename to e2e/vue-start/basic/tests/special-characters.spec.ts diff --git a/e2e/vue-start/basic-test-suite/src/streaming.spec.ts b/e2e/vue-start/basic/tests/streaming.spec.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/streaming.spec.ts rename to e2e/vue-start/basic/tests/streaming.spec.ts diff --git a/e2e/vue-start/basic-test-suite/src/utils/getE2EPortKey.ts b/e2e/vue-start/basic/tests/utils/getE2EPortKey.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/utils/getE2EPortKey.ts rename to e2e/vue-start/basic/tests/utils/getE2EPortKey.ts diff --git a/e2e/vue-start/basic-test-suite/src/utils/getPackageName.ts b/e2e/vue-start/basic/tests/utils/getPackageName.ts similarity index 100% rename from e2e/vue-start/basic-test-suite/src/utils/getPackageName.ts rename to e2e/vue-start/basic/tests/utils/getPackageName.ts diff --git a/e2e/vue-start/basic/tsconfig.json b/e2e/vue-start/basic/tsconfig.json index 6f99c0f49a..1b2ac7b943 100644 --- a/e2e/vue-start/basic/tsconfig.json +++ b/e2e/vue-start/basic/tsconfig.json @@ -9,6 +9,7 @@ "moduleResolution": "Bundler", "lib": ["DOM", "DOM.Iterable", "ES2022"], "isolatedModules": true, + "allowImportingTsExtensions": true, "resolveJsonModule": true, "skipLibCheck": true, "target": "ES2022", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a1cf6a433..274c59a39d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5674,9 +5674,6 @@ importers: tailwindcss: specifier: ^4.2.2 version: 4.2.2 - tanstack-vue-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite typescript: specifier: ^6.0.2 version: 6.0.2 @@ -5797,66 +5794,6 @@ importers: specifier: ^4.74.0 version: 4.75.0 - e2e/vue-start/basic-prerender: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - tanstack-vue-start-e2e-basic: - specifier: workspace:* - version: link:../basic - tanstack-vue-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite - - e2e/vue-start/basic-preview: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - tanstack-vue-start-e2e-basic: - specifier: workspace:* - version: link:../basic - tanstack-vue-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite - - e2e/vue-start/basic-spa: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - tanstack-vue-start-e2e-basic: - specifier: workspace:* - version: link:../basic - tanstack-vue-start-e2e-basic-test-suite: - specifier: workspace:* - version: link:../basic-test-suite - - e2e/vue-start/basic-test-suite: - devDependencies: - '@playwright/test': - specifier: ^1.57.0 - version: 1.58.0 - '@tanstack/router-e2e-utils': - specifier: workspace:^ - version: link:../../e2e-utils - '@types/node': - specifier: 25.0.9 - version: 25.0.9 - combinate: - specifier: ^1.1.11 - version: 1.1.11 - e2e/vue-start/basic-tsr-config: dependencies: '@tanstack/vue-router': From de26ec1161d51405ec1fae740b906f6d02542e18 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Fri, 17 Apr 2026 11:29:02 +0200 Subject: [PATCH 7/9] docs(nx): clarify playwright mode build and preview usage --- scripts/nx/playwright-plugin.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/scripts/nx/playwright-plugin.md b/scripts/nx/playwright-plugin.md index f4f2ea54d2..7a8efac375 100644 --- a/scripts/nx/playwright-plugin.md +++ b/scripts/nx/playwright-plugin.md @@ -83,6 +83,10 @@ vite build && tsc --noEmit with the mode/toolchain env above. +This command is intentionally fixed (it does not call `pnpm build`). This avoids +accidentally selecting the wrong build path in projects that include multiple +toolchains. + The inferred build target also hashes these env vars as target inputs: - `MODE` @@ -104,6 +108,17 @@ webServer: { } ``` +For preview mode, pass the inferred dist folder: + +```ts +const distDir = process.env.E2E_DIST_DIR ?? 'dist' + +webServer: { + command: `pnpm preview --outDir ${distDir} --port ${PORT}`, + url: baseURL, +} +``` + Avoid: ```ts @@ -152,6 +167,7 @@ const e2ePortKey = process.env.E2E_PORT_KEY ?? packageJson.name if (process.env.TEST_WORKER_INDEX === undefined) { for (const portFile of [ `port-${e2ePortKey}.txt`, + `port-${e2ePortKey}_start.txt`, `port-${e2ePortKey}-external.txt`, ]) { fs.rmSync(portFile, { force: true }) @@ -159,6 +175,9 @@ if (process.env.TEST_WORKER_INDEX === undefined) { } ``` +Include the `*_start` file only if your test setup allocates a separate +`START_PORT` key. + Avoid broad cleanup such as `rm -rf port*.txt` in shared shard runs. ## 7. Run inferred targets From 01a19754cbf2acd749c0b9a9d8542caa4b43826c Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Fri, 17 Apr 2026 11:41:53 +0200 Subject: [PATCH 8/9] refactor(nx): simplify playwright plugin target generation --- scripts/nx/playwright-plugin.md | 13 +-- scripts/nx/playwright-plugin.ts | 171 +++++++++----------------------- 2 files changed, 52 insertions(+), 132 deletions(-) diff --git a/scripts/nx/playwright-plugin.md b/scripts/nx/playwright-plugin.md index 7a8efac375..a9f33bb4c8 100644 --- a/scripts/nx/playwright-plugin.md +++ b/scripts/nx/playwright-plugin.md @@ -87,15 +87,10 @@ This command is intentionally fixed (it does not call `pnpm build`). This avoids accidentally selecting the wrong build path in projects that include multiple toolchains. -The inferred build target also hashes these env vars as target inputs: - -- `MODE` -- `TOOLCHAIN` -- `E2E_TOOLCHAIN` -- `E2E_DIST` -- `E2E_DIST_DIR` - -This keeps Nx cache entries distinct when dist output folders differ by mode. +The inferred build target uses standard production inputs and explicit mode- +specific outputs (`dist--`). Mode env values are passed through +the target command env, so they do not need to be duplicated as target input +env hashes. Because of this, Playwright `webServer.command` should only start the app. diff --git a/scripts/nx/playwright-plugin.ts b/scripts/nx/playwright-plugin.ts index dd6d9a979c..464ff3ac00 100644 --- a/scripts/nx/playwright-plugin.ts +++ b/scripts/nx/playwright-plugin.ts @@ -42,8 +42,6 @@ function createNodesInternal( root, packageName, playwrightModes, - ['default', '^production'], - ['production', '^production'], ) return { @@ -73,7 +71,6 @@ function createNodesInternal( root, packageName, projectConfiguration.nx.metadata.playwrightShards, - ['default', '^production'], ) // Project configuration to be merged into the rest of the Nx configuration return { @@ -93,6 +90,11 @@ function createNodesInternal( const CI_TARGET_NAME = 'test:e2e' const MODE_TARGET_SEPARATOR = '--' +const TEST_INPUTS: TargetConfiguration['inputs'] = ['default', '^production'] +const BUILD_INPUTS: TargetConfiguration['inputs'] = [ + 'production', + '^production', +] const PLAYWRIGHT_TOOLCHAINS = ['vite'] as const const PLAYWRIGHT_MODES = ['ssr', 'spa', 'prerender', 'preview'] as const @@ -112,92 +114,6 @@ type RawPlaywrightModeMetadata = { shards?: number } -function isPlaywrightToolchain( - toolchain: string, -): toolchain is PlaywrightToolchain { - return PLAYWRIGHT_TOOLCHAINS.includes(toolchain as PlaywrightToolchain) -} - -function isPlaywrightMode(mode: string): mode is PlaywrightMode { - return PLAYWRIGHT_MODES.includes(mode as PlaywrightMode) -} - -function withEnvInputs( - inputs: TargetConfiguration['inputs'], - envNames: Array, -): TargetConfiguration['inputs'] { - const baseInputs = inputs ?? [] - - return [...baseInputs, ...envNames.map((env) => ({ env }))] -} - -function getDistDirName({ toolchain, mode }: PlaywrightModeMetadata): string { - return `dist-${toolchain}-${mode}` -} - -function getBuildTargetName({ - toolchain, - mode, -}: PlaywrightModeMetadata): string { - return `build:e2e--${toolchain}-${mode}` -} - -function getModeTargetName({ - toolchain, - mode, -}: PlaywrightModeMetadata): string { - return `${CI_TARGET_NAME}--${toolchain}-${mode}` -} - -function getModeEnv( - modeMetadata: PlaywrightModeMetadata, -): Record { - const distDir = getDistDirName(modeMetadata) - - return { - MODE: modeMetadata.mode, - TOOLCHAIN: modeMetadata.toolchain, - E2E_TOOLCHAIN: modeMetadata.toolchain, - E2E_DIST: distDir, - E2E_DIST_DIR: distDir, - } -} - -function getModePortKey( - packageName: string, - modeMetadata: PlaywrightModeMetadata, - shardIndex?: number, - shardCount?: number, -): string { - const modePortKey = `${packageName}-${modeMetadata.toolchain}-${modeMetadata.mode}` - - if (shardIndex === undefined || shardCount === undefined) { - return modePortKey - } - - return `${modePortKey}-shard-${shardIndex}-of-${shardCount}` -} - -function normalizeShardCount( - shards: number | undefined, - targetName: string, -): number { - if (shards === undefined) { - return 1 - } - - const shardCount = Number(shards) - - if (!Number.isInteger(shardCount) || shardCount < 1) { - throw new Error( - `[Playwright Sharding Plugin] Invalid shard count for ${targetName}: ${shards}. ` + - `Expected a positive integer.`, - ) - } - - return shardCount -} - function validateModeMetadata( modeMetadata: RawPlaywrightModeMetadata, index: number, @@ -211,25 +127,38 @@ function validateModeMetadata( ) } - if (!isPlaywrightToolchain(modeMetadata.toolchain)) { + if ( + !PLAYWRIGHT_TOOLCHAINS.includes( + modeMetadata.toolchain as PlaywrightToolchain, + ) + ) { throw new Error( `[Playwright Sharding Plugin] Invalid toolchain for playwrightModes[${index}]: ` + `${modeMetadata.toolchain}. Supported toolchains: ${PLAYWRIGHT_TOOLCHAINS.join(', ')}.`, ) } - if (!isPlaywrightMode(modeMetadata.mode)) { + if (!PLAYWRIGHT_MODES.includes(modeMetadata.mode as PlaywrightMode)) { throw new Error( `[Playwright Sharding Plugin] Invalid mode for playwrightModes[${index}]: ` + `${modeMetadata.mode}. Supported modes: ${PLAYWRIGHT_MODES.join(', ')}.`, ) } - normalizeShardCount(modeMetadata.shards, targetName) + if (modeMetadata.shards !== undefined) { + const shardCount = Number(modeMetadata.shards) + + if (!Number.isInteger(shardCount) || shardCount < 1) { + throw new Error( + `[Playwright Sharding Plugin] Invalid shard count for ${targetName}: ${modeMetadata.shards}. ` + + `Expected a positive integer.`, + ) + } + } return { - toolchain: modeMetadata.toolchain, - mode: modeMetadata.mode, + toolchain: modeMetadata.toolchain as PlaywrightToolchain, + mode: modeMetadata.mode as PlaywrightMode, shards: modeMetadata.shards, } } @@ -238,8 +167,6 @@ function buildModeTargets( projectRoot: string, packageName: string, modes: Array, - testInputs: TargetConfiguration['inputs'], - buildInputs: TargetConfiguration['inputs'], ): { targets: Record targetGroupEntries: Array @@ -254,11 +181,18 @@ function buildModeTargets( for (const [index, rawModeMetadata] of modes.entries()) { const modeMetadata = validateModeMetadata(rawModeMetadata, index) - const modeTargetName = getModeTargetName(modeMetadata) - const buildTargetName = getBuildTargetName(modeMetadata) - const shardCount = normalizeShardCount(modeMetadata.shards, modeTargetName) - const modeEnv = getModeEnv(modeMetadata) - const modePortKey = getModePortKey(packageName, modeMetadata) + const modeTargetName = `${CI_TARGET_NAME}--${modeMetadata.toolchain}-${modeMetadata.mode}` + const buildTargetName = `build:e2e--${modeMetadata.toolchain}-${modeMetadata.mode}` + const shardCount = modeMetadata.shards ?? 1 + const distDir = `dist-${modeMetadata.toolchain}-${modeMetadata.mode}` + const modeEnv = { + MODE: modeMetadata.mode, + TOOLCHAIN: modeMetadata.toolchain, + E2E_TOOLCHAIN: modeMetadata.toolchain, + E2E_DIST: distDir, + E2E_DIST_DIR: distDir, + } + const modePortKey = `${packageName}-${modeMetadata.toolchain}-${modeMetadata.mode}` const modeDescription = `${modeMetadata.toolchain}/${modeMetadata.mode}` const modeShardTargets: Array = [] @@ -271,20 +205,15 @@ function buildModeTargets( }, parallelism: false, cache: true, - inputs: withEnvInputs(buildInputs, [ - 'MODE', - 'TOOLCHAIN', - 'E2E_TOOLCHAIN', - 'E2E_DIST', - 'E2E_DIST_DIR', - ]), + inputs: BUILD_INPUTS, dependsOn: ['^build'], - outputs: [`{projectRoot}/${getDistDirName(modeMetadata)}`], + outputs: [`{projectRoot}/${distDir}`], metadata: { technologies: ['playwright'], description: `Build artifacts for ${modeDescription} e2e tests`, }, } + targetGroup.push(buildTargetName) if (shardCount === 1) { targets[modeTargetName] = { @@ -298,22 +227,18 @@ function buildModeTargets( }, }, cache: true, - inputs: testInputs, + inputs: TEST_INPUTS, dependsOn: [buildTargetName], metadata: { technologies: ['playwright'], description: `Run Playwright tests for ${modeDescription}`, }, } + targetGroup.push(modeTargetName) } else { for (let shardIndex = 1; shardIndex <= shardCount; shardIndex++) { const shardTargetName = `${modeTargetName}${MODE_TARGET_SEPARATOR}shard-${shardIndex}-of-${shardCount}` - const shardPortKey = getModePortKey( - packageName, - modeMetadata, - shardIndex, - shardCount, - ) + const shardPortKey = `${modePortKey}-shard-${shardIndex}-of-${shardCount}` targets[shardTargetName] = { executor: 'nx:run-commands', @@ -326,7 +251,7 @@ function buildModeTargets( }, }, cache: true, - inputs: testInputs, + inputs: TEST_INPUTS, dependsOn: [buildTargetName], metadata: { technologies: ['playwright'], @@ -343,12 +268,13 @@ function buildModeTargets( } modeShardTargets.push(shardTargetName) + targetGroup.push(shardTargetName) } targets[modeTargetName] = { executor: 'nx:noop', cache: true, - inputs: testInputs, + inputs: TEST_INPUTS, dependsOn: modeShardTargets.map((shardTargetName) => ({ target: shardTargetName, projects: 'self' as const, @@ -359,6 +285,7 @@ function buildModeTargets( description: `Run all Playwright ${modeDescription} shards (${shardCount} shards)`, }, } + targetGroup.push(modeTargetName) } ciDependsOnTargets.push({ @@ -366,13 +293,12 @@ function buildModeTargets( projects: 'self', params: 'forward', }) - targetGroup.push(modeTargetName) } targets[CI_TARGET_NAME] = { executor: 'nx:noop', cache: true, - inputs: testInputs, + inputs: TEST_INPUTS, dependsOn: ciDependsOnTargets, metadata: { technologies: ['playwright'], @@ -390,7 +316,6 @@ function buildShardedTargets( projectRoot: string, packageName: string, shardCount: number, - testInputs: TargetConfiguration['inputs'], ): { targets: Record targetGroupEntries: Array @@ -413,7 +338,7 @@ function buildShardedTargets( }, }, cache: true, - inputs: testInputs, + inputs: TEST_INPUTS, dependsOn: [`^build`, 'build'], metadata: { technologies: ['playwright'], @@ -436,7 +361,7 @@ function buildShardedTargets( targets[CI_TARGET_NAME] = { executor: 'nx:noop', cache: true, - inputs: testInputs, + inputs: TEST_INPUTS, dependsOn: targetGroup.map((shardTargetName) => ({ target: shardTargetName, projects: 'self' as const, From bd6fd04d7f519f67d7d79d0a5469e01568ca17e6 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Sat, 18 Apr 2026 22:02:47 +0200 Subject: [PATCH 9/9] remover shards --- e2e/react-start/basic/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/e2e/react-start/basic/package.json b/e2e/react-start/basic/package.json index ddb9d962bc..6646dc03a3 100644 --- a/e2e/react-start/basic/package.json +++ b/e2e/react-start/basic/package.json @@ -46,8 +46,7 @@ "playwrightModes": [ { "toolchain": "vite", - "mode": "ssr", - "shards": 4 + "mode": "ssr" }, { "toolchain": "vite",