Skip to content

Commit 14fc45e

Browse files
chargomeclaude
andauthored
test(e2e): Move collectSpanNamesUntilSegment into @sentry-internal/test-utils (#24073)
Adds `collectStreamedSpansUntilSegment` and `collectSpanNamesUntilSegment` to `@sentry-internal/test-utils`. Both wait until the segment span of a trace has arrived, matched by name or by a custom check. The e2e tests inlined this check in ~70 places and copied the names helper into five Next.js apps. Those now use the shared helpers. Calls that also wait for specific child spans keep using `collectStreamedSpans`. No behavior change. Fixes #23944 Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 65976f3 commit 14fc45e

57 files changed

Lines changed: 296 additions & 335 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/callable.test.ts‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { expect, test } from '@playwright/test';
2-
import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
2+
import {
3+
collectStreamedSpans,
4+
collectStreamedSpansUntilSegment,
5+
getSpanOp,
6+
waitForStreamedSpan,
7+
} from '@sentry-internal/test-utils';
38

49
// The agent request segment is the Durable Object's `http.server` span. It has a parent because
510
// the worker propagates its trace over the RPC binding; the worker's own segment for the same URL
@@ -124,14 +129,12 @@ test('does not emit db.query spans for the agents runtime `cf_`-prefixed interna
124129
page,
125130
baseURL,
126131
}) => {
127-
const spansPromise = collectStreamedSpans('cloudflare-agent', spans =>
128-
spans.some(
129-
span =>
130-
getSpanOp(span) === 'http.server' &&
131-
span.is_segment &&
132-
span.attributes['url.path']?.value === '/agents/my-agent/user-123' &&
133-
span.parent_span_id !== undefined,
134-
),
132+
const spansPromise = collectStreamedSpansUntilSegment(
133+
'cloudflare-agent',
134+
span =>
135+
getSpanOp(span) === 'http.server' &&
136+
span.attributes['url.path']?.value === '/agents/my-agent/user-123' &&
137+
span.parent_span_id !== undefined,
135138
);
136139

137140
await page.goto(baseURL!);

‎dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument/tests/autoinstrument.test.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { expect, test } from '@playwright/test';
2-
import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
2+
import {
3+
collectStreamedSpans,
4+
collectStreamedSpansUntilSegment,
5+
getSpanOp,
6+
waitForStreamedSpan,
7+
} from '@sentry-internal/test-utils';
38
import { callRpc } from './agent-socket';
49

510
// The worker entry (`src/index.ts`) contains no Sentry calls at all — every
@@ -90,11 +95,9 @@ for (const { title, binding, agentClass } of [
9095
}
9196

9297
test('applies plain Durable Object instrumentation to a non-Agent class', async ({ baseURL }) => {
93-
const spansPromise = collectStreamedSpans('cloudflare-autoinstrument', spans =>
94-
spans.some(
95-
span =>
96-
getSpanOp(span) === 'http.server' && span.is_segment && span.attributes['url.path']?.value === '/plain-do',
97-
),
98+
const spansPromise = collectStreamedSpansUntilSegment(
99+
'cloudflare-autoinstrument',
100+
span => getSpanOp(span) === 'http.server' && span.attributes['url.path']?.value === '/plain-do',
98101
);
99102

100103
const res = await fetch(`${baseURL}/plain-do`);

‎dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts‎

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test } from '@playwright/test';
2-
import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
33

44
test('Sends a segment span for a successful route', async ({ baseURL, request }) => {
55
const spanPromise = waitForStreamedSpan('elysia-bun', span => {
@@ -70,9 +70,7 @@ test('Sends a segment span for an errored route', async ({ baseURL, request }) =
7070
});
7171

7272
test('Includes manually started spans with parent-child relationship', async ({ baseURL, request }) => {
73-
const spansPromise = collectStreamedSpans('elysia-bun', spans =>
74-
spans.some(span => span.name === 'GET /test-transaction' && span.is_segment),
75-
);
73+
const spansPromise = collectStreamedSpansUntilSegment('elysia-bun', 'GET /test-transaction');
7674

7775
await request.get(`${baseURL}/test-transaction`);
7876

@@ -102,9 +100,7 @@ test('Includes manually started spans with parent-child relationship', async ({
102100
});
103101

104102
test('Creates lifecycle spans for Elysia hooks', async ({ baseURL, request }) => {
105-
const spansPromise = collectStreamedSpans('elysia-bun', spans =>
106-
spans.some(span => span.name === 'GET /test-success' && span.is_segment),
107-
);
103+
const spansPromise = collectStreamedSpansUntilSegment('elysia-bun', 'GET /test-success');
108104

109105
await request.get(`${baseURL}/test-success`);
110106

@@ -128,9 +124,7 @@ test('Creates lifecycle spans for Elysia hooks', async ({ baseURL, request }) =>
128124
});
129125

130126
test('Names handler spans after the route instead of "<unknown>"', async ({ baseURL, request }) => {
131-
const spansPromise = collectStreamedSpans('elysia-bun', spans =>
132-
spans.some(span => span.name === 'GET /with-middleware/test' && span.is_segment),
133-
);
127+
const spansPromise = collectStreamedSpansUntilSegment('elysia-bun', 'GET /with-middleware/test');
134128

135129
// Use a route with middleware so there are child handler spans
136130
await request.get(`${baseURL}/with-middleware/test`);
@@ -153,9 +147,7 @@ test('Names handler spans after the route instead of "<unknown>"', async ({ base
153147
});
154148

155149
test('Creates lifecycle spans for route-specific middleware', async ({ baseURL, request }) => {
156-
const spansPromise = collectStreamedSpans('elysia-bun', spans =>
157-
spans.some(span => span.name === 'GET /with-middleware/test' && span.is_segment),
158-
);
150+
const spansPromise = collectStreamedSpansUntilSegment('elysia-bun', 'GET /with-middleware/test');
159151

160152
await request.get(`${baseURL}/with-middleware/test`);
161153

‎dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import { expect, test } from '@playwright/test';
2-
import { collectStreamedSpans, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpansUntilSegment, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
33

4-
// The generation-function spans are children of the segment span, which ends last, so accumulate
5-
// spans until the segment for this request arrives.
64
function collectSpansForTarget(httpTarget: string) {
7-
return collectStreamedSpans('nextjs-14', spans =>
8-
spans.some(span => span.is_segment && span.attributes['http.target']?.value === httpTarget),
9-
);
5+
return collectStreamedSpansUntilSegment('nextjs-14', span => span.attributes['http.target']?.value === httpTarget);
106
}
117

128
test('Should emit a span for a generateMetadata() function invocation', async ({ page }) => {

‎dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { expect, test } from '@playwright/test';
2-
import { collectStreamedSpans } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
33

44
test('Should send a fetch span', async ({ page }) => {
55
// The fetch spans are children of the segment span, which ends last.
6-
const spansPromise = collectStreamedSpans('nextjs-14', spans =>
7-
spans.some(span => span.name === 'GET /request-instrumentation' && span.is_segment),
8-
);
6+
const spansPromise = collectStreamedSpansUntilSegment('nextjs-14', 'GET /request-instrumentation');
97

108
await page.goto(`/request-instrumentation`);
119

‎dev-packages/e2e-tests/test-applications/nextjs-15/tests/prefetch-spans.test.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test } from '@playwright/test';
2-
import { collectStreamedSpans } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
33

44
test('Prefetch client spans should have a http.request.prefetch attribute', async ({ page }) => {
55
test.skip(
@@ -8,9 +8,7 @@ test('Prefetch client spans should have a http.request.prefetch attribute', asyn
88
);
99

1010
// The prefetch span is a child of the pageload segment span, which ends last.
11-
const spansPromise = collectStreamedSpans('nextjs-15', spans =>
12-
spans.some(span => span.name === '/prefetching' && span.is_segment),
13-
);
11+
const spansPromise = collectStreamedSpansUntilSegment('nextjs-15', '/prefetching');
1412

1513
await page.goto(`/prefetching`);
1614

‎dev-packages/e2e-tests/test-applications/nextjs-15/tests/server-components.test.ts‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { expect, test } from '@playwright/test';
2-
import { collectStreamedSpans } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
33

44
test('Sends a span for a request to app router with URL', async ({ page }) => {
5-
const spansPromise = collectStreamedSpans('nextjs-15', spans =>
6-
spans.some(
7-
span =>
8-
span.name === 'GET /parameterized/[one]/beep/[two]' &&
9-
span.is_segment &&
10-
String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
11-
),
5+
const spansPromise = collectStreamedSpansUntilSegment(
6+
'nextjs-15',
7+
span =>
8+
span.name === 'GET /parameterized/[one]/beep/[two]' &&
9+
String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
1210
);
1311

1412
await page.goto('/parameterized/1337/beep/42');

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

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
11
import { expect, test } from '@playwright/test';
2-
import { collectStreamedSpans } from '@sentry-internal/test-utils';
3-
4-
// Streamed spans are flushed across multiple envelopes as they end, so the server-component child spans
5-
// can arrive in a different (earlier) envelope than the `is_segment` root span. Accumulate spans across
6-
// envelopes until the root span (which ends last) is seen.
7-
function collectSpanNamesUntilSegment(segmentName: string): Promise<string[]> {
8-
return collectStreamedSpans('nextjs-16-bun', spans =>
9-
spans.some(span => span.name === segmentName && span.is_segment),
10-
).then(spans => spans.map(span => span.name));
11-
}
2+
import { collectSpanNamesUntilSegment, collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
123

134
test('Sends a span for a request to app router with URL', async ({ page }) => {
14-
const spansPromise = collectStreamedSpans('nextjs-16-bun', spans =>
15-
spans.some(
16-
span =>
17-
span.name === 'GET /parameterized/[one]/beep/[two]' &&
18-
span.is_segment &&
19-
String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
20-
),
5+
const spansPromise = collectStreamedSpansUntilSegment(
6+
'nextjs-16-bun',
7+
span =>
8+
span.name === 'GET /parameterized/[one]/beep/[two]' &&
9+
String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
2110
);
2211

2312
await page.goto('/parameterized/1337/beep/42');
@@ -54,7 +43,7 @@ test('Sends a span for a request to app router with URL', async ({ page }) => {
5443
test('Will create spans for every server component and metadata generation functions when visiting a page', async ({
5544
page,
5645
}) => {
57-
const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout');
46+
const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-bun', 'GET /nested-layout');
5847

5948
await page.goto('/nested-layout');
6049

@@ -73,7 +62,7 @@ test('Will create spans for every server component and metadata generation funct
7362
test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({
7463
page,
7564
}) => {
76-
const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout/[dynamic]');
65+
const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-bun', 'GET /nested-layout/[dynamic]');
7766

7867
await page.goto('/nested-layout/123');
7968

‎dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts‎

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

55
test('Prefetch client spans should have a http.request.prefetch attribute', async ({ page }) => {
66
test.skip(isDevMode, "Prefetch requests don't have the prefetch header in dev mode");
77

88
// The prefetch span is a child of the pageload segment span, which ends last.
9-
const spansPromise = collectStreamedSpans('nextjs-16-cf-workers', spans =>
10-
spans.some(span => span.name === '/prefetching' && span.is_segment),
11-
);
9+
const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', '/prefetching');
1210

1311
await page.goto(`/prefetching`);
1412

‎dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts‎

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,13 @@
11
import { expect, test } from '@playwright/test';
2-
import { collectStreamedSpans } from '@sentry-internal/test-utils';
3-
4-
// Streamed spans are flushed across multiple envelopes as they end, so the server-component child spans
5-
// can arrive in a different (earlier) envelope than the `is_segment` root span. Accumulate spans across
6-
// envelopes until the root span (which ends last) is seen.
7-
function collectSpanNamesUntilSegment(segmentName: string): Promise<string[]> {
8-
return collectStreamedSpans('nextjs-16-cf-workers', spans =>
9-
spans.some(span => span.name === segmentName && span.is_segment),
10-
).then(spans => spans.map(span => span.name));
11-
}
2+
import { collectSpanNamesUntilSegment, collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
123

134
// TODO: Server component tests need SDK adjustments for Cloudflare Workers
145
test.skip('Sends a span for a request to app router with URL', async ({ page }) => {
15-
const spansPromise = collectStreamedSpans('nextjs-16-cf-workers', spans =>
16-
spans.some(
17-
span =>
18-
span.name === 'GET /parameterized/[one]/beep/[two]' &&
19-
span.is_segment &&
20-
String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
21-
),
6+
const spansPromise = collectStreamedSpansUntilSegment(
7+
'nextjs-16-cf-workers',
8+
span =>
9+
span.name === 'GET /parameterized/[one]/beep/[two]' &&
10+
String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'),
2211
);
2312

2413
await page.goto('/parameterized/1337/beep/42');
@@ -56,7 +45,7 @@ test.skip('Sends a span for a request to app router with URL', async ({ page })
5645
test.skip('Will create spans for every server component and metadata generation functions when visiting a page', async ({
5746
page,
5847
}) => {
59-
const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout');
48+
const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout');
6049

6150
await page.goto('/nested-layout');
6251

@@ -76,7 +65,7 @@ test.skip('Will create spans for every server component and metadata generation
7665
test.skip('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({
7766
page,
7867
}) => {
79-
const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout/[dynamic]');
68+
const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout/[dynamic]');
8069

8170
await page.goto('/nested-layout/123');
8271

0 commit comments

Comments
 (0)