diff --git a/e2e/react-start/css-modules/tests/css.spec.ts b/e2e/react-start/css-modules/tests/css.spec.ts index a823b5a693f..02dd96d30c8 100644 --- a/e2e/react-start/css-modules/tests/css.spec.ts +++ b/e2e/react-start/css-modules/tests/css.spec.ts @@ -12,28 +12,6 @@ const whitelistErrors = [ test.describe('CSS styles in SSR (dev mode)', () => { test.use({ whitelistErrors }) - // Warmup: trigger Vite's dependency optimization before running tests - // This prevents "optimized dependencies changed. reloading" during actual tests - // We use a real browser context since dep optimization happens on JS load, not HTTP requests - test.beforeAll(async ({ browser, baseURL }) => { - const context = await browser.newContext() - const page = await context.newPage() - try { - // Load both pages to trigger dependency optimization - await page.goto(baseURL!) - await page.waitForTimeout(2000) // Wait for deps to optimize - await page.goto(`${baseURL}/modules`) - await page.waitForTimeout(2000) - // Load again after optimization completes - await page.goto(baseURL!) - await page.waitForTimeout(1000) - } catch { - // Ignore errors during warmup - } finally { - await context.close() - } - }) - // Helper to build full URL from baseURL and path // Playwright's goto with absolute paths (like '/modules') ignores baseURL's path portion // So we need to manually construct the full URL @@ -223,44 +201,35 @@ test.describe('CSS styles in SSR (dev mode)', () => { // Start from home await page.goto(buildUrl(baseURL!, '/')) - // Verify initial styles with retry to handle potential Vite dep optimization reload + // Verify initial styles const globalElement = page.getByTestId('global-styled') - await expect(async () => { - await expect(globalElement).toBeVisible() - const backgroundColor = await globalElement.evaluate( - (el) => getComputedStyle(el).backgroundColor, - ) - expect(backgroundColor).toBe('rgb(59, 130, 246)') - }).toPass({ timeout: 10000 }) + await expect(globalElement).toBeVisible() + let backgroundColor = await globalElement.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(59, 130, 246)') // Navigate to modules page await page.getByTestId('nav-modules').click() - // Use glob pattern to match with or without basepath await page.waitForURL('**/modules') - // Verify CSS modules styles with retry to handle potential Vite dep optimization reload + // Verify CSS modules styles const card = page.getByTestId('module-card') - await expect(async () => { - await expect(card).toBeVisible() - const backgroundColor = await card.evaluate( - (el) => getComputedStyle(el).backgroundColor, - ) - expect(backgroundColor).toBe('rgb(240, 253, 244)') - }).toPass({ timeout: 10000 }) + await expect(card).toBeVisible() + backgroundColor = await card.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(240, 253, 244)') // Navigate back to home await page.getByTestId('nav-home').click() - // Match home URL with or without trailing slash and optional query string - // Matches: /, /?, /my-app, /my-app/, /my-app?foo=bar await page.waitForURL(/\/([^/]*)(\/)?($|\?)/) - // Verify global styles still work with retry - await expect(async () => { - await expect(globalElement).toBeVisible() - const backgroundColor = await globalElement.evaluate( - (el) => getComputedStyle(el).backgroundColor, - ) - expect(backgroundColor).toBe('rgb(59, 130, 246)') - }).toPass({ timeout: 10000 }) + // Verify global styles still work + await expect(globalElement).toBeVisible() + backgroundColor = await globalElement.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(59, 130, 246)') }) }) diff --git a/e2e/react-start/css-modules/tests/setup/global.setup.ts b/e2e/react-start/css-modules/tests/setup/global.setup.ts index 3593d10ab90..413c460f56f 100644 --- a/e2e/react-start/css-modules/tests/setup/global.setup.ts +++ b/e2e/react-start/css-modules/tests/setup/global.setup.ts @@ -1,6 +1,77 @@ -import { e2eStartDummyServer } from '@tanstack/router-e2e-utils' +import { chromium } from '@playwright/test' +import { + e2eStartDummyServer, + getTestServerPort, +} from '@tanstack/router-e2e-utils' import packageJson from '../../package.json' with { type: 'json' } +async function waitForServer(url: string) { + const start = Date.now() + while (Date.now() - start < 30_000) { + try { + const res = await fetch(url, { redirect: 'manual' }) + if (res.ok) return + } catch { + // ignore + } + await new Promise((r) => setTimeout(r, 250)) + } + throw new Error(`Timed out waiting for dev server at ${url}`) +} + +async function preOptimizeDevServer(baseURL: string) { + const browser = await chromium.launch() + const context = await browser.newContext() + const page = await context.newPage() + + try { + await page.goto(`${baseURL}/`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('global-styled').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + await page.goto(`${baseURL}/modules`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('module-card').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + await page.goto(`${baseURL}/sass-mixin`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('mixin-styled').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + await page.goto(`${baseURL}/quotes`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('quote-styled').waitFor({ state: 'visible' }) + await page.getByTestId('after-quote-styled').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + // Ensure we end in a stable state. Vite's optimize step triggers a reload; + // this waits until no further navigations happen for a short window. + for (let i = 0; i < 40; i++) { + const currentUrl = page.url() + await page.waitForTimeout(250) + if (page.url() === currentUrl) { + await page.waitForTimeout(250) + if (page.url() === currentUrl) return + } + } + + throw new Error('Dev server did not reach a stable URL after warmup') + } finally { + await context.close() + await browser.close() + } +} + export default async function setup() { await e2eStartDummyServer(packageJson.name) + + if (process.env.MODE !== 'dev') return + + const viteConfig = process.env.VITE_CONFIG // 'nitro' | 'basepath' | 'cloudflare' | undefined + const port = await getTestServerPort( + viteConfig ? `${packageJson.name}-${viteConfig}` : packageJson.name, + ) + const basePath = viteConfig === 'basepath' ? '/my-app' : '' + const baseURL = `http://localhost:${port}${basePath}` + + await waitForServer(baseURL) + await preOptimizeDevServer(baseURL) } diff --git a/e2e/solid-start/css-modules/tests/css.spec.ts b/e2e/solid-start/css-modules/tests/css.spec.ts index 0a83800fded..0b34d9e6516 100644 --- a/e2e/solid-start/css-modules/tests/css.spec.ts +++ b/e2e/solid-start/css-modules/tests/css.spec.ts @@ -12,28 +12,6 @@ const whitelistErrors = [ test.describe('CSS styles in SSR (dev mode)', () => { test.use({ whitelistErrors }) - // Warmup: trigger Vite's dependency optimization before running tests - // This prevents "optimized dependencies changed. reloading" during actual tests - // We use a real browser context since dep optimization happens on JS load, not HTTP requests - test.beforeAll(async ({ browser, baseURL }) => { - const context = await browser.newContext() - const page = await context.newPage() - try { - // Load both pages to trigger dependency optimization - await page.goto(baseURL!) - await page.waitForTimeout(2000) // Wait for deps to optimize - await page.goto(`${baseURL}/modules`) - await page.waitForTimeout(2000) - // Load again after optimization completes - await page.goto(baseURL!) - await page.waitForTimeout(1000) - } catch { - // Ignore errors during warmup - } finally { - await context.close() - } - }) - // Helper to build full URL from baseURL and path // Playwright's goto with absolute paths (like '/modules') ignores baseURL's path portion // So we need to manually construct the full URL @@ -195,44 +173,35 @@ test.describe('CSS styles in SSR (dev mode)', () => { // Start from home await page.goto(buildUrl(baseURL!, '/')) - // Verify initial styles with retry to handle potential Vite dep optimization reload + // Verify initial styles const globalElement = page.getByTestId('global-styled') - await expect(async () => { - await expect(globalElement).toBeVisible() - const backgroundColor = await globalElement.evaluate( - (el) => getComputedStyle(el).backgroundColor, - ) - expect(backgroundColor).toBe('rgb(59, 130, 246)') - }).toPass({ timeout: 10000 }) + await expect(globalElement).toBeVisible() + let backgroundColor = await globalElement.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(59, 130, 246)') // Navigate to modules page await page.getByTestId('nav-modules').click() - // Use glob pattern to match with or without basepath await page.waitForURL('**/modules') - // Verify CSS modules styles with retry to handle potential Vite dep optimization reload + // Verify CSS modules styles const card = page.getByTestId('module-card') - await expect(async () => { - await expect(card).toBeVisible() - const backgroundColor = await card.evaluate( - (el) => getComputedStyle(el).backgroundColor, - ) - expect(backgroundColor).toBe('rgb(240, 253, 244)') - }).toPass({ timeout: 10000 }) + await expect(card).toBeVisible() + backgroundColor = await card.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(240, 253, 244)') // Navigate back to home await page.getByTestId('nav-home').click() - // Match home URL with or without trailing slash and optional query string - // Matches: /, /?, /my-app, /my-app/, /my-app?foo=bar await page.waitForURL(/\/([^/]*)(\/)?($|\?)/) - // Verify global styles still work with retry - await expect(async () => { - await expect(globalElement).toBeVisible() - const backgroundColor = await globalElement.evaluate( - (el) => getComputedStyle(el).backgroundColor, - ) - expect(backgroundColor).toBe('rgb(59, 130, 246)') - }).toPass({ timeout: 10000 }) + // Verify global styles still work + await expect(globalElement).toBeVisible() + backgroundColor = await globalElement.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(59, 130, 246)') }) }) diff --git a/e2e/solid-start/css-modules/tests/setup/global.setup.ts b/e2e/solid-start/css-modules/tests/setup/global.setup.ts index 3593d10ab90..eaf5091c740 100644 --- a/e2e/solid-start/css-modules/tests/setup/global.setup.ts +++ b/e2e/solid-start/css-modules/tests/setup/global.setup.ts @@ -1,6 +1,68 @@ -import { e2eStartDummyServer } from '@tanstack/router-e2e-utils' +import { chromium } from '@playwright/test' +import { + e2eStartDummyServer, + getTestServerPort, +} from '@tanstack/router-e2e-utils' import packageJson from '../../package.json' with { type: 'json' } +async function waitForServer(url: string) { + const start = Date.now() + while (Date.now() - start < 30_000) { + try { + const res = await fetch(url, { redirect: 'manual' }) + if (res.ok) return + } catch { + // ignore + } + await new Promise((r) => setTimeout(r, 250)) + } + throw new Error(`Timed out waiting for dev server at ${url}`) +} + +async function preOptimizeDevServer(baseURL: string) { + const browser = await chromium.launch() + const context = await browser.newContext() + const page = await context.newPage() + + try { + await page.goto(`${baseURL}/`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('global-styled').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + await page.goto(`${baseURL}/modules`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('module-card').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + await page.goto(`${baseURL}/sass-mixin`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('mixin-styled').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + // Ensure we end in a stable state. Vite's optimize step triggers a reload; + // this waits until no further navigations happen for a short window. + for (let i = 0; i < 40; i++) { + const currentUrl = page.url() + await page.waitForTimeout(250) + if (page.url() === currentUrl) { + await page.waitForTimeout(250) + if (page.url() === currentUrl) return + } + } + + throw new Error('Dev server did not reach a stable URL after warmup') + } finally { + await context.close() + await browser.close() + } +} + export default async function setup() { await e2eStartDummyServer(packageJson.name) + + if (process.env.MODE !== 'dev') return + + const port = await getTestServerPort(packageJson.name) + const baseURL = `http://localhost:${port}` + + await waitForServer(baseURL) + await preOptimizeDevServer(baseURL) } diff --git a/e2e/vue-start/css-modules/tests/css.spec.ts b/e2e/vue-start/css-modules/tests/css.spec.ts index f3d06bcb1c0..7646eb750eb 100644 --- a/e2e/vue-start/css-modules/tests/css.spec.ts +++ b/e2e/vue-start/css-modules/tests/css.spec.ts @@ -14,28 +14,6 @@ const whitelistErrors = [ test.describe('CSS styles in SSR (dev mode)', () => { test.use({ whitelistErrors }) - // Warmup: trigger Vite's dependency optimization before running tests - // This prevents "optimized dependencies changed. reloading" during actual tests - // We use a real browser context since dep optimization happens on JS load, not HTTP requests - test.beforeAll(async ({ browser, baseURL }) => { - const context = await browser.newContext() - const page = await context.newPage() - try { - // Load both pages to trigger dependency optimization - await page.goto(baseURL!) - await page.waitForTimeout(2000) // Wait for deps to optimize - await page.goto(`${baseURL}/modules`) - await page.waitForTimeout(2000) - // Load again after optimization completes - await page.goto(baseURL!) - await page.waitForTimeout(1000) - } catch { - // Ignore errors during warmup - } finally { - await context.close() - } - }) - // Helper to build full URL from baseURL and path // Playwright's goto with absolute paths (like '/modules') ignores baseURL's path portion // So we need to manually construct the full URL @@ -197,44 +175,35 @@ test.describe('CSS styles in SSR (dev mode)', () => { // Start from home await page.goto(buildUrl(baseURL!, '/')) - // Verify initial styles with retry to handle potential Vite dep optimization reload + // Verify initial styles const globalElement = page.getByTestId('global-styled') - await expect(async () => { - await expect(globalElement).toBeVisible() - const backgroundColor = await globalElement.evaluate( - (el) => getComputedStyle(el).backgroundColor, - ) - expect(backgroundColor).toBe('rgb(59, 130, 246)') - }).toPass({ timeout: 10000 }) + await expect(globalElement).toBeVisible() + let backgroundColor = await globalElement.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(59, 130, 246)') // Navigate to modules page await page.getByTestId('nav-modules').click() - // Use glob pattern to match with or without basepath await page.waitForURL('**/modules') - // Verify CSS modules styles with retry to handle potential Vite dep optimization reload + // Verify CSS modules styles const card = page.getByTestId('module-card') - await expect(async () => { - await expect(card).toBeVisible() - const backgroundColor = await card.evaluate( - (el) => getComputedStyle(el).backgroundColor, - ) - expect(backgroundColor).toBe('rgb(240, 253, 244)') - }).toPass({ timeout: 10000 }) + await expect(card).toBeVisible() + backgroundColor = await card.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(240, 253, 244)') // Navigate back to home await page.getByTestId('nav-home').click() - // Match home URL with or without trailing slash and optional query string - // Matches: /, /?, /my-app, /my-app/, /my-app?foo=bar await page.waitForURL(/\/([^/]*)(\/)?($|\?)/) - // Verify global styles still work with retry - await expect(async () => { - await expect(globalElement).toBeVisible() - const backgroundColor = await globalElement.evaluate( - (el) => getComputedStyle(el).backgroundColor, - ) - expect(backgroundColor).toBe('rgb(59, 130, 246)') - }).toPass({ timeout: 10000 }) + // Verify global styles still work + await expect(globalElement).toBeVisible() + backgroundColor = await globalElement.evaluate( + (el) => getComputedStyle(el).backgroundColor, + ) + expect(backgroundColor).toBe('rgb(59, 130, 246)') }) }) diff --git a/e2e/vue-start/css-modules/tests/setup/global.setup.ts b/e2e/vue-start/css-modules/tests/setup/global.setup.ts index 3593d10ab90..eaf5091c740 100644 --- a/e2e/vue-start/css-modules/tests/setup/global.setup.ts +++ b/e2e/vue-start/css-modules/tests/setup/global.setup.ts @@ -1,6 +1,68 @@ -import { e2eStartDummyServer } from '@tanstack/router-e2e-utils' +import { chromium } from '@playwright/test' +import { + e2eStartDummyServer, + getTestServerPort, +} from '@tanstack/router-e2e-utils' import packageJson from '../../package.json' with { type: 'json' } +async function waitForServer(url: string) { + const start = Date.now() + while (Date.now() - start < 30_000) { + try { + const res = await fetch(url, { redirect: 'manual' }) + if (res.ok) return + } catch { + // ignore + } + await new Promise((r) => setTimeout(r, 250)) + } + throw new Error(`Timed out waiting for dev server at ${url}`) +} + +async function preOptimizeDevServer(baseURL: string) { + const browser = await chromium.launch() + const context = await browser.newContext() + const page = await context.newPage() + + try { + await page.goto(`${baseURL}/`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('global-styled').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + await page.goto(`${baseURL}/modules`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('module-card').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + await page.goto(`${baseURL}/sass-mixin`, { waitUntil: 'domcontentloaded' }) + await page.getByTestId('mixin-styled').waitFor({ state: 'visible' }) + await page.waitForLoadState('networkidle') + + // Ensure we end in a stable state. Vite's optimize step triggers a reload; + // this waits until no further navigations happen for a short window. + for (let i = 0; i < 40; i++) { + const currentUrl = page.url() + await page.waitForTimeout(250) + if (page.url() === currentUrl) { + await page.waitForTimeout(250) + if (page.url() === currentUrl) return + } + } + + throw new Error('Dev server did not reach a stable URL after warmup') + } finally { + await context.close() + await browser.close() + } +} + export default async function setup() { await e2eStartDummyServer(packageJson.name) + + if (process.env.MODE !== 'dev') return + + const port = await getTestServerPort(packageJson.name) + const baseURL = `http://localhost:${port}` + + await waitForServer(baseURL) + await preOptimizeDevServer(baseURL) }