···12121313test.describe.configure({ mode: "serial" });
14141515+/**
1616+ * Wait for dev server to complete a build/rerun by polling logs
1717+ */
1818+async function waitForBuildComplete(devServer: any, timeoutMs = 20000): Promise<string[]> {
1919+ const startTime = Date.now();
2020+2121+ while (Date.now() - startTime < timeoutMs) {
2222+ const logs = devServer.getLogs(100);
2323+ const logsText = logs.join("\n").toLowerCase();
2424+2525+ // Look for completion messages
2626+ if (logsText.includes("finished") ||
2727+ logsText.includes("rerun finished") ||
2828+ logsText.includes("build finished")) {
2929+ return logs;
3030+ }
3131+3232+ // Wait 100ms before checking again
3333+ await new Promise(resolve => setTimeout(resolve, 100));
3434+ }
3535+3636+ throw new Error(`Build did not complete within ${timeoutMs}ms`);
3737+}
3838+1539test.describe("Hot Reload", () => {
4040+ // Increase timeout for these tests since they involve compilation
4141+ test.setTimeout(60000);
4242+1643 const fixturePath = resolve(__dirname, "..", "fixtures", "hot-reload");
1744 const indexPath = resolve(fixturePath, "src", "pages", "index.rs");
1845 const mainPath = resolve(fixturePath, "src", "main.rs");
4646+ const dataPath = resolve(fixturePath, "data.txt");
1947 let originalIndexContent: string;
2048 let originalMainContent: string;
4949+ let originalDataContent: string;
21502251 test.beforeAll(async () => {
2352 // Save original content
2453 originalIndexContent = readFileSync(indexPath, "utf-8");
2554 originalMainContent = readFileSync(mainPath, "utf-8");
5555+ originalDataContent = readFileSync(dataPath, "utf-8");
5656+5757+ // Ensure files are in original state
5858+ writeFileSync(indexPath, originalIndexContent, "utf-8");
5959+ writeFileSync(mainPath, originalMainContent, "utf-8");
6060+ writeFileSync(dataPath, originalDataContent, "utf-8");
2661 });
27622828- test.afterEach(async () => {
6363+ test.afterEach(async ({ devServer }) => {
2964 // Restore original content after each test
3065 writeFileSync(indexPath, originalIndexContent, "utf-8");
3166 writeFileSync(mainPath, originalMainContent, "utf-8");
3232- // Wait a bit for the rebuild
3333- await new Promise((resolve) => setTimeout(resolve, 2000));
6767+ writeFileSync(dataPath, originalDataContent, "utf-8");
6868+6969+ // Only wait for build if devServer is available (startup might have failed)
7070+ if (devServer) {
7171+ try {
7272+ devServer.clearLogs();
7373+ await waitForBuildComplete(devServer);
7474+ } catch (error) {
7575+ console.warn("Failed to wait for build completion in afterEach:", error);
7676+ }
7777+ }
3478 });
35793680 test.afterAll(async () => {
3781 // Restore original content
3882 writeFileSync(indexPath, originalIndexContent, "utf-8");
3983 writeFileSync(mainPath, originalMainContent, "utf-8");
8484+ writeFileSync(dataPath, originalDataContent, "utf-8");
4085 });
41864287 test("should recompile when Rust code changes (dependencies)", async ({ page, devServer }) => {
···55100 );
56101 writeFileSync(mainPath, modifiedMain, "utf-8");
571025858- // Wait for rebuild to complete - look for "finished" in logs
5959- await new Promise((resolve) => {
6060- const checkInterval = setInterval(() => {
6161- const logs = devServer.getLogs(50).join("\n");
6262- if (logs.includes("finished") || logs.includes("Rebuild")) {
6363- clearInterval(checkInterval);
6464- resolve(null);
6565- }
6666- }, 100);
6767-6868- // Timeout after 15 seconds
6969- setTimeout(() => {
7070- clearInterval(checkInterval);
7171- resolve(null);
7272- }, 15000);
7373- });
103103+ // Wait for rebuild to complete
104104+ const logs = await waitForBuildComplete(devServer, 20000);
105105+ const logsText = logs.join("\n");
7410675107 // Check logs to verify it actually recompiled (ran cargo)
7676- const logs = devServer.getLogs(50).join("\n");
7777- expect(logs).toContain("rebuilding");
7878- expect(logs).not.toContain("Rerunning binary");
7979- expect(logs).not.toContain("rerunning binary");
108108+ expect(logsText).toContain("rebuilding");
109109+ // Make sure it didn't just rerun the binary
110110+ expect(logsText.toLowerCase()).not.toContain("rerunning binary");
80111 });
811128282- test("should rerun without recompile when template changes (non-dependencies)", async ({
8383- page,
8484- devServer
113113+ test("should rerun without recompile when non-dependency files change", async ({
114114+ page,
115115+ devServer,
85116 }) => {
86117 await page.goto(devServer.url);
8711888119 // Verify initial content
89120 await expect(page.locator("#title")).toHaveText("Original Title");
901219191- // Prepare to wait for actual reload
9292- const currentUrl = page.url();
9393-94122 // Clear logs to track what happens after this point
95123 devServer.clearLogs();
961249797- // Modify the template in index.rs - this should NOT require recompilation
9898- // since it's just the HTML template, not the actual Rust code structure
9999- const modifiedContent = originalIndexContent.replace(
100100- 'h1 id="title" { "Original Title" }',
101101- 'h1 id="title" { "Template Updated" }',
102102- );
103103- writeFileSync(indexPath, modifiedContent, "utf-8");
125125+ // Modify data.txt - this file is NOT in the .d dependencies
126126+ // So it should trigger a rerun without recompilation
127127+ writeFileSync(dataPath, "Modified data", "utf-8");
104128105105- // Wait for the page to reload
106106- await page.waitForURL(currentUrl, { timeout: 15000 });
107107-108108- // Verify the updated content
109109- await expect(page.locator("#title")).toHaveText("Template Updated", { timeout: 15000 });
110110-111111- // Give logs time to be captured
112112- await new Promise((resolve) => setTimeout(resolve, 1000));
129129+ // Wait for build/rerun to complete
130130+ const logs = await waitForBuildComplete(devServer, 20000);
131131+ const logsText = logs.join("\n");
113132114114- // Check logs to verify it did NOT recompile
115115- const logs = devServer.getLogs(50).join("\n");
116116-117117- // Should see "rerunning binary" or similar message
118118- const hasRerunMessage = logs.toLowerCase().includes("rerunning") ||
119119- logs.toLowerCase().includes("rerun");
133133+ // Should see "rerunning binary" message (case insensitive)
134134+ const hasRerunMessage = logsText.toLowerCase().includes("rerunning binary");
120135 expect(hasRerunMessage).toBe(true);
121121-122122- // Should NOT see cargo compilation messages
123123- expect(logs).not.toContain("Compiling");
124124- expect(logs.toLowerCase()).not.toContain("rebuilding");
136136+137137+ // Should NOT see "rebuilding" message
138138+ expect(logsText.toLowerCase()).not.toContain("rebuilding");
125139 });
126140127141 test("should show updated content after file changes", async ({ page, devServer }) => {
+4-2
e2e/tests/test-utils.ts
···64646565 const outputPromise = new Promise<number>((resolve, reject) => {
6666 const timeout = setTimeout(() => {
6767- reject(new Error("Dev server did not start within 60 seconds"));
6868- }, 60000);
6767+ console.error("[test-utils] Dev server startup timeout. Recent logs:");
6868+ console.error(capturedLogs.slice(-20).join("\n"));
6969+ reject(new Error("Dev server did not start within 120 seconds"));
7070+ }, 120000); // Increased to 120 seconds for CI
69717072 childProcess.stdout?.on("data", (data: Buffer) => {
7173 const output = data.toString();