Skip to content

Commit cc3fe1f

Browse files
chargomeclaude
andauthored
test(nextjs): Add webpack variant for span-streaming e2e app (#22704)
Discovered in #22674 that this was not running on webpack so here we go. I added a reusable helper to aggregate streamed spans for e2e tests Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 32c5db9 commit cc3fe1f

4 files changed

Lines changed: 61 additions & 12 deletions

File tree

dev-packages/e2e-tests/test-applications/nextjs-16-streaming/package.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
"dev": "next dev",
77
"build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
88
"clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs",
9+
"dev:webpack": "next dev --webpack",
10+
"build-webpack": "next build --webpack > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
911
"start": "next start",
1012
"lint": "eslint",
1113
"test:prod": "TEST_ENV=production playwright test",
1214
"test:dev": "TEST_ENV=development playwright test",
15+
"test:dev-webpack": "TEST_ENV=development-webpack playwright test",
1316
"test:build": "pnpm install && pnpm build",
14-
"test:assert": "pnpm test:prod && pnpm test:dev"
17+
"test:build-webpack": "pnpm install && pnpm build-webpack",
18+
"test:assert": "pnpm test:prod && pnpm test:dev",
19+
"test:assert-webpack": "pnpm test:prod && pnpm test:dev-webpack"
1520
},
1621
"dependencies": {
1722
"@sentry/nextjs": "file:../../packed/sentry-nextjs-packed.tgz",
@@ -37,5 +42,14 @@
3742
},
3843
"volta": {
3944
"extends": "../../package.json"
45+
},
46+
"sentryTest": {
47+
"variants": [
48+
{
49+
"build-command": "pnpm test:build-webpack",
50+
"label": "nextjs-16-streaming (webpack)",
51+
"assert-command": "pnpm test:assert-webpack"
52+
}
53+
]
4054
}
4155
}

dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/server-components.test.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForStreamedSpan, waitForStreamedSpans, getSpanOp } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpans, waitForStreamedSpan, getSpanOp } from '@sentry-internal/test-utils';
33
import { isDevMode } from './isDevMode';
44

5+
// Streamed spans are flushed across multiple envelopes as they end, so the server-component child spans
6+
// can arrive in a different (earlier) envelope than the `is_segment` root span. Accumulate spans across
7+
// envelopes until the root span (which ends last) is seen.
8+
function collectSpanNamesUntilSegment(segmentName: string): Promise<string[]> {
9+
return collectStreamedSpans('nextjs-16-streaming', spans =>
10+
spans.some(span => span.name === segmentName && span.is_segment),
11+
).then(spans => spans.map(span => span.name));
12+
}
13+
514
test('Sends a streamed span for a request to app router with URL', async ({ page }) => {
615
test.skip(isDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode');
716

@@ -22,14 +31,11 @@ test('Will create streamed spans for every server component and metadata generat
2231
}) => {
2332
test.skip(isDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode');
2433

25-
const spansPromise = waitForStreamedSpans('nextjs-16-streaming', spans => {
26-
return spans.some(span => span.name === 'GET /nested-layout' && span.is_segment);
27-
});
34+
const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout');
2835

2936
await page.goto('/nested-layout');
3037

31-
const spans = await spansPromise;
32-
const spanNames = spans.map(span => span.name);
38+
const spanNames = await spanNamesPromise;
3339

3440
expect(spanNames).toContainEqual('render route (app) /nested-layout');
3541
expect(spanNames).toContainEqual('build component tree');
@@ -46,14 +52,11 @@ test('Will create streamed spans for every server component and metadata generat
4652
}) => {
4753
test.skip(isDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode');
4854

49-
const spansPromise = waitForStreamedSpans('nextjs-16-streaming', spans => {
50-
return spans.some(span => span.name === 'GET /nested-layout/[dynamic]' && span.is_segment);
51-
});
55+
const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout/[dynamic]');
5256

5357
await page.goto('/nested-layout/123');
5458

55-
const spans = await spansPromise;
56-
const spanNames = spans.map(span => span.name);
59+
const spanNames = await spanNamesPromise;
5760

5861
expect(spanNames).toContainEqual('resolve page components');
5962
expect(spanNames).toContainEqual('render route (app) /nested-layout/[dynamic]');

dev-packages/test-utils/src/event-proxy-server.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,37 @@ export function waitForStreamedSpans(
606606
});
607607
}
608608

609+
/**
610+
* Accumulate streamed Span V2 spans across multiple envelopes until `isDone` returns true.
611+
*
612+
* Unlike {@link waitForStreamedSpans}, which resolves with the spans of a single envelope, this
613+
* collects spans from every Span V2 envelope as they arrive and resolves with the full set once
614+
* `isDone` is satisfied. Streamed spans are flushed in multiple envelopes as they end (a child span
615+
* can be sent before its root segment span), so any assertion that needs the whole trace must
616+
* accumulate rather than snapshot a single envelope.
617+
*
618+
* `isDone` receives all spans collected so far. A common predicate is "the segment/root span has
619+
* arrived", since the root ends last and therefore flushes after its children:
620+
*
621+
* @example
622+
* ```ts
623+
* const spans = await collectStreamedSpans(PROXY_SERVER_NAME, allSpans =>
624+
* allSpans.some(span => span.name === 'GET /nested-layout' && span.is_segment),
625+
* );
626+
* expect(spans.map(span => span.name)).toContainEqual('build component tree');
627+
* ```
628+
*/
629+
export function collectStreamedSpans(
630+
proxyServerName: string,
631+
isDone: (spans: SerializedStreamedSpan[]) => boolean,
632+
): Promise<SerializedStreamedSpan[]> {
633+
const collected: SerializedStreamedSpan[] = [];
634+
return waitForStreamedSpans(proxyServerName, spans => {
635+
collected.push(...spans);
636+
return isDone(collected);
637+
}).then(() => collected);
638+
}
639+
609640
/**
610641
* Helper to get the span operation from a Span V2 JSON object.
611642
*

dev-packages/test-utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export {
1111
waitForStreamedSpan,
1212
waitForStreamedSpans,
1313
waitForStreamedSpanEnvelope,
14+
collectStreamedSpans,
1415
getSpanOp,
1516
} from './event-proxy-server';
1617

0 commit comments

Comments
 (0)