diff --git a/e2e/react-router/basic-esbuild-file-based/package.json b/e2e/react-router/basic-esbuild-file-based/package.json index efb145fa32..d8b80dcb33 100644 --- a/e2e/react-router/basic-esbuild-file-based/package.json +++ b/e2e/react-router/basic-esbuild-file-based/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "esbuild src/main.tsx --serve=3001 --bundle --outfile=dist/main.js --watch --servedir=.", + "dev": "esbuild src/main.tsx --serve=3090 --bundle --outfile=dist/main.js --watch --servedir=.", "build": "esbuild src/main.tsx --bundle --outfile=dist/main.js", "serve": "esbuild src/main.tsx --bundle --outfile=dist/main.js --servedir=.", "start": "dev", diff --git a/e2e/react-router/basic-esbuild-file-based/playwright.config.ts b/e2e/react-router/basic-esbuild-file-based/playwright.config.ts index f83eacf129..942acab61b 100644 --- a/e2e/react-router/basic-esbuild-file-based/playwright.config.ts +++ b/e2e/react-router/basic-esbuild-file-based/playwright.config.ts @@ -10,12 +10,12 @@ export default defineConfig({ use: { /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3001/', + baseURL: 'http://localhost:3090/', }, webServer: { command: 'pnpm run dev', - url: 'http://localhost:3001', + url: 'http://localhost:3090', reuseExistingServer: !process.env.CI, stdout: 'pipe', }, diff --git a/e2e/react-router/basic-file-based-code-splitting/package.json b/e2e/react-router/basic-file-based-code-splitting/package.json index fbdbada18b..8fdd690222 100644 --- a/e2e/react-router/basic-file-based-code-splitting/package.json +++ b/e2e/react-router/basic-file-based-code-splitting/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3001", + "dev": "vite --port 3000", + "dev:e2e": "vite", "build": "vite build && tsc --noEmit", "serve": "vite preview", "start": "vite", @@ -22,6 +23,9 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", - "vite": "^5.4.11" + "get-port-please": "^3.1.2", + "terminate": "^2.8.0", + "vite": "^5.4.11", + "wait-port": "^1.1.0" } } diff --git a/e2e/react-router/basic-file-based-code-splitting/playwright.config.ts b/e2e/react-router/basic-file-based-code-splitting/playwright.config.ts index f83eacf129..9b8de920f8 100644 --- a/e2e/react-router/basic-file-based-code-splitting/playwright.config.ts +++ b/e2e/react-router/basic-file-based-code-splitting/playwright.config.ts @@ -8,17 +8,17 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3001/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3001/', + // }, - webServer: { - command: 'pnpm run dev', - url: 'http://localhost:3001', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // command: 'pnpm run dev', + // url: 'http://localhost:3001', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/react-router/basic-file-based-code-splitting/tests/app.spec.ts b/e2e/react-router/basic-file-based-code-splitting/tests/app.spec.ts index f19a88b569..6be50ac079 100644 --- a/e2e/react-router/basic-file-based-code-splitting/tests/app.spec.ts +++ b/e2e/react-router/basic-file-based-code-splitting/tests/app.spec.ts @@ -1,7 +1,12 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' -test.beforeEach(async ({ page }) => { - await page.goto('/') +test.beforeEach(async ({ page, setupApp }) => { + await page.goto(setupApp.ADDR + '/') +}) + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() }) test('Navigating to a post page', async ({ page }) => { @@ -11,7 +16,6 @@ test('Navigating to a post page', async ({ page }) => { }) test('Navigating nested layouts', async ({ page }) => { - await page.goto('/') await page.getByRole('link', { name: 'Layout', exact: true }).click() await expect(page.locator('#app')).toContainText("I'm a layout") diff --git a/e2e/react-router/basic-file-based-code-splitting/tests/preload.test.ts b/e2e/react-router/basic-file-based-code-splitting/tests/preload.spec.ts similarity index 66% rename from e2e/react-router/basic-file-based-code-splitting/tests/preload.test.ts rename to e2e/react-router/basic-file-based-code-splitting/tests/preload.spec.ts index e1334df0d2..05def2566c 100644 --- a/e2e/react-router/basic-file-based-code-splitting/tests/preload.test.ts +++ b/e2e/react-router/basic-file-based-code-splitting/tests/preload.spec.ts @@ -1,7 +1,12 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' -test.beforeEach(async ({ page }) => { - await page.goto('/') +test.beforeEach(async ({ page, setupApp }) => { + await page.goto(setupApp.ADDR + '/') +}) + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() }) test('hovering a link with preload=intent to a route without a loader should preload route', async ({ diff --git a/e2e/react-router/basic-file-based-code-splitting/tests/utils.ts b/e2e/react-router/basic-file-based-code-splitting/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/react-router/basic-file-based-code-splitting/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/react-router/basic-file-based/package.json b/e2e/react-router/basic-file-based/package.json index 40d2ebb582..26ea8d4301 100644 --- a/e2e/react-router/basic-file-based/package.json +++ b/e2e/react-router/basic-file-based/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3001", + "dev": "vite --port 3000", + "dev:e2e": "vite", "build": "vite build && tsc --noEmit", "serve": "vite preview", "start": "vite", @@ -24,6 +25,9 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", - "vite": "^5.4.11" + "get-port-please": "^3.1.2", + "terminate": "^2.8.0", + "vite": "^5.4.11", + "wait-port": "^1.1.0" } } diff --git a/e2e/react-router/basic-file-based/playwright.config.ts b/e2e/react-router/basic-file-based/playwright.config.ts index f83eacf129..9b8de920f8 100644 --- a/e2e/react-router/basic-file-based/playwright.config.ts +++ b/e2e/react-router/basic-file-based/playwright.config.ts @@ -8,17 +8,17 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3001/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3001/', + // }, - webServer: { - command: 'pnpm run dev', - url: 'http://localhost:3001', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // command: 'pnpm run dev', + // url: 'http://localhost:3001', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/react-router/basic-file-based/tests/app.spec.ts b/e2e/react-router/basic-file-based/tests/app.spec.ts index de29539c67..1de6b52526 100644 --- a/e2e/react-router/basic-file-based/tests/app.spec.ts +++ b/e2e/react-router/basic-file-based/tests/app.spec.ts @@ -1,8 +1,14 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' import type { Page } from '@playwright/test' -test.beforeEach(async ({ page }) => { - await page.goto('/') +test.beforeEach(async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') +}) + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() }) test('Navigating to a post page', async ({ page }) => { @@ -12,7 +18,6 @@ test('Navigating to a post page', async ({ page }) => { }) test('Navigating nested layouts', async ({ page }) => { - await page.goto('/') await page.getByRole('link', { name: 'Layout', exact: true }).click() await expect(page.locator('#app')).toContainText("I'm a layout") @@ -72,9 +77,10 @@ testCases.forEach(({ description, testId }) => { }) }) -test('navigating to an unnested route', async ({ page }) => { +test('navigating to an unnested route', async ({ page, setupApp }) => { + const { ADDR } = setupApp const postId = 'hello-world' - page.goto(`/posts/${postId}/edit`) + page.goto(ADDR + `/posts/${postId}/edit`) await expect(page.getByTestId('params-via-hook')).toContainText(postId) await expect(page.getByTestId('params-via-route-hook')).toContainText(postId) await expect(page.getByTestId('params-via-route-api')).toContainText(postId) @@ -86,8 +92,12 @@ async function getRenderCount(page: Page) { ) return renderCount } -async function structuralSharingTest(page: Page, enabled: boolean) { - page.goto(`/structural-sharing/${enabled}/?foo=f1&bar=b1`) +async function structuralSharingTest( + page: Page, + baseUrl: string, + enabled: boolean, +) { + page.goto(baseUrl + `/structural-sharing/${enabled}/?foo=f1&bar=b1`) await expect(page.getByTestId('enabled')).toHaveText(JSON.stringify(enabled)) async function checkSearch({ foo, bar }: { foo: string; bar: string }) { @@ -107,13 +117,13 @@ async function structuralSharingTest(page: Page, enabled: boolean) { await checkSearch({ bar: 'b2', foo: 'f2' }) } -test('structural sharing disabled', async ({ page }) => { - await structuralSharingTest(page, false) +test('structural sharing disabled', async ({ page, setupApp }) => { + await structuralSharingTest(page, setupApp.ADDR, false) expect(await getRenderCount(page)).toBeGreaterThan(2) }) -test('structural sharing enabled', async ({ page }) => { - await structuralSharingTest(page, true) +test('structural sharing enabled', async ({ page, setupApp }) => { + await structuralSharingTest(page, setupApp.ADDR, true) expect(await getRenderCount(page)).toBe(2) await page.getByTestId('link').click() expect(await getRenderCount(page)).toBe(2) diff --git a/e2e/react-router/basic-file-based/tests/utils.ts b/e2e/react-router/basic-file-based/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/react-router/basic-file-based/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/react-router/basic-react-query-file-based/package.json b/e2e/react-router/basic-react-query-file-based/package.json index 5b9590dae6..93ecc52637 100644 --- a/e2e/react-router/basic-react-query-file-based/package.json +++ b/e2e/react-router/basic-react-query-file-based/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3001", + "dev": "vite --port 3000", + "dev:e2e": "vite", "build": "vite build && tsc --noEmit", "serve": "vite preview", "start": "vite", @@ -25,6 +26,9 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", - "vite": "^5.4.11" + "get-port-please": "^3.1.2", + "terminate": "^2.8.0", + "vite": "^5.4.11", + "wait-port": "^1.1.0" } } diff --git a/e2e/react-router/basic-react-query-file-based/playwright.config.ts b/e2e/react-router/basic-react-query-file-based/playwright.config.ts index f83eacf129..9b8de920f8 100644 --- a/e2e/react-router/basic-react-query-file-based/playwright.config.ts +++ b/e2e/react-router/basic-react-query-file-based/playwright.config.ts @@ -8,17 +8,17 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3001/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3001/', + // }, - webServer: { - command: 'pnpm run dev', - url: 'http://localhost:3001', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // command: 'pnpm run dev', + // url: 'http://localhost:3001', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/react-router/basic-react-query-file-based/tests/app.spec.ts b/e2e/react-router/basic-react-query-file-based/tests/app.spec.ts index f19a88b569..6be50ac079 100644 --- a/e2e/react-router/basic-react-query-file-based/tests/app.spec.ts +++ b/e2e/react-router/basic-react-query-file-based/tests/app.spec.ts @@ -1,7 +1,12 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' -test.beforeEach(async ({ page }) => { - await page.goto('/') +test.beforeEach(async ({ page, setupApp }) => { + await page.goto(setupApp.ADDR + '/') +}) + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() }) test('Navigating to a post page', async ({ page }) => { @@ -11,7 +16,6 @@ test('Navigating to a post page', async ({ page }) => { }) test('Navigating nested layouts', async ({ page }) => { - await page.goto('/') await page.getByRole('link', { name: 'Layout', exact: true }).click() await expect(page.locator('#app')).toContainText("I'm a layout") diff --git a/e2e/react-router/basic-react-query-file-based/tests/utils.ts b/e2e/react-router/basic-react-query-file-based/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/react-router/basic-react-query-file-based/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/react-router/basic-react-query/package.json b/e2e/react-router/basic-react-query/package.json index 99a6f1683f..3c998230ae 100644 --- a/e2e/react-router/basic-react-query/package.json +++ b/e2e/react-router/basic-react-query/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3001", + "dev": "vite --port 3000", + "dev:e2e": "vite", "build": "vite build && tsc --noEmit", "serve": "vite preview", "start": "vite", @@ -23,6 +24,9 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", - "vite": "^5.4.11" + "get-port-please": "^3.1.2", + "terminate": "^2.8.0", + "vite": "^5.4.11", + "wait-port": "^1.1.0" } } diff --git a/e2e/react-router/basic-react-query/playwright.config.ts b/e2e/react-router/basic-react-query/playwright.config.ts index f83eacf129..9b8de920f8 100644 --- a/e2e/react-router/basic-react-query/playwright.config.ts +++ b/e2e/react-router/basic-react-query/playwright.config.ts @@ -8,17 +8,17 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3001/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3001/', + // }, - webServer: { - command: 'pnpm run dev', - url: 'http://localhost:3001', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // command: 'pnpm run dev', + // url: 'http://localhost:3001', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/react-router/basic-react-query/tests/app.spec.ts b/e2e/react-router/basic-react-query/tests/app.spec.ts index f19a88b569..6be50ac079 100644 --- a/e2e/react-router/basic-react-query/tests/app.spec.ts +++ b/e2e/react-router/basic-react-query/tests/app.spec.ts @@ -1,7 +1,12 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' -test.beforeEach(async ({ page }) => { - await page.goto('/') +test.beforeEach(async ({ page, setupApp }) => { + await page.goto(setupApp.ADDR + '/') +}) + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() }) test('Navigating to a post page', async ({ page }) => { @@ -11,7 +16,6 @@ test('Navigating to a post page', async ({ page }) => { }) test('Navigating nested layouts', async ({ page }) => { - await page.goto('/') await page.getByRole('link', { name: 'Layout', exact: true }).click() await expect(page.locator('#app')).toContainText("I'm a layout") diff --git a/e2e/react-router/basic-react-query/tests/utils.ts b/e2e/react-router/basic-react-query/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/react-router/basic-react-query/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/react-router/basic-scroll-restoration/package.json b/e2e/react-router/basic-scroll-restoration/package.json index 64d199dc92..3f6d616fde 100644 --- a/e2e/react-router/basic-scroll-restoration/package.json +++ b/e2e/react-router/basic-scroll-restoration/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3001", + "dev": "vite --port 3000", + "dev:e2e": "vite", "build": "vite build && tsc --noEmit", "serve": "vite preview", "start": "vite", @@ -11,8 +12,8 @@ }, "dependencies": { "@tanstack/react-router": "workspace:^", - "@tanstack/router-devtools": "workspace:^", "@tanstack/react-virtual": "^3.10.9", + "@tanstack/router-devtools": "workspace:^", "react": "^18.2.0", "react-dom": "^18.2.0", "redaxios": "^0.5.1" @@ -22,6 +23,9 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", - "vite": "^5.4.11" + "get-port-please": "^3.1.2", + "terminate": "^2.8.0", + "vite": "^5.4.11", + "wait-port": "^1.1.0" } } diff --git a/e2e/react-router/basic-scroll-restoration/playwright.config.ts b/e2e/react-router/basic-scroll-restoration/playwright.config.ts index f83eacf129..9b8de920f8 100644 --- a/e2e/react-router/basic-scroll-restoration/playwright.config.ts +++ b/e2e/react-router/basic-scroll-restoration/playwright.config.ts @@ -8,17 +8,17 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3001/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3001/', + // }, - webServer: { - command: 'pnpm run dev', - url: 'http://localhost:3001', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // command: 'pnpm run dev', + // url: 'http://localhost:3001', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/react-router/basic-scroll-restoration/tests/app.spec.ts b/e2e/react-router/basic-scroll-restoration/tests/app.spec.ts index 155f95ca30..d49e1689ae 100644 --- a/e2e/react-router/basic-scroll-restoration/tests/app.spec.ts +++ b/e2e/react-router/basic-scroll-restoration/tests/app.spec.ts @@ -1,10 +1,16 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() +}) test('restore scroll positions by page, home pages top message should not display "shown" on navigating back', async ({ page, + setupApp: { ADDR }, }) => { // Step 1: Navigate to the home page - await page.goto('/') + await page.goto(ADDR + '/') await expect(page.locator('#greeting')).toContainText('Welcome Home!') await expect(page.locator('#top-message')).toContainText('shown') @@ -39,9 +45,10 @@ test('restore scroll positions by page, home pages top message should not displa test('restore scroll positions by element, first regular list item should not display "shown" on navigating back', async ({ page, + setupApp: { ADDR }, }) => { // Step 1: Navigate to the by-element page - await page.goto('/by-element') + await page.goto(ADDR + '/by-element') // Step 2: Scroll to a position that hides the first list item in regular list const targetScrollPosition = 1000 diff --git a/e2e/react-router/basic-scroll-restoration/tests/router-events.spec.ts b/e2e/react-router/basic-scroll-restoration/tests/router-events.spec.ts index 2116c31be4..81d2b53663 100644 --- a/e2e/react-router/basic-scroll-restoration/tests/router-events.spec.ts +++ b/e2e/react-router/basic-scroll-restoration/tests/router-events.spec.ts @@ -1,10 +1,16 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() +}) test('after a navigation, should have emitted "onBeforeRouteMount","onResolved" and useLayoutEffect setup in the correct order', async ({ page, + setupApp: { ADDR }, }) => { // Navigate to the Home page - await page.goto('/') + await page.goto(ADDR + '/') await expect(page.locator('#greeting')).toContainText('Welcome Home!') let orders = await page.evaluate(() => window.invokeOrders) @@ -28,7 +34,11 @@ test('after a navigation, should have emitted "onBeforeRouteMount","onResolved" expectItemOrder(orders, 'onBeforeRouteMount', 'about-useLayoutEffect') }) -function expectItemOrder(array: T[], firstItem: T, secondItem: T) { +function expectItemOrder( + array: Array, + firstItem: TItem, + secondItem: TItem, +) { const firstIndex = array.findIndex((item) => item === firstItem) const secondIndex = array.findIndex((item) => item === secondItem) diff --git a/e2e/react-router/basic-scroll-restoration/tests/utils.ts b/e2e/react-router/basic-scroll-restoration/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/react-router/basic-scroll-restoration/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/react-router/basic-virtual-file-based/package.json b/e2e/react-router/basic-virtual-file-based/package.json index 27e37daede..7f500a206c 100644 --- a/e2e/react-router/basic-virtual-file-based/package.json +++ b/e2e/react-router/basic-virtual-file-based/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3001", + "dev": "vite --port 3000", + "dev:e2e": "vite", "build": "vite build && tsc --noEmit", "serve": "vite preview", "start": "vite", @@ -24,6 +25,9 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", - "vite": "^5.4.11" + "get-port-please": "^3.1.2", + "terminate": "^2.8.0", + "vite": "^5.4.11", + "wait-port": "^1.1.0" } } diff --git a/e2e/react-router/basic-virtual-file-based/playwright.config.ts b/e2e/react-router/basic-virtual-file-based/playwright.config.ts index f83eacf129..9b8de920f8 100644 --- a/e2e/react-router/basic-virtual-file-based/playwright.config.ts +++ b/e2e/react-router/basic-virtual-file-based/playwright.config.ts @@ -8,17 +8,17 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3001/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3001/', + // }, - webServer: { - command: 'pnpm run dev', - url: 'http://localhost:3001', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // command: 'pnpm run dev', + // url: 'http://localhost:3001', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/react-router/basic-virtual-file-based/tests/app.spec.ts b/e2e/react-router/basic-virtual-file-based/tests/app.spec.ts index f19a88b569..2c75a8fe7a 100644 --- a/e2e/react-router/basic-virtual-file-based/tests/app.spec.ts +++ b/e2e/react-router/basic-virtual-file-based/tests/app.spec.ts @@ -1,7 +1,12 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' -test.beforeEach(async ({ page }) => { - await page.goto('/') +test.beforeEach(async ({ page, setupApp: { ADDR } }) => { + await page.goto(ADDR + '/') +}) + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() }) test('Navigating to a post page', async ({ page }) => { @@ -11,7 +16,6 @@ test('Navigating to a post page', async ({ page }) => { }) test('Navigating nested layouts', async ({ page }) => { - await page.goto('/') await page.getByRole('link', { name: 'Layout', exact: true }).click() await expect(page.locator('#app')).toContainText("I'm a layout") diff --git a/e2e/react-router/basic-virtual-file-based/tests/utils.ts b/e2e/react-router/basic-virtual-file-based/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/react-router/basic-virtual-file-based/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/react-router/basic/package.json b/e2e/react-router/basic/package.json index a0fadf5782..cff39d812e 100644 --- a/e2e/react-router/basic/package.json +++ b/e2e/react-router/basic/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3001", + "dev": "vite --port 3000", + "dev:e2e": "vite", "build": "vite build && tsc --noEmit", "serve": "vite preview", "start": "vite", @@ -21,6 +22,9 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", - "vite": "^5.4.11" + "get-port-please": "^3.1.2", + "terminate": "^2.8.0", + "vite": "^5.4.11", + "wait-port": "^1.1.0" } } diff --git a/e2e/react-router/basic/playwright.config.ts b/e2e/react-router/basic/playwright.config.ts index f83eacf129..9b8de920f8 100644 --- a/e2e/react-router/basic/playwright.config.ts +++ b/e2e/react-router/basic/playwright.config.ts @@ -8,17 +8,17 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3001/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3001/', + // }, - webServer: { - command: 'pnpm run dev', - url: 'http://localhost:3001', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // command: 'pnpm run dev', + // url: 'http://localhost:3001', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/react-router/basic/tests/app.spec.ts b/e2e/react-router/basic/tests/app.spec.ts index 0c04b2b7ac..8aedde6c06 100644 --- a/e2e/react-router/basic/tests/app.spec.ts +++ b/e2e/react-router/basic/tests/app.spec.ts @@ -1,7 +1,13 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' -test.beforeEach(async ({ page }) => { - await page.goto('/') +test.beforeEach(async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') +}) + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() }) test('Navigating to a post page', async ({ page }) => { @@ -11,7 +17,6 @@ test('Navigating to a post page', async ({ page }) => { }) test('Navigating nested layouts', async ({ page }) => { - await page.goto('/') await page.getByRole('link', { name: 'Layout', exact: true }).click() await expect(page.locator('#app')).toContainText("I'm a layout") diff --git a/e2e/react-router/basic/tests/utils.ts b/e2e/react-router/basic/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/react-router/basic/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/react-router/scroll-restoration-sandbox-vite/package.json b/e2e/react-router/scroll-restoration-sandbox-vite/package.json index 3f39141e5a..5147a8f04e 100644 --- a/e2e/react-router/scroll-restoration-sandbox-vite/package.json +++ b/e2e/react-router/scroll-restoration-sandbox-vite/package.json @@ -3,7 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3001", + "dev": "vite --port 3000", + "dev:e2e": "vite", "build": "vite build", "serve": "vite preview", "start": "vite", @@ -24,6 +25,9 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", - "vite": "^5.4.11" + "get-port-please": "^3.1.2", + "terminate": "^2.8.0", + "vite": "^5.4.11", + "wait-port": "^1.1.0" } } diff --git a/e2e/react-router/scroll-restoration-sandbox-vite/playwright.config.ts b/e2e/react-router/scroll-restoration-sandbox-vite/playwright.config.ts index 9480b1bf72..1f3667ddcc 100644 --- a/e2e/react-router/scroll-restoration-sandbox-vite/playwright.config.ts +++ b/e2e/react-router/scroll-restoration-sandbox-vite/playwright.config.ts @@ -8,18 +8,18 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3001/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3001/', + // }, - webServer: { - // TODO: build && start seems broken, use that if it's working - command: 'pnpm run dev', - url: 'http://localhost:3001', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // // TODO: build && start seems broken, use that if it's working + // command: 'pnpm run dev', + // url: 'http://localhost:3001', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/react-router/scroll-restoration-sandbox-vite/tests/app.spec.ts b/e2e/react-router/scroll-restoration-sandbox-vite/tests/app.spec.ts index deb4dd9fa6..84b5f73cd2 100644 --- a/e2e/react-router/scroll-restoration-sandbox-vite/tests/app.spec.ts +++ b/e2e/react-router/scroll-restoration-sandbox-vite/tests/app.spec.ts @@ -1,8 +1,13 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' import { linkOptions } from '@tanstack/react-router' +import { test } from './utils' -test('Smoke - Renders home', async ({ page }) => { - await page.goto('/') +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() +}) + +test('Smoke - Renders home', async ({ page, setupApp: { ADDR } }) => { + await page.goto(ADDR + '/') await expect( page.getByRole('heading', { name: 'Welcome Home!' }), ).toBeVisible() @@ -18,8 +23,9 @@ test('Smoke - Renders home', async ({ page }) => { ].forEach((options) => { test(`On navigate to ${options.to} (from the header), scroll should be at top`, async ({ page, + setupApp: { ADDR }, }) => { - await page.goto('/') + await page.goto(ADDR + '/') await page.getByRole('link', { name: `Head-${options.to}` }).click() await expect(page.getByTestId('at-the-top')).toBeInViewport() }) @@ -27,8 +33,9 @@ test('Smoke - Renders home', async ({ page }) => { // scroll should be at the bottom on navigation after the page is loaded test(`On navigate via index page tests to ${options.to}, scroll should resolve at the bottom`, async ({ page, + setupApp: { ADDR }, }) => { - await page.goto('/') + await page.goto(ADDR + '/') await page .getByRole('link', { name: `${options.to}#at-the-bottom` }) .click() @@ -38,12 +45,13 @@ test('Smoke - Renders home', async ({ page }) => { // scroll should be at the bottom on first load test(`On first load of ${options.to}, scroll should resolve resolve at the bottom`, async ({ page, + setupApp: { ADDR }, }) => { let url: string = options.to if ('search' in options) { url = `${url}?where=${options.search}` } - await page.goto(`${url}#at-the-bottom`) + await page.goto(ADDR + `${url}#at-the-bottom`) await expect(page.getByTestId('at-the-bottom')).toBeInViewport() }) }) diff --git a/e2e/react-router/scroll-restoration-sandbox-vite/tests/utils.ts b/e2e/react-router/scroll-restoration-sandbox-vite/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/react-router/scroll-restoration-sandbox-vite/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/start/basic-auth/package.json b/e2e/start/basic-auth/package.json index 86f7bbacf3..dcbf2023a9 100644 --- a/e2e/start/basic-auth/package.json +++ b/e2e/start/basic-auth/package.json @@ -4,7 +4,8 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vinxi dev --port 3002", + "dev": "vinxi dev --port 3000", + "dev:e2e": "vinxi dev", "build": "vinxi build", "start": "vinxi start", "prisma-generate": "prisma generate", @@ -31,9 +32,12 @@ "@types/react-dom": "^18.2.21", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", + "get-port-please": "^3.1.2", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", + "terminate": "^2.8.0", "typescript": "^5.7.2", - "vite-tsconfig-paths": "^5.1.3" + "vite-tsconfig-paths": "^5.1.3", + "wait-port": "^1.1.0" } } diff --git a/e2e/start/basic-auth/playwright.config.ts b/e2e/start/basic-auth/playwright.config.ts index 39b002e974..e83a8c3fd9 100644 --- a/e2e/start/basic-auth/playwright.config.ts +++ b/e2e/start/basic-auth/playwright.config.ts @@ -12,18 +12,18 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3002/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3002/', + // }, - webServer: { - // TODO: build && start seems broken, use that if it's working - command: 'pnpm dev', - url: 'http://localhost:3002', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // // TODO: build && start seems broken, use that if it's working + // command: 'pnpm dev', + // url: 'http://localhost:3002', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/start/basic-auth/tests/app.spec.ts b/e2e/start/basic-auth/tests/app.spec.ts index 1adf77ddc3..d0b1947054 100644 --- a/e2e/start/basic-auth/tests/app.spec.ts +++ b/e2e/start/basic-auth/tests/app.spec.ts @@ -1,15 +1,26 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' import type { Page } from '@playwright/test' -async function signup(page: Page, email: string, password: string) { - await page.goto('/signup') +async function signup( + page: Page, + baseUrl: string, + email: string, + password: string, +) { + await page.goto(baseUrl + '/signup') await page.fill('input[name="email"]', email) await page.fill('input[name="password"]', password) await page.click('button[type="submit"]') } +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() +}) + async function login( page: Page, + baseUrl: string, email: string, password: string, signupOnFail = false, @@ -26,28 +37,31 @@ async function login( } } -test('Posts redirects to login when not authenticated', async ({ page }) => { - await page.goto('/posts') +test('Posts redirects to login when not authenticated', async ({ + page, + setupApp, +}) => { + await page.goto(setupApp.ADDR + '/posts') await expect(page.locator('h1')).toContainText('Login') }) -test('Login fails with user not found', async ({ page }) => { - await login(page, 'bad@gmail.com', 'badpassword') +test('Login fails with user not found', async ({ page, setupApp }) => { + await login(page, setupApp.ADDR, 'bad@gmail.com', 'badpassword') expect(page.getByText('User not found')).toBeTruthy() }) -test('Login fails with incorrect password', async ({ page }) => { - await signup(page, 'test@gmail.com', 'badpassword') +test('Login fails with incorrect password', async ({ page, setupApp }) => { + await signup(page, setupApp.ADDR, 'test@gmail.com', 'badpassword') expect(page.getByText('Incorrect password')).toBeTruthy() }) -test('Can sign up from a not found user', async ({ page }) => { - await login(page, 'test2@gmail.com', 'badpassword', true) +test('Can sign up from a not found user', async ({ page, setupApp }) => { + await login(page, setupApp.ADDR, 'test2@gmail.com', 'badpassword', true) expect(page.getByText('test@gmail.com')).toBeTruthy() }) -test('Navigating to post after logging in', async ({ page }) => { - await login(page, 'test@gmail.com', 'test') +test('Navigating to post after logging in', async ({ page, setupApp }) => { + await login(page, setupApp.ADDR, 'test@gmail.com', 'test') await new Promise((r) => setTimeout(r, 1000)) await page.getByRole('link', { name: 'Posts' }).click() await page.getByRole('link', { name: 'sunt aut facere repe' }).click() diff --git a/e2e/start/basic-auth/tests/utils.ts b/e2e/start/basic-auth/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/start/basic-auth/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/start/basic-react-query/app/utils/users.tsx b/e2e/start/basic-react-query/app/utils/users.tsx index b143db16bb..26b934fcb3 100644 --- a/e2e/start/basic-react-query/app/utils/users.tsx +++ b/e2e/start/basic-react-query/app/utils/users.tsx @@ -7,7 +7,10 @@ export type User = { email: string } -export const DEPLOY_URL = 'http://localhost:3002' +const PORT = + import.meta.env.VITE_SERVER_PORT || process.env.VITE_SERVER_PORT || 3000 + +export const DEPLOY_URL = `http://localhost:${PORT}` export const usersQueryOptions = () => queryOptions({ diff --git a/e2e/start/basic-react-query/package.json b/e2e/start/basic-react-query/package.json index 6c69c01387..1cebf677e1 100644 --- a/e2e/start/basic-react-query/package.json +++ b/e2e/start/basic-react-query/package.json @@ -4,7 +4,8 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vinxi dev --port 3002", + "dev": "vinxi dev --port 3000", + "dev:e2e": "vinxi dev", "build": "vinxi build", "start": "vinxi start", "test:e2e": "playwright test --project=chromium" @@ -29,9 +30,12 @@ "@types/react-dom": "^18.2.21", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", + "get-port-please": "^3.1.2", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", + "terminate": "^2.8.0", "typescript": "^5.7.2", - "vite-tsconfig-paths": "^5.1.3" + "vite-tsconfig-paths": "^5.1.3", + "wait-port": "^1.1.0" } } diff --git a/e2e/start/basic-react-query/playwright.config.ts b/e2e/start/basic-react-query/playwright.config.ts index d24b416373..896de93a24 100644 --- a/e2e/start/basic-react-query/playwright.config.ts +++ b/e2e/start/basic-react-query/playwright.config.ts @@ -8,18 +8,18 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3002/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3002/', + // }, - webServer: { - // TODO: build && start seems broken, use that if it's working - command: 'pnpm run dev', - url: 'http://localhost:3002', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // // TODO: build && start seems broken, use that if it's working + // command: 'pnpm run dev', + // url: 'http://localhost:3002', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/start/basic-react-query/tests/app.spec.ts b/e2e/start/basic-react-query/tests/app.spec.ts index 2c6b772461..30a10c4c70 100644 --- a/e2e/start/basic-react-query/tests/app.spec.ts +++ b/e2e/start/basic-react-query/tests/app.spec.ts @@ -1,22 +1,33 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() +}) + +test('Navigating to post', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') -test('Navigating to post', async ({ page }) => { - await page.goto('/') await page.getByRole('link', { name: 'Posts' }).click() await page.getByRole('link', { name: 'sunt aut facere repe' }).click() await page.getByRole('link', { name: 'Deep View' }).click() await expect(page.getByRole('heading')).toContainText('sunt aut facere') }) -test('Navigating to user', async ({ page }) => { - await page.goto('/') +test('Navigating to user', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') + await page.getByRole('link', { name: 'Users' }).click() await page.getByRole('link', { name: 'Leanne Graham' }).click() await expect(page.getByRole('heading')).toContainText('Leanne Graham') }) -test('Navigating nested layouts', async ({ page }) => { - await page.goto('/') +test('Navigating nested layouts', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') + await page.getByRole('link', { name: 'Layout', exact: true }).click() await page.getByRole('link', { name: 'Layout A' }).click() await expect(page.locator('body')).toContainText("I'm A!") @@ -24,8 +35,10 @@ test('Navigating nested layouts', async ({ page }) => { await expect(page.locator('body')).toContainText("I'm B!") }) -test('Navigating to a not-found route', async ({ page }) => { - await page.goto('/') +test('Navigating to a not-found route', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') + await page.getByRole('link', { name: 'This Route Does Not Exist' }).click() await page.getByRole('link', { name: 'Start Over' }).click() await expect(page.getByRole('heading')).toContainText('Welcome Home!') diff --git a/e2e/start/basic-react-query/tests/utils.ts b/e2e/start/basic-react-query/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/start/basic-react-query/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/start/basic-tsr-config/package.json b/e2e/start/basic-tsr-config/package.json index d7862c7113..c95716752f 100644 --- a/e2e/start/basic-tsr-config/package.json +++ b/e2e/start/basic-tsr-config/package.json @@ -4,7 +4,8 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vinxi dev --port 3002", + "dev": "vinxi dev --port 3000", + "dev:e2e": "vinxi dev", "build": "vinxi build", "start": "vinxi start", "test:e2e": "playwright test --project=chromium" @@ -20,6 +21,9 @@ "@types/node": "^22.5.4", "@types/react": "^18.2.65", "@types/react-dom": "^18.2.21", - "typescript": "^5.7.2" + "get-port-please": "^3.1.2", + "terminate": "^2.8.0", + "typescript": "^5.7.2", + "wait-port": "^1.1.0" } } diff --git a/e2e/start/basic-tsr-config/playwright.config.ts b/e2e/start/basic-tsr-config/playwright.config.ts index d24b416373..896de93a24 100644 --- a/e2e/start/basic-tsr-config/playwright.config.ts +++ b/e2e/start/basic-tsr-config/playwright.config.ts @@ -8,18 +8,18 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3002/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3002/', + // }, - webServer: { - // TODO: build && start seems broken, use that if it's working - command: 'pnpm run dev', - url: 'http://localhost:3002', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // // TODO: build && start seems broken, use that if it's working + // command: 'pnpm run dev', + // url: 'http://localhost:3002', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/start/basic-tsr-config/tests/app.spec.ts b/e2e/start/basic-tsr-config/tests/app.spec.ts index f61b9788db..a23d587854 100644 --- a/e2e/start/basic-tsr-config/tests/app.spec.ts +++ b/e2e/start/basic-tsr-config/tests/app.spec.ts @@ -1,7 +1,14 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() +}) + +test('opening the app', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') -test('opening the app', async ({ page }) => { - await page.goto('/') await expect(page.getByText('Add 1 to 0?')).toBeTruthy() await page.click('button') await expect(page.getByText('Add 1 to 1?')).toBeTruthy() diff --git a/e2e/start/basic-tsr-config/tests/utils.ts b/e2e/start/basic-tsr-config/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/start/basic-tsr-config/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/start/basic/app/utils/users.tsx b/e2e/start/basic/app/utils/users.tsx index 86d8d60b51..79b45867d5 100644 --- a/e2e/start/basic/app/utils/users.tsx +++ b/e2e/start/basic/app/utils/users.tsx @@ -4,4 +4,7 @@ export type User = { email: string } -export const DEPLOY_URL = 'http://localhost:3002' +const PORT = + import.meta.env.VITE_SERVER_PORT || process.env.VITE_SERVER_PORT || 3000 + +export const DEPLOY_URL = `http://localhost:${PORT}` diff --git a/e2e/start/basic/package.json b/e2e/start/basic/package.json index 33bd74226f..60dff7a5d2 100644 --- a/e2e/start/basic/package.json +++ b/e2e/start/basic/package.json @@ -4,7 +4,8 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vinxi dev --port 3002", + "dev": "vinxi dev --port 3000", + "dev:e2e": "vinxi dev", "build": "vinxi build", "start": "vinxi start", "test:e2e": "playwright test --project=chromium" @@ -27,9 +28,12 @@ "@types/react-dom": "^18.2.21", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", + "get-port-please": "^3.1.2", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", + "terminate": "^2.8.0", "typescript": "^5.7.2", - "vite-tsconfig-paths": "^5.1.3" + "vite-tsconfig-paths": "^5.1.3", + "wait-port": "^1.1.0" } } diff --git a/e2e/start/basic/playwright.config.ts b/e2e/start/basic/playwright.config.ts index d24b416373..896de93a24 100644 --- a/e2e/start/basic/playwright.config.ts +++ b/e2e/start/basic/playwright.config.ts @@ -8,18 +8,18 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3002/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3002/', + // }, - webServer: { - // TODO: build && start seems broken, use that if it's working - command: 'pnpm run dev', - url: 'http://localhost:3002', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // // TODO: build && start seems broken, use that if it's working + // command: 'pnpm run dev', + // url: 'http://localhost:3002', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/start/basic/tests/app.spec.ts b/e2e/start/basic/tests/app.spec.ts deleted file mode 100644 index b313b236a4..0000000000 --- a/e2e/start/basic/tests/app.spec.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { expect, test } from '@playwright/test' - -test('Navigating to post', async ({ page }) => { - await page.goto('/') - await page.getByRole('link', { name: 'Posts' }).click() - await page.getByRole('link', { name: 'sunt aut facere repe' }).click() - await page.getByRole('link', { name: 'Deep View' }).click() - await expect(page.getByRole('heading')).toContainText('sunt aut facere') -}) - -test('Navigating to user', async ({ page }) => { - await page.goto('/') - await page.getByRole('link', { name: 'Users' }).click() - await page.getByRole('link', { name: 'Leanne Graham' }).click() - await expect(page.getByRole('heading')).toContainText('Leanne Graham') -}) - -test('Navigating nested layouts', async ({ page }) => { - await page.goto('/') - await page.getByRole('link', { name: 'Layout', exact: true }).click() - - await expect(page.locator('body')).toContainText("I'm a layout") - await expect(page.locator('body')).toContainText("I'm a nested layout") - - await page.getByRole('link', { name: 'Layout A' }).click() - await expect(page.locator('body')).toContainText("I'm layout A!") - - await page.getByRole('link', { name: 'Layout B' }).click() - await expect(page.locator('body')).toContainText("I'm layout B!") -}) - -test('Navigating to a not-found route', async ({ page }) => { - await page.goto('/') - await page.getByRole('link', { name: 'This Route Does Not Exist' }).click() - await page.getByRole('link', { name: 'Start Over' }).click() - await expect(page.getByRole('heading')).toContainText('Welcome Home!') -}) - -test('Navigating to deferred route', async ({ page }) => { - await page.goto('/') - await page.getByRole('link', { name: 'Deferred' }).click() - - await expect(page.getByTestId('regular-person')).toContainText('John Doe') - await expect(page.getByTestId('deferred-person')).toContainText( - 'Tanner Linsley', - ) - await expect(page.getByTestId('deferred-stuff')).toContainText( - 'Hello deferred!', - ) -}) - -test('Directly visiting the deferred route', async ({ page }) => { - await page.goto('/deferred') - - await expect(page.getByTestId('regular-person')).toContainText('John Doe') - await expect(page.getByTestId('deferred-person')).toContainText( - 'Tanner Linsley', - ) - await expect(page.getByTestId('deferred-stuff')).toContainText( - 'Hello deferred!', - ) -}) - -test('Directly visiting the search-params route without search param set', async ({ - page, -}) => { - await page.goto('/search-params') - await new Promise((r) => setTimeout(r, 500)) - await expect(page.getByTestId('search-param')).toContainText('a') - expect(page.url().endsWith('/search-params?step=a')) -}) - -test('Directly visiting the search-params route with search param set', async ({ - page, -}) => { - await page.goto('/search-params?step=b') - await new Promise((r) => setTimeout(r, 500)) - await expect(page.getByTestId('search-param')).toContainText('b') - expect(page.url().endsWith('/search-params?step=b')) -}) - -test('invoking a server function with custom response status code', async ({ - page, -}) => { - await page.goto('/status') - - await page.waitForLoadState('networkidle') - await page.getByTestId('invoke-server-fn').click() - - const requestPromise = new Promise((resolve) => { - page.on('response', async (response) => { - expect(response.status()).toBe(225) - expect(response.statusText()).toBe('hello') - expect(response.headers()['content-type']).toBe('application/json') - expect(await response.json()).toEqual({ - result: { hello: 'world' }, - context: {}, - }) - resolve() - }) - }) - await requestPromise -}) - -test('Consistent server function returns both on client and server for GET and POST calls', async ({ - page, -}) => { - await page.goto('/server-fns') - - await page.waitForLoadState('networkidle') - const expected = - (await page - .getByTestId('expected-consistent-server-fns-result') - .textContent()) || '' - expect(expected).not.toBe('') - - await page.getByTestId('test-consistent-server-fn-calls-btn').click() - await page.waitForLoadState('networkidle') - - // GET calls - await expect(page.getByTestId('cons_serverGetFn1-response')).toContainText( - expected, - ) - await expect(page.getByTestId('cons_getFn1-response')).toContainText(expected) - - // POST calls - await expect(page.getByTestId('cons_serverPostFn1-response')).toContainText( - expected, - ) - await expect(page.getByTestId('cons_postFn1-response')).toContainText( - expected, - ) -}) - -test('submitting multipart/form-data as server function input', async ({ - page, -}) => { - await page.goto('/server-fns') - - await page.waitForLoadState('networkidle') - const expected = - (await page - .getByTestId('expected-multipart-server-fn-result') - .textContent()) || '' - expect(expected).not.toBe('') - - const fileChooserPromise = page.waitForEvent('filechooser') - await page.getByTestId('multipart-form-file-input').click() - const fileChooser = await fileChooserPromise - await fileChooser.setFiles({ - name: 'my_file.txt', - mimeType: 'text/plain', - buffer: Buffer.from('test data', 'utf-8'), - }) - await page.getByText('Submit (onClick)').click() - await page.waitForLoadState('networkidle') - - await expect(page.getByTestId('multipart-form-response')).toContainText( - expected, - ) -}) diff --git a/e2e/start/basic/tests/base.spec.ts b/e2e/start/basic/tests/base.spec.ts new file mode 100644 index 0000000000..f41397d95f --- /dev/null +++ b/e2e/start/basic/tests/base.spec.ts @@ -0,0 +1,78 @@ +import { expect } from '@playwright/test' +import { test } from './utils' + +test.afterEach(async ({ setupApp: setup }) => { + await setup.killProcess() +}) + +test('Navigating to post', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') + + await page.getByRole('link', { name: 'Posts' }).click() + await page.getByRole('link', { name: 'sunt aut facere repe' }).click() + await page.getByRole('link', { name: 'Deep View' }).click() + await expect(page.getByRole('heading')).toContainText('sunt aut facere') +}) + +test('Navigating to user', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') + + await page.getByRole('link', { name: 'Users' }).click() + await page.getByRole('link', { name: 'Leanne Graham' }).click() + await expect(page.getByRole('heading')).toContainText('Leanne Graham') +}) + +test('Navigating nested layouts', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') + + await page.getByRole('link', { name: 'Layout', exact: true }).click() + + await expect(page.locator('body')).toContainText("I'm a layout") + await expect(page.locator('body')).toContainText("I'm a nested layout") + + await page.getByRole('link', { name: 'Layout A' }).click() + await expect(page.locator('body')).toContainText("I'm layout A!") + + await page.getByRole('link', { name: 'Layout B' }).click() + await expect(page.locator('body')).toContainText("I'm layout B!") +}) + +test('Navigating to a not-found route', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') + + await page.getByRole('link', { name: 'This Route Does Not Exist' }).click() + await page.getByRole('link', { name: 'Start Over' }).click() + await expect(page.getByRole('heading')).toContainText('Welcome Home!') +}) + +test('Navigating to deferred route', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') + + await page.getByRole('link', { name: 'Deferred' }).click() + + await expect(page.getByTestId('regular-person')).toContainText('John Doe') + await expect(page.getByTestId('deferred-person')).toContainText( + 'Tanner Linsley', + ) + await expect(page.getByTestId('deferred-stuff')).toContainText( + 'Hello deferred!', + ) +}) + +test('Directly visiting the deferred route', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/deferred') + + await expect(page.getByTestId('regular-person')).toContainText('John Doe') + await expect(page.getByTestId('deferred-person')).toContainText( + 'Tanner Linsley', + ) + await expect(page.getByTestId('deferred-stuff')).toContainText( + 'Hello deferred!', + ) +}) diff --git a/e2e/start/basic/tests/search-params.spec.ts b/e2e/start/basic/tests/search-params.spec.ts new file mode 100644 index 0000000000..d38819fa54 --- /dev/null +++ b/e2e/start/basic/tests/search-params.spec.ts @@ -0,0 +1,30 @@ +import { expect } from '@playwright/test' +import { test } from './utils' + +test.afterEach(async ({ setupApp: setup }) => { + await setup.killProcess() +}) + +test('Directly visiting the search-params route without search param set', async ({ + page, + setupApp, +}) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/search-params') + + await new Promise((r) => setTimeout(r, 500)) + await expect(page.getByTestId('search-param')).toContainText('a') + expect(page.url().endsWith('/search-params?step=a')) +}) + +test('Directly visiting the search-params route with search param set', async ({ + page, + setupApp, +}) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/search-params?step=b') + + await new Promise((r) => setTimeout(r, 500)) + await expect(page.getByTestId('search-param')).toContainText('b') + expect(page.url().endsWith('/search-params?step=b')) +}) diff --git a/e2e/start/basic/tests/server-fns.spec.ts b/e2e/start/basic/tests/server-fns.spec.ts new file mode 100644 index 0000000000..b31563c33c --- /dev/null +++ b/e2e/start/basic/tests/server-fns.spec.ts @@ -0,0 +1,93 @@ +import { expect } from '@playwright/test' +import { test } from './utils' + +test.afterEach(async ({ setupApp: setup }) => { + await setup.killProcess() +}) + +test('invoking a server function with custom response status code', async ({ + page, + setupApp, +}) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/status') + + await page.waitForLoadState('networkidle') + await page.getByTestId('invoke-server-fn').click() + + const requestPromise = new Promise((resolve) => { + page.on('response', async (response) => { + expect(response.status()).toBe(225) + expect(response.statusText()).toBe('hello') + expect(response.headers()['content-type']).toBe('application/json') + expect(await response.json()).toEqual({ + result: { hello: 'world' }, + context: {}, + }) + resolve() + }) + }) + await requestPromise +}) + +test('Consistent server function returns both on client and server for GET and POST calls', async ({ + page, + setupApp, +}) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/server-fns') + + await page.waitForLoadState('networkidle') + const expected = + (await page + .getByTestId('expected-consistent-server-fns-result') + .textContent()) || '' + expect(expected).not.toBe('') + + await page.getByTestId('test-consistent-server-fn-calls-btn').click() + await page.waitForLoadState('networkidle') + + // GET calls + await expect(page.getByTestId('cons_serverGetFn1-response')).toContainText( + expected, + ) + await expect(page.getByTestId('cons_getFn1-response')).toContainText(expected) + + // POST calls + await expect(page.getByTestId('cons_serverPostFn1-response')).toContainText( + expected, + ) + await expect(page.getByTestId('cons_postFn1-response')).toContainText( + expected, + ) +}) + +test('submitting multipart/form-data as server function input', async ({ + page, + setupApp, +}) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/server-fns') + + await page.waitForLoadState('networkidle') + const expected = + (await page + .getByTestId('expected-multipart-server-fn-result') + .textContent()) || '' + expect(expected).not.toBe('') + + const fileChooserPromise = page.waitForEvent('filechooser') + await page.getByTestId('multipart-form-file-input').click() + const fileChooser = await fileChooserPromise + await fileChooser.setFiles({ + name: 'my_file.txt', + mimeType: 'text/plain', + buffer: Buffer.from('test data', 'utf-8'), + }) + await page.getByText('Submit (onClick)').click() + await page.waitForLoadState('networkidle') + + await expect(page.getByTestId('multipart-form-response')).toContainText( + expected, + ) +}) diff --git a/e2e/start/basic/tests/utils.ts b/e2e/start/basic/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/start/basic/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/start/clerk-basic/package.json b/e2e/start/clerk-basic/package.json index d83a745b47..2e3a12cd83 100644 --- a/e2e/start/clerk-basic/package.json +++ b/e2e/start/clerk-basic/package.json @@ -4,7 +4,8 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vinxi dev --port 3002", + "dev": "vinxi dev --port 3000", + "dev:e2e": "vinxi dev", "build": "vinxi build", "start": "vinxi start", "test:e2e": "exit 0; playwright test --project=chromium" @@ -29,9 +30,12 @@ "@types/react-dom": "^18.2.21", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", + "get-port-please": "^3.1.2", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", + "terminate": "^2.8.0", "typescript": "^5.7.2", - "vite-tsconfig-paths": "^5.1.3" + "vite-tsconfig-paths": "^5.1.3", + "wait-port": "^1.1.0" } } diff --git a/e2e/start/clerk-basic/playwright.config.ts b/e2e/start/clerk-basic/playwright.config.ts index 7110d8422c..a99ad3c037 100644 --- a/e2e/start/clerk-basic/playwright.config.ts +++ b/e2e/start/clerk-basic/playwright.config.ts @@ -12,18 +12,18 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3002/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3002/', + // }, - webServer: { - // TODO: build && start seems broken, use that if it's working - command: 'pnpm dev', - url: 'http://localhost:3002', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // // TODO: build && start seems broken, use that if it's working + // command: 'pnpm dev', + // url: 'http://localhost:3002', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/start/clerk-basic/tests/app.spec.ts b/e2e/start/clerk-basic/tests/app.spec.ts index 208a95cadd..5eacd2cd00 100644 --- a/e2e/start/clerk-basic/tests/app.spec.ts +++ b/e2e/start/clerk-basic/tests/app.spec.ts @@ -1,6 +1,10 @@ -import { expect, test } from '@playwright/test' -import type { Page } from '@playwright/test' +import { test } from './utils' -test('loads', async ({ page }) => { - await page.goto('http://localhost:3000') +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() +}) + +test('loads', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/') }) diff --git a/e2e/start/clerk-basic/tests/utils.ts b/e2e/start/clerk-basic/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/start/clerk-basic/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/e2e/start/website/package.json b/e2e/start/website/package.json index 0c29ef6c7e..2d2dfb10de 100644 --- a/e2e/start/website/package.json +++ b/e2e/start/website/package.json @@ -4,7 +4,8 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vinxi dev --port 3010", + "dev": "vinxi dev --port 3000", + "dev:e2e": "vinxi dev", "build": "vinxi build", "start": "vinxi start", "test:e2e": "playwright test --project=chromium" @@ -27,9 +28,12 @@ "@types/react-dom": "^18.2.21", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", + "get-port-please": "^3.1.2", "postcss": "^8.4.49", "tailwindcss": "^3.4.15", + "terminate": "^2.8.0", "typescript": "^5.7.2", - "vite-tsconfig-paths": "^5.1.3" + "vite-tsconfig-paths": "^5.1.3", + "wait-port": "^1.1.0" } } diff --git a/e2e/start/website/playwright.config.ts b/e2e/start/website/playwright.config.ts index 5fbb3e3429..01d8f90726 100644 --- a/e2e/start/website/playwright.config.ts +++ b/e2e/start/website/playwright.config.ts @@ -8,18 +8,18 @@ export default defineConfig({ reporter: [['line']], - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:3010/', - }, + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3010/', + // }, - webServer: { - // TODO: build && start seems broken, use that if it's working - command: 'pnpm run dev', - url: 'http://localhost:3010', - reuseExistingServer: !process.env.CI, - stdout: 'pipe', - }, + // webServer: { + // // TODO: build && start seems broken, use that if it's working + // command: 'pnpm run dev', + // url: 'http://localhost:3010', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, projects: [ { diff --git a/e2e/start/website/tests/app.spec.ts b/e2e/start/website/tests/app.spec.ts index f115180956..de50d9928e 100644 --- a/e2e/start/website/tests/app.spec.ts +++ b/e2e/start/website/tests/app.spec.ts @@ -1,17 +1,25 @@ -import { expect, test } from '@playwright/test' +import { expect } from '@playwright/test' +import { test } from './utils' + +test.afterEach(async ({ setupApp }) => { + await setupApp.killProcess() +}) const routeTestId = 'selected-route-label' test('resolves to the latest version on load of a project like "/router"', async ({ page, + setupApp, }) => { - await page.goto('/router') + const { ADDR } = setupApp + await page.goto(ADDR + '/router') await expect(page.getByTestId(routeTestId)).toContainText('/router/latest') }) -test('resolves to the overview docs page', async ({ page }) => { - await page.goto('/router/latest/docs') +test('resolves to the overview docs page', async ({ page, setupApp }) => { + const { ADDR } = setupApp + await page.goto(ADDR + '/router/latest/docs') await expect(page.getByTestId(routeTestId)).toContainText( '/router/latest/docs/framework/react/overview', diff --git a/e2e/start/website/tests/utils.ts b/e2e/start/website/tests/utils.ts new file mode 100644 index 0000000000..0ebc907203 --- /dev/null +++ b/e2e/start/website/tests/utils.ts @@ -0,0 +1,40 @@ +import { exec } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import { getRandomPort } from 'get-port-please' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup(): Promise<{ + PORT: number + PID: number + ADDR: string + killProcess: () => Promise +}> { + const PORT = await getRandomPort() + const ADDR = `http://localhost:${PORT}` + + const childProcess = exec( + `VITE_SERVER_PORT=${PORT} pnpm run dev:e2e --port ${PORT}`, + ) + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + await waitPort({ port: PORT }) + + const PID = childProcess.pid! + const killProcess = () => terminate(PID) + + return { PORT, PID, ADDR, killProcess } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ setupApp: SetupApp }>({ + // don't know why playwright wants this fist argument to be destructured + // eslint-disable-next-line unused-imports/no-unused-vars + setupApp: async ({ page }, use) => { + const setup = await _setup() + await use(setup) + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d08d07043a..773d76ba69 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -142,9 +142,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 + terminate: + specifier: ^2.8.0 + version: 2.8.0 vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.9.3)(terser@5.36.0) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/react-router/basic-esbuild-file-based: dependencies: @@ -225,9 +234,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 + terminate: + specifier: ^2.8.0 + version: 2.8.0 vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.9.3)(terser@5.36.0) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/react-router/basic-file-based-code-splitting: dependencies: @@ -262,9 +280,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 + terminate: + specifier: ^2.8.0 + version: 2.8.0 vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.9.3)(terser@5.36.0) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/react-router/basic-react-query: dependencies: @@ -302,9 +329,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 + terminate: + specifier: ^2.8.0 + version: 2.8.0 vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.9.3)(terser@5.36.0) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/react-router/basic-react-query-file-based: dependencies: @@ -348,9 +384,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 + terminate: + specifier: ^2.8.0 + version: 2.8.0 vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.9.3)(terser@5.36.0) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/react-router/basic-scroll-restoration: dependencies: @@ -385,9 +430,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 + terminate: + specifier: ^2.8.0 + version: 2.8.0 vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.9.3)(terser@5.36.0) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/react-router/basic-virtual-file-based: dependencies: @@ -428,9 +482,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 + terminate: + specifier: ^2.8.0 + version: 2.8.0 vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.9.3)(terser@5.36.0) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/react-router/scroll-restoration-sandbox-vite: dependencies: @@ -471,9 +534,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.3.4(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 + terminate: + specifier: ^2.8.0 + version: 2.8.0 vite: specifier: ^5.4.11 version: 5.4.11(@types/node@22.9.3)(terser@5.36.0) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/start/basic: dependencies: @@ -523,18 +595,27 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 postcss: specifier: ^8.4.49 version: 8.4.49 tailwindcss: specifier: ^3.4.15 version: 3.4.15 + terminate: + specifier: ^2.8.0 + version: 2.8.0 typescript: specifier: ^5.7.2 version: 5.7.2 vite-tsconfig-paths: specifier: ^5.1.3 version: 5.1.3(typescript@5.7.2)(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/start/basic-auth: dependencies: @@ -593,18 +674,27 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 postcss: specifier: ^8.4.49 version: 8.4.49 tailwindcss: specifier: ^3.4.15 version: 3.4.15 + terminate: + specifier: ^2.8.0 + version: 2.8.0 typescript: specifier: ^5.7.2 version: 5.7.2 vite-tsconfig-paths: specifier: ^5.1.3 version: 5.1.3(typescript@5.7.2)(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/start/basic-react-query: dependencies: @@ -660,18 +750,27 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 postcss: specifier: ^8.4.49 version: 8.4.49 tailwindcss: specifier: ^3.4.15 version: 3.4.15 + terminate: + specifier: ^2.8.0 + version: 2.8.0 typescript: specifier: ^5.7.2 version: 5.7.2 vite-tsconfig-paths: specifier: ^5.1.3 version: 5.1.3(typescript@5.7.2)(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/start/basic-rsc: dependencies: @@ -749,9 +848,18 @@ importers: '@types/react-dom': specifier: ^18.2.21 version: 18.3.1 + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 + terminate: + specifier: ^2.8.0 + version: 2.8.0 typescript: specifier: ^5.7.2 version: 5.7.2 + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/start/clerk-basic: dependencies: @@ -807,18 +915,27 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 postcss: specifier: ^8.4.49 version: 8.4.49 tailwindcss: specifier: ^3.4.15 version: 3.4.15 + terminate: + specifier: ^2.8.0 + version: 2.8.0 typescript: specifier: ^5.7.2 version: 5.7.2 vite-tsconfig-paths: specifier: ^5.1.3 version: 5.1.3(typescript@5.7.2)(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 e2e/start/website: dependencies: @@ -868,18 +985,27 @@ importers: autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) + get-port-please: + specifier: ^3.1.2 + version: 3.1.2 postcss: specifier: ^8.4.49 version: 8.4.49 tailwindcss: specifier: ^3.4.15 version: 3.4.15 + terminate: + specifier: ^2.8.0 + version: 2.8.0 typescript: specifier: ^5.7.2 version: 5.7.2 vite-tsconfig-paths: specifier: ^5.1.3 version: 5.1.3(typescript@5.7.2)(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) + wait-port: + specifier: ^1.1.0 + version: 1.1.0 examples/react/authenticated-routes: dependencies: @@ -1887,10 +2013,10 @@ importers: version: 18.3.1 html-webpack-plugin: specifier: ^5.6.3 - version: 5.6.3(@rspack/core@1.1.4(@swc/helpers@0.5.15))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + version: 5.6.3(@rspack/core@1.1.4(@swc/helpers@0.5.15))(webpack@5.96.1) swc-loader: specifier: ^0.2.6 - version: 0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + version: 0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.96.1) typescript: specifier: ^5.7.2 version: 5.7.2 @@ -3117,7 +3243,7 @@ importers: version: 4.3.4(vite@5.4.11(@types/node@22.9.3)(terser@5.36.0)) html-webpack-plugin: specifier: ^5.6.0 - version: 5.6.3(@rspack/core@1.1.4(@swc/helpers@0.5.15))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + version: 5.6.3(@rspack/core@1.1.4(@swc/helpers@0.5.15))(webpack@5.96.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -3126,7 +3252,7 @@ importers: version: 18.3.1(react@18.3.1) swc-loader: specifier: ^0.2.6 - version: 0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + version: 0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.96.1) typescript: specifier: ^5.7.2 version: 5.7.2 @@ -6839,6 +6965,10 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -7579,6 +7709,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -7766,6 +7899,9 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + front-matter@4.0.2: resolution: {integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==} @@ -8561,6 +8697,9 @@ packages: resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} + map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + markdown-it@14.1.0: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true @@ -9127,6 +9266,9 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -9424,6 +9566,11 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} @@ -9912,6 +10059,9 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -9938,6 +10088,9 @@ packages: std-env@3.8.0: resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + streamx@2.20.2: resolution: {integrity: sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==} @@ -10080,6 +10233,10 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + terminate@2.8.0: + resolution: {integrity: sha512-bcbjJEg0wY5nuJXvGxxHfmoEPkyHLCctUKO6suwtxy7jVSgGcgPeGwpbLDLELFhIaxCGRr3dPvyNg1yuz2V0eg==} + engines: {node: '>=12'} + terser-webpack-plugin@5.3.10: resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} @@ -10677,6 +10834,11 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + wait-port@1.1.0: + resolution: {integrity: sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==} + engines: {node: '>=10'} + hasBin: true + watchpack@2.4.2: resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} @@ -13602,17 +13764,17 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.96.1)': dependencies: webpack: 5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.96.1)': dependencies: webpack: 5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1))(webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.96.1))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.96.1)': dependencies: webpack: 5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1) @@ -14167,6 +14329,8 @@ snapshots: commander@8.3.0: {} + commander@9.5.0: {} + commondir@1.0.1: {} compare-func@2.0.0: @@ -15069,6 +15233,16 @@ snapshots: etag@1.8.1: {} + event-stream@3.3.4: + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} @@ -15284,6 +15458,8 @@ snapshots: fresh@0.5.2: {} + from@0.1.7: {} + front-matter@4.0.2: dependencies: js-yaml: 3.14.1 @@ -15568,7 +15744,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.3(@rspack/core@1.1.4(@swc/helpers@0.5.15))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)): + html-webpack-plugin@5.6.3(@rspack/core@1.1.4(@swc/helpers@0.5.15))(webpack@5.96.1): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -16111,6 +16287,8 @@ snapshots: map-obj@4.3.0: {} + map-stream@0.1.0: {} + markdown-it@14.1.0: dependencies: argparse: 2.0.1 @@ -16783,6 +16961,10 @@ snapshots: pathval@2.0.0: {} + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -17045,6 +17227,10 @@ snapshots: proxy-from-env@1.1.0: {} + ps-tree@1.2.0: + dependencies: + event-stream: 3.3.4 + pseudomap@1.0.2: {} psl@1.13.0: @@ -17586,6 +17772,10 @@ snapshots: split2@4.2.0: {} + split@0.3.3: + dependencies: + through: 2.3.8 + sprintf-js@1.0.3: {} stable-hash@0.0.4: {} @@ -17602,6 +17792,10 @@ snapshots: std-env@3.8.0: {} + stream-combiner@0.0.4: + dependencies: + duplexer: 0.1.2 + streamx@2.20.2: dependencies: fast-fifo: 1.3.2 @@ -17712,7 +17906,7 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - swc-loader@0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)): + swc-loader@0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.96.1): dependencies: '@swc/core': 1.9.3(@swc/helpers@0.5.15) '@swc/counter': 0.1.3 @@ -17782,26 +17976,30 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - terser-webpack-plugin@5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)): + terminate@2.8.0: + dependencies: + ps-tree: 1.2.0 + + terser-webpack-plugin@5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.36.0 - webpack: 5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) + webpack: 5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0) optionalDependencies: '@swc/core': 1.9.3(@swc/helpers@0.5.15) esbuild: 0.24.0 - terser-webpack-plugin@5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)): + terser-webpack-plugin@5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.96.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.36.0 - webpack: 5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0) + webpack: 5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) optionalDependencies: '@swc/core': 1.9.3(@swc/helpers@0.5.15) esbuild: 0.24.0 @@ -18425,6 +18623,14 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + wait-port@1.1.0: + dependencies: + chalk: 4.1.2 + commander: 9.5.0 + debug: 4.3.7(supports-color@9.4.0) + transitivePeerDependencies: + - supports-color + watchpack@2.4.2: dependencies: glob-to-regexp: 0.4.1 @@ -18447,9 +18653,9 @@ snapshots: webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1))(webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.96.1))(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.96.1) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.96.1) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.96.1) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 @@ -18463,7 +18669,7 @@ snapshots: optionalDependencies: webpack-dev-server: 5.1.0(webpack-cli@5.1.4)(webpack@5.96.1) - webpack-dev-middleware@7.4.2(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)): + webpack-dev-middleware@7.4.2(webpack@5.96.1): dependencies: colorette: 2.0.20 memfs: 4.14.0 @@ -18502,7 +18708,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + webpack-dev-middleware: 7.4.2(webpack@5.96.1) ws: 8.18.0 optionalDependencies: webpack: 5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) @@ -18575,7 +18781,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.96.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.96.1) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: