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';