Skip to content

Commit 2be523a

Browse files
nicohrubecclaude
andcommitted
test(tanstackstart-react): Add failing E2E tests for early client init
Reproduces #21088: errors thrown in the client entry before hydration are silently lost because Sentry.init() runs inside getRouter() which is called during hydration. The test also verifies that early console breadcrumbs are attached to the error report. These tests are expected to fail with the current setup and pass once Sentry.init() is moved to the client entry point. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ba97988 commit 2be523a

6 files changed

Lines changed: 113 additions & 0 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { StartClient } from '@tanstack/react-start/client';
2+
import { StrictMode, startTransition } from 'react';
3+
import { hydrateRoot } from 'react-dom/client';
4+
5+
console.log('early-breadcrumb-from-client-entry');
6+
7+
if (window.location.pathname === '/crash-before-hydration') {
8+
throw new Error('Client Entry Crash');
9+
}
10+
11+
startTransition(() => {
12+
hydrateRoot(
13+
document,
14+
<StrictMode>
15+
<StartClient />
16+
</StrictMode>,
17+
);
18+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { createFileRoute } from '@tanstack/react-router';
2+
3+
export const Route = createFileRoute('/crash-before-hydration')({
4+
component: CrashPage,
5+
});
6+
7+
function CrashPage() {
8+
return (
9+
<div>
10+
<p>This page crashes in client.tsx before hydration</p>
11+
</div>
12+
);
13+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForError } from '@sentry-internal/test-utils';
3+
4+
test('should capture errors thrown in client entry before hydration', async ({ page }) => {
5+
const errorEventPromise = waitForError('tanstackstart-react-cloudflare', errorEvent => {
6+
return errorEvent?.exception?.values?.[0]?.value === 'Client Entry Crash';
7+
});
8+
9+
await page.goto('/crash-before-hydration');
10+
11+
const errorEvent = await errorEventPromise;
12+
13+
expect(errorEvent.exception?.values?.[0]).toMatchObject({
14+
type: 'Error',
15+
value: 'Client Entry Crash',
16+
});
17+
18+
const consoleBreadcrumbs = errorEvent.breadcrumbs?.filter(
19+
b => b.category === 'console' && b.message?.includes('early-breadcrumb-from-client-entry'),
20+
);
21+
22+
expect(consoleBreadcrumbs?.length).toBeGreaterThanOrEqual(1);
23+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { StartClient } from '@tanstack/react-start/client';
2+
import { StrictMode, startTransition } from 'react';
3+
import { hydrateRoot } from 'react-dom/client';
4+
5+
console.log('early-breadcrumb-from-client-entry');
6+
7+
if (window.location.pathname === '/crash-before-hydration') {
8+
throw new Error('Client Entry Crash');
9+
}
10+
11+
startTransition(() => {
12+
hydrateRoot(
13+
document,
14+
<StrictMode>
15+
<StartClient />
16+
</StrictMode>,
17+
);
18+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { createFileRoute } from '@tanstack/react-router';
2+
3+
export const Route = createFileRoute('/crash-before-hydration')({
4+
component: CrashPage,
5+
});
6+
7+
function CrashPage() {
8+
return (
9+
<div>
10+
<p>This page crashes in client.tsx before hydration</p>
11+
</div>
12+
);
13+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForError } from '@sentry-internal/test-utils';
3+
4+
const usesManagedTunnelRoute =
5+
(process.env.E2E_TEST_TUNNEL_ROUTE_MODE ?? 'off') !== 'off' || process.env.E2E_TEST_CUSTOM_TUNNEL_ROUTE === '1';
6+
7+
test.skip(usesManagedTunnelRoute, 'Default e2e suites run only in the proxy variant');
8+
9+
test('should capture errors thrown in client entry before hydration', async ({ page }) => {
10+
const errorEventPromise = waitForError('tanstackstart-react', errorEvent => {
11+
return errorEvent?.exception?.values?.[0]?.value === 'Client Entry Crash';
12+
});
13+
14+
await page.goto('/crash-before-hydration');
15+
16+
const errorEvent = await errorEventPromise;
17+
18+
expect(errorEvent.exception?.values?.[0]).toMatchObject({
19+
type: 'Error',
20+
value: 'Client Entry Crash',
21+
});
22+
23+
const consoleBreadcrumbs = errorEvent.breadcrumbs?.filter(
24+
b => b.category === 'console' && b.message?.includes('early-breadcrumb-from-client-entry'),
25+
);
26+
27+
expect(consoleBreadcrumbs?.length).toBeGreaterThanOrEqual(1);
28+
});

0 commit comments

Comments
 (0)