From e71e0b68f787578196a71e5ae66e196af153d552 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 4 Sep 2026 12:16:22 +0200 Subject: [PATCH] test(e2e): Move `collectSpanNamesUntilSegment` into `@sentry-internal/test-utils` Add collectStreamedSpansUntilSegment and collectSpanNamesUntilSegment to the shared test-utils and replace the inlined "collect until the segment span arrived" check across the e2e suites. The names helper was copied into five Next.js apps and the same check was inlined in ~70 more places. Calls that also wait for specific child spans keep using collectStreamedSpans. No behavior change. Fixes #23944 Refs JS-3537 Co-Authored-By: Claude Fable 5.1 --- .../cloudflare-agent/tests/callable.test.ts | 21 ++++---- .../tests/autoinstrument.test.ts | 15 +++--- .../elysia-bun/tests/transactions.test.ts | 18 ++----- .../tests/generation-functions.test.ts | 8 +-- .../tests/request-instrumentation.test.ts | 6 +-- .../nextjs-15/tests/prefetch-spans.test.ts | 6 +-- .../nextjs-15/tests/server-components.test.ts | 14 +++-- .../tests/server-components.test.ts | 27 +++------- .../tests/prefetch-spans.test.ts | 6 +-- .../tests/server-components.test.ts | 27 +++------- .../tests/server-components.test.ts | 15 ++---- .../nextjs-16/tests/db-page.test.ts | 6 +-- .../nextjs-16/tests/middleware.test.ts | 11 ++-- .../nextjs-16/tests/prefetch-spans.test.ts | 6 +-- .../nextjs-16/tests/server-components.test.ts | 27 +++------- .../connected-servercomponent-trace.test.ts | 15 ++---- .../tests/request-instrumentation.test.ts | 6 +-- .../tests/server-components.test.ts | 14 ++--- .../nextjs-app-dir/tests/transactions.test.ts | 13 +++-- .../tests/async-context-edge.test.ts | 6 +-- .../tests/request-instrumentation.test.ts | 6 +-- .../tests/errors.test.ts | 6 +-- .../tests/tracing.client.test.ts | 7 +-- .../nuxt-3/tests/cache.test.ts | 7 +-- .../nuxt-3/tests/database-multi.test.ts | 7 +-- .../nuxt-3/tests/database.test.ts | 7 +-- .../nuxt-3/tests/middleware.test.ts | 7 +-- .../nuxt-3/tests/storage-aliases.test.ts | 6 +-- .../nuxt-3/tests/storage.test.ts | 6 +-- .../nuxt-3/tests/tracing.client.test.ts | 7 +-- .../nuxt-4/tests/cache.test.ts | 7 +-- .../nuxt-4/tests/database-multi.test.ts | 7 +-- .../nuxt-4/tests/database.test.ts | 7 +-- .../nuxt-4/tests/db-drivers.test.ts | 6 +-- .../nuxt-4/tests/middleware.test.ts | 7 +-- .../nuxt-4/tests/storage-aliases.test.ts | 6 +-- .../nuxt-4/tests/storage.test.ts | 6 +-- .../nuxt-4/tests/tracing.client.test.ts | 12 +++-- .../nuxt-5/tests/cache.test.ts | 7 +-- .../nuxt-5/tests/database-multi.test.ts | 7 +-- .../nuxt-5/tests/database.test.ts | 7 +-- .../nuxt-5/tests/middleware.test.ts | 7 +-- .../nuxt-5/tests/storage-aliases.test.ts | 6 +-- .../nuxt-5/tests/storage.test.ts | 6 +-- .../nuxt-5/tests/tracing.client.test.ts | 12 +++-- .../nuxt-5/tests/tracing.server.test.ts | 7 +-- .../tests/spans.test.ts | 16 +++--- .../tests/spans.test.ts | 9 ++-- .../tests/errors/errors.server.test.ts | 11 ++-- .../tests/performance/db.server.test.ts | 10 ++-- .../tests/performance/lazy.server.test.ts | 14 ++--- .../performance/middleware.server.test.ts | 14 ++--- .../tests/spans.test.ts | 34 +++++++------ .../supabase-nextjs/tests/performance.test.ts | 14 ++--- .../svelte-5/tests/spans.test.ts | 6 +-- .../test-utils/src/event-proxy-server.ts | 51 +++++++++++++++++-- dev-packages/test-utils/src/index.ts | 2 + 57 files changed, 296 insertions(+), 335 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/callable.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/callable.test.ts index 7eb1d5433baa..69cd0ac49b44 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/callable.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-agent/tests/callable.test.ts @@ -1,5 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { + collectStreamedSpans, + collectStreamedSpansUntilSegment, + getSpanOp, + waitForStreamedSpan, +} from '@sentry-internal/test-utils'; // The agent request segment is the Durable Object's `http.server` span. It has a parent because // 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 page, baseURL, }) => { - const spansPromise = collectStreamedSpans('cloudflare-agent', spans => - spans.some( - span => - getSpanOp(span) === 'http.server' && - span.is_segment && - span.attributes['url.path']?.value === '/agents/my-agent/user-123' && - span.parent_span_id !== undefined, - ), + const spansPromise = collectStreamedSpansUntilSegment( + 'cloudflare-agent', + span => + getSpanOp(span) === 'http.server' && + span.attributes['url.path']?.value === '/agents/my-agent/user-123' && + span.parent_span_id !== undefined, ); await page.goto(baseURL!); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument/tests/autoinstrument.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument/tests/autoinstrument.test.ts index d0f0308dfeff..c89951751732 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument/tests/autoinstrument.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument/tests/autoinstrument.test.ts @@ -1,5 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { + collectStreamedSpans, + collectStreamedSpansUntilSegment, + getSpanOp, + waitForStreamedSpan, +} from '@sentry-internal/test-utils'; import { callRpc } from './agent-socket'; // The worker entry (`src/index.ts`) contains no Sentry calls at all — every @@ -90,11 +95,9 @@ for (const { title, binding, agentClass } of [ } test('applies plain Durable Object instrumentation to a non-Agent class', async ({ baseURL }) => { - const spansPromise = collectStreamedSpans('cloudflare-autoinstrument', spans => - spans.some( - span => - getSpanOp(span) === 'http.server' && span.is_segment && span.attributes['url.path']?.value === '/plain-do', - ), + const spansPromise = collectStreamedSpansUntilSegment( + 'cloudflare-autoinstrument', + span => getSpanOp(span) === 'http.server' && span.attributes['url.path']?.value === '/plain-do', ); const res = await fetch(`${baseURL}/plain-do`); diff --git a/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts index 431ce4233d89..178155b028fe 100644 --- a/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/elysia-bun/tests/transactions.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('Sends a segment span for a successful route', async ({ baseURL, request }) => { const spanPromise = waitForStreamedSpan('elysia-bun', span => { @@ -70,9 +70,7 @@ test('Sends a segment span for an errored route', async ({ baseURL, request }) = }); test('Includes manually started spans with parent-child relationship', async ({ baseURL, request }) => { - const spansPromise = collectStreamedSpans('elysia-bun', spans => - spans.some(span => span.name === 'GET /test-transaction' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('elysia-bun', 'GET /test-transaction'); await request.get(`${baseURL}/test-transaction`); @@ -102,9 +100,7 @@ test('Includes manually started spans with parent-child relationship', async ({ }); test('Creates lifecycle spans for Elysia hooks', async ({ baseURL, request }) => { - const spansPromise = collectStreamedSpans('elysia-bun', spans => - spans.some(span => span.name === 'GET /test-success' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('elysia-bun', 'GET /test-success'); await request.get(`${baseURL}/test-success`); @@ -128,9 +124,7 @@ test('Creates lifecycle spans for Elysia hooks', async ({ baseURL, request }) => }); test('Names handler spans after the route instead of ""', async ({ baseURL, request }) => { - const spansPromise = collectStreamedSpans('elysia-bun', spans => - spans.some(span => span.name === 'GET /with-middleware/test' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('elysia-bun', 'GET /with-middleware/test'); // Use a route with middleware so there are child handler spans await request.get(`${baseURL}/with-middleware/test`); @@ -153,9 +147,7 @@ test('Names handler spans after the route instead of ""', async ({ base }); test('Creates lifecycle spans for route-specific middleware', async ({ baseURL, request }) => { - const spansPromise = collectStreamedSpans('elysia-bun', spans => - spans.some(span => span.name === 'GET /with-middleware/test' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('elysia-bun', 'GET /with-middleware/test'); await request.get(`${baseURL}/with-middleware/test`); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts index ea81a6b76328..698f6dd1e48b 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts @@ -1,12 +1,8 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; -// The generation-function spans are children of the segment span, which ends last, so accumulate -// spans until the segment for this request arrives. function collectSpansForTarget(httpTarget: string) { - return collectStreamedSpans('nextjs-14', spans => - spans.some(span => span.is_segment && span.attributes['http.target']?.value === httpTarget), - ); + return collectStreamedSpansUntilSegment('nextjs-14', span => span.attributes['http.target']?.value === httpTarget); } test('Should emit a span for a generateMetadata() function invocation', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts index 396f1e9e1605..43a4e8210049 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts @@ -1,11 +1,9 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test('Should send a fetch span', async ({ page }) => { // The fetch spans are children of the segment span, which ends last. - const spansPromise = collectStreamedSpans('nextjs-14', spans => - spans.some(span => span.name === 'GET /request-instrumentation' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-14', 'GET /request-instrumentation'); await page.goto(`/request-instrumentation`); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/prefetch-spans.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/prefetch-spans.test.ts index b79e8159737e..26a1fc7f774f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/prefetch-spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/prefetch-spans.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test('Prefetch client spans should have a http.request.prefetch attribute', async ({ page }) => { test.skip( @@ -8,9 +8,7 @@ test('Prefetch client spans should have a http.request.prefetch attribute', asyn ); // The prefetch span is a child of the pageload segment span, which ends last. - const spansPromise = collectStreamedSpans('nextjs-15', spans => - spans.some(span => span.name === '/prefetching' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-15', '/prefetching'); await page.goto(`/prefetching`); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/server-components.test.ts index 4dd80c0dfbc8..675cc2c164b7 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/server-components.test.ts @@ -1,14 +1,12 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test('Sends a span for a request to app router with URL', async ({ page }) => { - const spansPromise = collectStreamedSpans('nextjs-15', spans => - spans.some( - span => - span.name === 'GET /parameterized/[one]/beep/[two]' && - span.is_segment && - String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), - ), + const spansPromise = collectStreamedSpansUntilSegment( + 'nextjs-15', + span => + span.name === 'GET /parameterized/[one]/beep/[two]' && + String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), ); await page.goto('/parameterized/1337/beep/42'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts index 224278629d84..0bc599f836f2 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts @@ -1,23 +1,12 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; - -// Streamed spans are flushed across multiple envelopes as they end, so the server-component child spans -// can arrive in a different (earlier) envelope than the `is_segment` root span. Accumulate spans across -// envelopes until the root span (which ends last) is seen. -function collectSpanNamesUntilSegment(segmentName: string): Promise { - return collectStreamedSpans('nextjs-16-bun', spans => - spans.some(span => span.name === segmentName && span.is_segment), - ).then(spans => spans.map(span => span.name)); -} +import { collectSpanNamesUntilSegment, collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test('Sends a span for a request to app router with URL', async ({ page }) => { - const spansPromise = collectStreamedSpans('nextjs-16-bun', spans => - spans.some( - span => - span.name === 'GET /parameterized/[one]/beep/[two]' && - span.is_segment && - String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), - ), + const spansPromise = collectStreamedSpansUntilSegment( + 'nextjs-16-bun', + span => + span.name === 'GET /parameterized/[one]/beep/[two]' && + String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), ); 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 }) => { test('Will create spans for every server component and metadata generation functions when visiting a page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-bun', 'GET /nested-layout'); await page.goto('/nested-layout'); @@ -73,7 +62,7 @@ test('Will create spans for every server component and metadata generation funct test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout/[dynamic]'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-bun', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts index 81659848d63c..53cebde1ff21 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts @@ -1,14 +1,12 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; test('Prefetch client spans should have a http.request.prefetch attribute', async ({ page }) => { test.skip(isDevMode, "Prefetch requests don't have the prefetch header in dev mode"); // The prefetch span is a child of the pageload segment span, which ends last. - const spansPromise = collectStreamedSpans('nextjs-16-cf-workers', spans => - spans.some(span => span.name === '/prefetching' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', '/prefetching'); await page.goto(`/prefetching`); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts index c4e94f494ab6..98d242896465 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts @@ -1,24 +1,13 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; - -// Streamed spans are flushed across multiple envelopes as they end, so the server-component child spans -// can arrive in a different (earlier) envelope than the `is_segment` root span. Accumulate spans across -// envelopes until the root span (which ends last) is seen. -function collectSpanNamesUntilSegment(segmentName: string): Promise { - return collectStreamedSpans('nextjs-16-cf-workers', spans => - spans.some(span => span.name === segmentName && span.is_segment), - ).then(spans => spans.map(span => span.name)); -} +import { collectSpanNamesUntilSegment, collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; // TODO: Server component tests need SDK adjustments for Cloudflare Workers test.skip('Sends a span for a request to app router with URL', async ({ page }) => { - const spansPromise = collectStreamedSpans('nextjs-16-cf-workers', spans => - spans.some( - span => - span.name === 'GET /parameterized/[one]/beep/[two]' && - span.is_segment && - String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), - ), + const spansPromise = collectStreamedSpansUntilSegment( + 'nextjs-16-cf-workers', + span => + span.name === 'GET /parameterized/[one]/beep/[two]' && + String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), ); 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 }) test.skip('Will create spans for every server component and metadata generation functions when visiting a page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout'); await page.goto('/nested-layout'); @@ -76,7 +65,7 @@ test.skip('Will create spans for every server component and metadata generation test.skip('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout/[dynamic]'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/server-components.test.ts index e0469b42af34..75973b85c105 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/server-components.test.ts @@ -1,16 +1,7 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, waitForStreamedSpan, getSpanOp } from '@sentry-internal/test-utils'; +import { collectSpanNamesUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; -// Streamed spans are flushed across multiple envelopes as they end, so the server-component child spans -// can arrive in a different (earlier) envelope than the `is_segment` root span. Accumulate spans across -// envelopes until the root span (which ends last) is seen. -function collectSpanNamesUntilSegment(segmentName: string): Promise { - return collectStreamedSpans('nextjs-16-streaming', spans => - spans.some(span => span.name === segmentName && span.is_segment), - ).then(spans => spans.map(span => span.name)); -} - test('Sends a streamed span for a request to app router with URL', async ({ page }) => { test.skip(isDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode'); @@ -31,7 +22,7 @@ test('Will create streamed spans for every server component and metadata generat }) => { test.skip(isDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode'); - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-streaming', 'GET /nested-layout'); await page.goto('/nested-layout'); @@ -52,7 +43,7 @@ test('Will create streamed spans for every server component and metadata generat }) => { test.skip(isDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode'); - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout/[dynamic]'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-streaming', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts index 07a415148de5..30392d2bf741 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts @@ -1,11 +1,9 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test('Instruments DB calls made during server-side rendering of a page', async ({ page }) => { // The db spans are children of the segment span, which ends last. - const spansPromise = collectStreamedSpans('nextjs-16', spans => - spans.some(span => span.name === 'GET /db-page' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'GET /db-page'); await page.goto('/db-page'); await expect(page.locator('#answer')).toHaveText('answer: 42'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts index 765d3961238f..f91168e54038 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts @@ -1,11 +1,14 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { + collectStreamedSpans, + collectStreamedSpansUntilSegment, + getSpanOp, + waitForStreamedSpan, +} from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; test('Should create a span for middleware', async ({ request }) => { - const spansPromise = collectStreamedSpans('nextjs-16', spans => - spans.some(span => span.name === 'middleware GET' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'middleware GET'); const routeSpanPromise = waitForStreamedSpan('nextjs-16', span => { return span.name === 'GET /api/endpoint-behind-middleware' && span.is_segment; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/prefetch-spans.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/prefetch-spans.test.ts index ffd692598836..5c973b9bb2df 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/prefetch-spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/prefetch-spans.test.ts @@ -1,14 +1,12 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; test('Prefetch client spans should have a http.request.prefetch attribute', async ({ page }) => { test.skip(isDevMode, "Prefetch requests don't have the prefetch header in dev mode"); // The prefetch span is a child of the pageload segment span, which ends last. - const spansPromise = collectStreamedSpans('nextjs-16', spans => - spans.some(span => span.name === '/prefetching' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', '/prefetching'); await page.goto(`/prefetching`); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts index d7bdca7bb0b0..bb3380a9e774 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts @@ -1,26 +1,15 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectSpanNamesUntilSegment, collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; import { isTurbopackDevMode } from './isDevMode'; -// Streamed spans are flushed across multiple envelopes as they end, so the server-component child spans -// can arrive in a different (earlier) envelope than the `is_segment` root span. Accumulate spans across -// envelopes until the root span (which ends last) is seen. -function collectSpanNamesUntilSegment(segmentName: string): Promise { - return collectStreamedSpans('nextjs-16', spans => - spans.some(span => span.name === segmentName && span.is_segment), - ).then(spans => spans.map(span => span.name)); -} - test('Sends a span for a request to app router with URL', async ({ page }) => { test.skip(isTurbopackDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode'); - const spansPromise = collectStreamedSpans('nextjs-16', spans => - spans.some( - span => - span.name === 'GET /parameterized/[one]/beep/[two]' && - span.is_segment && - String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), - ), + const spansPromise = collectStreamedSpansUntilSegment( + 'nextjs-16', + span => + span.name === 'GET /parameterized/[one]/beep/[two]' && + String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), ); await page.goto('/parameterized/1337/beep/42'); @@ -57,7 +46,7 @@ test('Sends a span for a request to app router with URL', async ({ page }) => { test('Will create spans for every server component and metadata generation functions when visiting a page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16', 'GET /nested-layout'); await page.goto('/nested-layout'); @@ -78,7 +67,7 @@ test('Will create spans for every server component and metadata generation funct }) => { test.skip(isTurbopackDevMode, 'Turbopack intermittently returns 404 for dynamic routes in dev mode'); - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout/[dynamic]'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts index 59f08b1f55d3..fb438ba98825 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts @@ -1,19 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; - -// Streamed spans are flushed across multiple envelopes as they end, so the server-component child spans -// can arrive in a different (earlier) envelope than the `is_segment` root span. Accumulate spans across -// envelopes until the root span (which ends last) is seen. -function collectSpanNamesUntilSegment(segmentName: string): Promise { - return collectStreamedSpans('nextjs-app-dir', spans => - spans.some(span => span.name === segmentName && span.is_segment), - ).then(spans => spans.map(span => span.name)); -} +import { collectSpanNamesUntilSegment } from '@sentry-internal/test-utils'; test('Will create spans for every server component and metadata generation functions when visiting a page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-app-dir', 'GET /nested-layout'); await page.goto('/nested-layout'); @@ -34,7 +25,7 @@ test('Will create spans for every server component and metadata generation funct test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('GET /nested-layout/[dynamic]'); + const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-app-dir', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/request-instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/request-instrumentation.test.ts index e14817c72967..5b83f884d7f7 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/request-instrumentation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/request-instrumentation.test.ts @@ -1,13 +1,11 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; // Note(lforst): I officially declare bancruptcy on this test. I tried a million ways to make it work but it kept flaking. // Sometimes the request span was included in the handler span, more often it wasn't. I have no idea why. Maybe one day we will // figure it out. Today is not that day. test.skip('Should send a http span', async ({ request }) => { - const spansPromise = collectStreamedSpans('nextjs-app-dir', spans => - spans.some(span => span.name === 'GET /api/request-instrumentation' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-app-dir', 'GET /api/request-instrumentation'); await request.get('/api/request-instrumentation'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts index 3f26e2a520d7..89c473c98b2d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts @@ -1,13 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -// Streamed spans are flushed across multiple envelopes as they end, so child spans can arrive in an -// earlier envelope than the `is_segment` root span. Accumulate spans until the root span is seen. -function collectSpansUntilSegment(segmentName: string) { - return collectStreamedSpans('nextjs-app-dir', spans => - spans.some(span => span.name === segmentName && span.is_segment), - ); -} +import { collectStreamedSpansUntilSegment, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('Sends a span for a request to app router', async ({ page }) => { const serverComponentSpanPromise = waitForStreamedSpan('nextjs-app-dir', span => { @@ -55,7 +47,7 @@ test('Should not set an error status on an app router span when it redirects', a test('Should set a "not_found" status on a server component span when notFound() is called and the request span should have status ok', async ({ page, }) => { - const spansPromise = collectSpansUntilSegment('GET /server-component/not-found'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-app-dir', 'GET /server-component/not-found'); await page.goto('/server-component/not-found'); @@ -89,7 +81,7 @@ test('Should set a "not_found" status on a server component span when notFound() }); test('Should capture an error and spans for a app router page', async ({ page }) => { - const spansPromise = collectSpansUntilSegment('GET /server-component/faulty'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-app-dir', 'GET /server-component/faulty'); const errorEventPromise = waitForError('nextjs-app-dir', errorEvent => { return errorEvent?.exception?.values?.[0]?.value === 'I am a faulty server component'; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts index ba76004d717c..a1a0144f706b 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts @@ -1,5 +1,11 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { + collectStreamedSpans, + collectStreamedSpansUntilSegment, + getSpanOp, + waitForError, + waitForStreamedSpan, +} from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; const packageJson = require('../package.json'); @@ -145,8 +151,9 @@ test('Should not capture "NEXT_REDIRECT" control-flow errors for server actions test('Will not include spans with faulty timestamps for slow loading pages', async ({ page }) => { test.slow(); - const spansPromise = collectStreamedSpans('nextjs-app-dir', spans => - spans.some(span => span.name === '/very-slow-component' && getSpanOp(span) === 'pageload' && span.is_segment), + const spansPromise = collectStreamedSpansUntilSegment( + 'nextjs-app-dir', + span => span.name === '/very-slow-component' && getSpanOp(span) === 'pageload', ); await page.goto('/very-slow-component', { timeout: 11000 }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/async-context-edge.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/async-context-edge.test.ts index c44d2bc2cb83..9602b1cb18c1 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/async-context-edge.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/async-context-edge.test.ts @@ -1,12 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test('Should allow for async context isolation in the edge SDK', async ({ request }) => { // The inner and outer spans are children of the segment span, which ends last, so accumulate until // the segment arrives to be sure both children are in hand. - const spansPromise = collectStreamedSpans('nextjs-pages-dir', spans => - spans.some(span => span.name === 'GET /api/async-context-edge-endpoint' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-pages-dir', 'GET /api/async-context-edge-endpoint'); await request.get('/api/async-context-edge-endpoint'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/request-instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/request-instrumentation.test.ts index 964fc10f3a50..74b78ea64ec1 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/request-instrumentation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/request-instrumentation.test.ts @@ -1,13 +1,11 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; // Note(lforst): I officially declare bancruptcy on this test. I tried a million ways to make it work but it kept flaking. // Sometimes the request span was included in the handler span, more often it wasn't. I have no idea why. Maybe one day we will // figure it out. Today is not that day. test.skip('Should send a http span', async ({ request }) => { - const spansPromise = collectStreamedSpans('nextjs-pages-dir', spans => - spans.some(span => span.name === 'GET /api/request-instrumentation' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-pages-dir', 'GET /api/request-instrumentation'); await request.get('/api/request-instrumentation'); diff --git a/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/errors.test.ts index 3697502984e1..f3d1a49919c0 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/errors.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, waitForError } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, waitForError } from '@sentry-internal/test-utils'; test('Sends correct error event', async ({ baseURL }) => { const errorEventPromise = waitForError('node-express-streaming', event => { @@ -8,9 +8,7 @@ test('Sends correct error event', async ({ baseURL }) => { // In streaming mode there is no transaction event; the request's spans are streamed individually. // The root segment span flushes last, so collecting until it arrives captures the whole trace. - const spansPromise = collectStreamedSpans('node-express-streaming', spans => - spans.some(span => span.name === 'GET /test-exception/:id' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('node-express-streaming', 'GET /test-exception/:id'); await fetch(`${baseURL}/test-exception/123`); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/tests/tracing.client.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/tests/tracing.client.test.ts index 7172e69c052a..aed3ad44fd0a 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/tests/tracing.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/tests/tracing.client.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('sends a pageload root span with a parameterized URL', async ({ page }) => { const pageloadSpanPromise = waitForStreamedSpan('nuxt-3-dynamic-import', span => { @@ -24,8 +24,9 @@ test('sends a pageload root span with a parameterized URL', async ({ page }) => }); test('sends component tracking spans when `trackComponents` is enabled', async ({ page }) => { - const spansPromise = collectStreamedSpans('nuxt-3-dynamic-import', spans => - spans.some(span => span.name === '/client-error' && span.is_segment && getSpanOp(span) === 'pageload'), + const spansPromise = collectStreamedSpansUntilSegment( + 'nuxt-3-dynamic-import', + span => span.name === '/client-error' && getSpanOp(span) === 'pageload', ); await page.goto(`/client-error`); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/cache.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/cache.test.ts index 32aa9f71db41..6daa3425a92d 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/cache.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/cache.test.ts @@ -1,13 +1,14 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test.describe('Cache Instrumentation', () => { const SEMANTIC_ATTRIBUTE_CACHE_KEY = 'cache.key'; const SEMANTIC_ATTRIBUTE_CACHE_HIT = 'cache.hit'; async function collectCacheSpans() { - const spans = await collectStreamedSpans('nuxt-3', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/cache-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-3', + span => span.attributes['url.path']?.value === '/api/cache-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/cache-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database-multi.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database-multi.test.ts index fa9e0159fb9f..1668dcde6150 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database-multi.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database-multi.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; async function collectDbSpans() { - const spans = await collectStreamedSpans('nuxt-3', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-multi-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-3', + span => span.attributes['url.path']?.value === '/api/db-multi-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-multi-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database.test.ts index 0ede8db39345..5c58b6c1a165 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForError } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForError } from '@sentry-internal/test-utils'; async function collectDbSpans() { - const spans = await collectStreamedSpans('nuxt-3', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-3', + span => span.attributes['url.path']?.value === '/api/db-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/middleware.test.ts index 8c3266994886..c0ea4be01331 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/middleware.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/middleware.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForError } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForError } from '@sentry-internal/test-utils'; async function collectRequestSpans() { - const spans = await collectStreamedSpans('nuxt-3', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/middleware-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-3', + span => span.attributes['url.path']?.value === '/api/middleware-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/middleware-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/storage-aliases.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/storage-aliases.test.ts index 603f9b6dd66f..9b3412bf1884 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/storage-aliases.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/storage-aliases.test.ts @@ -1,10 +1,8 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; async function collectStorageSpans(route: string) { - const spans = await collectStreamedSpans('nuxt-3', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === route), - ); + const spans = await collectStreamedSpansUntilSegment('nuxt-3', span => span.attributes['url.path']?.value === route); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === route); return spans.filter( diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/storage.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/storage.test.ts index 4d184661ba39..47096fa9c348 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/storage.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/storage.test.ts @@ -1,10 +1,8 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; async function collectStorageSpans(route: string) { - const spans = await collectStreamedSpans('nuxt-3', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === route), - ); + const spans = await collectStreamedSpansUntilSegment('nuxt-3', span => span.attributes['url.path']?.value === route); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === route); return spans.filter( diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.client.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.client.test.ts index cb7c7d29bef0..b31d9afd311f 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/tracing.client.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('sends a pageload root span with a parameterized URL', async ({ page }) => { const pageloadSpanPromise = waitForStreamedSpan('nuxt-3', span => { @@ -24,8 +24,9 @@ test('sends a pageload root span with a parameterized URL', async ({ page }) => }); test('sends component tracking spans when `trackComponents` is enabled', async ({ page }) => { - const spansPromise = collectStreamedSpans('nuxt-3', spans => - spans.some(span => span.name === '/client-error' && span.is_segment && getSpanOp(span) === 'pageload'), + const spansPromise = collectStreamedSpansUntilSegment( + 'nuxt-3', + span => span.name === '/client-error' && getSpanOp(span) === 'pageload', ); await page.goto(`/client-error`); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/cache.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/cache.test.ts index c273df34e0a6..0917da52d140 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/cache.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/cache.test.ts @@ -1,13 +1,14 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test.describe('Cache Instrumentation', () => { const SEMANTIC_ATTRIBUTE_CACHE_KEY = 'cache.key'; const SEMANTIC_ATTRIBUTE_CACHE_HIT = 'cache.hit'; async function collectCacheSpans() { - const spans = await collectStreamedSpans('nuxt-4', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/cache-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-4', + span => span.attributes['url.path']?.value === '/api/cache-test', ); return spans.filter(span => span.attributes['sentry.origin']?.value === 'auto.cache.nuxt'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database-multi.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database-multi.test.ts index 6abe2528ed31..bec2b7c3b269 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database-multi.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database-multi.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; async function collectDbSpans() { - const spans = await collectStreamedSpans('nuxt-4', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-multi-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-4', + span => span.attributes['url.path']?.value === '/api/db-multi-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-multi-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database.test.ts index 9902a02a7424..7f0b60244f62 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForError } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForError } from '@sentry-internal/test-utils'; async function collectDbSpans() { - const spans = await collectStreamedSpans('nuxt-4', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-4', + span => span.attributes['url.path']?.value === '/api/db-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/db-drivers.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/db-drivers.test.ts index 455632b544fb..db585cb03a4a 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/db-drivers.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/db-drivers.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; // The Nuxt module auto-wires the orchestrion build-time transform, which injects // `diagnostics_channel` publishers into these drivers as Nitro bundles them. That @@ -10,9 +10,7 @@ import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; // segment is selected via its `url.path` attribute. Driver spans can flush before // the segment, so accumulate until the segment arrives and filter by its trace. async function collectRequestSpans(path: string) { - const spans = await collectStreamedSpans('nuxt-4', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === path), - ); + const spans = await collectStreamedSpansUntilSegment('nuxt-4', span => span.attributes['url.path']?.value === path); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === path); return spans.filter(span => span.trace_id === rootSpan?.trace_id); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/middleware.test.ts index 8c0a618b353a..fe44b684fe14 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/middleware.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/middleware.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForError } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForError } from '@sentry-internal/test-utils'; async function collectRequestSpans() { - const spans = await collectStreamedSpans('nuxt-4', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/middleware-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-4', + span => span.attributes['url.path']?.value === '/api/middleware-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/middleware-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/storage-aliases.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/storage-aliases.test.ts index 97d9c782b502..8482fea9fe79 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/storage-aliases.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/storage-aliases.test.ts @@ -1,10 +1,8 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; async function collectStorageSpans(route: string) { - const spans = await collectStreamedSpans('nuxt-4', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === route), - ); + const spans = await collectStreamedSpansUntilSegment('nuxt-4', span => span.attributes['url.path']?.value === route); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === route); return spans.filter( diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/storage.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/storage.test.ts index 370e1424c823..ecee3f1146db 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/storage.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/storage.test.ts @@ -1,10 +1,8 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; async function collectStorageSpans(route: string) { - const spans = await collectStreamedSpans('nuxt-4', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === route), - ); + const spans = await collectStreamedSpansUntilSegment('nuxt-4', span => span.attributes['url.path']?.value === route); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === route); return spans.filter( diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/tracing.client.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/tracing.client.test.ts index 76648c4430c4..b073eda0b333 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/tracing.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/tracing.client.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('sends a pageload root span with a parameterized URL', async ({ page }) => { const pageloadSpanPromise = waitForStreamedSpan('nuxt-4', span => { @@ -46,8 +46,9 @@ test('sends a navigation root span with a parameterized URL', async ({ page }) = }); test('sends an application render span and a root component span on pageload', async ({ page }) => { - const spansPromise = collectStreamedSpans('nuxt-4', spans => - spans.some(span => span.name === '/client-error' && span.is_segment && getSpanOp(span) === 'pageload'), + const spansPromise = collectStreamedSpansUntilSegment( + 'nuxt-4', + span => span.name === '/client-error' && getSpanOp(span) === 'pageload', ); await page.goto(`/client-error`); @@ -89,8 +90,9 @@ test('sends an application render span and a root component span on pageload', a }); test('sends component tracking spans when `trackComponents` is enabled', async ({ page }) => { - const spansPromise = collectStreamedSpans('nuxt-4', spans => - spans.some(span => span.name === '/client-error' && span.is_segment && getSpanOp(span) === 'pageload'), + const spansPromise = collectStreamedSpansUntilSegment( + 'nuxt-4', + span => span.name === '/client-error' && getSpanOp(span) === 'pageload', ); await page.goto(`/client-error`); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/cache.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/cache.test.ts index 229887910b97..676ecc4ab91f 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/cache.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/cache.test.ts @@ -1,13 +1,14 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test.describe('Cache Instrumentation', () => { const SEMANTIC_ATTRIBUTE_CACHE_KEY = 'cache.key'; const SEMANTIC_ATTRIBUTE_CACHE_HIT = 'cache.hit'; async function collectCacheSpans() { - const spans = await collectStreamedSpans('nuxt-5', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/cache-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-5', + span => span.attributes['url.path']?.value === '/api/cache-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/cache-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database-multi.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database-multi.test.ts index 6572989abcc3..62e233af87d6 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database-multi.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database-multi.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; async function collectDbSpans() { - const spans = await collectStreamedSpans('nuxt-5', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-multi-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-5', + span => span.attributes['url.path']?.value === '/api/db-multi-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-multi-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database.test.ts index 4ec28d9237be..2a1b16f23d5b 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForError } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForError } from '@sentry-internal/test-utils'; async function collectDbSpans() { - const spans = await collectStreamedSpans('nuxt-5', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-test'), + const spans = await collectStreamedSpansUntilSegment( + 'nuxt-5', + span => span.attributes['url.path']?.value === '/api/db-test', ); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === '/api/db-test'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/middleware.test.ts index 0d8aa3d701e3..fdcade103524 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/middleware.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/middleware.test.ts @@ -1,9 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForError } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForError } from '@sentry-internal/test-utils'; function collectRequestSpans() { - return collectStreamedSpans('nuxt-5', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/middleware-test'), + return collectStreamedSpansUntilSegment( + 'nuxt-5', + span => span.attributes['url.path']?.value === '/api/middleware-test', ); } diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/storage-aliases.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/storage-aliases.test.ts index 4fb0460fb65d..0dc4353a927f 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/storage-aliases.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/storage-aliases.test.ts @@ -1,10 +1,8 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; async function collectStorageSpans(route: string) { - const spans = await collectStreamedSpans('nuxt-5', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === route), - ); + const spans = await collectStreamedSpansUntilSegment('nuxt-5', span => span.attributes['url.path']?.value === route); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === route); return spans.filter( diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/storage.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/storage.test.ts index cc4452473d30..e8c10e66901d 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/storage.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/storage.test.ts @@ -1,10 +1,8 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; async function collectStorageSpans(route: string) { - const spans = await collectStreamedSpans('nuxt-5', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === route), - ); + const spans = await collectStreamedSpansUntilSegment('nuxt-5', span => span.attributes['url.path']?.value === route); const rootSpan = spans.find(span => span.is_segment && span.attributes['url.path']?.value === route); return spans.filter( diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/tracing.client.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/tracing.client.test.ts index cb5bbab420e8..5935f1ef1c3c 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/tracing.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/tracing.client.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('sends a pageload root span with a parameterized URL', async ({ page }) => { const pageloadSpanPromise = waitForStreamedSpan('nuxt-5', span => { @@ -51,8 +51,9 @@ test('sends component tracking spans when `trackComponents` is enabled', async ( // tracking works without it. test.fail(true, 'Vue tracing is registered through app.mixin(), which needs the Options API'); - const spansPromise = collectStreamedSpans('nuxt-5', spans => - spans.some(span => span.name === '/client-error' && span.is_segment && getSpanOp(span) === 'pageload'), + const spansPromise = collectStreamedSpansUntilSegment( + 'nuxt-5', + span => span.name === '/client-error' && getSpanOp(span) === 'pageload', ); await page.goto(`/client-error`); @@ -80,8 +81,9 @@ test('sends an application render span and a root component span on pageload', a // the root spans stop depending on the mixin. test.fail(true, 'Vue tracing is registered through app.mixin(), which needs the Options API'); - const spansPromise = collectStreamedSpans('nuxt-5', spans => - spans.some(span => span.name === '/client-error' && span.is_segment && getSpanOp(span) === 'pageload'), + const spansPromise = collectStreamedSpansUntilSegment( + 'nuxt-5', + span => span.name === '/client-error' && getSpanOp(span) === 'pageload', ); await page.goto(`/client-error`); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/tracing.server.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/tracing.server.test.ts index f9da1e425c0e..9ffae3e03885 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/tracing.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/tracing.server.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('sends a server root span on pageload', async ({ page }) => { const serverSpanPromise = waitForStreamedSpan('nuxt-5', span => { @@ -40,8 +40,9 @@ test('does not send spans for build asset folder "_nuxt"', async ({ page }) => { // TODO: Make test work with Nuxt 5 test.skip('captures server API calls made with Nitro $fetch', async ({ page }) => { - const spansPromise = collectStreamedSpans('nuxt-5', spans => - spans.some(span => span.is_segment && span.attributes['url.path']?.value === '/api/nitro-fetch'), + const spansPromise = collectStreamedSpansUntilSegment( + 'nuxt-5', + span => span.attributes['url.path']?.value === '/api/nitro-fetch', ); await page.goto(`/fetch-server-routes`); diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/spans.test.ts index 5736ae47040f..f06656daca76 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/spans.test.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; import { - collectStreamedSpans, + collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan, waitForStreamedSpans, @@ -84,9 +84,10 @@ test('Captures a navigation span', async ({ page }) => { }); test('Captures a lazy pageload span', async ({ page }) => { - const spansPromise = collectStreamedSpans('react-create-browser-router', spans => { - return spans.some(span => getSpanOp(span) === 'pageload' && span.is_segment); - }); + const spansPromise = collectStreamedSpansUntilSegment( + 'react-create-browser-router', + span => getSpanOp(span) === 'pageload', + ); await page.goto('/lazy-loaded-user/5/foo'); @@ -123,9 +124,10 @@ test('Captures a lazy pageload span', async ({ page }) => { }); test('Captures a lazy navigation span', async ({ page }) => { - const spansPromise = collectStreamedSpans('react-create-browser-router', spans => { - return spans.some(span => getSpanOp(span) === 'navigation' && span.is_segment); - }); + const spansPromise = collectStreamedSpansUntilSegment( + 'react-create-browser-router', + span => getSpanOp(span) === 'navigation', + ); await page.goto('/'); const linkElement = page.locator('id=lazy-navigation'); diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/spans.test.ts index d42b08d85a5a..e1b9c218ff43 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/spans.test.ts @@ -1,6 +1,6 @@ import { expect, test } from '@playwright/test'; import { - collectStreamedSpans, + collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan, waitForStreamedSpans, @@ -14,9 +14,10 @@ const BROWSER_TIMING_OPS = [ ]; test('Captures a pageload span', async ({ page }) => { - const spansPromise = collectStreamedSpans('react-create-hash-router', spans => { - return spans.some(span => getSpanOp(span) === 'pageload' && span.is_segment); - }); + const spansPromise = collectStreamedSpansUntilSegment( + 'react-create-hash-router', + span => getSpanOp(span) === 'pageload', + ); await page.goto('/'); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/errors/errors.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/errors/errors.server.test.ts index 1cbbc96db7e3..10cf2e5be8e0 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/errors/errors.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/errors/errors.server.test.ts @@ -1,5 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { + collectStreamedSpansUntilSegment, + getSpanOp, + waitForError, + waitForStreamedSpan, +} from '@sentry-internal/test-utils'; import { APP_NAME } from '../constants'; test.describe('server - instrumentation API error capture', () => { @@ -42,9 +47,7 @@ test.describe('server - instrumentation API error capture', () => { }); test('should include loader span in the segment even when loader throws', async ({ page }) => { - const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => - spansOfTrace.some(span => span.name === 'GET /performance/error-loader' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /performance/error-loader'); await page.goto(`/performance/error-loader`).catch(() => { // Expected to fail due to loader error diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/db.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/db.server.test.ts index 92806fd517e7..ed0f3e415c36 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/db.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/db.server.test.ts @@ -1,14 +1,12 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; import { APP_NAME } from '../constants'; // Same spans in both runs, from two injectors: the build-time transform in the server bundle, and // the runtime hook in `react-router dev`, where the drivers stay on Node's own loader. test.describe('server - orchestrion db instrumentation', () => { test('instruments ioredis automatically via orchestrion', async ({ page }) => { - const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => - spansOfTrace.some(span => span.name === 'GET /performance/db-ioredis' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /performance/db-ioredis'); await page.goto('/performance/db-ioredis'); @@ -73,9 +71,7 @@ test.describe('server - orchestrion db instrumentation', () => { // Under span streaming the mysql span name is the query summary, so both queries below are named // `SELECT`. `db.query.text` is what tells them apart. test('instruments mysql automatically via orchestrion', async ({ page }) => { - const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => - spansOfTrace.some(span => span.name === 'GET /performance/db-mysql' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /performance/db-mysql'); await page.goto('/performance/db-mysql'); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/lazy.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/lazy.server.test.ts index ef9412297d83..ca252bc8c706 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/lazy.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/lazy.server.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; import { APP_NAME } from '../constants'; // Known React Router limitation: route.lazy hooks only work in Data Mode (createBrowserRouter). @@ -8,9 +8,7 @@ import { APP_NAME } from '../constants'; // Using test.fail() to auto-detect when React Router fixes this upstream. test.describe('server - instrumentation API lazy loading', () => { test.fail('should instrument lazy route loading with instrumentation API origin', async ({ page }) => { - const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => - spansOfTrace.some(span => span.name === 'GET /performance/lazy-route' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /performance/lazy-route'); await page.goto(`/performance/lazy-route`); @@ -50,9 +48,7 @@ test.describe('server - instrumentation API lazy loading', () => { }); test('should include loader span after lazy loading completes', async ({ page }) => { - const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => - spansOfTrace.some(span => span.name === 'GET /performance/lazy-route' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /performance/lazy-route'); await page.goto(`/performance/lazy-route`); @@ -75,9 +71,7 @@ test.describe('server - instrumentation API lazy loading', () => { }); test.fail('should have correct span ordering: lazy before loader', async ({ page }) => { - const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => - spansOfTrace.some(span => span.name === 'GET /performance/lazy-route' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /performance/lazy-route'); await page.goto(`/performance/lazy-route`); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/middleware.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/middleware.server.test.ts index 8ce2a929dec6..3bf9a2c46904 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/middleware.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/middleware.server.test.ts @@ -1,12 +1,10 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; import { APP_NAME } from '../constants'; test.describe('server - instrumentation API middleware', () => { test('should instrument server middleware with instrumentation API origin', async ({ page }) => { - const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => - spansOfTrace.some(span => span.name === 'GET /performance/with-middleware' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /performance/with-middleware'); await page.goto(`/performance/with-middleware`); @@ -57,9 +55,7 @@ test.describe('server - instrumentation API middleware', () => { }); test('should have middleware span run before loader span', async ({ page }) => { - const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => - spansOfTrace.some(span => span.name === 'GET /performance/with-middleware' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /performance/with-middleware'); await page.goto(`/performance/with-middleware`); @@ -76,9 +72,7 @@ test.describe('server - instrumentation API middleware', () => { }); test('should track multiple middlewares with correct indices', async ({ page }) => { - const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => - spansOfTrace.some(span => span.name === 'GET /performance/multi-middleware' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /performance/multi-middleware'); await page.goto(`/performance/multi-middleware`); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/spans.test.ts index 08d17d4baeb1..d0dc72192dca 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/spans.test.ts @@ -1,7 +1,7 @@ import { expect, test } from '@playwright/test'; import type { SerializedStreamedSpan } from '@sentry-internal/test-utils'; import { - collectStreamedSpans, + collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan, waitForStreamedSpans, @@ -917,9 +917,7 @@ test('Correctly names pageload span for slow lazy route with fetch', async ({ pa // This test verifies that a slow lazy route (with top-level await and fetch) // creates a correctly named pageload span - const spansPromise = collectStreamedSpans('react-router-7-lazy-routes', spansOfTrace => - spansOfTrace.some(span => span.name === '/slow-fetch/:id' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('react-router-7-lazy-routes', '/slow-fetch/:id'); await page.goto('/slow-fetch/123'); @@ -1394,9 +1392,10 @@ test('Route manifest provides correct name when pageload span ends before lazy r test('GQL fetch span is attributed to the correct navigation segment when navigating from index to lazy GQL page', async ({ page, }) => { - const pageloadSpansPromise = collectStreamedSpans('react-router-7-lazy-routes', spans => { - return spans.some(span => getSpanOp(span) === 'pageload' && span.is_segment && span.name === '/'); - }); + const pageloadSpansPromise = collectStreamedSpansUntilSegment( + 'react-router-7-lazy-routes', + span => getSpanOp(span) === 'pageload' && span.name === '/', + ); await page.goto('/'); const pageloadSpans = await pageloadSpansPromise; @@ -1407,9 +1406,10 @@ test('GQL fetch span is attributed to the correct navigation segment when naviga ); expect(pageloadGqlSpans.length).toBe(0); - const navigationSpansPromise = collectStreamedSpans('react-router-7-lazy-routes', spans => { - return spans.some(span => getSpanOp(span) === 'navigation' && span.is_segment && span.name === '/lazy-gql-a/fetch'); - }); + const navigationSpansPromise = collectStreamedSpansUntilSegment( + 'react-router-7-lazy-routes', + span => getSpanOp(span) === 'navigation' && span.name === '/lazy-gql-a/fetch', + ); // Navigate to lazy GQL page A const gqlLink = page.locator('id=navigation-to-gql-a'); @@ -1445,9 +1445,10 @@ test('GQL fetch spans are attributed to correct navigation segments when navigat await page.waitForTimeout(500); // Navigate to GQL page A - const firstNavSpansPromise = collectStreamedSpans('react-router-7-lazy-routes', spans => { - return spans.some(span => getSpanOp(span) === 'navigation' && span.is_segment && span.name === '/lazy-gql-a/fetch'); - }); + const firstNavSpansPromise = collectStreamedSpansUntilSegment( + 'react-router-7-lazy-routes', + span => getSpanOp(span) === 'navigation' && span.name === '/lazy-gql-a/fetch', + ); const gqlALink = page.locator('id=navigation-to-gql-a'); await expect(gqlALink).toBeVisible(); @@ -1473,9 +1474,10 @@ test('GQL fetch spans are attributed to correct navigation segments when navigat expect(firstUserBSpans.length).toBe(0); // Now navigate from GQL page A to GQL page B - const secondNavSpansPromise = collectStreamedSpans('react-router-7-lazy-routes', spans => { - return spans.some(span => getSpanOp(span) === 'navigation' && span.is_segment && span.name === '/lazy-gql-b/fetch'); - }); + const secondNavSpansPromise = collectStreamedSpansUntilSegment( + 'react-router-7-lazy-routes', + span => getSpanOp(span) === 'navigation' && span.name === '/lazy-gql-b/fetch', + ); const gqlBLink = page.locator('id=navigate-to-gql-b'); await expect(gqlBLink).toBeVisible(); diff --git a/dev-packages/e2e-tests/test-applications/supabase-nextjs/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/supabase-nextjs/tests/performance.test.ts index 6fe1a285f77b..79c9861008f7 100644 --- a/dev-packages/e2e-tests/test-applications/supabase-nextjs/tests/performance.test.ts +++ b/dev-packages/e2e-tests/test-applications/supabase-nextjs/tests/performance.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; type StreamedSpan = Awaited>; @@ -12,12 +12,6 @@ const DB_ATTRIBUTES = { 'sentry.origin': { value: 'auto.db.supabase', type: 'string' }, }; -function collectSpansUntilSegment(segmentName: string): Promise { - return collectStreamedSpans('supabase-nextjs', spans => - spans.some(span => span.name === segmentName && span.is_segment), - ); -} - function expectDbSpan( span: StreamedSpan | undefined, name: string, @@ -39,7 +33,7 @@ function expectDbSpan( // This should be the first test as it will be needed for the other tests test('Sends server-side Supabase auth admin `createUser` span', async ({ baseURL }) => { - const spansPromise = collectSpansUntilSegment('GET /api/create-test-user'); + const spansPromise = collectStreamedSpansUntilSegment('supabase-nextjs', 'GET /api/create-test-user'); await fetch(`${baseURL}/api/create-test-user`); const spans = await spansPromise; @@ -110,7 +104,7 @@ test('Sends client-side Supabase db-operation spans to Sentry', async ({ page }) }); test('Sends server-side Supabase db-operation spans to Sentry', async ({ baseURL }) => { - const spansPromise = collectSpansUntilSegment('GET /api/add-todo-entry'); + const spansPromise = collectStreamedSpansUntilSegment('supabase-nextjs', 'GET /api/add-todo-entry'); await fetch(`${baseURL}/api/add-todo-entry`); const spans = await spansPromise; @@ -135,7 +129,7 @@ test('Sends server-side Supabase db-operation spans to Sentry', async ({ baseURL }); test('Sends server-side Supabase auth admin `listUsers` span', async ({ baseURL }) => { - const spansPromise = collectSpansUntilSegment('GET /api/list-users'); + const spansPromise = collectStreamedSpansUntilSegment('supabase-nextjs', 'GET /api/list-users'); await fetch(`${baseURL}/api/list-users`); const spans = await spansPromise; diff --git a/dev-packages/e2e-tests/test-applications/svelte-5/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/svelte-5/tests/spans.test.ts index 45d1fae25167..6b83cb13d53c 100644 --- a/dev-packages/e2e-tests/test-applications/svelte-5/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/svelte-5/tests/spans.test.ts @@ -1,10 +1,8 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; test('sends a pageload span with component tracking init spans', async ({ page }) => { - const spansPromise = collectStreamedSpans('svelte-5', spansOfTrace => - spansOfTrace.some(span => getSpanOp(span) === 'pageload' && span.is_segment), - ); + const spansPromise = collectStreamedSpansUntilSegment('svelte-5', span => getSpanOp(span) === 'pageload'); await page.goto(`/`); diff --git a/dev-packages/test-utils/src/event-proxy-server.ts b/dev-packages/test-utils/src/event-proxy-server.ts index 7de1582b2b10..7c37b6d60752 100644 --- a/dev-packages/test-utils/src/event-proxy-server.ts +++ b/dev-packages/test-utils/src/event-proxy-server.ts @@ -621,12 +621,17 @@ export function waitForStreamedSpans( * can still satisfy the predicate in its own right: when several tests exercise the same route, * the predicate has to name something unique to the request under test. * + * When the trace is complete once its segment span has arrived, prefer + * {@link collectStreamedSpansUntilSegment}. + * * @example * ```ts - * const spans = await collectStreamedSpans(PROXY_SERVER_NAME, spansOfTrace => - * spansOfTrace.some(span => span.name === 'GET /nested-layout' && span.is_segment), + * const spans = await collectStreamedSpans( + * PROXY_SERVER_NAME, + * spansOfTrace => + * spansOfTrace.some(span => span.name === 'GET /performance/redis' && span.is_segment) && + * spansOfTrace.filter(span => getSpanOp(span) === 'db.query').length >= 2, * ); - * expect(spans.map(span => span.name)).toContainEqual('build component tree'); * ``` */ export function collectStreamedSpans( @@ -659,6 +664,46 @@ export function collectStreamedSpans( }).then(() => matched ?? []); } +/** + * Accumulate the spans of a trace until its segment (root) span has arrived. + * + * The segment span ends last, so its children typically flush in an earlier envelope; waiting for + * the segment is the common way to know that the whole trace is in hand. `segment` is either the + * segment span's exact name or a predicate over the segment span, for cases where the name alone is + * not unique (e.g. matching on `url.path` or the op). + * + * Use {@link collectStreamedSpans} directly when the trace is only complete once specific child + * spans have arrived as well. + * + * @example + * ```ts + * const spans = await collectStreamedSpansUntilSegment(PROXY_SERVER_NAME, 'GET /nested-layout'); + * const spans = await collectStreamedSpansUntilSegment(PROXY_SERVER_NAME, span => getSpanOp(span) === 'pageload'); + * ``` + */ +export function collectStreamedSpansUntilSegment( + proxyServerName: string, + segment: string | ((segmentSpan: SerializedStreamedSpan) => boolean), +): Promise { + const matchesSegment = + typeof segment === 'string' ? (span: SerializedStreamedSpan) => span.name === segment : segment; + + return collectStreamedSpans(proxyServerName, spansOfTrace => + spansOfTrace.some(span => span.is_segment && matchesSegment(span)), + ); +} + +/** + * Like {@link collectStreamedSpansUntilSegment}, but resolves with just the span names, for tests + * that only assert which spans a request produced. + */ +export function collectSpanNamesUntilSegment( + proxyServerName: string, + segment: string | ((segmentSpan: SerializedStreamedSpan) => boolean), +): Promise { + return collectStreamedSpansUntilSegment(proxyServerName, segment).then(spans => spans.map(span => span.name)); +} + /** * Helper to get the span operation from a Span V2 JSON object. * diff --git a/dev-packages/test-utils/src/index.ts b/dev-packages/test-utils/src/index.ts index ebb89fd08e5f..c0abd256d1ea 100644 --- a/dev-packages/test-utils/src/index.ts +++ b/dev-packages/test-utils/src/index.ts @@ -12,6 +12,8 @@ export { waitForStreamedSpans, waitForStreamedSpanEnvelope, collectStreamedSpans, + collectStreamedSpansUntilSegment, + collectSpanNamesUntilSegment, getSpanOp, } from './event-proxy-server'; export type { SerializedStreamedSpan } from '@sentry/core';