From a96254ef855aa613c71e5f387f71b7d00b1a0e16 Mon Sep 17 00:00:00 2001 From: Katja Lutz Date: Tue, 25 Jun 2024 17:00:32 +0200 Subject: [PATCH] fix: wait for hydration via react effect --- test/basic.test.ts | 1 + test/fs-router.test.ts | 3 +++ test/helpers/create-fixture.ts | 3 +++ test/helpers/playwright-fixture.ts | 28 +++++++++++++++++++++++++ test/hmr.test.ts | 9 +------- test/playwright.config.ts | 4 ++-- test/templates/react-ssr-fs/app/app.tsx | 16 ++++++++++++-- test/templates/react/app/root.tsx | 17 ++++++++++++--- 8 files changed, 66 insertions(+), 15 deletions(-) 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..cc78c118 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,6 +77,7 @@ testDevAndProd("fs-router", ({ createFixture }) => { await app.clickElement("a[href='/hello']"); await app.waitForURL("/hello"); + await app.isReady(); await new Promise((r) => setTimeout(r, 1000)); expect(await app.getHtml("[data-test-id=title]")).toBe( @@ -87,6 +89,7 @@ testDevAndProd("fs-router", ({ createFixture }) => { await app.clickElement("a[href='/']"); await app.waitForURL("/"); + await app.isReady(); await new Promise((r) => setTimeout(r, 2000)); expect(await app.getHtml("[data-test-id=title]")).toBe( 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..ba4c2790 100644 --- a/test/helpers/playwright-fixture.ts +++ b/test/helpers/playwright-fixture.ts @@ -190,6 +190,34 @@ 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 = true || !!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) + 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..62d51924 100644 --- a/test/playwright.config.ts +++ b/test/playwright.config.ts @@ -4,13 +4,13 @@ import { devices } from "@playwright/test"; const config: PlaywrightTestConfig = { testDir: ".", testMatch: ["**/*.test.ts"], + workers: 1, 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/templates/react-ssr-fs/app/app.tsx b/test/templates/react-ssr-fs/app/app.tsx index 0bf0c666..e0e92483 100644 --- a/test/templates/react-ssr-fs/app/app.tsx +++ b/test/templates/react-ssr-fs/app/app.tsx @@ -1,12 +1,24 @@ -import React from "react"; +import React, { useEffect, useRef } from "react"; import { Counter } from "./Counter"; import "./style.css"; +const useReadyRef = () => { + const ref = useRef(); + useEffect(() => { + ref.current.dataset.ready = ""; + return () => { + ref.current.dataset.ready = null; + } + }, []); + return ref; +} + export default function App({ assets, children }) { const [count, setCount] = React.useState(0); + const ref = useReadyRef(); return ( - + {assets} diff --git a/test/templates/react/app/root.tsx b/test/templates/react/app/root.tsx index 908eb70e..d4afbadb 100644 --- a/test/templates/react/app/root.tsx +++ b/test/templates/react/app/root.tsx @@ -1,10 +1,21 @@ -import React from "react"; - +import React, { useEffect, useRef } from "react"; import { Counter } from "./Counter"; +const useReadyRef = () => { + const ref = useRef(); + useEffect(() => { + ref.current.dataset.ready = ""; + return () => { + ref.current.dataset.ready = null; + } + }, []); + return ref; +} + export default function App({ assets }) { + const ref = useReadyRef(); return ( - + {assets}