diff --git a/dev-packages/e2e-tests/test-applications/effect-3-node/src/app.ts b/dev-packages/e2e-tests/test-applications/effect-3-node/src/app.ts index 6ddbc1fc5e28..9de243f3cab3 100644 --- a/dev-packages/e2e-tests/test-applications/effect-3-node/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/effect-3-node/src/app.ts @@ -10,7 +10,6 @@ import { createServer } from 'http'; const SentryLive = Layer.mergeAll( Sentry.effectLayer({ - traceLifecycle: 'static', dsn: process.env.E2E_TEST_DSN, environment: 'qa', debug: !!process.env.DEBUG, diff --git a/dev-packages/e2e-tests/test-applications/effect-3-node/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/effect-3-node/tests/spans.test.ts new file mode 100644 index 000000000000..3b04189aea2f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/effect-3-node/tests/spans.test.ts @@ -0,0 +1,65 @@ +import { expect, test } from '@playwright/test'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; + +['test-success', 'test-error'].forEach(route => { + test(`Sends an HTTP segment for ${route}`, async ({ baseURL }) => { + const spanPromise = waitForStreamedSpan( + 'effect-3-node', + span => + span.is_segment && getSpanOp(span) === 'http.server' && span.attributes['url.path']?.value === `/${route}`, + ); + + await fetch(`${baseURL}/${route}`); + + const span = await spanPromise; + expect(span.name).toBe('http.server GET'); + expect(span.attributes['sentry.origin']?.value).toBe('auto.http.effect'); + }); +}); + +test('Sends a manual Effect span', async ({ baseURL }) => { + const spansPromise = collectStreamedSpans( + 'effect-3-node', + spans => + spans.some(span => span.is_segment && getSpanOp(span) === 'http.server') && + spans.some(span => span.name === 'test-span'), + ); + + await fetch(`${baseURL}/test-transaction`); + + const spans = await spansPromise; + const segment = spans.find(span => span.is_segment)!; + const children = spans.filter(span => !span.is_segment); + expect(segment.name).toBe('http.server GET'); + expect(children).toHaveLength(1); + expect(children[0]).toMatchObject({ name: 'test-span', parent_span_id: segment.span_id }); +}); + +test('Sends Effect spans with correct parent-child structure', async ({ baseURL }) => { + const spansPromise = collectStreamedSpans( + 'effect-3-node', + spans => + spans.some(span => span.is_segment && getSpanOp(span) === 'http.server') && + spans.some(span => span.name === 'custom-effect-span') && + spans.some(span => span.name === 'nested-span'), + ); + + await fetch(`${baseURL}/test-effect-span`); + + const spans = await spansPromise; + const segment = spans.find(span => span.is_segment)!; + const children = spans.filter(span => !span.is_segment); + expect(segment.name).toBe('http.server GET'); + expect(segment.attributes['sentry.origin']?.value).toBe('auto.http.effect'); + expect(segment.attributes['sentry.sdk.name']?.value).toBe('sentry.javascript.effect'); + expect(children).toHaveLength(2); + const parent = children.find(span => span.name === 'custom-effect-span')!; + const nested = children.find(span => span.name === 'nested-span')!; + expect(parent.parent_span_id).toBe(segment.span_id); + expect(nested.parent_span_id).toBe(parent.span_id); + for (const child of children) { + expect(getSpanOp(child)).toBe('function'); + expect(child.attributes['sentry.origin']?.value).toBe('auto.function.effect'); + expect(child.trace_id).toBe(segment.trace_id); + } +}); diff --git a/dev-packages/e2e-tests/test-applications/effect-3-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/effect-3-node/tests/transactions.test.ts deleted file mode 100644 index 0992d01969d7..000000000000 --- a/dev-packages/e2e-tests/test-applications/effect-3-node/tests/transactions.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -test('Sends an HTTP transaction', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('effect-3-node', transactionEvent => { - return transactionEvent?.transaction === 'http.server GET'; - }); - - await fetch(`${baseURL}/test-success`); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent.transaction).toBe('http.server GET'); -}); - -test('Sends transaction with manual Effect span', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('effect-3-node', transactionEvent => { - return ( - transactionEvent?.transaction === 'http.server GET' && - transactionEvent?.spans?.some(span => span.description === 'test-span') - ); - }); - - await fetch(`${baseURL}/test-transaction`); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent.transaction).toBe('http.server GET'); - - const spans = transactionEvent.spans || []; - expect(spans).toEqual([ - expect.objectContaining({ - description: 'test-span', - }), - ]); -}); - -test('Sends Effect spans with correct parent-child structure', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('effect-3-node', transactionEvent => { - return ( - transactionEvent?.transaction === 'http.server GET' && - transactionEvent?.spans?.some(span => span.description === 'custom-effect-span') - ); - }); - - await fetch(`${baseURL}/test-effect-span`); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent.transaction).toBe('http.server GET'); - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - origin: 'auto.http.effect', - }), - }), - spans: [ - expect.objectContaining({ - description: 'custom-effect-span', - op: 'function', - origin: 'auto.function.effect', - }), - expect.objectContaining({ - description: 'nested-span', - op: 'function', - origin: 'auto.function.effect', - }), - ], - sdk: expect.objectContaining({ - name: 'sentry.javascript.effect', - packages: [ - expect.objectContaining({ - name: 'npm:@sentry/effect', - }), - expect.objectContaining({ - name: 'npm:@sentry/node', - }), - ], - }), - }), - ); - - const parentSpan = transactionEvent.spans?.[0]?.span_id; - const nestedSpan = transactionEvent.spans?.[1]?.parent_span_id; - - expect(nestedSpan).toBe(parentSpan); -}); - -test('Sends transaction for error route', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('effect-3-node', transactionEvent => { - return transactionEvent?.transaction === 'http.server GET'; - }); - - await fetch(`${baseURL}/test-error`); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent.transaction).toBe('http.server GET'); -}); diff --git a/dev-packages/e2e-tests/test-applications/effect-4-node/src/app.ts b/dev-packages/e2e-tests/test-applications/effect-4-node/src/app.ts index 7ccc484ea40f..1109f1a412b9 100644 --- a/dev-packages/e2e-tests/test-applications/effect-4-node/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/effect-4-node/src/app.ts @@ -11,7 +11,6 @@ import { createServer } from 'http'; const SentryLive = Layer.mergeAll( Sentry.effectLayer({ - traceLifecycle: 'static', dsn: process.env.E2E_TEST_DSN, environment: 'qa', debug: !!process.env.DEBUG, diff --git a/dev-packages/e2e-tests/test-applications/effect-4-node/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/effect-4-node/tests/spans.test.ts new file mode 100644 index 000000000000..7477d91a04be --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/effect-4-node/tests/spans.test.ts @@ -0,0 +1,65 @@ +import { expect, test } from '@playwright/test'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; + +['test-success', 'test-error'].forEach(route => { + test(`Sends an HTTP segment for ${route}`, async ({ baseURL }) => { + const spanPromise = waitForStreamedSpan( + 'effect-4-node', + span => + span.is_segment && getSpanOp(span) === 'http.server' && span.attributes['url.path']?.value === `/${route}`, + ); + + await fetch(`${baseURL}/${route}`); + + const span = await spanPromise; + expect(span.name).toBe('http.server GET'); + expect(span.attributes['sentry.origin']?.value).toBe('auto.http.effect'); + }); +}); + +test('Sends a manual Effect span', async ({ baseURL }) => { + const spansPromise = collectStreamedSpans( + 'effect-4-node', + spans => + spans.some(span => span.is_segment && getSpanOp(span) === 'http.server') && + spans.some(span => span.name === 'test-span'), + ); + + await fetch(`${baseURL}/test-transaction`); + + const spans = await spansPromise; + const segment = spans.find(span => span.is_segment)!; + const children = spans.filter(span => !span.is_segment); + expect(segment.name).toBe('http.server GET'); + expect(children).toHaveLength(1); + expect(children[0]).toMatchObject({ name: 'test-span', parent_span_id: segment.span_id }); +}); + +test('Sends Effect spans with correct parent-child structure', async ({ baseURL }) => { + const spansPromise = collectStreamedSpans( + 'effect-4-node', + spans => + spans.some(span => span.is_segment && getSpanOp(span) === 'http.server') && + spans.some(span => span.name === 'custom-effect-span') && + spans.some(span => span.name === 'nested-span'), + ); + + await fetch(`${baseURL}/test-effect-span`); + + const spans = await spansPromise; + const segment = spans.find(span => span.is_segment)!; + const children = spans.filter(span => !span.is_segment); + expect(segment.name).toBe('http.server GET'); + expect(segment.attributes['sentry.origin']?.value).toBe('auto.http.effect'); + expect(segment.attributes['sentry.sdk.name']?.value).toBe('sentry.javascript.effect'); + expect(children).toHaveLength(2); + const parent = children.find(span => span.name === 'custom-effect-span')!; + const nested = children.find(span => span.name === 'nested-span')!; + expect(parent.parent_span_id).toBe(segment.span_id); + expect(nested.parent_span_id).toBe(parent.span_id); + for (const child of children) { + expect(getSpanOp(child)).toBe('function'); + expect(child.attributes['sentry.origin']?.value).toBe('auto.function.effect'); + expect(child.trace_id).toBe(segment.trace_id); + } +}); diff --git a/dev-packages/e2e-tests/test-applications/effect-4-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/effect-4-node/tests/transactions.test.ts deleted file mode 100644 index 599d69a8cc84..000000000000 --- a/dev-packages/e2e-tests/test-applications/effect-4-node/tests/transactions.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -test('Sends an HTTP transaction', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('effect-4-node', transactionEvent => { - return transactionEvent?.transaction === 'http.server GET'; - }); - - await fetch(`${baseURL}/test-success`); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent.transaction).toBe('http.server GET'); -}); - -test('Sends transaction with manual Effect span', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('effect-4-node', transactionEvent => { - return ( - transactionEvent?.transaction === 'http.server GET' && - transactionEvent?.spans?.some(span => span.description === 'test-span') - ); - }); - - await fetch(`${baseURL}/test-transaction`); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent.transaction).toBe('http.server GET'); - - const spans = transactionEvent.spans || []; - expect(spans).toEqual([ - expect.objectContaining({ - description: 'test-span', - }), - ]); -}); - -test('Sends Effect spans with correct parent-child structure', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('effect-4-node', transactionEvent => { - return ( - transactionEvent?.transaction === 'http.server GET' && - transactionEvent?.spans?.some(span => span.description === 'custom-effect-span') - ); - }); - - await fetch(`${baseURL}/test-effect-span`); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent.transaction).toBe('http.server GET'); - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - origin: 'auto.http.effect', - }), - }), - spans: [ - expect.objectContaining({ - description: 'custom-effect-span', - op: 'function', - origin: 'auto.function.effect', - }), - expect.objectContaining({ - description: 'nested-span', - op: 'function', - origin: 'auto.function.effect', - }), - ], - sdk: expect.objectContaining({ - name: 'sentry.javascript.effect', - packages: [ - expect.objectContaining({ - name: 'npm:@sentry/effect', - }), - expect.objectContaining({ - name: 'npm:@sentry/node', - }), - ], - }), - }), - ); - - const parentSpan = transactionEvent.spans?.[0]?.span_id; - const nestedSpan = transactionEvent.spans?.[1]?.parent_span_id; - - expect(nestedSpan).toBe(parentSpan); -}); - -test('Sends transaction for error route', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('effect-4-node', transactionEvent => { - return transactionEvent?.transaction === 'http.server GET'; - }); - - await fetch(`${baseURL}/test-error`); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent.transaction).toBe('http.server GET'); -});