Skip to content

Commit 7d6f9f8

Browse files
authored
test(tanstackstart-react): Wait for hydration before clicking client-error button (#21065)
## Summary - Adds a hydration marker (`data-hydrated="true"` on `<html>`) in `__root.tsx` of the tanstackstart-react e2e app, set in a `useEffect` so it fires only after React hydration completes. - Updates the two flaky client-error tests (`errors.test.ts:9`, `tunnel.test.ts:17`) to wait on that marker before clicking the "Break the client" button. ## Root cause (shared by three reported flakes) [#20641](#20641), [#20685](#20685), and [#20867](#20867) all fail with the same pattern: ``` Test timeout of 30000ms exceeded. ``` …after the same test step: click "Break the client", await the error event. The three issues differ only in which variant they were captured in (tunnel-object, tunnel-static, proxy). Same root cause. The "Break the client" button is server-rendered HTML; its onClick handler (`() => { throw new Error('Sentry Client Test Error'); }`) is attached only when React hydrates. Playwright's `toBeVisible()` polls the DOM for visibility but doesn't wait for hydration — on slow CI runs, the click can fire before React has attached the handler. With no handler attached, the click does nothing, no error is thrown, the Sentry SDK never sees an error, and `await errorEventPromise` times out at 30 s. ## Fix In `__root.tsx`: ```tsx useEffect(() => { document.documentElement.setAttribute('data-hydrated', 'true'); }, []); ``` The `useEffect` only runs on the client after hydration. Tests now wait on it explicitly: ```ts await page.goto('/'); await page.locator('html[data-hydrated="true"]').waitFor(); // ← new await expect(page.locator('button').filter({ hasText: 'Break the client' })).toBeVisible(); await page.locator('button').filter({ hasText: 'Break the client' }).click(); ``` This is the standard Playwright + SSR-React pattern. Deterministic (no `networkidle` flakiness), self-documenting, and the marker is now reusable for any future client-interaction test in this app. Fixes #20641 Fixes #20685 Fixes #20867 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent 95b909e commit 7d6f9f8

3 files changed

Lines changed: 19 additions & 1 deletion

File tree

dev-packages/e2e-tests/test-applications/tanstackstart-react/src/routes/__root.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ReactNode } from 'react';
1+
import { useEffect, type ReactNode } from 'react';
22
import { Outlet, createRootRoute, HeadContent, Scripts } from '@tanstack/react-router';
33

44
export const Route = createRootRoute({
@@ -20,6 +20,14 @@ export const Route = createRootRoute({
2020
});
2121

2222
function RootComponent() {
23+
// Mark the document as hydrated so tests can wait for React to attach event
24+
// handlers before clicking. `toBeVisible()` only confirms the SSR HTML is
25+
// rendered; on slow CI runs Playwright can click before hydration completes,
26+
// the onClick handler isn't yet attached, and tests asserting on the
27+
// resulting client-side error time out (see #20641, #20685, #20867).
28+
useEffect(() => {
29+
document.documentElement.setAttribute('data-hydrated', 'true');
30+
}, []);
2331
return (
2432
<RootDocument>
2533
<Outlet />

dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/errors.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ test('Sends client-side error to Sentry with auto-instrumentation', async ({ pag
1313

1414
await page.goto(`/`);
1515

16+
// Wait for React to hydrate (see __root.tsx) before clicking — the SSR HTML
17+
// renders the button before the onClick handler is attached, and clicking
18+
// pre-hydration would fire no handler and produce no error.
19+
await page.locator('html[data-hydrated="true"]').waitFor();
20+
1621
await expect(page.locator('button').filter({ hasText: 'Break the client' })).toBeVisible();
1722

1823
await page.locator('button').filter({ hasText: 'Break the client' }).click();

dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/tunnel.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ test('Sends client-side errors through the configured tunnel route', async ({ pa
2222
await page.goto('/');
2323
const pageOrigin = new URL(page.url()).origin;
2424

25+
// Wait for React to hydrate (see __root.tsx) before clicking — the SSR HTML
26+
// renders the button before the onClick handler is attached, and clicking
27+
// pre-hydration would fire no handler and produce no error.
28+
await page.locator('html[data-hydrated="true"]').waitFor();
29+
2530
await expect(page.locator('button').filter({ hasText: 'Break the client' })).toBeVisible();
2631

2732
const managedTunnelResponsePromise = page.waitForResponse(response => {

0 commit comments

Comments
 (0)