1- import { expect , request } from '@playwright/test'
1+ import { expect } from '@playwright/test'
22import { test } from '@tanstack/router-e2e-utils'
33
4+ // Whitelist errors that can occur in CI:
5+ // - net::ERR_NAME_NOT_RESOLVED: transient network issues
6+ // - 504 (Outdated Optimize Dep): Vite dependency optimization reload
7+ const whitelistErrors = [
8+ 'Failed to load resource: net::ERR_NAME_NOT_RESOLVED' ,
9+ 'Failed to load resource: the server responded with a status of 504' ,
10+ ]
11+
412test . describe ( 'CSS styles in SSR (dev mode)' , ( ) => {
13+ test . use ( { whitelistErrors } )
14+
515 // Warmup: trigger Vite's dependency optimization before running tests
6- // This prevents "504 (Outdated Optimize Dep)" errors during actual tests
7- test . beforeAll ( async ( { baseURL } ) => {
8- const context = await request . newContext ( )
16+ // This prevents "optimized dependencies changed. reloading" during actual tests
17+ // We use a real browser context since dep optimization happens on JS load, not HTTP requests
18+ test . beforeAll ( async ( { browser, baseURL } ) => {
19+ const context = await browser . newContext ( )
20+ const page = await context . newPage ( )
921 try {
10- // Hit both pages to trigger any dependency optimization
11- await context . get ( baseURL ! )
12- await context . get ( `${ baseURL } /modules` )
13- // Give Vite time to complete optimization
14- await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) )
15- // Hit again after optimization
16- await context . get ( baseURL ! )
22+ // Load both pages to trigger dependency optimization
23+ await page . goto ( baseURL ! )
24+ await page . waitForTimeout ( 2000 ) // Wait for deps to optimize
25+ await page . goto ( `${ baseURL } /modules` )
26+ await page . waitForTimeout ( 2000 )
27+ // Load again after optimization completes
28+ await page . goto ( baseURL ! )
29+ await page . waitForTimeout ( 1000 )
1730 } catch {
1831 // Ignore errors during warmup
1932 } finally {
20- await context . dispose ( )
33+ await context . close ( )
2134 }
2235 } )
2336
37+ // Helper to build full URL from baseURL and path
38+ // Playwright's goto with absolute paths (like '/modules') ignores baseURL's path portion
39+ // So we need to manually construct the full URL
40+ const buildUrl = ( baseURL : string , path : string ) => {
41+ return baseURL . replace ( / \/ $ / , '' ) + path
42+ }
43+
2444 test . describe ( 'with JavaScript disabled' , ( ) => {
25- test . use ( { javaScriptEnabled : false } )
45+ test . use ( { javaScriptEnabled : false , whitelistErrors } )
2646
27- test ( 'global CSS is applied on initial page load' , async ( { page } ) => {
28- await page . goto ( '/' )
47+ test ( 'global CSS is applied on initial page load' , async ( {
48+ page,
49+ baseURL,
50+ } ) => {
51+ await page . goto ( buildUrl ( baseURL ! , '/' ) )
2952
3053 const element = page . getByTestId ( 'global-styled' )
3154 await expect ( element ) . toBeVisible ( )
@@ -48,8 +71,11 @@ test.describe('CSS styles in SSR (dev mode)', () => {
4871 expect ( borderRadius ) . toBe ( '12px' )
4972 } )
5073
51- test ( 'CSS modules are applied on initial page load' , async ( { page } ) => {
52- await page . goto ( '/modules' )
74+ test ( 'CSS modules are applied on initial page load' , async ( {
75+ page,
76+ baseURL,
77+ } ) => {
78+ await page . goto ( buildUrl ( baseURL ! , '/modules' ) )
5379
5480 const card = page . getByTestId ( 'module-card' )
5581 await expect ( card ) . toBeVisible ( )
@@ -77,8 +103,8 @@ test.describe('CSS styles in SSR (dev mode)', () => {
77103 expect ( borderRadius ) . toBe ( '8px' )
78104 } )
79105
80- test ( 'global CSS class names are NOT scoped' , async ( { page } ) => {
81- await page . goto ( '/' )
106+ test ( 'global CSS class names are NOT scoped' , async ( { page, baseURL } ) => {
107+ await page . goto ( buildUrl ( baseURL ! , '/' ) )
82108
83109 const element = page . getByTestId ( 'global-styled' )
84110 await expect ( element ) . toBeVisible ( )
@@ -89,37 +115,47 @@ test.describe('CSS styles in SSR (dev mode)', () => {
89115 } )
90116 } )
91117
92- test ( 'styles persist after hydration' , async ( { page } ) => {
93- await page . goto ( '/' )
94-
95- // Wait for hydration
96- await page . waitForTimeout ( 1000 )
118+ test ( 'styles persist after hydration' , async ( { page, baseURL } ) => {
119+ await page . goto ( buildUrl ( baseURL ! , '/' ) )
97120
121+ // Wait for hydration and styles to be applied
98122 const element = page . getByTestId ( 'global-styled' )
99- const backgroundColor = await element . evaluate (
100- ( el ) => getComputedStyle ( el ) . backgroundColor ,
101- )
102- expect ( backgroundColor ) . toBe ( 'rgb(59, 130, 246)' )
103- } )
123+ await expect ( element ) . toBeVisible ( )
104124
105- test ( 'CSS modules styles persist after hydration' , async ( { page } ) => {
106- await page . goto ( '/modules' )
125+ // Wait for CSS to be applied (background color should not be transparent)
126+ await expect ( async ( ) => {
127+ const backgroundColor = await element . evaluate (
128+ ( el ) => getComputedStyle ( el ) . backgroundColor ,
129+ )
130+ expect ( backgroundColor ) . toBe ( 'rgb(59, 130, 246)' )
131+ } ) . toPass ( { timeout : 5000 } )
132+ } )
107133
108- // Wait for hydration
109- await page . waitForTimeout ( 1000 )
134+ test ( 'CSS modules styles persist after hydration' , async ( {
135+ page,
136+ baseURL,
137+ } ) => {
138+ await page . goto ( buildUrl ( baseURL ! , '/modules' ) )
110139
140+ // Wait for hydration and styles to be applied
111141 const card = page . getByTestId ( 'module-card' )
112- const backgroundColor = await card . evaluate (
113- ( el ) => getComputedStyle ( el ) . backgroundColor ,
114- )
115- expect ( backgroundColor ) . toBe ( 'rgb(240, 253, 244)' )
142+ await expect ( card ) . toBeVisible ( )
143+
144+ // Wait for CSS to be applied (background color should not be transparent)
145+ await expect ( async ( ) => {
146+ const backgroundColor = await card . evaluate (
147+ ( el ) => getComputedStyle ( el ) . backgroundColor ,
148+ )
149+ expect ( backgroundColor ) . toBe ( 'rgb(240, 253, 244)' )
150+ } ) . toPass ( { timeout : 5000 } )
116151 } )
117152
118153 test ( 'styles work correctly after client-side navigation' , async ( {
119154 page,
155+ baseURL,
120156 } ) => {
121157 // Start from home
122- await page . goto ( '/' )
158+ await page . goto ( buildUrl ( baseURL ! , '/' ) )
123159 await page . waitForTimeout ( 1000 )
124160
125161 // Verify initial styles
@@ -132,7 +168,8 @@ test.describe('CSS styles in SSR (dev mode)', () => {
132168
133169 // Navigate to modules page
134170 await page . getByTestId ( 'nav-modules' ) . click ( )
135- await page . waitForURL ( '/modules' )
171+ // Use glob pattern to match with or without basepath
172+ await page . waitForURL ( '**/modules' )
136173
137174 // Verify CSS modules styles
138175 const card = page . getByTestId ( 'module-card' )
@@ -144,7 +181,9 @@ test.describe('CSS styles in SSR (dev mode)', () => {
144181
145182 // Navigate back to home
146183 await page . getByTestId ( 'nav-home' ) . click ( )
147- await page . waitForURL ( '/' )
184+ // Match home URL with or without trailing slash and optional query string
185+ // Matches: /, /?, /my-app, /my-app/, /my-app?foo=bar
186+ await page . waitForURL ( / \/ ( [ ^ / ] * ) ( \/ ) ? ( $ | \? ) / )
148187
149188 // Verify global styles still work
150189 await expect ( globalElement ) . toBeVisible ( )
0 commit comments