A tool for people curious about the React Server Components protocol rscexplorer.dev/
rsc react

add ci and unflake tests

+384 -108
+23
.github/workflows/ci.yml
··· 1 + name: CI 2 + 3 + on: 4 + push: 5 + branches: [main] 6 + pull_request: 7 + branches: [main] 8 + 9 + jobs: 10 + test: 11 + runs-on: ubuntu-latest 12 + steps: 13 + - uses: actions/checkout@v4 14 + - uses: actions/setup-node@v4 15 + with: 16 + node-version: 22 17 + cache: npm 18 + - run: npm install 19 + - run: npm run lint 20 + - run: npm run format:check 21 + - run: npx playwright install --with-deps chromium 22 + - run: npm test 23 + - run: npm run build:versions
+1 -1
package.json
··· 10 10 "build:versions": "node scripts/build-version.js --all", 11 11 "dev": "vite", 12 12 "preview": "vite preview", 13 - "deploy": "npm install && npm test && npm run build:versions && wrangler pages deploy dist --project-name=rscexplorer", 13 + "deploy": "npm install && npm run lint && npm test && npm run build:versions && wrangler pages deploy dist --project-name=rscexplorer", 14 14 "test": "vitest run --reporter=verbose", 15 15 "lint": "eslint .", 16 16 "format": "prettier --write .",
+1 -1
src/client/samples.js
··· 248 248 borderRadius: 8, 249 249 textAlign: 'center' 250 250 }}> 251 - <p style={{ fontFamily: 'monospace', fontSize: 32, margin: 0 }}>{seconds}s</p> 251 + <p style={{ fontFamily: 'monospace', fontSize: 32, margin: 0 }}>Timer: {seconds}s</p> 252 252 </div> 253 253 ) 254 254 }
+12 -5
tests/async.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 32 31 </Suspense> 33 32 </div>" 34 33 `); 35 - expect(await h.preview()).toMatchInlineSnapshot(`"Async Component Loading..."`); 34 + expect(await h.preview("Loading")).toMatchInlineSnapshot(` 35 + "Async Component 36 + 37 + Loading..." 38 + `); 36 39 37 40 // Tree changes when async data resolves 38 41 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 45 48 </Suspense> 46 49 </div>" 47 50 `); 48 - expect(await h.preview()).toMatchInlineSnapshot(`"Async Component Loading..."`); 51 + expect(await h.preview("Data loaded")).toMatchInlineSnapshot(` 52 + "Async Component 53 + 54 + Data loaded!" 55 + `); 49 56 });
+62 -15
tests/bound.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 31 30 <Greeter action={[Function: bound greet]} /> 32 31 </div>" 33 32 `); 34 - expect(await h.preview()).toMatchInlineSnapshot( 35 - `"Bound Actions Same action, different bound greetings: Greet Greet Greet"`, 33 + expect(await h.preview("bound greetings")).toMatchInlineSnapshot( 34 + ` 35 + "Bound Actions 36 + 37 + Same action, different bound greetings: 38 + 39 + Greet 40 + Greet 41 + Greet" 42 + `, 36 43 ); 37 44 }); 38 45 ··· 49 56 <Greeter action={[Function: bound greet]} /> 50 57 </div>" 51 58 `); 52 - expect(await h.preview()).toMatchInlineSnapshot( 53 - `"Bound Actions Same action, different bound greetings: Greet Greet Greet"`, 59 + expect(await h.preview("bound greetings")).toMatchInlineSnapshot( 60 + ` 61 + "Bound Actions 62 + 63 + Same action, different bound greetings: 64 + 65 + Greet 66 + Greet 67 + Greet" 68 + `, 54 69 ); 55 70 56 71 // Fill all three inputs and submit all three forms ··· 67 82 68 83 // First action pending 69 84 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 70 - expect(await h.preview()).toMatchInlineSnapshot( 71 - `"Bound Actions Same action, different bound greetings: Greet Greet Greet"`, 85 + expect(await h.preview("bound greetings")).toMatchInlineSnapshot( 86 + ` 87 + "Bound Actions 88 + 89 + Same action, different bound greetings: 90 + 91 + Greet 92 + Greet 93 + Greet" 94 + `, 72 95 ); 73 96 74 97 // First action resolves - Hello greeting (second still pending) 75 98 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 76 - expect(await h.preview()).toMatchInlineSnapshot( 77 - `"Bound Actions Same action, different bound greetings: GreetHello, Alice! Greet Greet"`, 99 + expect(await h.preview("Hello, Alice")).toMatchInlineSnapshot( 100 + ` 101 + "Bound Actions 102 + 103 + Same action, different bound greetings: 104 + 105 + GreetHello, Alice! 106 + Greet 107 + Greet" 108 + `, 78 109 ); 79 110 80 111 // Second action resolves - Howdy greeting (third still pending) 81 112 expect(await h.stepAll()).toMatchInlineSnapshot(`"Pending"`); 82 - expect(await h.preview()).toMatchInlineSnapshot( 83 - `"Bound Actions Same action, different bound greetings: GreetHello, Alice! GreetHowdy, Bob! Greet"`, 113 + expect(await h.preview("Howdy, Bob")).toMatchInlineSnapshot( 114 + ` 115 + "Bound Actions 116 + 117 + Same action, different bound greetings: 118 + 119 + GreetHello, Alice! 120 + GreetHowdy, Bob! 121 + Greet" 122 + `, 84 123 ); 85 124 86 125 // Third action resolves - Hey greeting 87 126 expect(await h.stepAll()).toMatchInlineSnapshot(`""Hey, Charlie!""`); 88 - expect(await h.preview()).toMatchInlineSnapshot( 89 - `"Bound Actions Same action, different bound greetings: GreetHello, Alice! GreetHowdy, Bob! GreetHey, Charlie!"`, 127 + expect(await h.preview("Hey, Charlie")).toMatchInlineSnapshot( 128 + ` 129 + "Bound Actions 130 + 131 + Same action, different bound greetings: 132 + 133 + GreetHello, Alice! 134 + GreetHowdy, Bob! 135 + GreetHey, Charlie!" 136 + `, 90 137 ); 91 138 });
+9 -4
tests/clientref.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 37 36 </div> 38 37 </div>" 39 38 `); 40 - expect(await h.preview()).toMatchInlineSnapshot(`"Client Reference Dark theme Light theme"`); 39 + expect(await h.preview("Dark theme")).toMatchInlineSnapshot( 40 + ` 41 + "Client Reference 42 + Dark theme 43 + Light theme" 44 + `, 45 + ); 41 46 });
+18 -5
tests/counter.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 27 26 <Counter initialCount={0} /> 28 27 </div>" 29 28 `); 30 - expect(await h.preview()).toMatchInlineSnapshot(`"Counter Count: 0 − +"`); 29 + expect(await h.preview("Count: 0")).toMatchInlineSnapshot(` 30 + "Counter 31 + 32 + Count: 0 33 + 34 + 35 + +" 36 + `); 31 37 32 38 // Client interactivity works 33 39 await h.frame().locator(".preview-container button").last().click(); 34 - expect(await h.preview()).toContain("Count: 1"); 40 + expect(await h.preview("Count: 1")).toMatchInlineSnapshot(` 41 + "Counter 42 + 43 + Count: 1 44 + 45 + 46 + +" 47 + `); 35 48 });
+15 -6
tests/errors.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 44 43 </ErrorBoundary> 45 44 </div>" 46 45 `); 47 - expect(await h.preview()).toContain("Error Handling"); 46 + expect(await h.preview("Loading user")).toMatchInlineSnapshot(` 47 + "Error Handling 48 + 49 + Loading user..." 50 + `); 48 51 49 52 // After async resolves with error, error boundary catches it 50 53 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 69 72 </ErrorBoundary> 70 73 </div>" 71 74 `); 72 - expect(await h.preview()).toContain("Failed to load user"); 73 - expect(await h.preview()).toContain("Please try again later"); 75 + expect(await h.preview("Failed to load user")).toMatchInlineSnapshot( 76 + ` 77 + "Error Handling 78 + Failed to load user 79 + 80 + Please try again later." 81 + `, 82 + ); 74 83 });
+18 -6
tests/form.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 27 26 <Form greetAction={[Function: greet]} /> 28 27 </div>" 29 28 `); 30 - expect(await h.preview()).toMatchInlineSnapshot(`"Form Action Greet"`); 29 + expect(await h.preview("Greet")).toMatchInlineSnapshot(` 30 + "Form Action 31 + Greet" 32 + `); 31 33 32 34 // Submit form 33 35 await h.frame().locator('.preview-container input[name="name"]').fill("World"); 34 36 await h.frame().locator(".preview-container button").click(); 35 - expect(await h.preview()).toMatchInlineSnapshot(`"Form Action Sending..."`); 37 + expect(await h.preview("Sending")).toMatchInlineSnapshot(` 38 + "Form Action 39 + Sending..." 40 + `); 36 41 37 42 // Action response 38 43 expect(await h.stepAll()).toMatchInlineSnapshot(`"{ message: "Hello, World!", error: null }"`); 39 - expect(await h.preview()).toMatchInlineSnapshot(`"Form Action Greet Hello, World!"`); 44 + expect(await h.preview("Hello, World")).toMatchInlineSnapshot( 45 + ` 46 + "Form Action 47 + Greet 48 + 49 + Hello, World!" 50 + `, 51 + ); 40 52 });
+29 -27
tests/globalSetup.js
··· 1 - import { spawn, execSync } from "child_process"; 2 - 3 - let devServer; 4 - 5 - export async function setup() { 6 - await new Promise((resolve, reject) => { 7 - devServer = spawn("npm", ["run", "dev", "--", "--port", "5599"], { 8 - stdio: ["ignore", "pipe", "pipe"], 9 - detached: false, 10 - }); 1 + import { spawn } from "child_process"; 11 2 12 - devServer.stdout.on("data", (data) => { 13 - if (data.toString().includes("Local:")) { 14 - resolve(); 15 - } 16 - }); 3 + let server; 17 4 18 - devServer.stderr.on("data", (data) => { 19 - console.error(data.toString()); 20 - }); 5 + async function waitForServer(url, timeout = 30000) { 6 + const start = Date.now(); 7 + while (Date.now() - start < timeout) { 8 + try { 9 + const res = await fetch(url); 10 + if (res.ok) return; 11 + } catch { 12 + // Server not ready yet 13 + } 14 + await new Promise((r) => setTimeout(r, 100)); 15 + } 16 + throw new Error("Test server start timeout"); 17 + } 21 18 22 - devServer.on("error", reject); 19 + export async function setup() { 20 + server = spawn("npx", ["vite", "--port", "5599", "--strictPort"], { 21 + stdio: "inherit", 22 + shell: true, 23 + }); 23 24 24 - // Timeout after 30s 25 - setTimeout(() => reject(new Error("Dev server failed to start")), 30000); 25 + server.on("close", (code) => { 26 + if (code === 1) { 27 + console.error("\n\x1b[31mTest server failed to start (port 5599 in use?)\x1b[0m\n"); 28 + process.exit(1); 29 + } 26 30 }); 31 + 32 + await waitForServer("http://localhost:5599"); 27 33 } 28 34 29 35 export async function teardown() { 30 - if (devServer) { 31 - devServer.kill("SIGTERM"); 32 - // Also kill any process on the port 33 - try { 34 - execSync("lsof -ti:5599 | xargs kill -9 2>/dev/null || true"); 35 - } catch {} 36 + if (server) { 37 + server.kill(); 36 38 } 37 39 }
+3 -4
tests/hello.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 22 21 await h.load("hello"); 23 22 24 23 expect(await h.stepAll()).toMatchInlineSnapshot(`"<h1>Hello World</h1>"`); 25 - expect(await h.preview()).toMatchInlineSnapshot(`"Hello World"`); 24 + expect(await h.preview("Hello World")).toMatchInlineSnapshot(`"Hello World"`); 26 25 });
+12 -2
tests/helpers.js
··· 1 1 import { expect } from "vitest"; 2 + import { chromium } from "playwright"; 3 + 4 + export async function launchBrowser() { 5 + const executablePath = process.env.CHROMIUM_PATH; 6 + return chromium.launch(executablePath ? { executablePath } : undefined); 7 + } 2 8 3 9 let prevRowTexts = []; 4 10 let prevStatuses = []; ··· 25 31 } 26 32 27 33 async function getPreviewText() { 28 - return (await frameRef.locator(".preview-container").innerText()).trim().replace(/\s+/g, " "); 34 + return (await frameRef.locator(".preview-container").innerText()).trim(); 29 35 } 30 36 31 37 async function doStep() { ··· 130 136 return await tree(); 131 137 } 132 138 133 - async function preview() { 139 + async function preview(waitFor) { 140 + if (waitFor) { 141 + // Wait for preview to contain the marker 142 + await expect.poll(() => getPreviewText(), { timeout: 10000 }).toContain(waitFor); 143 + } 134 144 const current = await getPreviewText(); 135 145 if (current !== prevPreview) { 136 146 prevPreview = current;
+62 -6
tests/kitchensink.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 36 35 </Suspense> 37 36 </div>" 38 37 `); 39 - expect(await h.preview()).toMatchInlineSnapshot(`"Kitchen Sink Loading..."`); 38 + expect(await h.preview("Loading...")).toMatchInlineSnapshot(` 39 + "Kitchen Sink 40 + 41 + Loading..." 42 + `); 40 43 41 44 // Step to resolve async content (with delayed promise still pending) 42 45 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 133 136 </Suspense> 134 137 </div>" 135 138 `); 136 - expect(await h.preview()).toMatchInlineSnapshot(`"Kitchen Sink Loading..."`); 139 + expect(await h.preview("Loading...")).toMatchInlineSnapshot(` 140 + "Kitchen Sink 141 + 142 + Loading..." 143 + `); 137 144 138 145 // Step to resolve delayed promise 139 146 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 230 237 </Suspense> 231 238 </div>" 232 239 `); 233 - expect(await h.preview()).toMatchInlineSnapshot(`"Kitchen Sink Loading..."`); 240 + expect(await h.preview("hello world")).toMatchInlineSnapshot( 241 + ` 242 + "Kitchen Sink 243 + primitives 244 + null: null 245 + true: true 246 + false: false 247 + int: 42 248 + float: 3.14159 249 + string: hello world 250 + empty: 251 + dollar: $special 252 + unicode: Hello 世界 🌍 253 + special 254 + negZero: 0 255 + inf: Infinity 256 + negInf: -Infinity 257 + nan: NaN 258 + types 259 + date: 2024-01-15T12:00:00.000Z 260 + bigint: 12345678901234567890n 261 + symbol: Symbol(mySymbol) 262 + collections 263 + map: Map(2) 264 + set: Set(3) 265 + formData: FormData 266 + blob: Blob(5) 267 + arrays 268 + simple: [3 items] 269 + sparse: [4 items] 270 + nested: [2 items] 271 + objects 272 + simple: {...} 273 + nested: {...} 274 + elements 275 + div: {...} 276 + fragment: [2 items] 277 + suspense: {...} 278 + promises 279 + resolved: {...} 280 + delayed: {...} 281 + iterators 282 + sync: {...} 283 + refs 284 + dup: {...} 285 + cyclic: {...} 286 + action 287 + Call serverAction" 288 + `, 289 + ); 234 290 });
+105 -13
tests/pagination.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 32 31 </Suspense> 33 32 </div>" 34 33 `); 35 - expect(await h.preview()).toMatchInlineSnapshot(`"Pagination Loading recipes..."`); 34 + expect(await h.preview("Loading recipes")).toMatchInlineSnapshot( 35 + ` 36 + "Pagination 37 + 38 + Loading recipes..." 39 + `, 40 + ); 36 41 37 42 // Then resolves to Paginator with initial items 38 43 expect(await h.stepAll()).toMatchInlineSnapshot(` ··· 76 81 </Suspense> 77 82 </div>" 78 83 `); 79 - expect(await h.preview()).toMatchInlineSnapshot(`"Pagination Loading recipes..."`); 84 + expect(await h.preview("Grilled Cheese")).toMatchInlineSnapshot( 85 + ` 86 + "Pagination 87 + Pasta Carbonara 88 + 89 + 25 min · Medium 90 + 91 + Grilled Cheese 92 + 93 + 10 min · Easy 94 + 95 + Load More" 96 + `, 97 + ); 80 98 81 99 // First Load More 82 100 await h.frame().locator(".preview-container button").click(); 83 - expect(await h.preview()).toMatchInlineSnapshot( 84 - `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Loading..."`, 101 + expect(await h.preview("Loading...")).toMatchInlineSnapshot( 102 + ` 103 + "Pagination 104 + Pasta Carbonara 105 + 106 + 25 min · Medium 107 + 108 + Grilled Cheese 109 + 110 + 10 min · Easy 111 + 112 + Loading..." 113 + `, 85 114 ); 86 115 87 116 // Action returns new items ··· 123 152 hasMore: true 124 153 }" 125 154 `); 126 - expect(await h.preview()).toMatchInlineSnapshot( 127 - `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Load More"`, 155 + expect(await h.preview("Beef Tacos")).toMatchInlineSnapshot( 156 + ` 157 + "Pagination 158 + Pasta Carbonara 159 + 160 + 25 min · Medium 161 + 162 + Grilled Cheese 163 + 164 + 10 min · Easy 165 + 166 + Chicken Stir Fry 167 + 168 + 20 min · Easy 169 + 170 + Beef Tacos 171 + 172 + 30 min · Medium 173 + 174 + Load More" 175 + `, 128 176 ); 129 177 130 178 // Second Load More 131 179 await h.frame().locator(".preview-container button").click(); 132 - expect(await h.preview()).toMatchInlineSnapshot( 133 - `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Loading..."`, 180 + expect(await h.preview("Loading...")).toMatchInlineSnapshot( 181 + ` 182 + "Pagination 183 + Pasta Carbonara 184 + 185 + 25 min · Medium 186 + 187 + Grilled Cheese 188 + 189 + 10 min · Easy 190 + 191 + Chicken Stir Fry 192 + 193 + 20 min · Easy 194 + 195 + Beef Tacos 196 + 197 + 30 min · Medium 198 + 199 + Loading..." 200 + `, 134 201 ); 135 202 136 203 // Final items, hasMore: false ··· 172 239 hasMore: false 173 240 }" 174 241 `); 175 - expect(await h.preview()).toMatchInlineSnapshot( 176 - `"Pagination Pasta Carbonara 25 min · Medium Grilled Cheese 10 min · Easy Chicken Stir Fry 20 min · Easy Beef Tacos 30 min · Medium Caesar Salad 15 min · Easy Mushroom Risotto 45 min · Hard"`, 242 + expect(await h.preview("Mushroom Risotto")).toMatchInlineSnapshot( 243 + ` 244 + "Pagination 245 + Pasta Carbonara 246 + 247 + 25 min · Medium 248 + 249 + Grilled Cheese 250 + 251 + 10 min · Easy 252 + 253 + Chicken Stir Fry 254 + 255 + 20 min · Easy 256 + 257 + Beef Tacos 258 + 259 + 30 min · Medium 260 + 261 + Caesar Salad 262 + 263 + 15 min · Easy 264 + 265 + Mushroom Risotto 266 + 267 + 45 min · Hard" 268 + `, 177 269 ); 178 270 179 271 // No more items - button should be gone
+13 -12
tests/refresh.spec.js
··· 1 1 import { test, expect, beforeAll, afterAll, afterEach } from "vitest"; 2 - import { chromium } from "playwright"; 3 - import { createHelpers } from "./helpers.js"; 2 + import { createHelpers, launchBrowser } from "./helpers.js"; 4 3 5 4 let browser, page, h; 6 5 7 6 beforeAll(async () => { 8 - browser = await chromium.launch(); 7 + browser = await launchBrowser(); 9 8 page = await browser.newPage(); 10 9 h = createHelpers(page); 11 10 }); ··· 33 32 </Suspense> 34 33 </div>" 35 34 `); 36 - expect(await h.preview()).toMatchInlineSnapshot( 37 - `"Router Refresh Client state persists across server navigations Loading..."`, 35 + expect(await h.preview("Loading...")).toMatchInlineSnapshot( 36 + ` 37 + "Router Refresh 38 + 39 + Client state persists across server navigations 40 + 41 + Loading..." 42 + `, 38 43 ); 39 44 40 45 // Step to resolve async Timer content (color is random hsl value) ··· 42 47 expect(tree).toMatch(/Router Refresh/); 43 48 expect(tree).toMatch(/<Timer color="hsl\(\d+, 70%, 85%\)" \/>/); 44 49 45 - // Wait for preview to render the timer (shows "0s" or similar) 46 - await h.waitFor( 47 - () => /\d+s/.test(document.querySelector(".preview-container")?.textContent || ""), 48 - { timeout: 5000 }, 49 - ); 50 - const preview = await h.preview(); 51 - expect(preview).toMatch(/Router Refresh.*\d+s.*Refresh/); 50 + // Wait for preview to render the timer 51 + const preview = await h.preview("Timer:"); 52 + expect(preview).toMatch(/Timer: \d+s[\s\S]*Refresh/); 52 53 });
+1 -1
vitest.config.js
··· 2 2 3 3 export default defineConfig({ 4 4 test: { 5 - testTimeout: 15000, 5 + testTimeout: 30000, 6 6 fileParallelism: true, 7 7 globalSetup: "./tests/globalSetup.js", 8 8 },