From 18abe1105b65d75aad61898f9be529e55073d46b Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Fri, 13 Nov 2026 15:05:31 +0100 Subject: [PATCH] test(nextjs): Cover the server request link of a late trace meta tag The pageload waits at most 10 seconds for the trace meta tag of a resumed shell. When the tag arrives later, the pageload links the server request instead of continuing its trace. Unit tests cover this with fake timers. This adds an E2E test that streams the tag after 10.5 seconds in a real build. --- .../app/slow-stream/page.tsx | 20 +++++++++++++ .../cacheOrigin-pageloadConnection.spec.ts | 30 +++++++++++++++++++ .../app/slow-stream/page.tsx | 20 +++++++++++++ .../cacheOrigin-pageloadConnection.spec.ts | 30 +++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-stream/page.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/slow-stream/page.tsx diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-stream/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-stream/page.tsx new file mode 100644 index 000000000000..478ac04626a6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-stream/page.tsx @@ -0,0 +1,20 @@ +import { headers } from 'next/headers'; +import { Suspense } from 'react'; + +// Above the pageload's 10 second wait for the trace meta tag, but short enough that the tag still +// arrives before the pageload ends. +const DELAY_MS = 10_500; + +async function SlowContent() { + await headers(); + await new Promise(resolve => setTimeout(resolve, DELAY_MS)); + return

Slow content

; +} + +export default function Page() { + return ( + Loading...

}> + +
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts index f06c4da961cd..36efdae23642 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts @@ -212,3 +212,33 @@ test('keeps a navigation that starts while the pageload waits for the trace meta expect(pageloadTx.contexts?.trace?.data?.['sentry.idle_span_finish_reason']).toBe('cancelled'); expect(navigationTx.contexts?.trace?.data?.['sentry.idle_span_finish_reason']).toBe('idleTimeout'); }); + +test('links the server request when its trace meta tag arrives after the pageload stopped waiting', async ({ + page, +}) => { + test.skip(process.env.TEST_ENV !== 'production', 'Only a prerendered shell makes the trace meta tag arrive late'); + test.setTimeout(60_000); + + const pageloadTxPromise = waitForTransaction(APP, transactionEvent => { + return transactionEvent.transaction === '/slow-stream' && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/slow-stream', { waitUntil: 'commit' }); + await expect(page.locator('#slow-content')).toBeVisible({ timeout: 20_000 }); + const [requestTraceId, requestSpanId] = (await page + .locator('meta[name="sentry-trace"]') + .getAttribute('content'))!.split('-'); + + const pageloadTx = await pageloadTxPromise; + + // The tag arrived after the pageload had started its own trace, so the pageload only links it. + expect(pageloadTx.contexts?.trace?.trace_id).not.toBe(requestTraceId); + expect(pageloadTx.contexts?.trace?.links).toEqual([ + { + trace_id: requestTraceId, + span_id: requestSpanId, + sampled: true, + attributes: { [SENTRY_LINK_TYPE]: 'server_request' }, + }, + ]); +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/slow-stream/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/slow-stream/page.tsx new file mode 100644 index 000000000000..478ac04626a6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/slow-stream/page.tsx @@ -0,0 +1,20 @@ +import { headers } from 'next/headers'; +import { Suspense } from 'react'; + +// Above the pageload's 10 second wait for the trace meta tag, but short enough that the tag still +// arrives before the pageload ends. +const DELAY_MS = 10_500; + +async function SlowContent() { + await headers(); + await new Promise(resolve => setTimeout(resolve, DELAY_MS)); + return

Slow content

; +} + +export default function Page() { + return ( + Loading...

}> + +
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts index 3d1512789f82..b5eadf749de5 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts @@ -211,3 +211,33 @@ test('keeps a navigation that starts while the pageload waits for the trace meta value: 'idleTimeout', }); }); + +test('links the server request when its trace meta tag arrives after the pageload stopped waiting', async ({ + page, +}) => { + test.skip(process.env.TEST_ENV !== 'production', 'Only a prerendered shell makes the trace meta tag arrive late'); + test.setTimeout(60_000); + + const pageloadSpanPromise = waitForStreamedSpan(APP, span => { + return span.name === '/slow-stream' && getSpanOp(span) === 'pageload' && span.is_segment; + }); + + await page.goto('/slow-stream', { waitUntil: 'commit' }); + await expect(page.locator('#slow-content')).toBeVisible({ timeout: 20_000 }); + const [requestTraceId, requestSpanId] = (await page + .locator('meta[name="sentry-trace"]') + .getAttribute('content'))!.split('-'); + + const pageloadSpan = await pageloadSpanPromise; + + // The tag arrived after the pageload had started its own trace, so the pageload only links it. + expect(pageloadSpan.trace_id).not.toBe(requestTraceId); + expect(pageloadSpan.links).toEqual([ + { + trace_id: requestTraceId, + span_id: requestSpanId, + sampled: true, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'server_request' } }, + }, + ]); +});