From 48dd278d4493c4412e38a4281613f6a407bec4af Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 4 Sep 2026 15:25:39 +0200 Subject: [PATCH] test(e2e): Port the nestjs-microservices E2E app to span streaming Removes the `traceLifecycle: 'static'` pin and rewrites the specs against streamed spans. The specs that documented microservice spans arriving as standalone transactions now document them arriving as segment spans of their own traces; the underlying missing trace propagation is unchanged. Ref: #23801 Co-Authored-By: Claude Opus 5 --- .../nestjs-microservices/src/instrument.ts | 1 - .../nestjs-microservices/tests/errors.test.ts | 10 +-- .../tests/transactions.test.ts | 72 ++++++++----------- 3 files changed, 34 insertions(+), 49 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-microservices/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-microservices/src/instrument.ts index e7230cd2b44d..e0a1cead1153 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-microservices/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-microservices/src/instrument.ts @@ -1,7 +1,6 @@ import * as Sentry from '@sentry/nestjs'; Sentry.init({ - traceLifecycle: 'static', environment: 'qa', dsn: process.env.E2E_TEST_DSN, tunnel: `http://localhost:3031/`, diff --git a/dev-packages/e2e-tests/test-applications/nestjs-microservices/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-microservices/tests/errors.test.ts index 4aaa9b4878be..db1157c6d455 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-microservices/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-microservices/tests/errors.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('Captures manually reported error in microservice handler', async ({ baseURL }) => { const errorEventPromise = waitForError('nestjs-microservices', event => { @@ -15,7 +15,7 @@ test('Captures manually reported error in microservice handler', async ({ baseUR }); // To verify that an exception is NOT automatically captured, we trigger it, -// wait for the transaction from that request to confirm it completed, flush, +// wait for the segment span from that request to confirm it completed, flush, // and then assert no error event was received. test('Does not automatically capture exceptions thrown in microservice handler', async ({ baseURL }) => { let autoCaptureFired = false; @@ -27,13 +27,13 @@ test('Does not automatically capture exceptions thrown in microservice handler', return false; }); - const transactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => { - return transactionEvent?.transaction === 'GET /test-microservice-exception/:id'; + const segmentSpanPromise = waitForStreamedSpan('nestjs-microservices', span => { + return span.is_segment && span.name === 'GET /test-microservice-exception/:id'; }); await fetch(`${baseURL}/test-microservice-exception/123`); - await transactionPromise; + await segmentSpanPromise; await fetch(`${baseURL}/flush`); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-microservices/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-microservices/tests/transactions.test.ts index ba2343a5277a..ac905dfed1cd 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-microservices/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-microservices/tests/transactions.test.ts @@ -1,85 +1,71 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -test('Sends an HTTP transaction', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('nestjs-microservices', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'http.server' && - transactionEvent?.transaction === 'GET /test-transaction' - ); +import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; + +const APP_NAME = 'nestjs-microservices'; + +test('Sends an HTTP segment span', async ({ baseURL }) => { + const spanPromise = waitForStreamedSpan(APP_NAME, span => { + return span.is_segment && span.name === 'GET /test-transaction'; }); const response = await fetch(`${baseURL}/test-transaction`); expect(response.status).toBe(200); - const transactionEvent = await transactionEventPromise; + const span = await spanPromise; - expect(transactionEvent.contexts?.trace).toEqual( - expect.objectContaining({ - op: 'http.server', - status: 'ok', - }), - ); + expect(getSpanOp(span)).toBe('http.server'); + expect(span.status).toBe('ok'); }); // Trace context does not propagate over NestJS TCP transport, so RPC spans are disconnected from -// the HTTP transaction. Instead of appearing as child spans of the HTTP transaction, auto-instrumented -// NestJS guard/interceptor/pipe spans become separate standalone transactions. +// the HTTP trace. Instead of appearing as child spans of the HTTP segment span, auto-instrumented +// NestJS guard/interceptor/pipe spans become segment spans of their own traces. // This documents the current (broken) behavior — ideally these should be connected to the HTTP trace. -test('Microservice spans are not connected to the HTTP transaction', async ({ baseURL }) => { - const httpTransactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'http.server' && - transactionEvent?.transaction === 'GET /test-microservice-sum' - ); - }); +test('Microservice spans are not connected to the HTTP trace', async ({ baseURL }) => { + const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /test-microservice-sum'); const response = await fetch(`${baseURL}/test-microservice-sum`); expect(response.status).toBe(200); - const httpTransaction = await httpTransactionPromise; + const spans = await spansPromise; - // The microservice span should be part of this transaction but isn't due to missing trace propagation - const microserviceSpan = httpTransaction.spans?.find(span => span.description === 'microservice-sum-operation'); - expect(microserviceSpan).toBeUndefined(); + // The microservice span should be part of this trace but isn't due to missing trace propagation + expect(spans.find(span => span.name === 'microservice-sum-operation')).toBeUndefined(); }); -test('Microservice guard is emitted as a standalone transaction instead of being part of the HTTP trace', async ({ +test('Microservice guard is emitted as a segment span of its own trace instead of being part of the HTTP trace', async ({ baseURL, }) => { - const guardTransactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => { - return transactionEvent?.transaction === 'ExampleGuard'; + const guardSpanPromise = waitForStreamedSpan(APP_NAME, span => { + return span.is_segment && span.name === 'ExampleGuard'; }); await fetch(`${baseURL}/test-microservice-guard`); - const guardTransaction = await guardTransactionPromise; - expect(guardTransaction).toBeDefined(); + expect(await guardSpanPromise).toBeDefined(); }); -test('Microservice interceptor is emitted as a standalone transaction instead of being part of the HTTP trace', async ({ +test('Microservice interceptor is emitted as a segment span of its own trace instead of being part of the HTTP trace', async ({ baseURL, }) => { - const interceptorTransactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => { - return transactionEvent?.transaction === 'ExampleInterceptor'; + const interceptorSpanPromise = waitForStreamedSpan(APP_NAME, span => { + return span.is_segment && span.name === 'ExampleInterceptor'; }); await fetch(`${baseURL}/test-microservice-interceptor`); - const interceptorTransaction = await interceptorTransactionPromise; - expect(interceptorTransaction).toBeDefined(); + expect(await interceptorSpanPromise).toBeDefined(); }); -test('Microservice pipe is emitted as a standalone transaction instead of being part of the HTTP trace', async ({ +test('Microservice pipe is emitted as a segment span of its own trace instead of being part of the HTTP trace', async ({ baseURL, }) => { - const pipeTransactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => { - return transactionEvent?.transaction === 'ExamplePipe'; + const pipeSpanPromise = waitForStreamedSpan(APP_NAME, span => { + return span.is_segment && span.name === 'ExamplePipe'; }); await fetch(`${baseURL}/test-microservice-pipe`); - const pipeTransaction = await pipeTransactionPromise; - expect(pipeTransaction).toBeDefined(); + expect(await pipeSpanPromise).toBeDefined(); });