diff --git a/test/basic.test.ts b/test/basic.test.ts
index ae7c9c56..36b00125 100644
--- a/test/basic.test.ts
+++ b/test/basic.test.ts
@@ -44,6 +44,7 @@ testDevAndProd("basic", ({ createFixture }) => {
test("hydrates", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/", true);
+ await app.isReady();
expect(await app.getHtml("[data-test-id=content]")).toBe(
prettyHtml(`
Hello from Vinxi
`),
diff --git a/test/fs-router.test.ts b/test/fs-router.test.ts
index 6b927395..d3afed7e 100644
--- a/test/fs-router.test.ts
+++ b/test/fs-router.test.ts
@@ -60,6 +60,7 @@ testDevAndProd("fs-router", ({ createFixture }) => {
test("hydrates", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/", true);
+ await app.isReady();
expect(await app.getHtml("[data-test-id=title]")).toBe(
prettyHtml(`Vinxi Home
`),
@@ -76,7 +77,7 @@ testDevAndProd("fs-router", ({ createFixture }) => {
await app.clickElement("a[href='/hello']");
await app.waitForURL("/hello");
- await new Promise((r) => setTimeout(r, 1000));
+ await app.isReady();
expect(await app.getHtml("[data-test-id=title]")).toBe(
prettyHtml(`Hello from Vinxi
`),
@@ -87,7 +88,7 @@ testDevAndProd("fs-router", ({ createFixture }) => {
await app.clickElement("a[href='/']");
await app.waitForURL("/");
- await new Promise((r) => setTimeout(r, 2000));
+ await app.isReady();
expect(await app.getHtml("[data-test-id=title]")).toBe(
prettyHtml(`Vinxi Home
`),
diff --git a/test/helpers/create-fixture.ts b/test/helpers/create-fixture.ts
index 7146b5d4..5e7d2b23 100644
--- a/test/helpers/create-fixture.ts
+++ b/test/helpers/create-fixture.ts
@@ -133,8 +133,10 @@ export async function createDevFixture(init: FixtureInit) {
await fse.remove(path.join(projectDir, filename));
} else {
await fse.writeFile(path.join(projectDir, filename), prevValue);
+ await new Promise((r) => setTimeout(r, 2000));
}
}
+ cache.clear();
// await fse.remove(projectDir);
// projectDir = await createFixtureProject(init);
},
@@ -157,6 +159,7 @@ export async function createDevFixture(init: FixtureInit) {
}
await fse.writeFile(path.join(projectDir, filename), content);
+ await new Promise((r) => setTimeout(r, 2000));
},
createServer: async () => {
return {
diff --git a/test/helpers/playwright-fixture.ts b/test/helpers/playwright-fixture.ts
index d6bbc95a..428beae5 100644
--- a/test/helpers/playwright-fixture.ts
+++ b/test/helpers/playwright-fixture.ts
@@ -190,6 +190,36 @@ export class PlaywrightFixture {
cp.exec(`open ${this.app.serverUrl}${href}`);
return new Promise(res => setTimeout(res, ms));
}
+
+ /**
+ * Checks if the page is hydrated via a data-ready attribute,
+ * that is being set with useEffect.
+ */
+ async isReady () {
+ let DEBUG = !!process.env.DEBUG;
+
+ const readyCheck = async (timeout?: number) => {
+ await this.page.waitForLoadState("networkidle");
+ await this.page.waitForLoadState("load");
+ await this.page.locator('[data-ready]').waitFor({ state: "attached", timeout });
+ }
+
+ for(let i = 1; i <= 3; i++) {
+ try {
+ await readyCheck(20 * i * 1000)
+ // Give React Suspense some extra time to remove its magical display: hidden
+ await new Promise(res => setTimeout(res, 2000));
+ return true;
+ } catch(err) {
+ if (DEBUG) {
+ console.log('Something went wrong during the page hydration, reloading the page')
+ }
+ await this.page.reload()
+ }
+ }
+
+ throw new Error('Page hydration failed');
+ }
}
export async function getHtml(page: Page, selector?: string) {
diff --git a/test/hmr.test.ts b/test/hmr.test.ts
index 45429deb..7aa12919 100644
--- a/test/hmr.test.ts
+++ b/test/hmr.test.ts
@@ -74,8 +74,6 @@ test.describe("hmr", () => {
}`,
);
- await new Promise((r) => setTimeout(r, 1000));
-
res = await fixture.requestDocument("/");
expect(res.status).toBe(200);
expect(res.headers.get("Content-Type")).toBe("text/html");
@@ -87,6 +85,7 @@ test.describe("hmr", () => {
test("client hmr", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/", true);
+ await app.isReady();
expect(await app.getHtml("[data-test-id=content]")).toBe(
prettyHtml(`Hello from Vinxi
`),
@@ -118,8 +117,6 @@ test.describe("hmr", () => {
}`,
);
- await new Promise((r) => setTimeout(r, 1000));
-
expect(await app.getHtml("[data-test-id=button]")).toBe(
prettyHtml(``),
);
@@ -138,8 +135,6 @@ test.describe("hmr", () => {
}`,
);
- await new Promise((r) => setTimeout(r, 1000));
-
res = await fixture.requestDocument("/api/hello");
expect(res.status).toBe(200);
expect(res.headers.get("Content-Type")).toBe("text/html");
@@ -152,8 +147,6 @@ test.describe("hmr", () => {
}`,
);
- await new Promise((r) => setTimeout(r, 2000));
-
res = await fixture.requestDocument("/api/new");
expect(res.status).toBe(200);
expect(res.headers.get("Content-Type")).toBe("text/html");
diff --git a/test/playwright.config.ts b/test/playwright.config.ts
index d29cb353..c1432b75 100644
--- a/test/playwright.config.ts
+++ b/test/playwright.config.ts
@@ -5,12 +5,11 @@ const config: PlaywrightTestConfig = {
testDir: ".",
testMatch: ["**/*.test.ts"],
fullyParallel: false,
- timeout: process.env.CI ? 120_000 : 30_000, // 2 minutes in CI, 30 seconds locally
+ timeout: process.env.CI ? 360_000 : 30_000, // 5 minutes in CI, 30 seconds locally
expect: {
timeout: 5_000, // 5 second retries for assertions
},
forbidOnly: !!process.env.CI,
- retries: process.env.CI ? 2 : 0,
reporter: process.env.CI
? "github"
: [["html", { open: process.env.TEST_REPORT ? "always" : "none" }]],
diff --git a/test/rsc.test.ts b/test/rsc.test.ts
index b15c53bd..dc42d84b 100644
--- a/test/rsc.test.ts
+++ b/test/rsc.test.ts
@@ -36,6 +36,7 @@ test.describe("rsc", () => {
test("spa", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/", true);
+ await app.isReady();
expect(await app.getHtml("[data-test-id=title]")).toBe(
prettyHtml(`Hello from Vinxi
`),
diff --git a/test/srv-fn.test.ts b/test/srv-fn.test.ts
index 862ed6c0..d362e8c1 100644
--- a/test/srv-fn.test.ts
+++ b/test/srv-fn.test.ts
@@ -32,6 +32,7 @@ testDevAndProd("srv-fn", ({ createFixture }) => {
test("spa", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/", true);
+ await app.isReady();
let responses = app.collectResponses();
await app.clickElement("[data-test-id=button]");
diff --git a/test/templates/react-rsc/app/Counter.tsx b/test/templates/react-rsc/app/Counter.tsx
index 84657b21..be79514e 100644
--- a/test/templates/react-rsc/app/Counter.tsx
+++ b/test/templates/react-rsc/app/Counter.tsx
@@ -1,8 +1,14 @@
"use client";
-import { useState } from "react";
+import { useEffect, useState } from "react";
export function Counter({ onChange }) {
+ useEffect(() => {
+ document.documentElement.dataset.ready = "";
+ return () => {
+ document.documentElement.dataset.ready = null;
+ }
+ }, []);
const [count, setCount] = useState(0);
return (