From 0f5dec5da17fe5a77c50a896c849b5cd0e7894be Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 4 Sep 2026 15:52:15 +0200 Subject: [PATCH] test(e2e): Port plain browser and bundler E2E apps to span streaming Co-Authored-By: OpenAI Codex --- .../browser-mfe-vite/apps/shell/src/main.tsx | 1 - .../tests/mfe-span-attribution.test.ts | 30 +++-- .../browser-webworker-vite/src/main.ts | 1 - .../tests/errors.test.ts | 53 +++----- .../debug-id-sourcemaps/src/app.js | 1 - .../tests/__snapshots__/server.test.ts.snap | 4 +- .../default-browser-static/.gitignore | 29 ++++ .../default-browser-static/build.mjs | 49 +++++++ .../default-browser-static/package.json | 41 ++++++ .../playwright.config.mjs | 7 + .../default-browser-static/public/index.html | 21 +++ .../default-browser-static/src/index.js | 19 +++ .../start-event-proxy.mjs | 6 + .../tests/errors.test.ts | 58 ++++++++ .../tests/performance.test.ts | 99 ++++++++++++++ .../default-browser-static/tsconfig.json | 20 +++ .../default-browser/src/index.js | 1 - .../default-browser/tests/errors.test.ts | 12 +- .../default-browser/tests/performance.test.ts | 113 +++++----------- .../effect-3-browser/src/index.js | 1 - .../effect-3-browser/tests/errors.test.ts | 12 +- .../tests/transactions.test.ts | 126 ++++++------------ .../effect-4-browser/src/index.js | 1 - .../effect-4-browser/tests/errors.test.ts | 12 +- .../tests/transactions.test.ts | 126 ++++++------------ .../test-applications/webpack-4/entry.js | 1 - .../test-applications/webpack-5/entry.js | 1 - 27 files changed, 530 insertions(+), 315 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/build.mjs create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/package.json create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/public/index.html create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/src/index.js create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/tests/performance.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/browser-mfe-vite/apps/shell/src/main.tsx b/dev-packages/e2e-tests/test-applications/browser-mfe-vite/apps/shell/src/main.tsx index 25ed9ca863be..cf33087c4f04 100644 --- a/dev-packages/e2e-tests/test-applications/browser-mfe-vite/apps/shell/src/main.tsx +++ b/dev-packages/e2e-tests/test-applications/browser-mfe-vite/apps/shell/src/main.tsx @@ -3,7 +3,6 @@ import ReactDOM from 'react-dom/client'; import * as Sentry from '@sentry/react'; Sentry.init({ - traceLifecycle: 'static', dsn: import.meta.env.PUBLIC_E2E_TEST_DSN, environment: import.meta.env.MODE || 'development', integrations: [Sentry.browserTracingIntegration()], diff --git a/dev-packages/e2e-tests/test-applications/browser-mfe-vite/tests/mfe-span-attribution.test.ts b/dev-packages/e2e-tests/test-applications/browser-mfe-vite/tests/mfe-span-attribution.test.ts index 0f842bb64c7e..f913de18b663 100644 --- a/dev-packages/e2e-tests/test-applications/browser-mfe-vite/tests/mfe-span-attribution.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-mfe-vite/tests/mfe-span-attribution.test.ts @@ -1,28 +1,36 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; + +function hasUrlPath(span: { attributes: Record }, path: string): boolean { + return `${span.attributes['url.full']?.value}`.includes(path); +} test('attributes fetch spans to their originating microfrontend', async ({ page }) => { - const transactionPromise = waitForTransaction('browser-mfe-vite', transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + const spansPromise = collectStreamedSpans('browser-mfe-vite', spans => { + const httpSpans = spans.filter(span => getSpanOp(span) === 'http.client'); + + return ['/api/header-data', '/api/mfe-one-data', '/api/shell-config'].every(path => + httpSpans.some(span => hasUrlPath(span, path)), + ); }); await page.goto('/'); - const transactionEvent = await transactionPromise; - const httpSpans = transactionEvent.spans?.filter(span => span.op === 'http.client') || []; + const spans = await spansPromise; + const httpSpans = spans.filter(span => getSpanOp(span) === 'http.client'); // MFE spans carry the mfe.name attribute set via withScope + spanStart hook - const headerSpan = httpSpans.find(s => s.description?.includes('/api/header-data')); - const mfeOneSpan = httpSpans.find(s => s.description?.includes('/api/mfe-one-data')); - const shellSpan = httpSpans.find(s => s.description?.includes('/api/shell-config')); + const headerSpan = httpSpans.find(span => hasUrlPath(span, '/api/header-data')); + const mfeOneSpan = httpSpans.find(span => hasUrlPath(span, '/api/mfe-one-data')); + const shellSpan = httpSpans.find(span => hasUrlPath(span, '/api/shell-config')); expect(headerSpan).toBeDefined(); expect(mfeOneSpan).toBeDefined(); expect(shellSpan).toBeDefined(); - expect(headerSpan?.data?.['mfe.name']).toBe('mfe-header'); - expect(mfeOneSpan?.data?.['mfe.name']).toBe('mfe-one'); + expect(headerSpan?.attributes['mfe.name']).toEqual({ value: 'mfe-header', type: 'string' }); + expect(mfeOneSpan?.attributes['mfe.name']).toEqual({ value: 'mfe-one', type: 'string' }); // Shell span has no MFE tag - expect(shellSpan?.data?.['mfe.name']).toBeUndefined(); + expect(shellSpan?.attributes['mfe.name']).toBeUndefined(); }); diff --git a/dev-packages/e2e-tests/test-applications/browser-webworker-vite/src/main.ts b/dev-packages/e2e-tests/test-applications/browser-webworker-vite/src/main.ts index 08a7a340e799..238ec062663a 100644 --- a/dev-packages/e2e-tests/test-applications/browser-webworker-vite/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/browser-webworker-vite/src/main.ts @@ -3,7 +3,6 @@ import MyWorker2 from './worker2.ts?worker'; import * as Sentry from '@sentry/browser'; Sentry.init({ - traceLifecycle: 'static', dsn: import.meta.env.PUBLIC_E2E_TEST_DSN, environment: import.meta.env.MODE || 'development', tracesSampleRate: 1.0, diff --git a/dev-packages/e2e-tests/test-applications/browser-webworker-vite/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/browser-webworker-vite/tests/errors.test.ts index d12e61111c85..a253c5ef4847 100644 --- a/dev-packages/e2e-tests/test-applications/browser-webworker-vite/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-webworker-vite/tests/errors.test.ts @@ -1,14 +1,18 @@ import { expect, test } from '@playwright/test'; -import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; + +function waitForPageloadSpan() { + return waitForStreamedSpan('browser-webworker-vite', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; + }); +} test('captures an error with debug ids and pageload trace context', async ({ page }) => { const errorEventPromise = waitForError('browser-webworker-vite', async event => { return !event.type && !!event.exception?.values?.[0]; }); - const transactionPromise = waitForTransaction('browser-webworker-vite', transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; - }); + const pageloadSpanPromise = waitForPageloadSpan(); await page.goto('/'); @@ -17,10 +21,7 @@ test('captures an error with debug ids and pageload trace context', async ({ pag await page.waitForTimeout(1000); const errorEvent = await errorEventPromise; - const transactionEvent = await transactionPromise; - - const pageloadTraceId = transactionEvent.contexts?.trace?.trace_id; - const pageloadSpanId = transactionEvent.contexts?.trace?.span_id; + const pageloadSpan = await pageloadSpanPromise; expect(errorEvent.exception?.values).toHaveLength(1); expect(errorEvent.exception?.values?.[0]?.value).toBe('Uncaught Error: Uncaught error in worker'); @@ -28,7 +29,7 @@ test('captures an error with debug ids and pageload trace context', async ({ pag expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames?.[0]?.filename).toMatch(/worker-.+\.js$/); expect(errorEvent.transaction).toBe('/'); - expect(transactionEvent.transaction).toBe('/'); + expect(pageloadSpan.name).toBe('Pageload'); expect(errorEvent.request).toEqual({ url: 'http://localhost:3030/', @@ -36,8 +37,8 @@ test('captures an error with debug ids and pageload trace context', async ({ pag }); expect(errorEvent.contexts?.trace).toEqual({ - trace_id: pageloadTraceId, - span_id: pageloadSpanId, + trace_id: pageloadSpan.trace_id, + span_id: pageloadSpan.span_id, }); expect(errorEvent.debug_meta).toEqual({ @@ -77,9 +78,7 @@ test('captures an error from the second eagerly added worker', async ({ page }) return !event.type && !!event.exception?.values?.[0]; }); - const transactionPromise = waitForTransaction('browser-webworker-vite', transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; - }); + const pageloadSpanPromise = waitForPageloadSpan(); await page.goto('/'); @@ -88,10 +87,7 @@ test('captures an error from the second eagerly added worker', async ({ page }) await page.waitForTimeout(1000); const errorEvent = await errorEventPromise; - const transactionEvent = await transactionPromise; - - const pageloadTraceId = transactionEvent.contexts?.trace?.trace_id; - const pageloadSpanId = transactionEvent.contexts?.trace?.span_id; + const pageloadSpan = await pageloadSpanPromise; expect(errorEvent.exception?.values).toHaveLength(1); expect(errorEvent.exception?.values?.[0]?.value).toBe('Uncaught Error: Uncaught error in worker 2'); @@ -99,7 +95,7 @@ test('captures an error from the second eagerly added worker', async ({ page }) expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames?.[0]?.filename).toMatch(/worker2-.+\.js$/); expect(errorEvent.transaction).toBe('/'); - expect(transactionEvent.transaction).toBe('/'); + expect(pageloadSpan.name).toBe('Pageload'); expect(errorEvent.request).toEqual({ url: 'http://localhost:3030/', @@ -107,8 +103,8 @@ test('captures an error from the second eagerly added worker', async ({ page }) }); expect(errorEvent.contexts?.trace).toEqual({ - trace_id: pageloadTraceId, - span_id: pageloadSpanId, + trace_id: pageloadSpan.trace_id, + span_id: pageloadSpan.span_id, }); expect(errorEvent.debug_meta).toEqual({ @@ -127,9 +123,7 @@ test('captures an error from the third lazily added worker', async ({ page }) => return !event.type && !!event.exception?.values?.[0]; }); - const transactionPromise = waitForTransaction('browser-webworker-vite', transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; - }); + const pageloadSpanPromise = waitForPageloadSpan(); await page.goto('/'); @@ -138,10 +132,7 @@ test('captures an error from the third lazily added worker', async ({ page }) => await page.waitForTimeout(1000); const errorEvent = await errorEventPromise; - const transactionEvent = await transactionPromise; - - const pageloadTraceId = transactionEvent.contexts?.trace?.trace_id; - const pageloadSpanId = transactionEvent.contexts?.trace?.span_id; + const pageloadSpan = await pageloadSpanPromise; expect(errorEvent.exception?.values).toHaveLength(1); expect(errorEvent.exception?.values?.[0]?.value).toBe('Uncaught Error: Uncaught error in worker 3'); @@ -149,7 +140,7 @@ test('captures an error from the third lazily added worker', async ({ page }) => expect(errorEvent.exception?.values?.[0]?.stacktrace?.frames?.[0]?.filename).toMatch(/worker3-.+\.js$/); expect(errorEvent.transaction).toBe('/'); - expect(transactionEvent.transaction).toBe('/'); + expect(pageloadSpan.name).toBe('Pageload'); expect(errorEvent.request).toEqual({ url: 'http://localhost:3030/', @@ -157,8 +148,8 @@ test('captures an error from the third lazily added worker', async ({ page }) => }); expect(errorEvent.contexts?.trace).toEqual({ - trace_id: pageloadTraceId, - span_id: pageloadSpanId, + trace_id: pageloadSpan.trace_id, + span_id: pageloadSpan.span_id, }); expect(errorEvent.debug_meta).toEqual({ diff --git a/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js index a854b48b09ae..68a4aae16d85 100644 --- a/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js +++ b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js @@ -1,7 +1,6 @@ import * as Sentry from '@sentry/node'; Sentry.init({ - traceLifecycle: 'static', environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.E2E_TEST_DSN, }); diff --git a/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap index 3a1eb351ecc4..8b157beba9da 100644 --- a/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap +++ b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap @@ -4,13 +4,13 @@ exports[`Find symbolicated event on sentry 1`] = ` { "colno": 41, "contextLine": "const eventId = Sentry.captureException(new Error('Sentry Debug ID E2E Test Error'));", - "lineno": 9, + "lineno": 8, "postContext": [ "", "process.stdout.write(eventId);", ], "preContext": [ - " traceLifecycle: 'static',", + "Sentry.init({", " environment: 'qa', // dynamic sampling bias to keep transactions", " dsn: process.env.E2E_TEST_DSN,", "});", diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/.gitignore b/dev-packages/e2e-tests/test-applications/default-browser-static/.gitignore new file mode 100644 index 000000000000..84634c973eeb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/.gitignore @@ -0,0 +1,29 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +/test-results/ +/playwright-report/ +/playwright/.cache/ + +!*.d.ts diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/build.mjs b/dev-packages/e2e-tests/test-applications/default-browser-static/build.mjs new file mode 100644 index 000000000000..aeaad894bdbd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/build.mjs @@ -0,0 +1,49 @@ +import * as path from 'path'; +import * as url from 'url'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import TerserPlugin from 'terser-webpack-plugin'; +import webpack from 'webpack'; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +webpack( + { + entry: path.join(__dirname, 'src/index.js'), + output: { + path: path.join(__dirname, 'build'), + filename: 'app.js', + }, + optimization: { + minimize: true, + minimizer: [new TerserPlugin()], + }, + plugins: [ + new webpack.EnvironmentPlugin(['E2E_TEST_DSN']), + new HtmlWebpackPlugin({ + template: path.join(__dirname, 'public/index.html'), + }), + ], + mode: 'production', + }, + (err, stats) => { + if (err) { + console.error(err.stack || err); + if (err.details) { + console.error(err.details); + } + return; + } + + const info = stats.toJson(); + + if (stats.hasErrors()) { + console.error(info.errors); + process.exit(1); + } + + if (stats.hasWarnings()) { + console.warn(info.warnings); + process.exit(1); + } + }, +); diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/package.json b/dev-packages/e2e-tests/test-applications/default-browser-static/package.json new file mode 100644 index 000000000000..38ede5c5f123 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/package.json @@ -0,0 +1,41 @@ +{ + "name": "default-browser-static-test-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@sentry/browser": "file:../../packed/sentry-browser-packed.tgz", + "@types/node": "^18.19.1", + "typescript": "~5.0.0" + }, + "scripts": { + "start": "serve -s build", + "build": "node build.mjs", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install && pnpm build", + "test:assert": "pnpm test" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@playwright/test": "~1.56.0", + "@sentry-internal/test-utils": "link:../../../test-utils", + "webpack": "^5.91.0", + "serve": "14.0.1", + "terser-webpack-plugin": "^5.3.10", + "html-webpack-plugin": "^5.6.0" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/default-browser-static/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/playwright.config.mjs @@ -0,0 +1,7 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/public/index.html b/dev-packages/e2e-tests/test-applications/default-browser-static/public/index.html new file mode 100644 index 000000000000..b508284b391a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/public/index.html @@ -0,0 +1,21 @@ + + + + + + Default Browser App + + +
+ + + + + + + + diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/src/index.js b/dev-packages/e2e-tests/test-applications/default-browser-static/src/index.js new file mode 100644 index 000000000000..11c91d0994f8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/src/index.js @@ -0,0 +1,19 @@ +import * as Sentry from '@sentry/browser'; + +Sentry.init({ + traceLifecycle: 'static', + dsn: process.env.E2E_TEST_DSN, + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1.0, + release: 'e2e-test', + environment: 'qa', + tunnel: 'http://localhost:3031', +}); + +document.getElementById('exception-button').addEventListener('click', () => { + throw new Error('I am an error!'); +}); + +document.getElementById('navigation-link').addEventListener('click', () => { + document.getElementById('navigation-target').scrollIntoView({ behavior: 'smooth' }); +}); diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/default-browser-static/start-event-proxy.mjs new file mode 100644 index 000000000000..7f2de71f769b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'default-browser-static', +}); diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/default-browser-static/tests/errors.test.ts new file mode 100644 index 000000000000..49b388c938f4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/tests/errors.test.ts @@ -0,0 +1,58 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('captures an error', async ({ page }) => { + const errorEventPromise = waitForError('default-browser-static', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.transaction).toBe('/'); + + expect(errorEvent.request).toEqual({ + url: 'http://localhost:3030/', + headers: expect.any(Object), + }); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + }); +}); + +test('sets correct transactionName', async ({ page }) => { + const transactionPromise = waitForTransaction('default-browser-static', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const errorEventPromise = waitForError('default-browser-static', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + const transactionEvent = await transactionPromise; + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: transactionEvent.contexts?.trace?.trace_id, + span_id: expect.not.stringContaining(transactionEvent.contexts?.trace?.span_id || ''), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/default-browser-static/tests/performance.test.ts new file mode 100644 index 000000000000..767da77f4ea8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/tests/performance.test.ts @@ -0,0 +1,99 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('captures a pageload transaction', async ({ page }) => { + const transactionPromise = waitForTransaction('default-browser-static', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + await page.goto(`/`); + + const pageLoadTransaction = await transactionPromise; + + expect(pageLoadTransaction).toMatchObject({ + contexts: { + trace: { + data: expect.objectContaining({ + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.browser', + 'sentry.sample_rate': 1, + 'sentry.segment.name.source': 'url', + 'url.full': 'http://localhost:3030/', + 'url.path': '/', + }), + op: 'pageload', + origin: 'auto.pageload.browser', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + environment: 'qa', + event_id: expect.stringMatching(/[a-f0-9]{32}/), + measurements: expect.any(Object), + platform: 'javascript', + release: 'e2e-test', + request: { + headers: { + 'User-Agent': expect.any(String), + }, + url: 'http://localhost:3030/', + }, + sdk: { + integrations: expect.any(Array), + name: 'sentry.javascript.browser', + packages: [ + { + name: 'npm:@sentry/browser', + version: expect.any(String), + }, + ], + version: expect.any(String), + }, + spans: expect.any(Array), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + transaction: '/', + transaction_info: { + source: 'url', + }, + type: 'transaction', + }); +}); + +test('captures a navigation transaction', async ({ page }) => { + page.on('console', msg => console.log(msg.text())); + const pageLoadTransactionPromise = waitForTransaction('default-browser-static', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTransactionPromise = waitForTransaction('default-browser-static', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await pageLoadTransactionPromise; + + const linkElement = page.locator('id=navigation-link'); + + await linkElement.click(); + + const navigationTransaction = await navigationTransactionPromise; + + expect(navigationTransaction).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.browser', + data: expect.objectContaining({ + 'url.full': 'http://localhost:3030/#navigation-target', + 'url.path': '/', + }), + }, + }, + transaction: '/', + transaction_info: { + source: 'url', + }, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/tsconfig.json b/dev-packages/e2e-tests/test-applications/default-browser-static/tsconfig.json new file mode 100644 index 000000000000..4cc95dc2689a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es2018", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src", "tests"] +} diff --git a/dev-packages/e2e-tests/test-applications/default-browser/src/index.js b/dev-packages/e2e-tests/test-applications/default-browser/src/index.js index 11c91d0994f8..d3eea216fe84 100644 --- a/dev-packages/e2e-tests/test-applications/default-browser/src/index.js +++ b/dev-packages/e2e-tests/test-applications/default-browser/src/index.js @@ -1,7 +1,6 @@ import * as Sentry from '@sentry/browser'; Sentry.init({ - traceLifecycle: 'static', dsn: process.env.E2E_TEST_DSN, integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1.0, diff --git a/dev-packages/e2e-tests/test-applications/default-browser/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/default-browser/tests/errors.test.ts index a84bfe6d4788..0fe1253582a8 100644 --- a/dev-packages/e2e-tests/test-applications/default-browser/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/default-browser/tests/errors.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('captures an error', async ({ page }) => { const errorEventPromise = waitForError('default-browser', event => { @@ -30,8 +30,8 @@ test('captures an error', async ({ page }) => { }); test('sets correct transactionName', async ({ page }) => { - const transactionPromise = waitForTransaction('default-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + const pageloadSpanPromise = waitForStreamedSpan('default-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); const errorEventPromise = waitForError('default-browser', event => { @@ -39,7 +39,7 @@ test('sets correct transactionName', async ({ page }) => { }); await page.goto('/'); - const transactionEvent = await transactionPromise; + const pageloadSpan = await pageloadSpanPromise; const exceptionButton = page.locator('id=exception-button'); await exceptionButton.click(); @@ -52,7 +52,7 @@ test('sets correct transactionName', async ({ page }) => { expect(errorEvent.transaction).toEqual('/'); expect(errorEvent.contexts?.trace).toEqual({ - trace_id: transactionEvent.contexts?.trace?.trace_id, - span_id: expect.not.stringContaining(transactionEvent.contexts?.trace?.span_id || ''), + trace_id: pageloadSpan.trace_id, + span_id: expect.not.stringContaining(pageloadSpan.span_id), }); }); diff --git a/dev-packages/e2e-tests/test-applications/default-browser/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/default-browser/tests/performance.test.ts index 684005fdd1a6..cf25581ea1d8 100644 --- a/dev-packages/e2e-tests/test-applications/default-browser/tests/performance.test.ts +++ b/dev-packages/e2e-tests/test-applications/default-browser/tests/performance.test.ts @@ -1,99 +1,58 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; -test('captures a pageload transaction', async ({ page }) => { - const transactionPromise = waitForTransaction('default-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; +test('captures a pageload span', async ({ page }) => { + const spanPromise = waitForStreamedSpan('default-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); await page.goto(`/`); - const pageLoadTransaction = await transactionPromise; - - expect(pageLoadTransaction).toMatchObject({ - contexts: { - trace: { - data: expect.objectContaining({ - 'sentry.idle_span_finish_reason': 'idleTimeout', - 'sentry.op': 'pageload', - 'sentry.origin': 'auto.pageload.browser', - 'sentry.sample_rate': 1, - 'sentry.segment.name.source': 'url', - 'url.full': 'http://localhost:3030/', - 'url.path': '/', - }), - op: 'pageload', - origin: 'auto.pageload.browser', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }, - }, - environment: 'qa', - event_id: expect.stringMatching(/[a-f0-9]{32}/), - measurements: expect.any(Object), - platform: 'javascript', - release: 'e2e-test', - request: { - headers: { - 'User-Agent': expect.any(String), - }, - url: 'http://localhost:3030/', - }, - sdk: { - integrations: expect.any(Array), - name: 'sentry.javascript.browser', - packages: [ - { - name: 'npm:@sentry/browser', - version: expect.any(String), - }, - ], - version: expect.any(String), - }, - spans: expect.any(Array), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: '/', - transaction_info: { - source: 'url', - }, - type: 'transaction', + const span = await spanPromise; + + expect(span.name).toBe('Pageload'); + expect(span.status).toBe('ok'); + expect(span.span_id).toMatch(/[a-f0-9]{16}/); + expect(span.trace_id).toMatch(/[a-f0-9]{32}/); + expect(span.attributes).toMatchObject({ + 'sentry.idle_span_finish_reason': { value: 'idleTimeout', type: 'string' }, + 'sentry.op': { value: 'pageload', type: 'string' }, + 'sentry.origin': { value: 'auto.pageload.browser', type: 'string' }, + 'sentry.sample_rate': { value: 1, type: 'integer' }, + 'sentry.segment.name.source': { value: 'url', type: 'string' }, + 'url.full': { value: 'http://localhost:3030/', type: 'string' }, + 'url.path': { value: '/', type: 'string' }, }); }); -test('captures a navigation transaction', async ({ page }) => { +test('captures a navigation span', async ({ page }) => { page.on('console', msg => console.log(msg.text())); - const pageLoadTransactionPromise = waitForTransaction('default-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + const pageloadSpanPromise = waitForStreamedSpan('default-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); - const navigationTransactionPromise = waitForTransaction('default-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + const navigationSpanPromise = waitForStreamedSpan('default-browser', span => { + return getSpanOp(span) === 'navigation' && span.is_segment; }); await page.goto(`/`); - await pageLoadTransactionPromise; + await pageloadSpanPromise; const linkElement = page.locator('id=navigation-link'); await linkElement.click(); - const navigationTransaction = await navigationTransactionPromise; - - expect(navigationTransaction).toMatchObject({ - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.browser', - data: expect.objectContaining({ - 'url.full': 'http://localhost:3030/#navigation-target', - 'url.path': '/', - }), - }, - }, - transaction: '/', - transaction_info: { - source: 'url', - }, + const navigationSpan = await navigationSpanPromise; + + expect(navigationSpan.name).toBe('Navigation'); + expect(navigationSpan.status).toBe('ok'); + expect(navigationSpan.span_id).toMatch(/[a-f0-9]{16}/); + expect(navigationSpan.trace_id).toMatch(/[a-f0-9]{32}/); + expect(navigationSpan.attributes).toMatchObject({ + 'sentry.op': { value: 'navigation', type: 'string' }, + 'sentry.origin': { value: 'auto.navigation.browser', type: 'string' }, + 'sentry.segment.name.source': { value: 'url', type: 'string' }, + 'url.full': { value: 'http://localhost:3030/#navigation-target', type: 'string' }, + 'url.path': { value: '/', type: 'string' }, }); }); diff --git a/dev-packages/e2e-tests/test-applications/effect-3-browser/src/index.js b/dev-packages/e2e-tests/test-applications/effect-3-browser/src/index.js index 8950f0d4ada4..c9a6c74c5d90 100644 --- a/dev-packages/e2e-tests/test-applications/effect-3-browser/src/index.js +++ b/dev-packages/e2e-tests/test-applications/effect-3-browser/src/index.js @@ -9,7 +9,6 @@ import * as Effect from 'effect/Effect'; const LogLevelLive = Logger.minimumLogLevel(LogLevel.Debug); const AppLayer = Layer.mergeAll( Sentry.effectLayer({ - traceLifecycle: 'static', dsn: process.env.E2E_TEST_DSN, integrations: [Sentry.browserTracingIntegration(), Sentry.interactionsIntegration()], tracesSampleRate: 1.0, diff --git a/dev-packages/e2e-tests/test-applications/effect-3-browser/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/effect-3-browser/tests/errors.test.ts index bca922963ee1..edd4ab900380 100644 --- a/dev-packages/e2e-tests/test-applications/effect-3-browser/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/effect-3-browser/tests/errors.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('captures an error', async ({ page }) => { const errorEventPromise = waitForError('effect-3-browser', event => { @@ -29,8 +29,8 @@ test('captures an error', async ({ page }) => { }); test('sets correct transactionName', async ({ page }) => { - const transactionPromise = waitForTransaction('effect-3-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + const pageloadSpanPromise = waitForStreamedSpan('effect-3-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); const errorEventPromise = waitForError('effect-3-browser', event => { @@ -38,7 +38,7 @@ test('sets correct transactionName', async ({ page }) => { }); await page.goto('/'); - const transactionEvent = await transactionPromise; + const pageloadSpan = await pageloadSpanPromise; const exceptionButton = page.locator('id=exception-button'); await exceptionButton.click(); @@ -50,7 +50,7 @@ test('sets correct transactionName', async ({ page }) => { expect(errorEvent.transaction).toEqual('/'); expect(errorEvent.contexts?.trace).toEqual({ - trace_id: transactionEvent.contexts?.trace?.trace_id, - span_id: expect.not.stringContaining(transactionEvent.contexts?.trace?.span_id || ''), + trace_id: pageloadSpan.trace_id, + span_id: expect.not.stringContaining(pageloadSpan.span_id), }); }); diff --git a/dev-packages/e2e-tests/test-applications/effect-3-browser/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/effect-3-browser/tests/transactions.test.ts index d2b67c5697ba..331cb8b599e9 100644 --- a/dev-packages/e2e-tests/test-applications/effect-3-browser/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/effect-3-browser/tests/transactions.test.ts @@ -1,120 +1,78 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; -test('captures a pageload transaction', async ({ page }) => { - const transactionPromise = waitForTransaction('effect-3-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; +test('captures a pageload span', async ({ page }) => { + const spanPromise = waitForStreamedSpan('effect-3-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); await page.goto('/'); - const pageLoadTransaction = await transactionPromise; - - expect(pageLoadTransaction).toMatchObject({ - contexts: { - trace: { - data: expect.objectContaining({ - 'sentry.idle_span_finish_reason': 'idleTimeout', - 'sentry.op': 'pageload', - 'sentry.origin': 'auto.pageload.browser', - 'sentry.sample_rate': 1, - 'sentry.segment.name.source': 'url', - }), - op: 'pageload', - origin: 'auto.pageload.browser', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }, - }, - environment: 'qa', - event_id: expect.stringMatching(/[a-f0-9]{32}/), - measurements: expect.any(Object), - platform: 'javascript', - release: 'e2e-test', - request: { - headers: { - 'User-Agent': expect.any(String), - }, - url: 'http://localhost:3030/', - }, - spans: expect.any(Array), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: '/', - transaction_info: { - source: 'url', - }, - type: 'transaction', + const span = await spanPromise; + + expect(span.name).toBe('Pageload'); + expect(span.status).toBe('ok'); + expect(span.span_id).toMatch(/[a-f0-9]{16}/); + expect(span.trace_id).toMatch(/[a-f0-9]{32}/); + expect(span.attributes).toMatchObject({ + 'sentry.idle_span_finish_reason': { value: 'idleTimeout', type: 'string' }, + 'sentry.op': { value: 'pageload', type: 'string' }, + 'sentry.origin': { value: 'auto.pageload.browser', type: 'string' }, + 'sentry.sample_rate': { value: 1, type: 'integer' }, + 'sentry.segment.name.source': { value: 'url', type: 'string' }, }); }); -test('captures a navigation transaction', async ({ page }) => { - const pageLoadTransactionPromise = waitForTransaction('effect-3-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; +test('captures a navigation span', async ({ page }) => { + const pageloadSpanPromise = waitForStreamedSpan('effect-3-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); - const navigationTransactionPromise = waitForTransaction('effect-3-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + const navigationSpanPromise = waitForStreamedSpan('effect-3-browser', span => { + return getSpanOp(span) === 'navigation' && span.is_segment; }); await page.goto('/'); - await pageLoadTransactionPromise; + await pageloadSpanPromise; const linkElement = page.locator('id=navigation-link'); await linkElement.click(); - const navigationTransaction = await navigationTransactionPromise; - - expect(navigationTransaction).toMatchObject({ - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.browser', - }, - }, - transaction: '/', - transaction_info: { - source: 'url', - }, + const navigationSpan = await navigationSpanPromise; + + expect(navigationSpan.name).toBe('Navigation'); + expect(navigationSpan.status).toBe('ok'); + expect(navigationSpan.span_id).toMatch(/[a-f0-9]{16}/); + expect(navigationSpan.trace_id).toMatch(/[a-f0-9]{32}/); + expect(navigationSpan.attributes).toMatchObject({ + 'sentry.op': { value: 'navigation', type: 'string' }, + 'sentry.origin': { value: 'auto.navigation.browser', type: 'string' }, + 'sentry.segment.name.source': { value: 'url', type: 'string' }, }); }); test('captures Effect spans with correct parent-child structure', async ({ page }) => { - const pageloadPromise = waitForTransaction('effect-3-browser', transactionEvent => { - return transactionEvent?.contexts?.trace?.op === 'pageload'; + const pageloadSpanPromise = waitForStreamedSpan('effect-3-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); - const transactionPromise = waitForTransaction('effect-3-browser', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'ui.action.click' && - transactionEvent.spans?.some(span => span.description === 'custom-effect-span') - ); + const spansPromise = collectStreamedSpans('effect-3-browser', spans => { + return spans.some(span => span.name === 'custom-effect-span') && spans.some(span => span.name === 'nested-span'); }); await page.goto('/'); - await pageloadPromise; + await pageloadSpanPromise; const effectSpanButton = page.locator('id=effect-span-button'); await effectSpanButton.click(); await expect(page.locator('id=effect-span-result')).toHaveText('Span sent!'); - const transactionEvent = await transactionPromise; - const spans = transactionEvent.spans || []; - - expect(spans).toContainEqual( - expect.objectContaining({ - description: 'custom-effect-span', - }), - ); - - expect(spans).toContainEqual( - expect.objectContaining({ - description: 'nested-span', - }), - ); + const spans = await spansPromise; - const parentSpan = spans.find(s => s.description === 'custom-effect-span'); - const nestedSpan = spans.find(s => s.description === 'nested-span'); + const parentSpan = spans.find(span => span.name === 'custom-effect-span'); + const nestedSpan = spans.find(span => span.name === 'nested-span'); + expect(parentSpan).toBeDefined(); + expect(nestedSpan).toBeDefined(); expect(nestedSpan?.parent_span_id).toBe(parentSpan?.span_id); }); diff --git a/dev-packages/e2e-tests/test-applications/effect-4-browser/src/index.js b/dev-packages/e2e-tests/test-applications/effect-4-browser/src/index.js index ec173fb52269..dbb231ae8fdb 100644 --- a/dev-packages/e2e-tests/test-applications/effect-4-browser/src/index.js +++ b/dev-packages/e2e-tests/test-applications/effect-4-browser/src/index.js @@ -9,7 +9,6 @@ import * as Effect from 'effect/Effect'; const AppLayer = Layer.mergeAll( Sentry.effectLayer({ - traceLifecycle: 'static', dsn: process.env.E2E_TEST_DSN, integrations: [Sentry.browserTracingIntegration(), Sentry.interactionsIntegration()], tracesSampleRate: 1.0, diff --git a/dev-packages/e2e-tests/test-applications/effect-4-browser/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/effect-4-browser/tests/errors.test.ts index 25b5762390ad..5671c5be9562 100644 --- a/dev-packages/e2e-tests/test-applications/effect-4-browser/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/effect-4-browser/tests/errors.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; test('captures an error', async ({ page }) => { const errorEventPromise = waitForError('effect-4-browser', event => { @@ -29,8 +29,8 @@ test('captures an error', async ({ page }) => { }); test('sets correct transactionName', async ({ page }) => { - const transactionPromise = waitForTransaction('effect-4-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + const pageloadSpanPromise = waitForStreamedSpan('effect-4-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); const errorEventPromise = waitForError('effect-4-browser', event => { @@ -38,7 +38,7 @@ test('sets correct transactionName', async ({ page }) => { }); await page.goto('/'); - const transactionEvent = await transactionPromise; + const pageloadSpan = await pageloadSpanPromise; const exceptionButton = page.locator('id=exception-button'); await exceptionButton.click(); @@ -50,7 +50,7 @@ test('sets correct transactionName', async ({ page }) => { expect(errorEvent.transaction).toEqual('/'); expect(errorEvent.contexts?.trace).toEqual({ - trace_id: transactionEvent.contexts?.trace?.trace_id, - span_id: expect.not.stringContaining(transactionEvent.contexts?.trace?.span_id || ''), + trace_id: pageloadSpan.trace_id, + span_id: expect.not.stringContaining(pageloadSpan.span_id), }); }); diff --git a/dev-packages/e2e-tests/test-applications/effect-4-browser/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/effect-4-browser/tests/transactions.test.ts index 23087c719fef..971f5678c276 100644 --- a/dev-packages/e2e-tests/test-applications/effect-4-browser/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/effect-4-browser/tests/transactions.test.ts @@ -1,120 +1,78 @@ import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; -test('captures a pageload transaction', async ({ page }) => { - const transactionPromise = waitForTransaction('effect-4-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; +test('captures a pageload span', async ({ page }) => { + const spanPromise = waitForStreamedSpan('effect-4-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); await page.goto('/'); - const pageLoadTransaction = await transactionPromise; - - expect(pageLoadTransaction).toMatchObject({ - contexts: { - trace: { - data: expect.objectContaining({ - 'sentry.idle_span_finish_reason': 'idleTimeout', - 'sentry.op': 'pageload', - 'sentry.origin': 'auto.pageload.browser', - 'sentry.sample_rate': 1, - 'sentry.segment.name.source': 'url', - }), - op: 'pageload', - origin: 'auto.pageload.browser', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }, - }, - environment: 'qa', - event_id: expect.stringMatching(/[a-f0-9]{32}/), - measurements: expect.any(Object), - platform: 'javascript', - release: 'e2e-test', - request: { - headers: { - 'User-Agent': expect.any(String), - }, - url: 'http://localhost:3030/', - }, - spans: expect.any(Array), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: '/', - transaction_info: { - source: 'url', - }, - type: 'transaction', + const span = await spanPromise; + + expect(span.name).toBe('Pageload'); + expect(span.status).toBe('ok'); + expect(span.span_id).toMatch(/[a-f0-9]{16}/); + expect(span.trace_id).toMatch(/[a-f0-9]{32}/); + expect(span.attributes).toMatchObject({ + 'sentry.idle_span_finish_reason': { value: 'idleTimeout', type: 'string' }, + 'sentry.op': { value: 'pageload', type: 'string' }, + 'sentry.origin': { value: 'auto.pageload.browser', type: 'string' }, + 'sentry.sample_rate': { value: 1, type: 'integer' }, + 'sentry.segment.name.source': { value: 'url', type: 'string' }, }); }); -test('captures a navigation transaction', async ({ page }) => { - const pageLoadTransactionPromise = waitForTransaction('effect-4-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; +test('captures a navigation span', async ({ page }) => { + const pageloadSpanPromise = waitForStreamedSpan('effect-4-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); - const navigationTransactionPromise = waitForTransaction('effect-4-browser', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + const navigationSpanPromise = waitForStreamedSpan('effect-4-browser', span => { + return getSpanOp(span) === 'navigation' && span.is_segment; }); await page.goto('/'); - await pageLoadTransactionPromise; + await pageloadSpanPromise; const linkElement = page.locator('id=navigation-link'); await linkElement.click(); - const navigationTransaction = await navigationTransactionPromise; - - expect(navigationTransaction).toMatchObject({ - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.browser', - }, - }, - transaction: '/', - transaction_info: { - source: 'url', - }, + const navigationSpan = await navigationSpanPromise; + + expect(navigationSpan.name).toBe('Navigation'); + expect(navigationSpan.status).toBe('ok'); + expect(navigationSpan.span_id).toMatch(/[a-f0-9]{16}/); + expect(navigationSpan.trace_id).toMatch(/[a-f0-9]{32}/); + expect(navigationSpan.attributes).toMatchObject({ + 'sentry.op': { value: 'navigation', type: 'string' }, + 'sentry.origin': { value: 'auto.navigation.browser', type: 'string' }, + 'sentry.segment.name.source': { value: 'url', type: 'string' }, }); }); test('captures Effect spans with correct parent-child structure', async ({ page }) => { - const pageloadPromise = waitForTransaction('effect-4-browser', transactionEvent => { - return transactionEvent?.contexts?.trace?.op === 'pageload'; + const pageloadSpanPromise = waitForStreamedSpan('effect-4-browser', span => { + return getSpanOp(span) === 'pageload' && span.is_segment; }); - const transactionPromise = waitForTransaction('effect-4-browser', transactionEvent => { - return ( - transactionEvent?.contexts?.trace?.op === 'ui.action.click' && - transactionEvent.spans?.some(span => span.description === 'custom-effect-span') - ); + const spansPromise = collectStreamedSpans('effect-4-browser', spans => { + return spans.some(span => span.name === 'custom-effect-span') && spans.some(span => span.name === 'nested-span'); }); await page.goto('/'); - await pageloadPromise; + await pageloadSpanPromise; const effectSpanButton = page.locator('id=effect-span-button'); await effectSpanButton.click(); await expect(page.locator('id=effect-span-result')).toHaveText('Span sent!'); - const transactionEvent = await transactionPromise; - const spans = transactionEvent.spans || []; - - expect(spans).toContainEqual( - expect.objectContaining({ - description: 'custom-effect-span', - }), - ); - - expect(spans).toContainEqual( - expect.objectContaining({ - description: 'nested-span', - }), - ); + const spans = await spansPromise; - const parentSpan = spans.find(s => s.description === 'custom-effect-span'); - const nestedSpan = spans.find(s => s.description === 'nested-span'); + const parentSpan = spans.find(span => span.name === 'custom-effect-span'); + const nestedSpan = spans.find(span => span.name === 'nested-span'); + expect(parentSpan).toBeDefined(); + expect(nestedSpan).toBeDefined(); expect(nestedSpan?.parent_span_id).toBe(parentSpan?.span_id); }); diff --git a/dev-packages/e2e-tests/test-applications/webpack-4/entry.js b/dev-packages/e2e-tests/test-applications/webpack-4/entry.js index 6cb756072d55..69877184b91a 100644 --- a/dev-packages/e2e-tests/test-applications/webpack-4/entry.js +++ b/dev-packages/e2e-tests/test-applications/webpack-4/entry.js @@ -1,7 +1,6 @@ import { browserTracingIntegration, init } from '@sentry/browser'; init({ - traceLifecycle: 'static', dsn: process.env.E2E_TEST_DSN, integrations: [browserTracingIntegration()], tunnel: 'http://localhost:3031', diff --git a/dev-packages/e2e-tests/test-applications/webpack-5/entry.js b/dev-packages/e2e-tests/test-applications/webpack-5/entry.js index 6cb756072d55..69877184b91a 100644 --- a/dev-packages/e2e-tests/test-applications/webpack-5/entry.js +++ b/dev-packages/e2e-tests/test-applications/webpack-5/entry.js @@ -1,7 +1,6 @@ import { browserTracingIntegration, init } from '@sentry/browser'; init({ - traceLifecycle: 'static', dsn: process.env.E2E_TEST_DSN, integrations: [browserTracingIntegration()], tunnel: 'http://localhost:3031',