From 045963da7cda040226d07fd5012bbd501e32c940 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Wed, 2 Sep 2026 11:52:16 +0200 Subject: [PATCH] test(e2e): Port the SolidStart 2 E2E app to span streaming Removes the static trace lifecycle pin and rewrites the specs against streamed span v2. Server action children are collected across envelopes; the http.server segment is matched on url.path because an unparameterized streamed name is the method alone. Co-Authored-By: Cursor Grok 4.6 --- .../solidstart-2/server/plugins/sentry.ts | 1 - .../solidstart-2/src/entry-client.tsx | 1 - .../tests/performance.client.test.ts | 136 +++++++----------- .../tests/performance.server.test.ts | 71 +++++---- 4 files changed, 83 insertions(+), 126 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/solidstart-2/server/plugins/sentry.ts b/dev-packages/e2e-tests/test-applications/solidstart-2/server/plugins/sentry.ts index afb4cc632949..812abe8255fb 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-2/server/plugins/sentry.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart-2/server/plugins/sentry.ts @@ -4,7 +4,6 @@ import { definePlugin } from 'nitro'; // Runs once at server startup. Build-time instrumentation means no `--import` preload is needed. export default definePlugin(() => { 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-2/src/entry-client.tsx b/dev-packages/e2e-tests/test-applications/solidstart-2/src/entry-client.tsx index d9900ed5689d..11087fbb5918 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-2/src/entry-client.tsx +++ b/dev-packages/e2e-tests/test-applications/solidstart-2/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-2/tests/performance.client.test.ts b/dev-packages/e2e-tests/test-applications/solidstart-2/tests/performance.client.test.ts index b812500bc737..7548fb202815 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-2/tests/performance.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart-2/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-2', async transactionEvent => { - return transactionEvent?.transaction === '/' && transactionEvent.contexts?.trace?.op === 'pageload'; +test('sends a pageload span', async ({ page }) => { + const spanPromise = waitForStreamedSpan('solidstart-2', 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-2', 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-2', 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-2', 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-2', 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-2', async transactionEvent => { - return ( - transactionEvent?.transaction === '/back-navigation' && transactionEvent.contexts?.trace?.op === 'navigation' - ); + const backNavigationSpanPromise = waitForStreamedSpan('solidstart-2', 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-2/tests/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/solidstart-2/tests/performance.server.test.ts index 5324b427b8d7..0d636a5c423c 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-2/tests/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart-2/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-2', 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-2', + 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-2', 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-2', + 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' }, + }); });