diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/entry-client.tsx b/dev-packages/e2e-tests/test-applications/solidstart/src/entry-client.tsx index d9900ed5689d..11087fbb5918 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/src/entry-client.tsx +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/entry-client.tsx @@ -4,7 +4,6 @@ import { solidRouterBrowserTracingIntegration } from '@sentry/solidstart/solidro import { StartClient, mount } from '@solidjs/start/client'; Sentry.init({ - traceLifecycle: 'static', // We can't use env variables here, seems like they are stripped // out in production builds. dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/instrument.server.ts b/dev-packages/e2e-tests/test-applications/solidstart/src/instrument.server.ts index d89f2566f8be..3dd5d8933b7b 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/src/instrument.server.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/instrument.server.ts @@ -1,7 +1,6 @@ import * as Sentry from '@sentry/solidstart'; Sentry.init({ - traceLifecycle: 'static', dsn: process.env.E2E_TEST_DSN, environment: 'qa', // dynamic sampling bias to keep transactions tracesSampleRate: 1.0, // Capture 100% of the transactions diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/solidstart/tests/db.test.ts index ed2a1add22c1..38c6b0d0198e 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/db.test.ts @@ -1,88 +1,105 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; test('Instruments ioredis automatically via build-time orchestrion', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('solidstart', transactionEvent => { - return ( - transactionEvent.contexts?.trace?.op === 'http.server' && !!transactionEvent.transaction?.includes('db-ioredis') - ); - }); + const spansPromise = collectStreamedSpans( + 'solidstart', + spans => + spans.some( + span => + span.is_segment && + getSpanOp(span) === 'http.server' && + String(span.attributes['url.path']?.value ?? '').includes('db-ioredis'), + ) && + spans.some(span => span.attributes['db.query.text']?.value === 'set test-key [1 other arguments]') && + spans.some(span => span.attributes['db.query.text']?.value === 'get test-key'), + ); await fetch(`${baseURL}/api/db-ioredis`); - const transactionEvent = await transactionEventPromise; - const spans = transactionEvent.spans || []; + const spans = await spansPromise; + // ioredis also emits handshake commands (SETINFO, INFO) as db.query spans. + const redisSpans = spans.filter( + span => + getSpanOp(span) === 'db.query' && + (span.attributes['db.operation.name']?.value === 'set' || span.attributes['db.operation.name']?.value === 'get'), + ); - expect(spans).toContainEqual( + expect(redisSpans).toHaveLength(2); + expect(redisSpans).toContainEqual( expect.objectContaining({ - op: 'db.query', - origin: 'auto.db.redis', - description: 'set test-key [1 other arguments]', + name: 'set test-key [1 other arguments]', status: 'ok', - data: expect.objectContaining({ - 'db.system.name': 'redis', - 'db.operation.name': 'set', - 'db.query.text': 'set test-key [1 other arguments]', + attributes: expect.objectContaining({ + 'sentry.op': { type: 'string', value: 'db.query' }, + 'sentry.origin': { type: 'string', value: 'auto.db.redis' }, + 'db.system.name': { type: 'string', value: 'redis' }, + 'db.operation.name': { type: 'string', value: 'set' }, + 'db.query.text': { type: 'string', value: 'set test-key [1 other arguments]' }, }), }), ); - expect(spans).toContainEqual( + expect(redisSpans).toContainEqual( expect.objectContaining({ - op: 'db.query', - origin: 'auto.db.redis', - description: 'get test-key', + name: 'get test-key', status: 'ok', - data: expect.objectContaining({ - 'db.system.name': 'redis', - 'db.operation.name': 'get', - 'db.query.text': 'get test-key', + attributes: expect.objectContaining({ + 'sentry.op': { type: 'string', value: 'db.query' }, + 'sentry.origin': { type: 'string', value: 'auto.db.redis' }, + 'db.system.name': { type: 'string', value: 'redis' }, + 'db.operation.name': { type: 'string', value: 'get' }, + 'db.query.text': { type: 'string', value: 'get test-key' }, }), }), ); }); test('Instruments mysql automatically via build-time orchestrion', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('solidstart', transactionEvent => { - return ( - transactionEvent.contexts?.trace?.op === 'http.server' && !!transactionEvent.transaction?.includes('db-mysql') - ); - }); + const spansPromise = collectStreamedSpans( + 'solidstart', + spans => + spans.some( + span => + span.is_segment && + getSpanOp(span) === 'http.server' && + String(span.attributes['url.path']?.value ?? '').includes('db-mysql'), + ) && + spans.some(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution') && + spans.some(span => span.attributes['db.query.text']?.value === 'SELECT NOW()'), + ); await fetch(`${baseURL}/api/db-mysql`); - const transactionEvent = await transactionEventPromise; - const spans = transactionEvent.spans || []; + const spans = await spansPromise; + const mysqlSpans = spans.filter(span => span.attributes['sentry.origin']?.value === 'auto.db.mysql'); - expect(spans).toContainEqual( - expect.objectContaining({ - op: 'db', - origin: 'auto.db.mysql', - description: 'SELECT 1 + 1 AS solution', - status: 'ok', - data: expect.objectContaining({ - 'db.system.name': 'mysql', - 'db.query.text': 'SELECT 1 + 1 AS solution', - 'db.user': 'root', - 'db.connection_string': expect.any(String), - 'server.address': expect.any(String), - 'server.port': 3306, - }), - }), - ); - expect(spans).toContainEqual( - expect.objectContaining({ - op: 'db', - origin: 'auto.db.mysql', - description: 'SELECT NOW()', - status: 'ok', - data: expect.objectContaining({ - 'db.system.name': 'mysql', - 'db.query.text': 'SELECT NOW()', - 'db.user': 'root', - 'db.connection_string': expect.any(String), - 'server.address': expect.any(String), - 'server.port': 3306, - }), - }), - ); + const firstQuery = mysqlSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + expect(firstQuery).toBeDefined(); + expect(firstQuery!.name).toBe('SELECT'); + expect(firstQuery!.status).toBe('ok'); + expect(firstQuery!.attributes).toMatchObject({ + 'sentry.op': { type: 'string', value: 'db' }, + 'sentry.origin': { type: 'string', value: 'auto.db.mysql' }, + 'db.system.name': { type: 'string', value: 'mysql' }, + 'db.query.text': { type: 'string', value: 'SELECT 1 + 1 AS solution' }, + 'db.user': { type: 'string', value: 'root' }, + 'db.connection_string': { type: 'string', value: expect.any(String) }, + 'server.address': { type: 'string', value: expect.any(String) }, + 'server.port': { type: 'integer', value: 3306 }, + }); + + const secondQuery = mysqlSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT NOW()'); + expect(secondQuery).toBeDefined(); + expect(secondQuery!.name).toBe('SELECT'); + expect(secondQuery!.status).toBe('ok'); + expect(secondQuery!.attributes).toMatchObject({ + 'sentry.op': { type: 'string', value: 'db' }, + 'sentry.origin': { type: 'string', value: 'auto.db.mysql' }, + 'db.system.name': { type: 'string', value: 'mysql' }, + 'db.query.text': { type: 'string', value: 'SELECT NOW()' }, + 'db.user': { type: 'string', value: 'root' }, + 'db.connection_string': { type: 'string', value: expect.any(String) }, + 'server.address': { type: 'string', value: expect.any(String) }, + 'server.port': { type: 'integer', value: 3306 }, + }); }); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts b/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts index dabf85785aab..f4dab36a881b 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts @@ -1,119 +1,81 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; -test('sends a pageload transaction', async ({ page }) => { - const transactionPromise = waitForTransaction('solidstart', async transactionEvent => { - return transactionEvent?.transaction === '/' && transactionEvent.contexts?.trace?.op === 'pageload'; +test('sends a pageload span', async ({ page }) => { + const spanPromise = waitForStreamedSpan('solidstart', span => { + return span.name === '/' && getSpanOp(span) === 'pageload' && span.is_segment; }); await page.goto('/'); - const pageloadTransaction = await transactionPromise; + const pageloadSpan = await spanPromise; - expect(pageloadTransaction).toMatchObject({ - contexts: { - trace: { - op: 'pageload', - origin: 'auto.pageload.browser', - data: { - 'sentry.segment.name.source': 'route', - 'url.template': '/', - 'url.path': '/', - 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), - }, - }, - }, - transaction: '/', - transaction_info: { - source: 'route', - }, + expect(getSpanOp(pageloadSpan)).toBe('pageload'); + expect(pageloadSpan.attributes).toMatchObject({ + 'sentry.origin': { value: 'auto.pageload.browser', type: 'string' }, + 'sentry.op': { value: 'pageload', type: 'string' }, + 'sentry.segment.name.source': { value: 'route', type: 'string' }, + 'url.template': { value: '/', type: 'string' }, + 'url.path': { value: '/', type: 'string' }, + 'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), type: 'string' }, }); }); -test('sends a navigation transaction with parametrized route', async ({ page }) => { - const transactionPromise = waitForTransaction('solidstart', async transactionEvent => { - return transactionEvent?.transaction === '/users/:id' && transactionEvent.contexts?.trace?.op === 'navigation'; +test('sends a navigation span with parametrized route', async ({ page }) => { + const spanPromise = waitForStreamedSpan('solidstart', span => { + return span.name === '/users/:id' && getSpanOp(span) === 'navigation' && span.is_segment; }); await page.goto(`/`); await page.locator('#navLink').click(); - const navigationTransaction = await transactionPromise; + const navigationSpan = await spanPromise; - expect(navigationTransaction).toMatchObject({ - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.solidstart.solidrouter', - data: { - 'sentry.segment.name.source': 'route', - 'url.template': '/users/:id', - 'url.path': '/users/5', - 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/users\/5$/), - }, - }, - }, - transaction: '/users/:id', - transaction_info: { - source: 'route', - }, + expect(getSpanOp(navigationSpan)).toBe('navigation'); + expect(navigationSpan.attributes).toMatchObject({ + 'sentry.origin': { value: 'auto.navigation.solidstart.solidrouter', type: 'string' }, + 'sentry.op': { value: 'navigation', type: 'string' }, + 'sentry.segment.name.source': { value: 'route', type: 'string' }, + 'url.template': { value: '/users/:id', type: 'string' }, + 'url.path': { value: '/users/5', type: 'string' }, + 'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/users\/5$/), type: 'string' }, }); }); -test('updates the transaction when using the back button', async ({ page }) => { +test('updates the span when using the back button', async ({ page }) => { // Solid Router sends a `-1` navigation when using the back button. // The sentry solidRouterBrowserTracingIntegration tries to update such - // transactions with the proper name once the `useLocation` hook triggers. - const navigationTxnPromise = waitForTransaction('solidstart', async transactionEvent => { - return transactionEvent?.transaction === '/users/:id' && transactionEvent.contexts?.trace?.op === 'navigation'; + // spans with the proper name once the `useLocation` hook triggers. + const navigationSpanPromise = waitForStreamedSpan('solidstart', span => { + return span.name === '/users/:id' && getSpanOp(span) === 'navigation' && span.is_segment; }); await page.goto(`/back-navigation`); await page.locator('#navLink').click(); - const navigationTxn = await navigationTxnPromise; + const navigationSpan = await navigationSpanPromise; - expect(navigationTxn).toMatchObject({ - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.solidstart.solidrouter', - data: { - 'sentry.segment.name.source': 'route', - 'url.template': '/users/:id', - 'url.path': '/users/6', - 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/users\/6$/), - }, - }, - }, - transaction: '/users/:id', - transaction_info: { - source: 'route', - }, + expect(getSpanOp(navigationSpan)).toBe('navigation'); + expect(navigationSpan.attributes).toMatchObject({ + 'sentry.origin': { value: 'auto.navigation.solidstart.solidrouter', type: 'string' }, + 'sentry.op': { value: 'navigation', type: 'string' }, + 'sentry.segment.name.source': { value: 'route', type: 'string' }, + 'url.template': { value: '/users/:id', type: 'string' }, + 'url.path': { value: '/users/6', type: 'string' }, + 'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/users\/6$/), type: 'string' }, }); - const backNavigationTxnPromise = waitForTransaction('solidstart', async transactionEvent => { - return ( - transactionEvent?.transaction === '/back-navigation' && transactionEvent.contexts?.trace?.op === 'navigation' - ); + const backNavigationSpanPromise = waitForStreamedSpan('solidstart', span => { + return span.name === '/back-navigation' && getSpanOp(span) === 'navigation' && span.is_segment; }); await page.goBack(); - const backNavigationTxn = await backNavigationTxnPromise; + const backNavigationSpan = await backNavigationSpanPromise; - expect(backNavigationTxn).toMatchObject({ - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.solidstart.solidrouter', - data: { - 'sentry.segment.name.source': 'route', - 'url.template': '/back-navigation', - 'url.path': '/back-navigation', - 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/back-navigation$/), - }, - }, - }, - transaction: '/back-navigation', - transaction_info: { - source: 'route', - }, + expect(getSpanOp(backNavigationSpan)).toBe('navigation'); + expect(backNavigationSpan.attributes).toMatchObject({ + 'sentry.origin': { value: 'auto.navigation.solidstart.solidrouter', type: 'string' }, + 'sentry.op': { value: 'navigation', type: 'string' }, + 'sentry.segment.name.source': { value: 'route', type: 'string' }, + 'url.template': { value: '/back-navigation', type: 'string' }, + 'url.path': { value: '/back-navigation', type: 'string' }, + 'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/back-navigation$/), type: 'string' }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.server.test.ts index a70ed673de0b..c41660da02a9 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.server.test.ts @@ -1,49 +1,46 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; -import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/solidstart'; - -test('sends a server action transaction on pageload', async ({ page }) => { - const transactionPromise = waitForTransaction('solidstart', transactionEvent => { - return transactionEvent?.transaction === 'GET /users/6'; - }); +import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; + +test('sends a server action span on pageload', async ({ page }) => { + const spansPromise = collectStreamedSpans( + 'solidstart', + spans => + spans.some( + span => + span.is_segment && getSpanOp(span) === 'http.server' && span.attributes['url.path']?.value === '/users/6', + ) && spans.some(span => span.name === 'getPrefecture'), + ); await page.goto('/users/6'); - const transaction = await transactionPromise; - - expect(transaction.spans).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - description: 'getPrefecture', - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.solidstart', - }, - }), - ]), - ); -}); + const spans = await spansPromise; + const functionSpan = spans.find(span => span.name === 'getPrefecture'); -test('sends a server action transaction on client navigation', async ({ page }) => { - const transactionPromise = waitForTransaction('solidstart', transactionEvent => { - return transactionEvent?.transaction === 'POST getPrefecture'; + expect(functionSpan).toBeDefined(); + expect(functionSpan?.attributes).toMatchObject({ + 'sentry.op': { value: 'function', type: 'string' }, + 'sentry.origin': { value: 'auto.function.solidstart', type: 'string' }, }); +}); + +test('sends a server action span on client navigation', async ({ page }) => { + const spansPromise = collectStreamedSpans( + 'solidstart', + spans => + spans.some(span => span.is_segment && span.name === 'POST getPrefecture') && + spans.some(span => span.name === 'getPrefecture' && !span.is_segment), + ); await page.goto('/'); await page.locator('#navLink').click(); await page.waitForURL('/users/5'); - const transaction = await transactionPromise; - - expect(transaction.spans).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - description: 'getPrefecture', - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.solidstart', - }, - }), - ]), - ); + const spans = await spansPromise; + const functionSpan = spans.find(span => span.name === 'getPrefecture' && !span.is_segment); + + expect(functionSpan).toBeDefined(); + expect(functionSpan?.attributes).toMatchObject({ + 'sentry.op': { value: 'function', type: 'string' }, + 'sentry.origin': { value: 'auto.function.solidstart', type: 'string' }, + }); });