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' } }, + }, + ]); +});