diff --git a/dev-packages/e2e-tests/test-applications/nitro-3/package.json b/dev-packages/e2e-tests/test-applications/nitro-3/package.json index 99fd417c2a54..85713dccc138 100644 --- a/dev-packages/e2e-tests/test-applications/nitro-3/package.json +++ b/dev-packages/e2e-tests/test-applications/nitro-3/package.json @@ -19,7 +19,7 @@ "@playwright/test": "~1.56.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", - "nitro": "^3.0.260429-beta", + "nitro": "^3.0.260522-beta", "rolldown": "latest", "vite": "latest" }, diff --git a/dev-packages/e2e-tests/test-applications/nitro-3/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nitro-3/tests/errors.test.ts index 8e419ac9ba62..673a6e293fab 100644 --- a/dev-packages/e2e-tests/test-applications/nitro-3/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nitro-3/tests/errors.test.ts @@ -10,22 +10,16 @@ test('Sends an error event to Sentry', async ({ request }) => { const errorEvent = await errorEventPromise; - // Nitro wraps thrown errors in an HTTPError with .cause, producing a chained exception - expect(errorEvent.exception?.values).toHaveLength(2); + expect(errorEvent.exception?.values).toHaveLength(1); - // The innermost exception (values[0]) is the original thrown error expect(errorEvent.exception?.values?.[0]?.type).toBe('Error'); expect(errorEvent.exception?.values?.[0]?.value).toBe('This is a test error'); expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual( expect.objectContaining({ handled: false, - type: 'auto.function.nitro.captureErrorHook', + type: 'auto.http.nitro.onTraceError', }), ); - - // The outermost exception (values[1]) is the HTTPError wrapper - expect(errorEvent.exception?.values?.[1]?.type).toBe('HTTPError'); - expect(errorEvent.exception?.values?.[1]?.value).toBe('This is a test error'); }); test('Does not send 404 errors to Sentry', async ({ request }) => { diff --git a/dev-packages/e2e-tests/test-applications/nitro-3/tests/span-nesting.test.ts b/dev-packages/e2e-tests/test-applications/nitro-3/tests/span-nesting.test.ts index 090f8af36fb2..8a824c4fe429 100644 --- a/dev-packages/e2e-tests/test-applications/nitro-3/tests/span-nesting.test.ts +++ b/dev-packages/e2e-tests/test-applications/nitro-3/tests/span-nesting.test.ts @@ -33,17 +33,17 @@ test('Span nesting: h3 middleware spans are children of the srvx request span', expect(srvxSpan).toBeDefined(); // All h3 middleware spans should be children of the srvx span - const h3Spans = event.spans?.filter(span => span.origin === 'auto.http.nitro.h3'); - expect(h3Spans?.length).toBeGreaterThanOrEqual(1); + const h3MiddlewareSpans = event.spans?.filter( + span => span.origin === 'auto.http.nitro.h3' && span.op === 'middleware.nitro', + ); + expect(h3MiddlewareSpans?.length).toBeGreaterThanOrEqual(1); - for (const span of h3Spans ?? []) { + for (const span of h3MiddlewareSpans ?? []) { expect(span.parent_span_id).toBe(srvxSpan!.span_id); } }); -test('Span nesting: manual startSpan calls inside route handler are children of the srvx request span', async ({ - request, -}) => { +test('Span nesting: h3 route handler span is a child of the srvx request span', async ({ request }) => { const transactionEventPromise = waitForTransaction('nitro-3', event => { return event?.transaction === 'GET /api/test-nesting'; }); @@ -52,10 +52,28 @@ test('Span nesting: manual startSpan calls inside route handler are children of const event = await transactionEventPromise; - // Find the srvx request span — this is the parent of all h3 and manual spans const srvxSpan = event.spans?.find(span => span.origin === 'auto.http.nitro.srvx' && span.op === 'http.server'); expect(srvxSpan).toBeDefined(); - const srvxSpanId = srvxSpan!.span_id; + + const h3HandlerSpan = event.spans?.find(span => span.origin === 'auto.http.nitro.h3' && span.op === 'http.server'); + expect(h3HandlerSpan).toBeDefined(); + expect(h3HandlerSpan!.parent_span_id).toBe(srvxSpan!.span_id); +}); + +test('Span nesting: manual startSpan calls inside route handler are children of the h3 route handler span', async ({ + request, +}) => { + const transactionEventPromise = waitForTransaction('nitro-3', event => { + return event?.transaction === 'GET /api/test-nesting'; + }); + + await request.get('/api/test-nesting'); + + const event = await transactionEventPromise; + + // Find the h3 route handler span + const h3HandlerSpan = event.spans?.find(span => span.origin === 'auto.http.nitro.h3' && span.op === 'http.server'); + expect(h3HandlerSpan).toBeDefined(); // Find the manually created db spans const dbSelectSpan = event.spans?.find(span => span.op === 'db' && span.description === 'db.select'); @@ -63,12 +81,9 @@ test('Span nesting: manual startSpan calls inside route handler are children of expect(dbSelectSpan).toBeDefined(); expect(dbInsertSpan).toBeDefined(); - // FIXME: Once nitro's h3 tracing plugin emits a separate span for route handlers (type: "route"), - // the db spans should be children of the h3 route handler span, not the srvx span directly. - // Currently nitro bypasses h3's ~routes for file-based routing, so h3 only emits middleware spans. - // Both db spans should be children of the srvx request span - expect(dbSelectSpan!.parent_span_id).toBe(srvxSpanId); - expect(dbInsertSpan!.parent_span_id).toBe(srvxSpanId); + // Both db spans should be children of the h3 route handler span + expect(dbSelectSpan!.parent_span_id).toBe(h3HandlerSpan!.span_id); + expect(dbInsertSpan!.parent_span_id).toBe(h3HandlerSpan!.span_id); // Both db spans should be siblings (same parent) expect(dbSelectSpan!.parent_span_id).toBe(dbInsertSpan!.parent_span_id); @@ -79,49 +94,6 @@ test('Span nesting: manual startSpan calls inside route handler are children of expect(serializeSpan!.parent_span_id).toBe(dbInsertSpan!.span_id); }); -// FIXME: Nitro's file-based routing bypasses h3's ~routes, so h3's tracing plugin never wraps -// route handlers with type: "route". Once this is fixed upstream or we add our own wrapping, -// uncomment these tests to verify the h3 route handler span exists and is the parent of manual spans. -// -// test('Span nesting: h3 route handler span is a child of the srvx request span', async ({ request }) => { -// const transactionEventPromise = waitForTransaction('nitro-3', event => { -// return event?.transaction === 'GET /api/test-nesting'; -// }); -// -// await request.get('/api/test-nesting'); -// -// const event = await transactionEventPromise; -// -// const srvxSpan = event.spans?.find(span => span.origin === 'auto.http.nitro.srvx' && span.op === 'http.server'); -// expect(srvxSpan).toBeDefined(); -// -// const h3HandlerSpan = event.spans?.find( -// span => span.origin === 'auto.http.nitro.h3' && span.op === 'http.server', -// ); -// expect(h3HandlerSpan).toBeDefined(); -// expect(h3HandlerSpan!.parent_span_id).toBe(srvxSpan!.span_id); -// }); -// -// test('Span nesting: manual startSpan calls are children of the h3 route handler span', async ({ request }) => { -// const transactionEventPromise = waitForTransaction('nitro-3', event => { -// return event?.transaction === 'GET /api/test-nesting'; -// }); -// -// await request.get('/api/test-nesting'); -// -// const event = await transactionEventPromise; -// -// const h3HandlerSpan = event.spans?.find( -// span => span.origin === 'auto.http.nitro.h3' && span.op === 'http.server', -// ); -// expect(h3HandlerSpan).toBeDefined(); -// -// const dbSelectSpan = event.spans?.find(span => span.op === 'db' && span.description === 'db.select'); -// const dbInsertSpan = event.spans?.find(span => span.op === 'db' && span.description === 'db.insert'); -// expect(dbSelectSpan!.parent_span_id).toBe(h3HandlerSpan!.span_id); -// expect(dbInsertSpan!.parent_span_id).toBe(h3HandlerSpan!.span_id); -// }); - test('Span nesting: middleware spans start before manual spans in the span tree', async ({ request }) => { const transactionEventPromise = waitForTransaction('nitro-3', event => { return event?.transaction === 'GET /api/test-nesting';