From ee59488470deef2b69b5d40112c3a5b5f1e8e3bb Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 4 Sep 2026 15:25:37 +0200 Subject: [PATCH 1/2] test(e2e): Port the nestjs-basic-with-graphql E2E app to span streaming Removes the `traceLifecycle: 'static'` pin and rewrites the specs against streamed spans. Ref: #23801 Co-Authored-By: Claude Opus 5 --- .../src/instrument.ts | 1 - .../tests/errors.test.ts | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/src/instrument.ts index 6e84f952e762..f1f4de865435 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/src/instrument.ts @@ -1,7 +1,6 @@ import * as Sentry from '@sentry/nestjs'; Sentry.init({ - traceLifecycle: 'static', environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.E2E_TEST_DSN, tunnel: `http://localhost:3031/`, // proxy server diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/tests/errors.test.ts index 3c9d8532c889..19a993c12e2e 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/tests/errors.test.ts @@ -1,8 +1,18 @@ import { expect, test } from '@playwright/test'; -import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; + +const APP_NAME = 'nestjs-basic-with-graphql'; + +/** + * Resolves once the request's segment span has been streamed, which is how these specs know the + * request finished and any error it would have produced had its chance to be sent. + */ +function waitForSegmentSpan(name: string): Promise { + return waitForStreamedSpan(APP_NAME, span => span.is_segment && span.name === name); +} test('Sends exception to Sentry', async ({ baseURL }) => { - const errorEventPromise = waitForError('nestjs-basic-with-graphql', event => { + const errorEventPromise = waitForError(APP_NAME, event => { return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; }); @@ -38,7 +48,7 @@ test('Sends exception to Sentry', async ({ baseURL }) => { test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => { let errorEventOccurred = false; - waitForError('nestjs-basic-with-graphql', event => { + waitForError(APP_NAME, event => { if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 400 exception with id 123') { errorEventOccurred = true; } @@ -46,7 +56,7 @@ test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => { return event?.transaction === 'GET /test-expected-400-exception/:id'; }); - waitForError('nestjs-basic-with-graphql', event => { + waitForError(APP_NAME, event => { if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 500 exception with id 123') { errorEventOccurred = true; } @@ -54,13 +64,9 @@ test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => { return event?.transaction === 'GET /test-expected-500-exception/:id'; }); - const transactionEventPromise400 = waitForTransaction('nestjs-basic-with-graphql', transactionEvent => { - return transactionEvent?.transaction === 'GET /test-expected-400-exception/:id'; - }); + const segmentSpanPromise400 = waitForSegmentSpan('GET /test-expected-400-exception/:id'); - const transactionEventPromise500 = waitForTransaction('nestjs-basic-with-graphql', transactionEvent => { - return transactionEvent?.transaction === 'GET /test-expected-500-exception/:id'; - }); + const segmentSpanPromise500 = waitForSegmentSpan('GET /test-expected-500-exception/:id'); const response400 = await fetch(`${baseURL}/test-expected-400-exception/123`); expect(response400.status).toBe(400); @@ -68,8 +74,8 @@ test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => { const response500 = await fetch(`${baseURL}/test-expected-500-exception/123`); expect(response500.status).toBe(500); - await transactionEventPromise400; - await transactionEventPromise500; + await segmentSpanPromise400; + await segmentSpanPromise500; (await fetch(`${baseURL}/flush`)).text(); @@ -77,7 +83,7 @@ test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => { }); test('Sends graphql exception to Sentry', async ({ baseURL }) => { - const errorEventPromise = waitForError('nestjs-basic-with-graphql', event => { + const errorEventPromise = waitForError(APP_NAME, event => { return !event.type && event.exception?.values?.[0]?.value === 'This is an exception!'; }); From c3ccc8ce8253e0e19964aa6004c945ba685708d2 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 7 Sep 2026 10:05:46 +0200 Subject: [PATCH 2/2] test(e2e): Use the existing streamed-span helpers instead of a local wrapper The local `waitForSegmentSpan` only re-expressed what `collectStreamedSpansUntilSegment` and `waitForStreamedSpan` already do, and was copied into every ported app. Co-Authored-By: Claude Opus 5 --- .../tests/errors.test.ts | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/tests/errors.test.ts index 19a993c12e2e..e45f13986815 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/tests/errors.test.ts @@ -1,16 +1,8 @@ import { expect, test } from '@playwright/test'; -import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, waitForError } from '@sentry-internal/test-utils'; const APP_NAME = 'nestjs-basic-with-graphql'; -/** - * Resolves once the request's segment span has been streamed, which is how these specs know the - * request finished and any error it would have produced had its chance to be sent. - */ -function waitForSegmentSpan(name: string): Promise { - return waitForStreamedSpan(APP_NAME, span => span.is_segment && span.name === name); -} - test('Sends exception to Sentry', async ({ baseURL }) => { const errorEventPromise = waitForError(APP_NAME, event => { return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; @@ -64,9 +56,11 @@ test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => { return event?.transaction === 'GET /test-expected-500-exception/:id'; }); - const segmentSpanPromise400 = waitForSegmentSpan('GET /test-expected-400-exception/:id'); + // Waiting for each request's segment span is how this spec knows the request finished and + // any error it would have produced had its chance to be sent. + const spansPromise400 = collectStreamedSpansUntilSegment(APP_NAME, 'GET /test-expected-400-exception/:id'); - const segmentSpanPromise500 = waitForSegmentSpan('GET /test-expected-500-exception/:id'); + const spansPromise500 = collectStreamedSpansUntilSegment(APP_NAME, 'GET /test-expected-500-exception/:id'); const response400 = await fetch(`${baseURL}/test-expected-400-exception/123`); expect(response400.status).toBe(400); @@ -74,8 +68,8 @@ test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => { const response500 = await fetch(`${baseURL}/test-expected-500-exception/123`); expect(response500.status).toBe(500); - await segmentSpanPromise400; - await segmentSpanPromise500; + await spansPromise400; + await spansPromise500; (await fetch(`${baseURL}/flush`)).text();