diff --git a/.cursor/BUGBOT.md b/.cursor/BUGBOT.md index f70b4bc51da0..a394e1130d84 100644 --- a/.cursor/BUGBOT.md +++ b/.cursor/BUGBOT.md @@ -93,6 +93,7 @@ Keep reviews high-signal. Prefer actionable, high-confidence findings over specu - Flag usage of `expect.objectContaining` and other relaxed assertions, when a test expects something NOT to be included in a payload but there's no respective assertion. - Flag usage of conditionals in one test and recommend splitting up the test for the different paths. - Flag usage of loops testing multiple scenarios in one test and recommend using `(it)|(test).each` instead. +- In `dev-packages/node-integration-tests/suites/`, flag tests that check the Node version or the runtime themselves, for example with `process.versions`, `process.env.RUNTIME`, `ctx.skip()`, or a version or runtime condition in `skipIf` / `runIf`. Recommend `test.runIf(supports({ ... }))` or `describe.runIf(supports({ ... }))` from `utils/index.ts` instead. - Apply the `@sentry/conventions` constants rule from above to test files too. Test assertions on span attributes and ops must use the constants, not string literals. - Flag tests that are likely to introduce flakes. In our case this usually means we wait for some telemetry requests sent from an SDK. Patterns to look out for: - Only waiting for a request, after an action is performed. Instead, start waiting, perform action, await request promise. diff --git a/.github/actions/install-playwright/action.yml b/.github/actions/install-playwright/action.yml index d2f1fd723b1a..6c8ea05d3c91 100644 --- a/.github/actions/install-playwright/action.yml +++ b/.github/actions/install-playwright/action.yml @@ -7,6 +7,15 @@ inputs: cwd: description: 'The working directory to run Playwright in.' default: '.' + installed-version: + description: + 'A Playwright version that an earlier run of this action already installed. Skips the install if it matches.' + default: '' + +outputs: + version: + description: 'The resolved Playwright version, empty if Playwright is not installed or the install was skipped.' + value: ${{ steps.playwright-version.outputs.version }} runs: using: 'composite' @@ -18,8 +27,11 @@ runs: id: playwright-version run: | version=$(node -e "let v = ''; try { v = require('@playwright/test/package.json').version; } catch {} process.stdout.write(String(v));") + if [ "$version" = "$INSTALLED_VERSION" ]; then version=''; fi echo "version=$version" >> "$GITHUB_OUTPUT" shell: bash + env: + INSTALLED_VERSION: ${{ inputs.installed-version }} working-directory: ${{ inputs.cwd }} - name: Restore cached playwright binaries diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a37d3618311f..bfe174349582 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -337,12 +337,13 @@ jobs: uses: ./.github/actions/restore-cache with: dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - name: Lint source files - run: yarn lint - - name: Lint for ES compatibility - run: yarn lint:es-compatibility - - name: Lint types - run: yarn lint:types + - parallel: + - name: Lint source files + run: yarn lint + - name: Lint for ES compatibility + run: yarn lint:es-compatibility + - name: Lint types + run: yarn lint:types job_check_lockfile: name: Check lockfile @@ -502,11 +503,16 @@ jobs: if: github.event_name != 'pull_request' job_bun_unit_tests: - name: Bun Unit Tests + name: Bun (${{ matrix.bun }}) Unit Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_bun == 'true' || github.event_name != 'pull_request' timeout-minutes: 10 runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + # 1.3.14 is the oldest supported Bun version + bun: ['1.3.14', '1.4'] steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v7 @@ -519,7 +525,7 @@ jobs: - name: Set up Bun uses: oven-sh/setup-bun@v2 with: - bun-version: '1.3.14' + bun-version: ${{ matrix.bun }} - name: Restore caches uses: ./.github/actions/restore-cache with: @@ -903,7 +909,7 @@ jobs: run: yarn test job_bun_integration_tests: - name: Bun Integration Tests (${{ matrix.shard }}/2) + name: Bun (${{ matrix.bun }}) Integration Tests (${{ matrix.shard }}/2) needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_bun_integration == 'true' || github.event_name != 'pull_request' runs-on: ubuntu-24.04 @@ -911,6 +917,8 @@ jobs: strategy: fail-fast: false matrix: + # 1.3.14 is the oldest supported Bun version + bun: ['1.3.14', '1.4'] shard: [1, 2] steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) @@ -924,7 +932,7 @@ jobs: - name: Set up Bun uses: oven-sh/setup-bun@v2 with: - bun-version: '1.3.14' + bun-version: ${{ matrix.bun }} - name: Restore caches uses: ./.github/actions/restore-cache with: @@ -1091,6 +1099,16 @@ jobs: name: build-layer-output path: ${{ env.BUILD_LAYER_PATH }} + # Installs the root workspace's Playwright version while the app builds. Most apps use the same version. + # The app's own version is only known after its build, so it gets installed then if it differs. + # Started after the artifact downloads: starting next to them made `download-artifact` fail to read the event file. + - name: Install Playwright + id: install-playwright + background: true + uses: ./.github/actions/install-playwright + with: + browsers: chromium + - name: Prepare e2e tests run: yarn test:prepare working-directory: dev-packages/e2e-tests @@ -1116,11 +1134,14 @@ jobs: env: SENTRY_E2E_WORKSPACE_ROOT: ${{ github.workspace }} - - name: Install Playwright + - wait: [install-playwright] + + - name: Install Playwright for the app's version uses: ./.github/actions/install-playwright with: browsers: chromium cwd: ${{ runner.temp }}/test-application + installed-version: ${{ steps.install-playwright.outputs.version }} - name: Run E2E test working-directory: ${{ runner.temp }}/test-application @@ -1220,6 +1241,16 @@ jobs: name: build-tarball-output path: ${{ env.TARBALL_ARTIFACT_DOWNLOAD_PATH }} + # Installs the root workspace's Playwright version while the app builds. Most apps use the same version. + # The app's own version is only known after its build, so it gets installed then if it differs. + # Started after the artifact downloads: starting next to them made `download-artifact` fail to read the event file. + - name: Install Playwright + id: install-playwright + background: true + uses: ./.github/actions/install-playwright + with: + browsers: chromium + - name: Prepare E2E tests run: yarn test:prepare working-directory: dev-packages/e2e-tests @@ -1243,11 +1274,14 @@ jobs: timeout-minutes: 7 run: ${{ matrix.build-command || 'pnpm test:build' }} - - name: Install Playwright + - wait: [install-playwright] + + - name: Install Playwright for the app's version uses: ./.github/actions/install-playwright with: browsers: chromium cwd: ${{ runner.temp }}/test-application + installed-version: ${{ steps.install-playwright.outputs.version }} - name: Run E2E test working-directory: ${{ runner.temp }}/test-application diff --git a/.size-limit.js b/.size-limit.js index 6e085d4f6e52..b276522b06e3 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -121,7 +121,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'), gzip: true, - limit: '101 KB', + limit: '102 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -129,7 +129,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'feedbackIntegration'), gzip: true, - limit: '114 KB', + limit: '115 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -169,7 +169,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'logger'), gzip: true, - limit: '36 KB', + limit: '37 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -196,7 +196,7 @@ module.exports = [ import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'), ignore: ['react/jsx-runtime'], gzip: true, - limit: '59 KB', + limit: '60 KB', disablePlugins: ['@size-limit/esbuild'], }, // Vue SDK (ESM) @@ -235,7 +235,7 @@ module.exports = [ path: 'packages/remix/build/esm/v3/client-bundle.js', import: '*', gzip: true, - limit: '61 KB', + limit: '62 KB', disablePlugins: ['@size-limit/esbuild'], }, // Browser CDN bundles @@ -250,7 +250,7 @@ module.exports = [ name: 'CDN Bundle (incl. Tracing)', path: createCDNPath('bundle.tracing.min.js'), gzip: true, - limit: '57 KB', + limit: '58 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -264,7 +264,7 @@ module.exports = [ name: 'CDN Bundle (incl. Tracing, Logs, Metrics)', path: createCDNPath('bundle.tracing.logs.metrics.min.js'), gzip: true, - limit: '59 KB', + limit: '60 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -278,7 +278,7 @@ module.exports = [ name: 'CDN Bundle (incl. Tracing, Replay)', path: createCDNPath('bundle.tracing.replay.min.js'), gzip: true, - limit: '95 KB', + limit: '96 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -292,14 +292,14 @@ module.exports = [ name: 'CDN Bundle (incl. Tracing, Replay, Feedback)', path: createCDNPath('bundle.tracing.replay.feedback.min.js'), gzip: true, - limit: '101 KB', + limit: '102 KB', disablePlugins: ['@size-limit/esbuild'], }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)', path: createCDNPath('bundle.tracing.replay.feedback.logs.metrics.min.js'), gzip: true, - limit: '103 KB', + limit: '104 KB', disablePlugins: ['@size-limit/esbuild'], }, // browser CDN bundles (non-gzipped) @@ -316,7 +316,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.min.js'), gzip: false, brotli: false, - limit: '160 KB', + limit: '161 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -324,7 +324,7 @@ module.exports = [ path: createCDNPath('bundle.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '104 KB', + limit: '105 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -332,7 +332,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '166 KB', + limit: '167 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -340,7 +340,7 @@ module.exports = [ path: createCDNPath('bundle.replay.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '234 KB', + limit: '235 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -348,7 +348,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.min.js'), gzip: false, brotli: false, - limit: '280 KB', + limit: '281 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -356,7 +356,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '286 KB', + limit: '287 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -364,7 +364,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.feedback.min.js'), gzip: false, brotli: false, - limit: '293 KB', + limit: '295 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -372,7 +372,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.feedback.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '299 KB', + limit: '301 KB', disablePlugins: ['@size-limit/esbuild'], }, // Next.js SDK (ESM) @@ -392,7 +392,7 @@ module.exports = [ import: createImport('init'), ignore: ['$app/stores'], gzip: true, - limit: '57 KB', + limit: '58 KB', disablePlugins: ['@size-limit/esbuild'], }, // Core SDK subpath entry points (ESM) @@ -419,7 +419,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '151 KB', + limit: '156 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -443,7 +443,7 @@ module.exports = [ path: 'packages/node/build/esm/index.js', import: createImport('initWithoutDefaultIntegrations', 'getDefaultIntegrationsWithoutPerformance'), gzip: true, - limit: '99 KB', + limit: '100 KB', disablePlugins: ['@size-limit/esbuild'], ignore: [...builtinModules, ...nodePrefixedBuiltinModules], modifyWebpackConfig: function (config) { @@ -465,7 +465,7 @@ module.exports = [ path: 'packages/node/build/esm/index.js', import: createImport('init'), gzip: true, - limit: '130 KB', + limit: '135 KB', disablePlugins: ['@size-limit/esbuild'], ignore: [...builtinModules, ...nodePrefixedBuiltinModules], modifyWebpackConfig: function (config) { @@ -486,7 +486,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '107 KB', + limit: '108 KB', disablePlugins: ['@size-limit/esbuild'], }, // Cloudflare SDK (ESM) - compressed, minified to match `wrangler deploy --dry-run --minify` output @@ -497,7 +497,7 @@ module.exports = [ ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, brotli: false, - limit: '209 KiB', + limit: '210 KiB', disablePlugins: ['@size-limit/webpack'], webpack: false, modifyEsbuildConfig: function (config) { @@ -517,7 +517,7 @@ module.exports = [ ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, brotli: false, - limit: '512 KiB', + limit: '514 KiB', disablePlugins: ['@size-limit/webpack'], webpack: false, modifyEsbuildConfig: function (config) { @@ -542,7 +542,7 @@ module.exports = [ ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, brotli: false, - limit: '223 KiB', + limit: '228 KiB', disablePlugins: ['@size-limit/webpack'], webpack: false, modifyEsbuildConfig: function (config) { diff --git a/AGENTS.md b/AGENTS.md index c1ac68c704ff..8d307c72d452 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,7 +55,7 @@ Uses **Git Flow** (see `docs/gitflow.md`). 1. `yarn format` 2. `yarn build:dev` 3. `yarn lint` -4. `yarn test` +4. Run the unit and integration/E2E tests for the affected packages. 5. NEVER push on `develop` ## Pull Requests diff --git a/CHANGELOG.md b/CHANGELOG.md index 21394e7db5b2..0727144b61f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,157 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 11.7.0 + +### Important Changes + +- **feat(cloudflare): Add opt-in cron monitoring for Cron Triggers ([#25014](https://github.com/getsentry/sentry-javascript/pull/25014))** + + The new `cronTriggersIntegration` sends check-ins for Cron Triggers. Each check-in includes the schedule from `controller.cron`, so Sentry creates the monitor on the first run. The integration is opt-in, because monitors are billed. + + Cron Triggers have no names, so map each cron expression of your wrangler config to a monitor slug. With the Sentry Cloudflare Vite plugin, add the integration to `src/instrument.server.ts`: + + ```ts + // src/monitorSlugs.ts + export const monitorSlugs: Record = { + '30 9 * * MON-FRI': 'daily-report', + }; + + // src/instrument.server.ts + import * as Sentry from '@sentry/cloudflare'; + import { monitorSlugs } from './monitorSlugs'; + + export default Sentry.defineCloudflareOptions(env => ({ + dsn: env.SENTRY_DSN, + integrations: [Sentry.cronTriggersIntegration({ monitorSlug: cron => monitorSlugs[cron] })], + })); + + // src/index.ts + import { monitorSlugs } from './monitorSlugs'; + + export default { + async scheduled(controller, env) { + switch (monitorSlugs[controller.cron]) { + case 'daily-report': + // Your daily report job + break; + } + }, + }; + ``` + + The `monitorSlug` function can also return monitor settings, for example `{ monitorSlug: 'daily-report', maxRuntime: 30 }`, or `undefined` to send no check-ins for a trigger. Without `monitorSlug`, the SDK derives the slug from the cron expression. If you deploy with `wrangler` directly, pass the same integration to `Sentry.withSentry`. + +- **feat(node): Add neonIntegration for @neondatabase/serverless ([#25164](https://github.com/getsentry/sentry-javascript/pull/25164), [#25176](https://github.com/getsentry/sentry-javascript/pull/25176))** + + The new `neonIntegration` creates `db` spans for queries of the `@neondatabase/serverless` driver: for the WebSocket `Client` and `Pool`, and for the HTTP `neon()` function. This includes the `neon-http` and `neon-serverless` adapters of Drizzle. The integration is on by default when tracing is enabled. + +- **feat(node): Stop sending the process session once request sessions are tracked ([#24915](https://github.com/getsentry/sentry-javascript/pull/24915))** + + Node servers sent release health data two times: as request session aggregates, and as the process session of `processSessionIntegration`. An error in a request also marked the process session as errored, so the SDK counted the error two times. Now the SDK drops the process session when it tracks the first request session. Scripts, CLIs and workers that do not serve requests keep the process session, and so do servers with `httpServerIntegration({ sessions: false })`. This changes the release health numbers of Node servers, because they no longer send the errored or crashed update of the process session. + +- **feat(server-utils): Record decide calls as `gen_ai.decide` spans ([#25270](https://github.com/getsentry/sentry-javascript/pull/25270), [#25272](https://github.com/getsentry/sentry-javascript/pull/25272), [#25274](https://github.com/getsentry/sentry-javascript/pull/25274), [#25281](https://github.com/getsentry/sentry-javascript/pull/25281))** + + Vercel AI `experimental_decide` calls, Mastra classifier evaluations, TypeSafe and LangChain Jev calls, and Workers AI calls to TypeSafe Jev and Cloudflare Clef models now create `decide ` spans with the op `gen_ai.decide`. Before, these spans had the op `gen_ai.evaluate`, which is now deprecated in the Sentry conventions. If you use `gen_ai.evaluate` in queries, alerts or dashboards, change it to `gen_ai.decide`. + +- **fix(browser): Apply `urlQueryParams` to more URLs ([#25262](https://github.com/getsentry/sentry-javascript/pull/25262), [#25289](https://github.com/getsentry/sentry-javascript/pull/25289))** + + The `dataCollection.urlQueryParams` option now also filters the query parameters of these URLs: + - the page URL in `event.request.url` + - the `browser.*` and `resource.*` span names with `traceLifecycle: 'static'` + - the LCP URL attributes `lcp.url` and `browser.web_vital.lcp.url` + - the URLs in fetch, XHR and navigation breadcrumbs + - the `ui.element.url` attribute of `elementTimingIntegration` + + Before this fix, these URLs could send sensitive query values, for example `?jwt=...`, to Sentry. + +- **fix(nextjs): Apply top-level `bundleSizeOptimizations` to webpack and Turbopack builds ([#24818](https://github.com/getsentry/sentry-javascript/pull/24818))** + + The top-level `bundleSizeOptimizations` option now removes unused SDK code in webpack and in Turbopack builds. Before, the tree-shaking options had no effect on Turbopack. The `webpack.treeshake` option is deprecated. It still works, but when you set both options, `bundleSizeOptimizations` takes precedence. + +### Other Changes + +- feat(nuxt): Detect the server API with `@nuxt/kit` 4.6 ([#25138](https://github.com/getsentry/sentry-javascript/pull/25138)) +- fix(bundler-plugins): Escape JSON in generated injection code ([#25265](https://github.com/getsentry/sentry-javascript/pull/25265)) +- fix(core): Don't mark http client spans as errors when the caller aborts ([#25260](https://github.com/getsentry/sentry-javascript/pull/25260)) +- fix(core): Record `span` client report outcomes when discarding transactions ([#25006](https://github.com/getsentry/sentry-javascript/pull/25006)) +- fix(core): Record client outcomes when beforeSend callbacks return invalid values ([#25049](https://github.com/getsentry/sentry-javascript/pull/25049)) +- fix(core): Record span outcomes for spans dropped when building a transaction ([#25048](https://github.com/getsentry/sentry-javascript/pull/25048)) +- fix(core): Report actual item count in client reports of envelope send failures ([#25185](https://github.com/getsentry/sentry-javascript/pull/25185)) +- fix(core): Strip internal custom span name from streamed spans ([#25212](https://github.com/getsentry/sentry-javascript/pull/25212)) +- fix(nextjs): Filter query parameters in request error context ([#25233](https://github.com/getsentry/sentry-javascript/pull/25233)) +- fix(node): Don't mark fetch spans as errors when the caller aborts ([#25129](https://github.com/getsentry/sentry-javascript/pull/25129)) +- fix(nuxt): Delete public source maps before Nitro lists its public assets ([#25267](https://github.com/getsentry/sentry-javascript/pull/25267)) +- fix(nuxt): Keep `@sentry/server-runtime-injection` external on Nitro v3 ([#24901](https://github.com/getsentry/sentry-javascript/pull/24901)) +- fix(nuxt): Remove @opentelemetry/resources dev alias ([#25216](https://github.com/getsentry/sentry-javascript/pull/25216)) +- fix(profiling-node): Stop the trace lifecycle profiler when spans are unsampled ([#25210](https://github.com/getsentry/sentry-javascript/pull/25210)) +- fix(react-router): Respect custom default integrations ([#25259](https://github.com/getsentry/sentry-javascript/pull/25259)) +- fix(remix): Respect custom default integrations ([#25258](https://github.com/getsentry/sentry-javascript/pull/25258)) +- fix(server-utils): avoid quadratic SQL summary regex backtracking ([#25255](https://github.com/getsentry/sentry-javascript/pull/25255)) +- fix(server-utils): End Express layer spans when the client aborts ([#25209](https://github.com/getsentry/sentry-javascript/pull/25209)) +- fix(server-utils): Record amqplib consumer info before the first delivery and instrument the callback API ([#25047](https://github.com/getsentry/sentry-javascript/pull/25047)) +- fix(server-utils): Stop treating previous_response_id as the OpenAI conversation id ([#25269](https://github.com/getsentry/sentry-javascript/pull/25269)) +- fix(sveltekit): Read load event pathname untracked ([#25235](https://github.com/getsentry/sentry-javascript/pull/25235)) +- fix(sveltekit): Stop adapter-node 6 output dir probe from deleting the build ([#25193](https://github.com/getsentry/sentry-javascript/pull/25193)) + +
+ Internal Changes + +- chore: Add external contributor to CHANGELOG.md ([#25230](https://github.com/getsentry/sentry-javascript/pull/25230)) +- chore: Add external contributor to CHANGELOG.md ([#25271](https://github.com/getsentry/sentry-javascript/pull/25271)) +- chore(deps-dev): Bump oxlint-tsgolint from 7.0.2001 to 7.0.2003 ([#25237](https://github.com/getsentry/sentry-javascript/pull/25237)) +- chore(deps-dev): Bump rimraf from 5.0.10 to 6.1.3 ([#25241](https://github.com/getsentry/sentry-javascript/pull/25241)) +- chore(deps): Bump `@sentry/conventions` to 0.27.0 ([#25264](https://github.com/getsentry/sentry-javascript/pull/25264)) +- chore(deps): Bump next from 16.2.11 to 16.3.8 in /dev-packages/e2e-tests/test-applications/nextjs-16 ([#25179](https://github.com/getsentry/sentry-javascript/pull/25179)) +- chore(deps): Bump next from 16.2.11 to 16.3.8 in /dev-packages/e2e-tests/test-applications/nextjs-16-tunnel ([#25190](https://github.com/getsentry/sentry-javascript/pull/25190)) +- chore(size-limit): weekly auto-bump ([#25213](https://github.com/getsentry/sentry-javascript/pull/25213)) +- ci: Install Playwright in the background during E2E app builds ([#25207](https://github.com/getsentry/sentry-javascript/pull/25207)) +- ci: Run lint steps in parallel ([#25187](https://github.com/getsentry/sentry-javascript/pull/25187)) +- ci(bun): Test the oldest supported Bun version and the newest 1.4 ([#25203](https://github.com/getsentry/sentry-javascript/pull/25203)) +- docs: Scope pre-PR tests to affected suites ([#25198](https://github.com/getsentry/sentry-javascript/pull/25198)) +- feat(deps): Bump @aws-sdk/client-kinesis from 3.1068.0 to 3.1146.0 ([#25240](https://github.com/getsentry/sentry-javascript/pull/25240)) +- feat(deps): Bump handlebars from 4.7.9 to 4.7.10 ([#25206](https://github.com/getsentry/sentry-javascript/pull/25206)) +- feat(deps): Bump pino from 9.10.0 to 10.4.0 ([#25238](https://github.com/getsentry/sentry-javascript/pull/25238)) +- feat(deps): Bump reflect-metadata from 0.2.1 to 0.2.2 ([#25239](https://github.com/getsentry/sentry-javascript/pull/25239)) +- ref(core): Simplify transport item loss recording ([#25224](https://github.com/getsentry/sentry-javascript/pull/25224)) +- ref(server-utils): Export the pg query span helpers ([#25163](https://github.com/getsentry/sentry-javascript/pull/25163)) +- ref(server-utils): Share Orchestrion channel context ([#25208](https://github.com/getsentry/sentry-javascript/pull/25208)) +- test(browser): migrate trace lifecycle and span API tests to streaming ([#24881](https://github.com/getsentry/sentry-javascript/pull/24881)) +- test(browser): port browser tracing suites to span streaming ([#24888](https://github.com/getsentry/sentry-javascript/pull/24888)) +- test(browser): port requests and web vitals to span streaming ([#24886](https://github.com/getsentry/sentry-javascript/pull/24886)) +- test(browser): Register the LCP handlers in the bundle of the SDK ([#25263](https://github.com/getsentry/sentry-javascript/pull/25263)) +- test(bun): Run the Node suites that need Bun 1.4 on the Bun 1.4 job ([#25204](https://github.com/getsentry/sentry-javascript/pull/25204)) +- test(cloudflare): Add LangChain TypeSafeClassifier integration test ([#25234](https://github.com/getsentry/sentry-javascript/pull/25234)) +- test(core): Fix flaky span end timestamp test ([#25211](https://github.com/getsentry/sentry-javascript/pull/25211)) +- test(deno): Type-check Deno unit tests ([#25227](https://github.com/getsentry/sentry-javascript/pull/25227)) +- test(nextjs): Add vinext e2e tests ([#25001](https://github.com/getsentry/sentry-javascript/pull/25001)) +- test(node-integration): Replace `conditionalTest` with a `supports` helper ([#25202](https://github.com/getsentry/sentry-javascript/pull/25202)) +- test(node-integration): Stop exporting `NODE_VERSION` and `RUNTIME` from the test utils ([#25292](https://github.com/getsentry/sentry-javascript/pull/25292)) +- test(node): Add integration tests for the Neon serverless driver ([#25177](https://github.com/getsentry/sentry-javascript/pull/25177)) +- test(node): Port baggage and metadata tests to span streaming ([#25220](https://github.com/getsentry/sentry-javascript/pull/25220)) +- test(node): Port basic and nested startSpan tests to streaming ([#25214](https://github.com/getsentry/sentry-javascript/pull/25214)) +- test(node): Port fetch request suites to span streaming ([#25189](https://github.com/getsentry/sentry-javascript/pull/25189)) +- test(node): Port HTTP options and hooks to span streaming ([#25195](https://github.com/getsentry/sentry-javascript/pull/25195)) +- test(node): Port HTTP propagation and transport suites to streaming ([#25197](https://github.com/getsentry/sentry-javascript/pull/25197)) +- test(node): Port HTTP request suites to span streaming ([#25191](https://github.com/getsentry/sentry-javascript/pull/25191)) +- test(node): Port incoming HTTP behavior to span streaming ([#25194](https://github.com/getsentry/sentry-javascript/pull/25194)) +- test(node): Port MongoDB integration tests to span streaming ([#25150](https://github.com/getsentry/sentry-javascript/pull/25150)) +- test(node): Port Mongoose integration tests to span streaming ([#25151](https://github.com/getsentry/sentry-javascript/pull/25151)) +- test(node): Port outgoing HTTP client spans to streaming ([#25192](https://github.com/getsentry/sentry-javascript/pull/25192)) +- test(node): Port parallel span and naming tests to streaming ([#25217](https://github.com/getsentry/sentry-javascript/pull/25217)) +- test(node): Port Postgres integration tests to span streaming ([#25140](https://github.com/getsentry/sentry-javascript/pull/25140)) +- test(node): Port Prisma integration tests to span streaming ([#25152](https://github.com/getsentry/sentry-javascript/pull/25152)) +- test(node): Port Redis integration tests to span streaming ([#25153](https://github.com/getsentry/sentry-javascript/pull/25153)) +- test(node): Port span linking and raw OTel tests to streaming ([#25219](https://github.com/getsentry/sentry-javascript/pull/25219)) +- test(node): Port trace context tests to span streaming ([#25218](https://github.com/getsentry/sentry-javascript/pull/25218)) +- test(node): Run sampling propagation tests with default streaming ([#25221](https://github.com/getsentry/sentry-javascript/pull/25221)) +- test(node): Use default streaming in scope and span suites ([#25222](https://github.com/getsentry/sentry-javascript/pull/25222)) +- test(nuxt): Test imports from `nuxt/server` in the nuxt-5 E2E app ([#25232](https://github.com/getsentry/sentry-javascript/pull/25232)) + +
+ +Work in this release was contributed by @Philip-Nwabuwa and @4ktLuffy. Thank you for your contributions! + ## 11.6.0 ### Important Changes diff --git a/MIGRATION.md b/MIGRATION.md index 55be1d869f77..9785e86fd6a8 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -37,6 +37,8 @@ Version 11 of the Sentry SDK has new compatibility ranges for runtimes and frame **Deno:** The minimum supported Deno version is now **2.8.3**. +**Bun:** The minimum supported Bun version is now **1.3.14**. + **Browsers:** Support for **Safari 14** was dropped. Sentry now requires Safari 15 or higher. For the rest of the browser support matrix, refer to the [Sentry docs](https://docs.sentry.io/platforms/javascript/#browser-support). ### TypeScript Version Policy diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 6259df274c7d..febc4eb2057e 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -63,7 +63,7 @@ "@sentry/browser": "11.6.0", "@sentry/replay": "11.6.0", "@sentry/opentelemetry": "11.6.0", - "@sentry/conventions": "0.26.0", + "@sentry/conventions": "0.27.0", "@supabase/supabase-js": "2.109.0", "axios": "1.20.0", "babel-loader": "^10.1.1", diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/withSensitiveQuery/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/withSensitiveQuery/subject.js new file mode 100644 index 000000000000..e8ce3f2f4d3b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/withSensitiveQuery/subject.js @@ -0,0 +1,3 @@ +fetch('http://sentry-test.io/foo?token=sensitive-token-value&page=1').then(() => { + Sentry.captureException('test error'); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/withSensitiveQuery/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/withSensitiveQuery/test.ts new file mode 100644 index 000000000000..f4794f772eec --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/withSensitiveQuery/test.ts @@ -0,0 +1,25 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; + +sentryTest('filters sensitive query params in the fetch breadcrumb URL', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo?*', route => route.fulfill({ status: 200, body: '{}' })); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.breadcrumbs).toEqual([ + { + timestamp: expect.any(Number), + category: 'fetch', + type: 'http', + data: { + method: 'GET', + status_code: 200, + url: 'http://sentry-test.io/foo?token=[Filtered]&page=1', + }, + }, + ]); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/history/withSensitiveQuery/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/history/withSensitiveQuery/subject.js new file mode 100644 index 000000000000..3a8a0b883ce7 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/history/withSensitiveQuery/subject.js @@ -0,0 +1,4 @@ +history.pushState({}, '', '/foo?jwt=sensitive-jwt-value&a=1'); +history.pushState({}, '', '/bar'); + +Sentry.captureException('test exception'); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/history/withSensitiveQuery/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/history/withSensitiveQuery/test.ts new file mode 100644 index 000000000000..95d073806165 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/history/withSensitiveQuery/test.ts @@ -0,0 +1,29 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/browser'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; + +sentryTest('filters sensitive query params in navigation breadcrumbs', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.breadcrumbs).toEqual([ + { + category: 'navigation', + data: { + from: '/index.html', + to: '/foo?jwt=[Filtered]&a=1', + }, + timestamp: expect.any(Number), + }, + { + category: 'navigation', + data: { + from: '/foo?jwt=[Filtered]&a=1', + to: '/bar', + }, + timestamp: expect.any(Number), + }, + ]); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/withSensitiveQuery/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/withSensitiveQuery/subject.js new file mode 100644 index 000000000000..4599aa27ad14 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/withSensitiveQuery/subject.js @@ -0,0 +1,10 @@ +const xhr = new XMLHttpRequest(); + +xhr.open('GET', 'http://sentry-test.io/foo?token=sensitive-token-value&page=1'); +xhr.send(); + +xhr.addEventListener('readystatechange', function () { + if (xhr.readyState === 4) { + Sentry.captureException('test error'); + } +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/withSensitiveQuery/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/withSensitiveQuery/test.ts new file mode 100644 index 000000000000..ccb4c1206139 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/withSensitiveQuery/test.ts @@ -0,0 +1,25 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; + +sentryTest('filters sensitive query params in the XHR breadcrumb URL', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo?*', route => route.fulfill({ status: 200, body: '{}' })); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.breadcrumbs).toEqual([ + { + timestamp: expect.any(Number), + category: 'xhr', + type: 'http', + data: { + method: 'GET', + status_code: 200, + url: 'http://sentry-test.io/foo?token=[Filtered]&page=1', + }, + }, + ]); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpContext/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpContext/test.ts index db8a384510a4..e50a658b4c9a 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpContext/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpContext/test.ts @@ -25,6 +25,18 @@ sentryTest('httpContextIntegration captures user-agent and referrer', async ({ g }); }); +sentryTest('httpContextIntegration filters sensitive query params in the URL', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + const errorEventPromise = waitForErrorRequest(page); + + await page.goto(`${url}?jwt=sensitive-jwt-value&page=1`); + + const errorEvent = envelopeRequestParser(await errorEventPromise); + + expect(errorEvent.request?.url).toBe(`${url}?jwt=[Filtered]&page=1`); +}); + sentryTest('httpContextIntegration captures url, user-agent, and referer', async ({ getLocalTestUrl, page }) => { sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js index 43248f09b63d..f44669962912 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/init.js index bd0421b32761..7c200c542c56 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/test.ts index fbd30098773a..85b7663dabc0 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { Event as SentryEvent } from '@sentry/core'; import { sentryTest } from '../../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForErrorRequest } from '../../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest('should handle aborted fetch calls', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -14,7 +14,7 @@ sentryTest('should handle aborted fetch calls', async ({ getLocalTestUrl, page } // never fulfil this route because we abort the request as part of the test }); - const transactionEventPromise = getFirstSentryEnvelopeRequest(page); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'with-abort-controller'); const hasAbortedFetchPromise = new Promise(resolve => { page.on('console', msg => { @@ -29,10 +29,13 @@ sentryTest('should handle aborted fetch calls', async ({ getLocalTestUrl, page } await page.locator('[data-test-id=start-button]').click(); await page.locator('[data-test-id=abort-button]').click(); - const transactionEvent = await transactionEventPromise; + await spanPromise; + const eventPromise = waitForErrorRequest(page); + await page.evaluate(() => (window as any).Sentry.captureMessage('After aborted fetch')); + const event = envelopeRequestParser(await eventPromise); // assert that fetch calls do not return undefined - const fetchBreadcrumbs = transactionEvent.breadcrumbs?.filter( + const fetchBreadcrumbs = event.breadcrumbs?.filter( ({ category, data }) => category === 'fetch' && data === undefined, ); expect(fetchBreadcrumbs).toHaveLength(0); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDataCollection/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDataCollection/init.js index fd831d3f7c14..8584933da0fa 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDataCollection/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDataCollection/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDisabledDataCollection/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDisabledDataCollection/init.js index 7d53e07a4c0c..a20cf0664d13 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDisabledDataCollection/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDisabledDataCollection/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withHeadersOnlyDataCollection/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withHeadersOnlyDataCollection/init.js index 2739a4aeea64..5eafca47503b 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withHeadersOnlyDataCollection/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withHeadersOnlyDataCollection/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js index 43248f09b63d..f44669962912 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/withDataCollection/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/withDataCollection/init.js index fd831d3f7c14..8584933da0fa 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/withDataCollection/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/withDataCollection/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/init.js b/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/init.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/init.js rename to dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/init.js diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts b/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/test.ts similarity index 89% rename from dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts rename to dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/test.ts index c3218f27e98a..9491bc7feab6 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/test.ts @@ -1,3 +1,4 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../utils/helpers'; @@ -22,7 +23,7 @@ sentryTest('beforeSendSpan applies changes to streamed span', async ({ getLocalT spanId: '456', }, attributes: { - 'sentry.link.type': { type: 'string', value: 'custom_link' }, + [SENTRY_LINK_TYPE]: { type: 'string', value: 'custom_link' }, }, }, ]); diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/init.js b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/init.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/init.js rename to dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/init.js diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/test.ts b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/test.ts rename to dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/test.ts diff --git a/dev-packages/browser-integration-tests/suites/public-api/captureMessage/simple_message_no_stacktrace/test.ts b/dev-packages/browser-integration-tests/suites/public-api/captureMessage/simple_message_no_stacktrace/test.ts index ae9db5f3064d..2c42ced57737 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/captureMessage/simple_message_no_stacktrace/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/captureMessage/simple_message_no_stacktrace/test.ts @@ -1,12 +1,11 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; +import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; sentryTest('does not capture a stack trace if `attachStackTrace` is `false`', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = envelopeRequestParser(await waitForErrorRequestOnUrl(page, url)); expect(eventData.message).toBe('foo'); expect(eventData.level).toBe('info'); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts index 4d6d78f0eea1..7820d0108fc2 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts @@ -1,20 +1,14 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { - envelopeRequestParser, - shouldSkipTracingTest, - waitForTransactionRequestOnUrl, -} from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('sends an empty string attribute', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const req = await waitForTransactionRequestOnUrl(page, url); - const transaction = envelopeRequestParser(req); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'child_span'); + await page.goto(url); - const childSpan = transaction.spans?.[0]; - expect(childSpan?.data?.someAttribute).toBe(''); + const span = await spanPromise; + expect(span.attributes.someAttribute).toEqual({ type: 'string', value: '' }); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/init.js new file mode 100644 index 000000000000..527fad0d6f15 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + normalizeDepth: 10, +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/subject.js new file mode 100644 index 000000000000..35d41b55e69d --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/subject.js @@ -0,0 +1,14 @@ +async function run() { + Sentry.startSpan({ name: 'parent_span' }, () => { + Sentry.startSpan({ name: 'child_span' }, () => { + // whatever a user would do here + }); + + // unfinished spans are filtered out + Sentry.startInactiveSpan({ name: 'span_4' }); + }); +} + +(async () => { + await run(); +})(); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/test.ts new file mode 100644 index 000000000000..d3819b797a11 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/test.ts @@ -0,0 +1,52 @@ +import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + envelopeRequestParser, + shouldSkipTracingTest, + waitForTransactionRequestOnUrl, +} from '../../../../utils/helpers'; + +sentryTest( + 'sends a transaction in an envelope with manual origin and custom source', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForTransactionRequestOnUrl(page, url); + const transaction = envelopeRequestParser(req); + + const attributes = transaction.contexts?.trace?.data; + expect(attributes).toEqual({ + [SENTRY_ORIGIN]: 'manual', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'custom', + }); + + expect(transaction.transaction_info?.source).toBe('custom'); + + expect(transaction.transaction).toBe('parent_span'); + expect(transaction.spans).toBeDefined(); + }, +); + +sentryTest('should report finished spans as children of the root transaction', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForTransactionRequestOnUrl(page, url); + const transaction = envelopeRequestParser(req); + + expect(transaction.spans).toHaveLength(1); + + const span_1 = transaction.spans?.[0]; + expect(span_1?.description).toBe('child_span'); + expect(span_1?.parent_span_id).toEqual(transaction?.contexts?.trace?.span_id); + expect(span_1?.origin).toEqual('manual'); + expect(span_1?.data?.[SENTRY_ORIGIN]).toEqual('manual'); +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/init.js similarity index 77% rename from dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/init.js rename to dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/init.js index aaafd3396f14..e546c7cc6a94 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/init.js @@ -4,7 +4,6 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], tracesSampleRate: 1.0, debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js index 35d41b55e69d..afe2527d8c7e 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js @@ -1,14 +1,15 @@ -async function run() { - Sentry.startSpan({ name: 'parent_span' }, () => { - Sentry.startSpan({ name: 'child_span' }, () => { - // whatever a user would do here - }); - - // unfinished spans are filtered out - Sentry.startInactiveSpan({ name: 'span_4' }); +Sentry.startSpan({ name: 'test-span', op: 'test' }, () => { + Sentry.startSpan({ name: 'test-child-span', op: 'test-child' }, () => { + // noop }); -} -(async () => { - await run(); -})(); + const inactiveSpan = Sentry.startInactiveSpan({ name: 'test-inactive-span' }); + inactiveSpan.end(); + + Sentry.startSpanManual({ name: 'test-manual-span' }, span => { + // 2 = SPAN_STATUS_ERROR. The message must be preserved as the `sentry.status.message` + // attribute on the streamed span, since v2 statuses are reduced to ok/error. + span.setStatus({ code: 2, message: 'Connection Refused' }); + span.end(); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts index 824220e5133f..ba2404e747b5 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts @@ -1,52 +1,297 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + SENTRY_IS_LOCALHOST, + CULTURE_CALENDAR, + CULTURE_LOCALE, + CULTURE_TIMEZONE, + URL_FULL, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_TRACE_LIFECYCLE, + USER_AGENT_ORIGINAL, + SENTRY_ENVIRONMENT, + SENTRY_SDK_INTEGRATIONS, + SENTRY_STATUS_MESSAGE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; +import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { - envelopeRequestParser, - shouldSkipTracingTest, - waitForTransactionRequestOnUrl, -} from '../../../../utils/helpers'; - -sentryTest( - 'sends a transaction in an envelope with manual origin and custom source', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - const req = await waitForTransactionRequestOnUrl(page, url); - const transaction = envelopeRequestParser(req); - - const attributes = transaction.contexts?.trace?.data; - expect(attributes).toEqual({ - [SENTRY_ORIGIN]: 'manual', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'custom', - }); - - expect(transaction.transaction_info?.source).toBe('custom'); - - expect(transaction.transaction).toBe('parent_span'); - expect(transaction.spans).toBeDefined(); - }, -); - -sentryTest('should report finished spans as children of the root transaction', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; + +sentryTest('sends a streamed span envelope by default', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + + const spanEnvelopePromise = waitForStreamedSpanEnvelope(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const req = await waitForTransactionRequestOnUrl(page, url); - const transaction = envelopeRequestParser(req); + await page.goto(url); + + const spanEnvelope = await spanEnvelopePromise; + + const envelopeHeader = spanEnvelope[0]; + const envelopeItem = spanEnvelope[1]; + const spans = envelopeItem[0][1].items; + + expect(envelopeHeader).toEqual({ + sdk: { + name: 'sentry.javascript.browser', + version: SDK_VERSION, + }, + sent_at: expect.any(String), + trace: { + environment: 'production', + public_key: 'public', + sample_rand: expect.any(String), + sample_rate: '1', + sampled: 'true', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + transaction: 'test-span', + }, + }); + + const numericSampleRand = parseFloat(envelopeHeader.trace!.sample_rand!); + const traceId = envelopeHeader.trace!.trace_id; + + expect(Number.isNaN(numericSampleRand)).toBe(false); + + expect(envelopeItem).toEqual([ + [ + { content_type: 'application/vnd.sentry.items.span.v2+json', item_count: 4, type: 'span' }, + { + version: 2, + ingest_settings: { infer_ip: 'auto', infer_user_agent: 'auto' }, + items: expect.any(Array), + }, + ], + ]); - expect(transaction.spans).toHaveLength(1); + const segmentSpanId = spans.find(s => !!s.is_segment)?.span_id; + expect(segmentSpanId).toBeDefined(); - const span_1 = transaction.spans?.[0]; - expect(span_1?.description).toBe('child_span'); - expect(span_1?.parent_span_id).toEqual(transaction?.contexts?.trace?.span_id); - expect(span_1?.origin).toEqual('manual'); - expect(span_1?.data?.['sentry.origin']).toEqual('manual'); + expect(spans).toEqual([ + { + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [SENTRY_OP]: { + type: 'string', + value: 'test-child', + }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'manual', + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: segmentSpanId, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'test-span', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + }, + end_timestamp: expect.any(Number), + is_segment: false, + name: 'test-child-span', + parent_span_id: segmentSpanId, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: traceId, + }, + { + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'manual', + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: segmentSpanId, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'test-span', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + }, + end_timestamp: expect.any(Number), + is_segment: false, + name: 'test-inactive-span', + parent_span_id: segmentSpanId, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: traceId, + }, + { + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'manual', + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: segmentSpanId, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'test-span', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + [SENTRY_STATUS_MESSAGE]: { + type: 'string', + value: 'Connection Refused', + }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + }, + end_timestamp: expect.any(Number), + is_segment: false, + name: 'test-manual-span', + parent_span_id: segmentSpanId, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status: 'error', + trace_id: traceId, + }, + { + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [CULTURE_CALENDAR]: { + type: 'string', + value: expect.any(String), + }, + [CULTURE_LOCALE]: { + type: 'string', + value: expect.any(String), + }, + [CULTURE_TIMEZONE]: { + type: 'string', + value: expect.any(String), + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + [URL_FULL]: { + type: 'string', + value: expect.any(String), + }, + [SENTRY_OP]: { + type: 'string', + value: 'test', + }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'manual', + }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { + type: 'integer', + value: 1, + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SDK_INTEGRATIONS]: { + type: 'array', + value: expect.arrayContaining(['SpanStreaming']), + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: segmentSpanId, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'test-span', + }, + [SENTRY_SEGMENT_NAME_SOURCE]: { + type: 'string', + value: 'custom', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + }, + end_timestamp: expect.any(Number), + is_segment: true, + name: 'test-span', + span_id: segmentSpanId, + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: traceId, + }, + ]); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/init.js new file mode 100644 index 000000000000..527fad0d6f15 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + normalizeDepth: 10, +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/subject.js new file mode 100644 index 000000000000..c7aa1cd72dfe --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/subject.js @@ -0,0 +1,7 @@ +const chicken = {}; +const egg = { contains: chicken }; +chicken.lays = egg; + +Sentry.startSpan({ name: 'circular_object_test_transaction', attributes: { chicken, count: 42 } }, () => { + Sentry.startSpan({ op: 'circular_object_test_span', attributes: { chicken, count: 42 } }, () => undefined); +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/test.ts new file mode 100644 index 000000000000..d794f101112d --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/test.ts @@ -0,0 +1,28 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + envelopeRequestParser, + shouldSkipTracingTest, + waitForTransactionRequestOnUrl, +} from '../../../../utils/helpers'; + +sentryTest( + 'normalizes circular attributes on root and child spans with static tracing', + async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + const url = await getLocalTestUrl({ testDir: __dirname }); + const event = envelopeRequestParser(await waitForTransactionRequestOnUrl(page, url)); + + expect(event.transaction).toBe('circular_object_test_transaction'); + expect(event.contexts?.trace?.data).toMatchObject({ + chicken: { lays: { contains: '[Circular ~]' } }, + count: 42, + }); + expect(event.spans).toHaveLength(1); + expect(event.spans?.[0]).toMatchObject({ + op: 'circular_object_test_span', + parent_span_id: event.contexts?.trace?.span_id, + data: { chicken: { lays: { contains: '[Circular ~]' } }, count: 42 }, + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/subject.js index b5ee8624b124..c7aa1cd72dfe 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/subject.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/subject.js @@ -2,6 +2,6 @@ const chicken = {}; const egg = { contains: chicken }; chicken.lays = egg; -Sentry.startSpan({ name: 'circular_object_test_transaction', attributes: { chicken } }, () => { - Sentry.startSpan({ op: 'circular_object_test_span', attributes: { chicken } }, () => undefined); +Sentry.startSpan({ name: 'circular_object_test_transaction', attributes: { chicken, count: 42 } }, () => { + Sentry.startSpan({ op: 'circular_object_test_span', attributes: { chicken, count: 42 } }, () => undefined); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/test.ts index 41d49b6351ea..7860439b7707 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/test.ts @@ -1,29 +1,23 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; -sentryTest('should be able to handle circular data', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest( + 'drops circular object attributes while preserving supported attributes on root and child spans', + async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + const url = await getLocalTestUrl({ testDir: __dirname }); + const rootPromise = waitForStreamedSpan(page, span => span.name === 'circular_object_test_transaction'); + const childPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'circular_object_test_span'); + await page.goto(url); - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.type).toBe('transaction'); - expect(eventData.transaction).toBe('circular_object_test_transaction'); - - expect(eventData.contexts).toMatchObject({ - trace: { - data: { chicken: { lays: { contains: '[Circular ~]' } } }, - }, - }); - - expect(eventData?.spans?.[0]).toMatchObject({ - data: { chicken: { lays: { contains: '[Circular ~]' } } }, - op: 'circular_object_test_span', - }); - - await new Promise(resolve => setTimeout(resolve, 2000)); -}); + const [rootSpan, childSpan] = await Promise.all([rootPromise, childPromise]); + expect(rootSpan.is_segment).toBe(true); + expect(childSpan.parent_span_id).toBe(rootSpan.span_id); + expect(rootSpan.attributes.count).toEqual({ type: 'integer', value: 42 }); + expect(childSpan.attributes.count).toEqual({ type: 'integer', value: 42 }); + expect(rootSpan.attributes.chicken).toBeUndefined(); + expect(childSpan.attributes.chicken).toBeUndefined(); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-reject/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-reject/test.ts index d8cecd5f09c7..c70b05e99fea 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-reject/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-reject/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { envelopeRequestParser, waitForErrorRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'should capture a promise rejection within an async startSpan callback', @@ -11,17 +11,18 @@ sentryTest( } const url = await getLocalTestUrl({ testDir: __dirname }); - const envelopePromise = getMultipleSentryEnvelopeRequests(page, 2); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'parent_span'); + const errorPromise = waitForErrorRequest(page); await page.goto(url); const clickPromise = page.getByText('Button 1').click(); - const [, events] = await Promise.all([clickPromise, envelopePromise]); - const txn = events.find(event => event.type === 'transaction'); - const err = events.find(event => !event.type); + const [, span, errorRequest] = await Promise.all([clickPromise, spanPromise, errorPromise]); + const err = envelopeRequestParser(errorRequest); - expect(txn).toMatchObject({ transaction: 'parent_span' }); + expect(span.name).toBe('parent_span'); + expect(span.status).toBe('ok'); expect(err?.exception?.values?.[0]?.value).toBe( 'Non-Error promise rejection captured with value: Async Promise Rejection', diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw-not-awaited/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw-not-awaited/test.ts index 79ecf0f6e9e8..42c87027807f 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw-not-awaited/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw-not-awaited/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { envelopeRequestParser, waitForErrorRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( "should capture a thrown error within an async startSpan callback that's not awaited properly", @@ -9,7 +9,8 @@ sentryTest( if (shouldSkipTracingTest()) { sentryTest.skip(); } - const envelopePromise = getMultipleSentryEnvelopeRequests(page, 2); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'parent_span'); + const errorPromise = waitForErrorRequest(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); @@ -17,11 +18,11 @@ sentryTest( const clickPromise = page.getByText('Button 1').click(); // awaiting both events simultaneously to avoid race conditions - const [, events] = await Promise.all([clickPromise, envelopePromise]); - const txn = events.find(event => event.type === 'transaction'); - const err = events.find(event => !event.type); + const [, span, errorRequest] = await Promise.all([clickPromise, spanPromise, errorPromise]); + const err = envelopeRequestParser(errorRequest); - expect(txn).toMatchObject({ transaction: 'parent_span' }); + expect(span.name).toBe('parent_span'); + expect(span.status).toBe('error'); expect(err?.exception?.values?.[0]?.value).toBe('Async Thrown Error'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw/test.ts index 8f527b4e7238..ce66c78553bc 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw/test.ts @@ -1,13 +1,14 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { envelopeRequestParser, waitForErrorRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('should capture a thrown error within an async startSpan callback', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } - const envelopePromise = getMultipleSentryEnvelopeRequests(page, 2); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'parent_span'); + const errorPromise = waitForErrorRequest(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); @@ -15,10 +16,10 @@ sentryTest('should capture a thrown error within an async startSpan callback', a const clickPromise = page.getByText('Button 1').click(); // awaiting both events simultaneously to avoid race conditions - const [, events] = await Promise.all([clickPromise, envelopePromise]); - const txn = events.find(event => event.type === 'transaction'); - const err = events.find(event => !event.type); + const [, span, errorRequest] = await Promise.all([clickPromise, spanPromise, errorPromise]); + const err = envelopeRequestParser(errorRequest); - expect(txn).toMatchObject({ transaction: 'parent_span' }); + expect(span.name).toBe('parent_span'); + expect(span.status).toBe('error'); expect(err?.exception?.values?.[0]?.value).toBe('Async Thrown Error'); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-sync/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-sync/test.ts index 9880598b9294..2a1dc368e98f 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-sync/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-sync/test.ts @@ -1,11 +1,13 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; +import type * as SentryBrowser from '@sentry/browser'; import { sentryTest } from '../../../../utils/fixtures'; import { - getMultipleSentryEnvelopeRequests, + envelopeRequestParser, + waitForErrorRequest, runScriptInSandbox, shouldSkipTracingTest, } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'should capture an error within a sync startSpan callback', @@ -21,10 +23,20 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const errorEventsPromise = getMultipleSentryEnvelopeRequests(page, 2); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'parent_span'); + const errorPromise = waitForErrorRequest(page); await page.goto(url); + // Force a span envelope to arrive before the error so the error waiter must ignore it. + const precedingSpanPromise = waitForStreamedSpan(page, span => span.name === 'preceding_span'); + await page.evaluate(async () => { + const Sentry = (window as typeof window & { Sentry: typeof SentryBrowser }).Sentry; + Sentry.startSpan({ name: 'preceding_span' }, () => undefined); + await Sentry.flush(); + }); + await precedingSpanPromise; + await runScriptInSandbox(page, { content: ` function run() { @@ -37,12 +49,11 @@ sentryTest( `, }); - const events = await errorEventsPromise; - - const txn = events.find(event => event.type === 'transaction'); - const err = events.find(event => !event.type); + const [span, errorRequest] = await Promise.all([spanPromise, errorPromise]); + const err = envelopeRequestParser(errorRequest); - expect(txn).toMatchObject({ transaction: 'parent_span' }); + expect(span.name).toBe('parent_span'); + expect(span.status).toBe('error'); expect(err?.exception?.values?.[0]?.value).toBe('Sync Error'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js index 527fad0d6f15..3364667c960c 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1.0, normalizeDepth: 10, diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-in-scope/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-in-scope/test.ts index b3edbfac7dcf..b233cae07f97 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-in-scope/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-in-scope/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'should send manually started parallel root spans outside of root context', @@ -12,21 +12,21 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const transaction1ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_1'); - const transaction2ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_2'); + const transaction1ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_1'); + const transaction2ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_2'); await page.goto(url); const [transaction1Req, transaction2Req] = await Promise.all([transaction1ReqPromise, transaction2ReqPromise]); - const transaction1 = envelopeRequestParser(transaction1Req); - const transaction2 = envelopeRequestParser(transaction2Req); + const transaction1 = transaction1Req; + const transaction2 = transaction2Req; expect(transaction1).toBeDefined(); expect(transaction2).toBeDefined(); - const trace1Id = transaction1.contexts?.trace?.trace_id; - const trace2Id = transaction2.contexts?.trace?.trace_id; + const trace1Id = transaction1.trace_id; + const trace2Id = transaction2.trace_id; expect(trace1Id).toBeDefined(); expect(trace2Id).toBeDefined(); @@ -34,7 +34,7 @@ sentryTest( // We use the same traceID from the root propagation context here expect(trace1Id).toBe(trace2Id); - expect(transaction1.contexts?.trace?.parent_span_id).toBeUndefined(); - expect(transaction2.contexts?.trace?.parent_span_id).toBeUndefined(); + expect(transaction1.parent_span_id).toBeUndefined(); + expect(transaction2.parent_span_id).toBeUndefined(); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/test.ts index 62cdeb9bf7c9..49d52dce3c61 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'should send manually started parallel root spans in root context with parentSpanId', @@ -12,26 +12,26 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const transaction1ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_1'); - const transaction2ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_2'); + const transaction1ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_1'); + const transaction2ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_2'); await page.goto(url); const [transaction1Req, transaction2Req] = await Promise.all([transaction1ReqPromise, transaction2ReqPromise]); - const transaction1 = envelopeRequestParser(transaction1Req); - const transaction2 = envelopeRequestParser(transaction2Req); + const transaction1 = transaction1Req; + const transaction2 = transaction2Req; expect(transaction1).toBeDefined(); expect(transaction2).toBeDefined(); - const trace1Id = transaction1.contexts?.trace?.trace_id; - const trace2Id = transaction2.contexts?.trace?.trace_id; + const trace1Id = transaction1.trace_id; + const trace2Id = transaction2.trace_id; expect(trace1Id).toBe('12345678901234567890123456789012'); expect(trace2Id).toBe('12345678901234567890123456789012'); - expect(transaction1.contexts?.trace?.parent_span_id).toBe('1234567890123456'); - expect(transaction2.contexts?.trace?.parent_span_id).toBe('1234567890123456'); + expect(transaction1.parent_span_id).toBe('1234567890123456'); + expect(transaction2.parent_span_id).toBe('1234567890123456'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans/test.ts index 084b7100fa9b..1c1af189cc77 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('should send manually started parallel root spans in root context', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -10,21 +10,21 @@ sentryTest('should send manually started parallel root spans in root context', a const url = await getLocalTestUrl({ testDir: __dirname }); - const transaction1ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_1'); - const transaction2ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_2'); + const transaction1ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_1'); + const transaction2ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_2'); await page.goto(url); const [transaction1Req, transaction2Req] = await Promise.all([transaction1ReqPromise, transaction2ReqPromise]); - const transaction1 = envelopeRequestParser(transaction1Req); - const transaction2 = envelopeRequestParser(transaction2Req); + const transaction1 = transaction1Req; + const transaction2 = transaction2Req; expect(transaction1).toBeDefined(); expect(transaction2).toBeDefined(); - const trace1Id = transaction1.contexts?.trace?.trace_id; - const trace2Id = transaction2.contexts?.trace?.trace_id; + const trace1Id = transaction1.trace_id; + const trace2Id = transaction2.trace_id; expect(trace1Id).toBeDefined(); expect(trace2Id).toBeDefined(); @@ -32,6 +32,6 @@ sentryTest('should send manually started parallel root spans in root context', a // We use the same traceID from the root propagation context here expect(trace1Id).toBe(trace2Id); - expect(transaction1.contexts?.trace?.parent_span_id).toBeUndefined(); - expect(transaction2.contexts?.trace?.parent_span_id).toBeUndefined(); + expect(transaction1.parent_span_id).toBeUndefined(); + expect(transaction2.parent_span_id).toBeUndefined(); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/init.js new file mode 100644 index 000000000000..5f6b23836f8e --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + // setMeasurement is a legacy transaction API without a span v2 representation. + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + normalizeDepth: 10, +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/test.ts index 2604470d0a50..8c257a2645cc 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/test.ts @@ -3,19 +3,22 @@ import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; -sentryTest('should attach measurement to transaction', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest( + 'static tracing compatibility: should attach measurement to transaction', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } - const url = await getLocalTestUrl({ testDir: __dirname }); - const event = await getFirstSentryEnvelopeRequest(page, url); + const url = await getLocalTestUrl({ testDir: __dirname }); + const event = await getFirstSentryEnvelopeRequest(page, url); - expect(event.measurements?.['metric.foo'].value).toBe(42); - expect(event.measurements?.['metric.bar'].value).toBe(1337); - expect(event.measurements?.['metric.baz'].value).toBe(1); + expect(event.measurements?.['metric.foo'].value).toBe(42); + expect(event.measurements?.['metric.bar'].value).toBe(1337); + expect(event.measurements?.['metric.baz'].value).toBe(1); - expect(event.measurements?.['metric.foo'].unit).toBe('ms'); - expect(event.measurements?.['metric.bar'].unit).toBe('nanoseconds'); - expect(event.measurements?.['metric.baz'].unit).toBe(''); -}); + expect(event.measurements?.['metric.foo'].unit).toBe('ms'); + expect(event.measurements?.['metric.bar'].unit).toBe('nanoseconds'); + expect(event.measurements?.['metric.baz'].unit).toBe(''); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/subject.js deleted file mode 100644 index afe2527d8c7e..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/subject.js +++ /dev/null @@ -1,15 +0,0 @@ -Sentry.startSpan({ name: 'test-span', op: 'test' }, () => { - Sentry.startSpan({ name: 'test-child-span', op: 'test-child' }, () => { - // noop - }); - - const inactiveSpan = Sentry.startInactiveSpan({ name: 'test-inactive-span' }); - inactiveSpan.end(); - - Sentry.startSpanManual({ name: 'test-manual-span' }, span => { - // 2 = SPAN_STATUS_ERROR. The message must be preserved as the `sentry.status.message` - // attribute on the streamed span, since v2 statuses are reduced to ok/error. - span.setStatus({ code: 2, message: 'Connection Refused' }); - span.end(); - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/test.ts deleted file mode 100644 index 5cc3bd34f817..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/test.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { expect } from '@playwright/test'; -import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; -import { - SENTRY_SEGMENT_NAME_SOURCE, - SENTRY_SEGMENT_ID, - SENTRY_SEGMENT_NAME, - SENTRY_SDK_NAME, - SENTRY_SDK_VERSION, - SENTRY_TRACE_LIFECYCLE, - USER_AGENT_ORIGINAL, - SENTRY_STATUS_MESSAGE, - SENTRY_ENVIRONMENT, - SENTRY_SDK_INTEGRATIONS, - SENTRY_OP, - SENTRY_ORIGIN, -} from '@sentry/conventions/attributes'; - -sentryTest( - 'sends a streamed span envelope if spanStreamingIntegration is enabled', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const spanEnvelopePromise = waitForStreamedSpanEnvelope(page); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const spanEnvelope = await spanEnvelopePromise; - - const envelopeHeader = spanEnvelope[0]; - const envelopeItem = spanEnvelope[1]; - const spans = envelopeItem[0][1].items; - - expect(envelopeHeader).toEqual({ - sdk: { - name: 'sentry.javascript.browser', - version: SDK_VERSION, - }, - sent_at: expect.any(String), - trace: { - environment: 'production', - public_key: 'public', - sample_rand: expect.any(String), - sample_rate: '1', - sampled: 'true', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - transaction: 'test-span', - }, - }); - - const numericSampleRand = parseFloat(envelopeHeader.trace!.sample_rand!); - const traceId = envelopeHeader.trace!.trace_id; - - expect(Number.isNaN(numericSampleRand)).toBe(false); - - expect(envelopeItem).toEqual([ - [ - { content_type: 'application/vnd.sentry.items.span.v2+json', item_count: 4, type: 'span' }, - { - version: 2, - ingest_settings: { infer_ip: 'auto', infer_user_agent: 'auto' }, - items: expect.any(Array), - }, - ], - ]); - - const segmentSpanId = spans.find(s => !!s.is_segment)?.span_id; - expect(segmentSpanId).toBeDefined(); - - expect(spans).toEqual([ - { - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - [SENTRY_OP]: { - type: 'string', - value: 'test-child', - }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'manual', - }, - [SENTRY_SDK_NAME]: { - type: 'string', - value: 'sentry.javascript.browser', - }, - [SENTRY_SDK_VERSION]: { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SEGMENT_ID]: { - type: 'string', - value: segmentSpanId, - }, - [SENTRY_SEGMENT_NAME]: { - type: 'string', - value: 'test-span', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - }, - end_timestamp: expect.any(Number), - is_segment: false, - name: 'test-child-span', - parent_span_id: segmentSpanId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: traceId, - }, - { - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'manual', - }, - [SENTRY_SDK_NAME]: { - type: 'string', - value: 'sentry.javascript.browser', - }, - [SENTRY_SDK_VERSION]: { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SEGMENT_ID]: { - type: 'string', - value: segmentSpanId, - }, - [SENTRY_SEGMENT_NAME]: { - type: 'string', - value: 'test-span', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - }, - end_timestamp: expect.any(Number), - is_segment: false, - name: 'test-inactive-span', - parent_span_id: segmentSpanId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: traceId, - }, - { - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'manual', - }, - [SENTRY_SDK_NAME]: { - type: 'string', - value: 'sentry.javascript.browser', - }, - [SENTRY_SDK_VERSION]: { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SEGMENT_ID]: { - type: 'string', - value: segmentSpanId, - }, - [SENTRY_SEGMENT_NAME]: { - type: 'string', - value: 'test-span', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - [SENTRY_STATUS_MESSAGE]: { - type: 'string', - value: 'Connection Refused', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - }, - end_timestamp: expect.any(Number), - is_segment: false, - name: 'test-manual-span', - parent_span_id: segmentSpanId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'error', - trace_id: traceId, - }, - { - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - 'culture.calendar': { - type: 'string', - value: expect.any(String), - }, - 'culture.locale': { - type: 'string', - value: expect.any(String), - }, - 'culture.timezone': { - type: 'string', - value: expect.any(String), - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - 'url.full': { - type: 'string', - value: expect.any(String), - }, - [SENTRY_OP]: { - type: 'string', - value: 'test', - }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'manual', - }, - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { - type: 'integer', - value: 1, - }, - [SENTRY_SDK_NAME]: { - type: 'string', - value: 'sentry.javascript.browser', - }, - [SENTRY_SDK_VERSION]: { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SDK_INTEGRATIONS]: { - type: 'array', - value: expect.arrayContaining(['SpanStreaming']), - }, - [SENTRY_SEGMENT_ID]: { - type: 'string', - value: segmentSpanId, - }, - [SENTRY_SEGMENT_NAME]: { - type: 'string', - value: 'test-span', - }, - [SENTRY_SEGMENT_NAME_SOURCE]: { - type: 'string', - value: 'custom', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - }, - end_timestamp: expect.any(Number), - is_segment: true, - name: 'test-span', - span_id: segmentSpanId, - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: traceId, - }, - ]); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/thenable-with-extra-methods/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/thenable-with-extra-methods/test.ts index 44008a93a3e9..ed6ab723310c 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/thenable-with-extra-methods/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/thenable-with-extra-methods/test.ts @@ -1,6 +1,7 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('preserves extra methods on real jQuery jqXHR objects', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -8,7 +9,7 @@ sentryTest('preserves extra methods on real jQuery jqXHR objects', async ({ getL } const url = await getLocalTestUrl({ testDir: __dirname }); - const transactionPromise = waitForTransactionRequest(page); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'test-jqxhr'); await page.goto(url); @@ -27,9 +28,9 @@ sentryTest('preserves extra methods on real jQuery jqXHR objects', async ({ getL const testError = await page.evaluate(() => (window as any).jqXHRTestError); expect(testError).toBeNull(); - const transaction = envelopeRequestParser(await transactionPromise); - expect(transaction.transaction).toBe('test-jqxhr'); - expect(transaction.spans).toBeDefined(); + const span = await spanPromise; + expect(span.name).toBe('test-jqxhr'); + expect(span.is_segment).toBe(true); }); sentryTest('aborted request rejects promise correctly', async ({ getLocalTestUrl, page }) => { diff --git a/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/init.js b/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/init.js index bd0421b32761..7c200c542c56 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/test.ts b/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/test.ts index f95829febe1d..3b269806fc84 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/test.ts @@ -1,11 +1,7 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; -import { - envelopeRequestParser, - shouldSkipCdnBundleTest, - shouldSkipTracingTest, - waitForTransactionRequest, -} from '../../../utils/helpers'; +import { shouldSkipCdnBundleTest, shouldSkipTracingTest } from '../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest('bindScopeToEmitter runs listeners with the bound scope active', async ({ getLocalTestUrl, page }) => { // `bindScopeToEmitter` is not exported from the CDN bundles, only from npm. @@ -13,19 +9,19 @@ sentryTest('bindScopeToEmitter runs listeners with the bound scope active', asyn sentryTest.skip(); } - const req = waitForTransactionRequest(page, e => e.transaction === 'parent'); + const rootSpanPromise = waitForStreamedSpan(page, span => span.name === 'parent'); + const childSpanPromise = waitForStreamedSpan(page, span => span.name === 'child-bound'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const parentEvent = envelopeRequestParser(await req); - const parentSpanId = parentEvent.contexts?.trace?.span_id; - const parentTraceId = parentEvent.contexts?.trace?.trace_id; + const [parentEvent, childBound] = await Promise.all([rootSpanPromise, childSpanPromise]); + const parentSpanId = parentEvent.span_id; + const parentTraceId = parentEvent.trace_id; expect(parentSpanId).toMatch(/[a-f\d]{16}/); // The listener fired while no span was active, yet `child-bound` is nested under `parent` // because the parent scope was bound to the emitter. - const childBound = parentEvent.spans?.find(s => s.description === 'child-bound'); expect(childBound).toBeDefined(); expect(childBound?.parent_span_id).toBe(parentSpanId); expect(childBound?.trace_id).toBe(parentTraceId); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/async-spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/async-spans/init.js index bb6d4918d1bf..83076460599f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/async-spans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/async-spans/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/async-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/async-spans/test.ts index 5a2f34abecba..24516c270dba 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/async-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/async-spans/test.ts @@ -1,7 +1,6 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; type WindowWithSpan = Window & { firstWaitingSpan: any; @@ -19,8 +18,7 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const envelope = await getFirstSentryEnvelopeRequest(page); - expect(envelope).toBeDefined(); + await page.waitForFunction(() => !!(window as unknown as WindowWithSpan).secondWaitingSpan); const firstWaitingSpanValue = await page.evaluate( () => (window as unknown as WindowWithSpan).firstWaitingSpan.name, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/init.js index 1c1791241ab7..9a11fe3682bb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts index b6661c2638e2..944ed474f19e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts @@ -1,9 +1,10 @@ +import { SENTRY_STATUS_MESSAGE, SENTRY_CANCELLATION_REASON } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import type { StreamedSpanJSON } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../../utils/helpers'; -sentryTest('should finish a custom transaction when the page goes background', async ({ getLocalTestUrl, page }) => { +sentryTest('should finish a custom span when the page goes background', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } @@ -32,6 +33,6 @@ sentryTest('should finish a custom transaction when the page goes background', a expect(name_after).toBe(name_before); // a cancelled span is reported as `ok`, with the raw status kept as an attribute expect(spanJsonAfter.status).toBe('ok'); - expect(attributes_after['sentry.status.message']).toBeUndefined(); - expect(attributes_after['sentry.cancellation_reason']).toBe('document.hidden'); + expect(attributes_after[SENTRY_STATUS_MESSAGE]).toBeUndefined(); + expect(attributes_after[SENTRY_CANCELLATION_REASON]).toBe('document.hidden'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-relative-url-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/init.js similarity index 87% rename from dev-packages/browser-integration-tests/suites/tracing/request/xhr-relative-url-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/init.js index 5ab240338c8c..bb6d4918d1bf 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-relative-url-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/init.js @@ -3,8 +3,8 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, - autoSessionTracking: false, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/test.ts new file mode 100644 index 000000000000..626c7cbdcdbf --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-static/test.ts @@ -0,0 +1,23 @@ +import { PAGELOAD } from '@sentry/conventions/op'; +import { SENTRY_CANCELLATION_REASON } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('should finish pageload transaction when the page goes background', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + + await page.goto(url); + await page.locator('#go-background').click(); + + const pageloadTransaction = envelopeRequestParser(await pageloadPromise); + + expect(pageloadTransaction.contexts?.trace?.op).toBe(PAGELOAD); + expect(pageloadTransaction.contexts?.trace?.status).toBe('cancelled'); + expect(pageloadTransaction.contexts?.trace?.data?.[SENTRY_CANCELLATION_REASON]).toBe('document.hidden'); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/test.ts deleted file mode 100644 index f33f988be946..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; - -sentryTest('finishes streamed pageload span when the page goes background', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - - await page.goto(url); - await page.locator('#go-background').click(); - const pageloadSpan = await pageloadSpanPromise; - - // TODO: Is this what we want? - expect(pageloadSpan.status).toBe('ok'); - expect(pageloadSpan.attributes['sentry.cancellation_reason']?.value).toBe('document.hidden'); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-relative-url-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/init.js similarity index 87% rename from dev-packages/browser-integration-tests/suites/tracing/request/fetch-relative-url-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/init.js index 5ab240338c8c..83076460599f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-relative-url-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/init.js @@ -6,5 +6,4 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, - autoSessionTracking: false, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/test.ts index 80ea1f5fa09c..10380d5b0e1b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/test.ts @@ -1,20 +1,20 @@ +import { PAGELOAD } from '@sentry/conventions/op'; +import { SENTRY_CANCELLATION_REASON } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; -sentryTest('should finish pageload transaction when the page goes background', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest('finishes streamed pageload span when the page goes background', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === PAGELOAD); await page.goto(url); await page.locator('#go-background').click(); + const pageloadSpan = await pageloadSpanPromise; - const pageloadTransaction = await getFirstSentryEnvelopeRequest(page); - - expect(pageloadTransaction.contexts?.trace?.op).toBe('pageload'); - expect(pageloadTransaction.contexts?.trace?.status).toBe('cancelled'); - expect(pageloadTransaction.contexts?.trace?.data?.['sentry.cancellation_reason']).toBe('document.hidden'); + expect(getSpanOp(pageloadSpan)).toBe(PAGELOAD); + expect(pageloadSpan.status).toBe('ok'); + expect(pageloadSpan.attributes[SENTRY_CANCELLATION_REASON]?.value).toBe('document.hidden'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/error/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/error/test.ts index bf54209f053a..07a56ecdc184 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/error/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/error/test.ts @@ -1,47 +1,35 @@ +import { PAGELOAD } from '@sentry/conventions/op'; +import { URL_PATH } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { - getMultipleSentryEnvelopeRequests, + envelopeRequestParser, + waitForErrorRequest, runScriptInSandbox, shouldSkipTracingTest, } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( - 'should put the pageload transaction name onto an error event caught during pageload', + 'puts the page path onto an error caught during pageload', async ({ getLocalTestUrl, page, browserName }) => { - if (browserName === 'webkit') { - // This test fails on Webkit as errors thrown from `runScriptInSandbox` are Script Errors and skipped by Sentry - sentryTest.skip(); - } - - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - + // WebKit treats errors from runScriptInSandbox as Script Errors, which Sentry skips. + sentryTest.skip(shouldSkipTracingTest() || browserName === 'webkit'); const url = await getLocalTestUrl({ testDir: __dirname }); - - const errorEventsPromise = getMultipleSentryEnvelopeRequests(page, 2); - + const spans = collectStreamedSpans(page); + const pageloadPromise = waitForStreamedSpan(page, span => getSpanOp(span) === PAGELOAD); + const errorPromise = waitForErrorRequest(page); await page.goto(url); - - await runScriptInSandbox(page, { - content: ` - throw new Error('Error during pageload'); - `, - }); - - const [e1, e2] = await errorEventsPromise; - - const pageloadTxnEvent = e1.type === 'transaction' ? e1 : e2; - const errorEvent = e1.type === 'transaction' ? e2 : e1; - - expect(pageloadTxnEvent.contexts?.trace?.op).toEqual('pageload'); - expect(pageloadTxnEvent.spans?.length).toBeGreaterThan(0); - expect(errorEvent.exception?.values?.[0]).toBeDefined(); - - expect(pageloadTxnEvent.transaction?.endsWith('index.html')).toBe(true); - - expect(errorEvent.transaction).toEqual(pageloadTxnEvent.transaction); + await runScriptInSandbox(page, { content: "throw new Error('Error during pageload');" }); + const [pageload, errorRequest] = await Promise.all([pageloadPromise, errorPromise]); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(getSpanOp(pageload)).toBe(PAGELOAD); + expect(spans.filter(span => !span.is_segment)).not.toHaveLength(0); + const error = envelopeRequestParser(errorRequest); + expect(error.exception?.values?.[0]).toBeDefined(); + expect(pageload.name).toBe('Pageload'); + expect(error.transaction).toBe('/index.html'); + expect(error.transaction).toBe(pageload.attributes[URL_PATH]?.value); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-static/init.js similarity index 78% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-static/init.js index 8ec045483e0c..5e521633ad12 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-static/init.js @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ @@ -10,9 +11,6 @@ Sentry.init({ enableHTTPTimings: true, }, }), - Sentry.spanStreamingIntegration(), ], tracesSampleRate: 1, - traceLifecycle: 'stream', - debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-static/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-static/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-static/test.ts new file mode 100644 index 000000000000..bc64d2630c9b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-static/test.ts @@ -0,0 +1,75 @@ +import { PAGELOAD, HTTP_CLIENT } from '@sentry/conventions/op'; +import { + HTTP_REQUEST_REDIRECT_START, + HTTP_REQUEST_REDIRECT_END, + HTTP_REQUEST_WORKER_START, + HTTP_REQUEST_FETCH_START, + HTTP_REQUEST_DOMAIN_LOOKUP_START, + HTTP_REQUEST_DOMAIN_LOOKUP_END, + HTTP_REQUEST_CONNECT_START, + HTTP_REQUEST_SECURE_CONNECTION_START, + HTTP_REQUEST_CONNECTION_END, + HTTP_REQUEST_REQUEST_START, + HTTP_REQUEST_RESPONSE_START, + HTTP_REQUEST_RESPONSE_END, + HTTP_REQUEST_TIME_TO_FIRST_BYTE, + NETWORK_PROTOCOL_VERSION, +} from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('creates fetch spans with http timing', async ({ browserName, getLocalTestUrl, page }) => { + const supportedBrowsers = ['chromium', 'firefox']; + + if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) { + sentryTest.skip(); + } + await page.route('http://sentry-test-site.example/*', async route => { + const request = route.request(); + const postData = await request.postDataJSON(); + + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(Object.assign({ id: 1 }, postData)), + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + await page.goto(url); + const tracingEvent = envelopeRequestParser(await pageloadPromise); + + const requestSpans = tracingEvent.spans?.filter(({ op }) => op === HTTP_CLIENT); + + expect(requestSpans).toHaveLength(3); + + requestSpans?.forEach((span, index) => + expect(span).toMatchObject({ + description: `GET http://sentry-test-site.example/${index}`, + parent_span_id: tracingEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f\d]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: tracingEvent.contexts?.trace?.trace_id, + data: expect.objectContaining({ + [HTTP_REQUEST_REDIRECT_START]: expect.any(Number), + [HTTP_REQUEST_REDIRECT_END]: expect.any(Number), + [HTTP_REQUEST_WORKER_START]: expect.any(Number), + [HTTP_REQUEST_FETCH_START]: expect.any(Number), + [HTTP_REQUEST_DOMAIN_LOOKUP_START]: expect.any(Number), + [HTTP_REQUEST_DOMAIN_LOOKUP_END]: expect.any(Number), + [HTTP_REQUEST_CONNECT_START]: expect.any(Number), + [HTTP_REQUEST_SECURE_CONNECTION_START]: expect.any(Number), + [HTTP_REQUEST_CONNECTION_END]: expect.any(Number), + [HTTP_REQUEST_REQUEST_START]: expect.any(Number), + [HTTP_REQUEST_RESPONSE_START]: expect.any(Number), + [HTTP_REQUEST_RESPONSE_END]: expect.any(Number), + [HTTP_REQUEST_TIME_TO_FIRST_BYTE]: expect.any(Number), + [NETWORK_PROTOCOL_VERSION]: expect.any(String), + }), + }), + ); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-streamed/test.ts deleted file mode 100644 index 7c02a63fc41c..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings-streamed/test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpans } from '../../../../utils/spanUtils'; - -sentryTest( - 'adds http timing to http.client spans in span streaming mode', - async ({ browserName, getLocalTestUrl, page }) => { - const supportedBrowsers = ['chromium', 'firefox']; - - sentryTest.skip(shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)); - - await page.route('http://sentry-test-site.example/*', async route => { - const request = route.request(); - const postData = await request.postDataJSON(); - - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify(Object.assign({ id: 1 }, postData)), - }); - }); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'http.client')); - await page.goto(url); - - const requestSpans = (await spansPromise).filter(s => getSpanOp(s) === 'http.client'); - const pageloadSpan = (await spansPromise).find(s => getSpanOp(s) === 'pageload'); - - expect(pageloadSpan).toBeDefined(); - expect(requestSpans).toHaveLength(3); - - requestSpans?.forEach(span => - expect(span).toMatchObject({ - // Streamed span names drop the high-cardinality URL path. - name: 'GET sentry-test-site.example', - parent_span_id: pageloadSpan?.span_id, - span_id: expect.stringMatching(/[a-f\d]{16}/), - start_timestamp: expect.any(Number), - end_timestamp: expect.any(Number), - trace_id: pageloadSpan?.trace_id, - status: 'ok', - attributes: expect.objectContaining({ - 'http.request.redirect_start': expect.any(Object), - 'http.request.redirect_end': expect.any(Object), - 'http.request.worker_start': expect.any(Object), - 'http.request.fetch_start': expect.any(Object), - 'http.request.domain_lookup_start': expect.any(Object), - 'http.request.domain_lookup_end': expect.any(Object), - 'http.request.connect_start': expect.any(Object), - 'http.request.secure_connection_start': expect.any(Object), - 'http.request.connection_end': expect.any(Object), - 'http.request.request_start': expect.any(Object), - 'http.request.response_start': expect.any(Object), - 'http.request.response_end': expect.any(Object), - 'http.request.time_to_first_byte': expect.any(Object), - 'network.protocol.version': expect.any(Object), - }), - }), - ); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js index 5e521633ad12..575e3fa65693 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts index 6894d7407349..bb80bf6b8efb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts @@ -1,59 +1,113 @@ +import { PAGELOAD, HTTP_CLIENT } from '@sentry/conventions/op'; +import { + URL_FULL, + HTTP_REQUEST_REDIRECT_START, + HTTP_REQUEST_REDIRECT_END, + HTTP_REQUEST_WORKER_START, + HTTP_REQUEST_FETCH_START, + HTTP_REQUEST_DOMAIN_LOOKUP_START, + HTTP_REQUEST_DOMAIN_LOOKUP_END, + HTTP_REQUEST_CONNECT_START, + HTTP_REQUEST_SECURE_CONNECTION_START, + HTTP_REQUEST_CONNECTION_END, + HTTP_REQUEST_REQUEST_START, + HTTP_REQUEST_RESPONSE_START, + HTTP_REQUEST_RESPONSE_END, + HTTP_REQUEST_TIME_TO_FIRST_BYTE, + NETWORK_PROTOCOL_VERSION, +} from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('creates fetch spans with http timing', async ({ browserName, getLocalTestUrl, page }) => { - const supportedBrowsers = ['chromium', 'firefox']; - - if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) { - sentryTest.skip(); - } - await page.route('http://sentry-test-site.example/*', async route => { - const request = route.request(); - const postData = await request.postDataJSON(); - - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify(Object.assign({ id: 1 }, postData)), +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; + +sentryTest( + 'adds http timing to http.client spans in span streaming mode', + async ({ browserName, getLocalTestUrl, page }) => { + const supportedBrowsers = ['chromium', 'firefox']; + + sentryTest.skip(shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)); + + await page.route('http://sentry-test-site.example/*', async route => { + const request = route.request(); + const postData = await request.postDataJSON(); + + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(Object.assign({ id: 1 }, postData)), + }); }); - }); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); - const tracingEvent = envelopes[envelopes.length - 1]; // last envelope contains tracing data on all browsers - - const requestSpans = tracingEvent.spans?.filter(({ op }) => op === 'http.client'); - - expect(requestSpans).toHaveLength(3); - - await page.pause(); - requestSpans?.forEach((span, index) => - expect(span).toMatchObject({ - description: `GET http://sentry-test-site.example/${index}`, - parent_span_id: tracingEvent.contexts?.trace?.span_id, - span_id: expect.stringMatching(/[a-f\d]{16}/), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - trace_id: tracingEvent.contexts?.trace?.trace_id, - data: expect.objectContaining({ - 'http.request.redirect_start': expect.any(Number), - 'http.request.redirect_end': expect.any(Number), - 'http.request.worker_start': expect.any(Number), - 'http.request.fetch_start': expect.any(Number), - 'http.request.domain_lookup_start': expect.any(Number), - 'http.request.domain_lookup_end': expect.any(Number), - 'http.request.connect_start': expect.any(Number), - 'http.request.secure_connection_start': expect.any(Number), - 'http.request.connection_end': expect.any(Number), - 'http.request.request_start': expect.any(Number), - 'http.request.response_start': expect.any(Number), - 'http.request.response_end': expect.any(Number), - 'http.request.time_to_first_byte': expect.any(Number), - 'network.protocol.version': expect.any(String), + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const spans = collectStreamedSpans(page); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === PAGELOAD); + await page.goto(url); + + const pageloadSpan = await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const requestSpans = spans + .filter(s => getSpanOp(s) === HTTP_CLIENT) + .sort((a, b) => String(a.attributes[URL_FULL]?.value).localeCompare(String(b.attributes[URL_FULL]?.value))); + + expect(pageloadSpan).toBeDefined(); + expect(requestSpans).toHaveLength(3); + + requestSpans?.forEach((span, index) => + expect(span).toMatchObject({ + name: 'GET sentry-test-site.example', + parent_span_id: pageloadSpan?.span_id, + span_id: expect.stringMatching(/[a-f\d]{16}/), + start_timestamp: expect.any(Number), + end_timestamp: expect.any(Number), + trace_id: pageloadSpan?.trace_id, + status: 'ok', + attributes: expect.objectContaining({ + [URL_FULL]: { type: 'string', value: `http://sentry-test-site.example/${index}` }, + [HTTP_REQUEST_REDIRECT_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_REDIRECT_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_WORKER_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_FETCH_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_DOMAIN_LOOKUP_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_DOMAIN_LOOKUP_END]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_CONNECT_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_SECURE_CONNECTION_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_CONNECTION_END]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_REQUEST_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_RESPONSE_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_RESPONSE_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_TIME_TO_FIRST_BYTE]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [NETWORK_PROTOCOL_VERSION]: { type: 'string', value: expect.any(String) }, + }), }), - }), - ); -}); + ); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/init.js index bb6d4918d1bf..83076460599f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/init.js similarity index 93% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/init.js index 63afee65329a..04a299421004 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/init.js @@ -3,13 +3,13 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ linkPreviousTrace: 'in-memory', consistentTraceSampling: true, }), - Sentry.spanStreamingIntegration(), ], tracePropagationTargets: ['sentry-test-external.io'], tracesSampler: ctx => { diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/test.ts new file mode 100644 index 000000000000..4d13371bc693 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/default/test.ts @@ -0,0 +1,149 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { extractTraceparentData, parseBaggageHeader, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; +import { sentryTest } from '../../../../../../utils/fixtures'; +import { + eventAndTraceHeaderRequestParser, + shouldSkipTracingTest, + waitForTracingHeadersOnUrl, + waitForTransactionRequest, +} from '../../../../../../utils/helpers'; + +sentryTest.describe('When `consistentTraceSampling` is `true`', () => { + sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => { + return evt.contexts?.trace?.op === 'pageload'; + }); + await page.goto(url); + + const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); + const pageloadSampleRand = Number(res[1]?.sample_rand); + const pageloadTraceContext = res[0].contexts?.trace; + + expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); + expect(pageloadSampleRand).toBeGreaterThanOrEqual(0); + + return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand }; + }); + + const customTraceContext = await sentryTest.step('Custom trace', async () => { + const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + await page.locator('#btn1').click(); + const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser( + await customTrace1RequestPromise, + ); + + const customTraceContext = customTrace1Event.contexts?.trace; + + expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + // although we "continue the trace" from pageload, this is actually a root span, + // so there must not be a parent span id + expect(customTraceContext?.parent_span_id).toBeUndefined(); + + expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand)); + + return customTraceContext; + }); + + await sentryTest.step('Navigation', async () => { + const navigation1RequestPromise = waitForTransactionRequest( + page, + evt => evt.contexts?.trace?.op === 'navigation', + ); + await page.goto(`${url}#foo`); + const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser( + await navigation1RequestPromise, + ); + const navTraceContext = navigationEvent.contexts?.trace; + + expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id); + expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + + expect(navTraceContext?.links).toEqual([ + { + trace_id: customTraceContext?.trace_id, + span_id: customTraceContext?.span_id, + sampled: true, + attributes: { + [SENTRY_LINK_TYPE]: 'previous_trace', + }, + }, + ]); + expect(navTraceContext?.parent_span_id).toBeUndefined(); + + expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand)); + }); + }); + + sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + await page.goto(url); + + const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); + const pageloadSampleRand = Number(res[1]?.sample_rand); + + expect(pageloadSampleRand).toBeGreaterThanOrEqual(0); + + const pageloadTraceContext = res[0].contexts?.trace; + + expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); + + return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand }; + }); + + await sentryTest.step('Make fetch request', async () => { + const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'http://sentry-test-external.io'); + + await page.locator('#btn2').click(); + + const { baggage, sentryTrace } = await tracingHeadersPromise; + + const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise); + + const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand); + const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace; + const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client'); + + expect(fetchTraceSampleRand).toEqual(pageloadSampleRand); + + expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual( + pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE], + ); + expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + + expect(sentryTrace).toBeDefined(); + expect(baggage).toBeDefined(); + + expect(extractTraceparentData(sentryTrace)).toEqual({ + traceId: fetchTraceTraceContext?.trace_id, + parentSpanId: httpClientSpan?.span_id, + parentSampled: true, + }); + + expect(parseBaggageHeader(baggage)).toEqual({ + 'sentry-environment': 'production', + 'sentry-public_key': 'public', + 'sentry-sample_rand': `${pageloadSampleRand}`, + 'sentry-sample_rate': '1', + 'sentry-sampled': 'true', + 'sentry-trace_id': fetchTraceTraceContext?.trace_id, + 'sentry-transaction': 'custom root span 2', + }); + }); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/init.js similarity index 85% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/init.js index d570ac45144c..05c4ec8db85a 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/init.js @@ -3,15 +3,14 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ linkPreviousTrace: 'in-memory', consistentTraceSampling: true, }), - Sentry.spanStreamingIntegration(), ], - traceLifecycle: 'stream', tracePropagationTargets: ['sentry-test-external.io'], tracesSampleRate: 1, debug: true, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/test.ts similarity index 72% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/test.ts index efd4ccce4e37..d8d76a903cf3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-negative/test.ts @@ -1,15 +1,16 @@ import { expect } from '@playwright/test'; -import type { ClientReport, SerializedStreamedSpan } from '@sentry/core'; +import type { ClientReport } from '@sentry/core'; import { extractTraceparentData, parseBaggageHeader } from '@sentry/core'; import { sentryTest } from '../../../../../../utils/fixtures'; import { envelopeRequestParser, + envelopeUrlRegex, + getEnvelopeType, hidePage, shouldSkipTracingTest, waitForClientReportRequest, waitForTracingHeadersOnUrl, } from '../../../../../../utils/helpers'; -import { observeStreamedSpan } from '../../../../../../utils/spanUtils'; const metaTagSampleRand = 0.9; const metaTagSampleRate = 0.2; @@ -19,31 +20,38 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains sentryTest( 'Continues negative sampling decision from meta tag across all traces and downstream propagations', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } const url = await getLocalTestUrl({ testDir: __dirname }); - const spansReceived: SerializedStreamedSpan[] = []; - observeStreamedSpan(page, span => { - spansReceived.push(span); - return false; + let txnsReceived = 0; + page.on('request', request => { + if (envelopeUrlRegex.test(request.url()) && getEnvelopeType(request) === 'transaction') { + ++txnsReceived; + } }); - const clientReportPromise = waitForClientReportRequest(page); + const clientReportPromise = waitForClientReportRequest(page, report => + report.discarded_events.some( + event => event.category === 'transaction' && event.reason === 'sample_rate' && event.quantity === 4, + ), + ); await sentryTest.step('Initial pageload', async () => { await page.goto(url); - expect(spansReceived).toHaveLength(0); + expect(txnsReceived).toEqual(0); }); await sentryTest.step('Custom instrumented button click', async () => { await page.locator('#btn1').click(); - expect(spansReceived).toHaveLength(0); + expect(txnsReceived).toEqual(0); }); await sentryTest.step('Navigation', async () => { await page.goto(`${url}#foo`); - expect(spansReceived).toHaveLength(0); + expect(txnsReceived).toEqual(0); }); await sentryTest.step('Make fetch request', async () => { @@ -70,8 +78,6 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains 'sentry-trace_id': expect.not.stringContaining(metaTagTraceId), 'sentry-transaction': 'custom root span 2', }); - - expect(spansReceived).toHaveLength(0); }); await sentryTest.step('Client report', async () => { @@ -80,6 +86,11 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains expect(clientReport).toEqual({ timestamp: expect.any(Number), discarded_events: [ + { + category: 'transaction', + quantity: 4, + reason: 'sample_rate', + }, { category: 'span', quantity: expect.any(Number), @@ -87,11 +98,12 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains }, ], }); - // exact number depends on performance observer emissions - expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(10); }); - expect(spansReceived).toHaveLength(0); + await sentryTest.step('No transactions sent after processing completes', async () => { + expect(await page.evaluate(() => (window as any).Sentry.flush())).toBe(true); + expect(txnsReceived).toEqual(0); + }); }, ); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/init.js similarity index 92% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/init.js index 177fe4c4aeaf..fbf897d47eab 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/init.js @@ -3,13 +3,13 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ linkPreviousTrace: 'session-storage', consistentTraceSampling: true, }), - Sentry.spanStreamingIntegration(), ], tracePropagationTargets: ['sentry-test-external.io'], tracesSampler: ({ inheritOrSampleWith }) => { diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/page-1.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/page-1.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/page-1.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/page-1.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/page-2.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/page-2.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/page-2.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/page-2.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/test.ts similarity index 65% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/test.ts index cd64596f9bfd..65ad74b214f6 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta-precedence/test.ts @@ -4,12 +4,13 @@ import { extractTraceparentData, parseBaggageHeader } from '@sentry/core'; import { sentryTest } from '../../../../../../utils/fixtures'; import { envelopeRequestParser, + eventAndTraceHeaderRequestParser, hidePage, shouldSkipTracingTest, waitForClientReportRequest, waitForTracingHeadersOnUrl, + waitForTransactionRequest, } from '../../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpanEnvelope } from '../../../../../../utils/spanUtils'; const metaTagSampleRand = 0.9; const metaTagSampleRate = 0.2; @@ -20,14 +21,16 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains sentryTest( 'meta tag decision has precedence over sampling decision from previous trace in session storage', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } const url = await getLocalTestUrl({ testDir: __dirname }); const clientReportPromise = waitForClientReportRequest(page); await sentryTest.step('Initial pageload', async () => { - // negative sampling decision -> no pageload span + // negative sampling decision -> no pageload txn await page.goto(url); }); @@ -67,6 +70,11 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains expect(clientReport).toEqual({ timestamp: expect.any(Number), discarded_events: [ + { + category: 'transaction', + quantity: 2, + reason: 'sample_rate', + }, { category: 'span', quantity: expect.any(Number), @@ -74,43 +82,31 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains }, ], }); - // exact number depends on performance observer emissions - expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(3); }); await sentryTest.step('Navigate to another page with meta tags', async () => { - const page1PageloadEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload' && s.trace_id === metaTagTraceIdPage1), - ); + const page1Pageload = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); await page.locator('a').click(); - const envelope = await page1PageloadEnvelopePromise; - const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; + const [pageloadEvent, pageloadTraceHeader] = eventAndTraceHeaderRequestParser(await page1Pageload); + const pageloadTraceContext = pageloadEvent.contexts?.trace; - expect(Number(envelope[0].trace?.sample_rand)).toBe(0.12); - expect(Number(envelope[0].trace?.sample_rate)).toBe(0.2); - expect(pageloadSpan.trace_id).toEqual(metaTagTraceIdPage1); + expect(Number(pageloadTraceHeader?.sample_rand)).toBe(0.12); + expect(Number(pageloadTraceHeader?.sample_rate)).toBe(0.2); + expect(pageloadTraceContext?.trace_id).toEqual(metaTagTraceIdPage1); }); await sentryTest.step('Navigate to another page without meta tags', async () => { - const page2PageloadEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => - !!env[1][0][1].items.find( - s => - getSpanOp(s) === 'pageload' && s.trace_id !== metaTagTraceIdPage1 && s.trace_id !== metaTagTraceIdIndex, - ), - ); + const page2Pageload = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); await page.locator('a').click(); - const envelope = await page2PageloadEnvelopePromise; - const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; + const [pageloadEvent, pageloadTraceHeader] = eventAndTraceHeaderRequestParser(await page2Pageload); + const pageloadTraceContext = pageloadEvent.contexts?.trace; - expect(Number(envelope[0].trace?.sample_rand)).toBe(0.12); - expect(Number(envelope[0].trace?.sample_rate)).toBe(0.2); - expect(pageloadSpan.trace_id).not.toEqual(metaTagTraceIdPage1); - expect(pageloadSpan.trace_id).not.toEqual(metaTagTraceIdIndex); + expect(Number(pageloadTraceHeader?.sample_rand)).toBe(0.12); + expect(Number(pageloadTraceHeader?.sample_rate)).toBe(0.2); + expect(pageloadTraceContext?.trace_id).not.toEqual(metaTagTraceIdPage1); + expect(pageloadTraceContext?.trace_id).not.toEqual(metaTagTraceIdIndex); }); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/init.js similarity index 92% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/init.js index a1ddc5465950..61e2b2daa9ba 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/init.js @@ -3,13 +3,13 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ linkPreviousTrace: 'in-memory', consistentTraceSampling: true, }), - Sentry.spanStreamingIntegration(), ], tracePropagationTargets: ['sentry-test-external.io'], // only take into account sampling from meta tag; otherwise sample negatively diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/test.ts new file mode 100644 index 000000000000..ac2a865fb181 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/meta/test.ts @@ -0,0 +1,176 @@ +import { expect } from '@playwright/test'; +import { + extractTraceparentData, + parseBaggageHeader, + SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE, + SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, +} from '@sentry/core'; +import { sentryTest } from '../../../../../../utils/fixtures'; +import { + eventAndTraceHeaderRequestParser, + shouldSkipTracingTest, + waitForTracingHeadersOnUrl, + waitForTransactionRequest, +} from '../../../../../../utils/helpers'; + +const metaTagSampleRand = 0.051121; +const metaTagSampleRate = 0.2; + +sentryTest.describe('When `consistentTraceSampling` is `true` and page contains tags', () => { + sentryTest('Continues sampling decision across all traces from meta tag', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + + await page.goto(url); + + const [pageloadEvent, pageloadTraceHeader] = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); + const pageloadTraceContext = pageloadEvent.contexts?.trace; + + expect(Number(pageloadTraceHeader?.sample_rand)).toBe(metaTagSampleRand); + expect(Number(pageloadTraceHeader?.sample_rate)).toBe(metaTagSampleRate); + + // since the local sample rate was not applied, the sample rate attribute shouldn't be set + expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); + + return pageloadTraceContext; + }); + + const customTraceContext = await sentryTest.step('Custom trace', async () => { + const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + + await page.locator('#btn1').click(); + + const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser( + await customTrace1RequestPromise, + ); + + const customTraceContext = customTrace1Event.contexts?.trace; + + expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(customTraceContext?.parent_span_id).toBeUndefined(); + + expect(Number(customTraceTraceHeader?.sample_rand)).toBe(metaTagSampleRand); + expect(Number(customTraceTraceHeader?.sample_rate)).toBe(metaTagSampleRate); + expect(Boolean(customTraceTraceHeader?.sampled)).toBe(true); + + // since the local sample rate was not applied, the sample rate attribute shouldn't be set + expect(customTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + + // but we need to set this attribute to still be able to correctly add the sample rate to the DSC (checked above in trace header) + expect(customTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBe(metaTagSampleRate); + + return customTraceContext; + }); + + await sentryTest.step('Navigation', async () => { + const navigation1RequestPromise = waitForTransactionRequest( + page, + evt => evt.contexts?.trace?.op === 'navigation', + ); + + await page.goto(`${url}#foo`); + + const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser( + await navigation1RequestPromise, + ); + + const navigationTraceContext = navigationEvent.contexts?.trace; + + expect(navigationTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(navigationTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id); + + expect(navigationTraceContext?.parent_span_id).toBeUndefined(); + + expect(Number(navigationTraceHeader?.sample_rand)).toEqual(metaTagSampleRand); + expect(Number(navigationTraceHeader?.sample_rate)).toEqual(metaTagSampleRate); + expect(Boolean(navigationTraceHeader?.sampled)).toEqual(true); + + // since the local sample rate was not applied, the sample rate attribute shouldn't be set + expect(navigationTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + + // but we need to set this attribute to still be able to correctly add the sample rate to the DSC (checked above in trace header) + expect(navigationTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBe( + metaTagSampleRate, + ); + }); + }); + + sentryTest( + 'Propagates continued tag sampling decision to outgoing requests', + async ({ page, getLocalTestUrl }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + + await page.goto(url); + + const [pageloadEvent, pageloadTraceHeader] = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); + const pageloadTraceContext = pageloadEvent.contexts?.trace; + + expect(Number(pageloadTraceHeader?.sample_rand)).toBe(metaTagSampleRand); + expect(Number(pageloadTraceHeader?.sample_rate)).toBe(metaTagSampleRate); + + // since the local sample rate was not applied, the sample rate attribute shouldn't be set + expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); + + return pageloadTraceContext; + }); + + await sentryTest.step('Make fetch request', async () => { + const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'http://sentry-test-external.io'); + + await page.locator('#btn2').click(); + + const { baggage, sentryTrace } = await tracingHeadersPromise; + + const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise); + + const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand); + const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace; + const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client'); + + expect(fetchTraceSampleRand).toEqual(metaTagSampleRand); + + expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBe( + metaTagSampleRate, + ); + + expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + + expect(sentryTrace).toBeDefined(); + expect(baggage).toBeDefined(); + + expect(extractTraceparentData(sentryTrace)).toEqual({ + traceId: fetchTraceTraceContext?.trace_id, + parentSpanId: httpClientSpan?.span_id, + parentSampled: true, + }); + + expect(parseBaggageHeader(baggage)).toEqual({ + 'sentry-environment': 'production', + 'sentry-public_key': 'public', + 'sentry-sample_rand': `${metaTagSampleRand}`, + 'sentry-sample_rate': `${metaTagSampleRate}`, + 'sentry-sampled': 'true', + 'sentry-trace_id': fetchTraceTraceContext?.trace_id, + 'sentry-transaction': 'custom root span 2', + }); + }); + }, + ); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/init.js similarity index 94% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/init.js index 623db0ecc028..e2efc43e50f2 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/init.js @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ @@ -10,7 +11,6 @@ Sentry.init({ consistentTraceSampling: true, enableInp: false, }), - Sentry.spanStreamingIntegration(), ], tracePropagationTargets: ['sentry-test-external.io'], tracesSampler: ctx => { diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/test.ts similarity index 50% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/test.ts index 37e3b204fe88..247bd05503f8 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/consistent-sampling/tracesSampler-precedence/test.ts @@ -1,3 +1,4 @@ +import { PAGELOAD, NAVIGATION } from '@sentry/conventions/op'; import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; @@ -5,11 +6,12 @@ import type { ClientReport } from '@sentry/core'; import { sentryTest } from '../../../../../../utils/fixtures'; import { envelopeRequestParser, + eventAndTraceHeaderRequestParser, hidePage, shouldSkipTracingTest, waitForClientReportRequest, + waitForTransactionRequest, } from '../../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpanEnvelope } from '../../../../../../utils/spanUtils'; /** * This test demonstrates that: @@ -18,24 +20,26 @@ import { getSpanOp, waitForStreamedSpanEnvelope } from '../../../../../../utils/ */ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { sentryTest('explicit sampling decisions in `tracesSampler` have precedence', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } const url = await getLocalTestUrl({ testDir: __dirname }); - const { pageloadSpan } = await sentryTest.step('Initial pageload', async () => { - const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), - ); + const { pageloadTraceContext } = await sentryTest.step('Initial pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => { + return evt.contexts?.trace?.op === PAGELOAD; + }); await page.goto(url); - const envelope = await pageloadEnvelopePromise; - const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; + const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); + const pageloadSampleRand = Number(res[1]?.sample_rand); + const pageloadTraceContext = res[0].contexts?.trace; - expect(pageloadSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); - expect(Number(envelope[0].trace?.sample_rand)).toBeGreaterThanOrEqual(0); + expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); + expect(pageloadSampleRand).toBeGreaterThanOrEqual(0); - return { pageloadSpan }; + return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand }; }); await sentryTest.step('Custom trace is sampled negatively (explicitly in tracesSampler)', async () => { @@ -43,7 +47,6 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { await page.locator('#btn1').click(); - await page.waitForTimeout(500); await hidePage(page); const clientReport = envelopeRequestParser(await clientReportPromise); @@ -52,10 +55,15 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { timestamp: expect.any(Number), discarded_events: [ { - category: 'span', + category: 'transaction', quantity: 1, reason: 'sample_rate', }, + { + category: 'span', + quantity: expect.any(Number), + reason: 'sample_rate', + }, ], }); }); @@ -65,8 +73,6 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { await page.goto(`${url}#foo`); - await page.waitForTimeout(500); - await hidePage(page); const clientReport = envelopeRequestParser(await clientReportPromise); @@ -75,75 +81,66 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { timestamp: expect.any(Number), discarded_events: [ { - category: 'span', + category: 'transaction', quantity: 1, reason: 'sample_rate', }, + { + category: 'span', + quantity: expect.any(Number), + reason: 'sample_rate', + }, ], }); }); - const { customTrace2Span } = await sentryTest.step( + const { customTrace2Context } = await sentryTest.step( 'Custom trace 2 is sampled positively (explicitly in tracesSampler)', async () => { - const customEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), - ); + const customTrace2RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); await page.locator('#btn2').click(); - const envelope = await customEnvelopePromise; - const customTrace2Span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'custom')!; + const [customTrace2Event] = eventAndTraceHeaderRequestParser(await customTrace2RequestPromise); - expect(customTrace2Span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); - expect(customTrace2Span.trace_id).not.toEqual(pageloadSpan.trace_id); - expect(customTrace2Span.parent_span_id).toBeUndefined(); + const customTrace2Context = customTrace2Event.contexts?.trace; - expect(customTrace2Span.links).toEqual([ + expect(customTrace2Context?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); + expect(customTrace2Context?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(customTrace2Context?.parent_span_id).toBeUndefined(); + + expect(customTrace2Context?.links).toEqual([ { - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, + attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, sampled: false, span_id: expect.stringMatching(/^[\da-f]{16}$/), trace_id: expect.stringMatching(/^[\da-f]{32}$/), }, ]); - return { customTrace2Span }; + return { customTrace2Context }; }, ); await sentryTest.step('Navigation trace is sampled positively (inherited from previous trace)', async () => { - const navigationEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => env[0].trace?.sampled === 'true' && !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), - ); + const navigationRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === NAVIGATION); await page.goto(`${url}#bar`); - const envelope = await navigationEnvelopePromise; - const navigationSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; + const [navigationEvent] = eventAndTraceHeaderRequestParser(await navigationRequestPromise); + + const navigationTraceContext = navigationEvent.contexts?.trace; - expect(navigationSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); - expect(navigationSpan.trace_id).not.toEqual(customTrace2Span.trace_id); - expect(navigationSpan.parent_span_id).toBeUndefined(); + expect(navigationTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); + expect(navigationTraceContext?.trace_id).not.toEqual(customTrace2Context?.trace_id); + expect(navigationTraceContext?.parent_span_id).toBeUndefined(); - expect(navigationSpan.links).toEqual([ + expect(navigationTraceContext?.links).toEqual([ { - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, + attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, sampled: true, - span_id: customTrace2Span.span_id, - trace_id: customTrace2Span.trace_id, + span_id: customTrace2Context?.span_id, + trace_id: customTrace2Context?.trace_id, }, ]); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/init.js similarity index 66% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/init.js index 749560a5c459..bdd43f5ac757 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/init.js @@ -3,8 +3,9 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/custom-trace/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/custom-trace/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/custom-trace/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/custom-trace/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/test.ts new file mode 100644 index 000000000000..c4637dc25018 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/custom-trace/test.ts @@ -0,0 +1,62 @@ +import { expect } from '@playwright/test'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; + +sentryTest('manually started custom traces are linked correctly in the chain', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + await page.goto(url); + const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); + return pageloadRequest.contexts?.trace; + }); + + const customTrace1Context = await sentryTest.step('Custom trace', async () => { + const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + await page.locator('#btn1').click(); + const customTrace1Event = envelopeRequestParser(await customTrace1RequestPromise); + + const customTraceCtx = customTrace1Event.contexts?.trace; + + expect(customTraceCtx?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(customTraceCtx?.links).toEqual([ + { + trace_id: pageloadTraceContext?.trace_id, + span_id: pageloadTraceContext?.span_id, + sampled: true, + attributes: { + [SENTRY_LINK_TYPE]: 'previous_trace', + }, + }, + ]); + + return customTraceCtx; + }); + + await sentryTest.step('Navigation', async () => { + const navigation1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + await page.goto(`${url}#foo`); + const navigationEvent = envelopeRequestParser(await navigation1RequestPromise); + const navTraceContext = navigationEvent.contexts?.trace; + + expect(navTraceContext?.trace_id).not.toEqual(customTrace1Context?.trace_id); + expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + + expect(navTraceContext?.links).toEqual([ + { + trace_id: customTrace1Context?.trace_id, + span_id: customTrace1Context?.span_id, + sampled: true, + attributes: { + [SENTRY_LINK_TYPE]: 'previous_trace', + }, + }, + ]); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/default/init.js new file mode 100644 index 000000000000..bdd43f5ac757 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/default/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, + debug: true, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/default/test.ts new file mode 100644 index 000000000000..7399c2bb26c3 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/default/test.ts @@ -0,0 +1,99 @@ +import { expect } from '@playwright/test'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; + +sentryTest("navigation spans link back to previous trace's root span", async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + await page.goto(url); + const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); + return pageloadRequest.contexts?.trace; + }); + + const navigation1TraceContext = await sentryTest.step('First navigation', async () => { + const navigation1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + await page.goto(`${url}#foo`); + const navigation1Request = envelopeRequestParser(await navigation1RequestPromise); + return navigation1Request.contexts?.trace; + }); + + const navigation2TraceContext = await sentryTest.step('Second navigation', async () => { + const navigation2RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + await page.goto(`${url}#bar`); + const navigation2Request = envelopeRequestParser(await navigation2RequestPromise); + return navigation2Request.contexts?.trace; + }); + + const pageloadTraceId = pageloadTraceContext?.trace_id; + const navigation1TraceId = navigation1TraceContext?.trace_id; + const navigation2TraceId = navigation2TraceContext?.trace_id; + + expect(pageloadTraceContext?.links).toBeUndefined(); + + expect(navigation1TraceContext?.links).toEqual([ + { + trace_id: pageloadTraceId, + span_id: pageloadTraceContext?.span_id, + sampled: true, + attributes: { + [SENTRY_LINK_TYPE]: 'previous_trace', + }, + }, + ]); + + expect(navigation1TraceContext?.data).toMatchObject({ + 'sentry.previous_trace': `${pageloadTraceId}-${pageloadTraceContext?.span_id}-1`, + }); + + expect(navigation2TraceContext?.links).toEqual([ + { + trace_id: navigation1TraceId, + span_id: navigation1TraceContext?.span_id, + sampled: true, + attributes: { + [SENTRY_LINK_TYPE]: 'previous_trace', + }, + }, + ]); + + expect(navigation2TraceContext?.data).toMatchObject({ + 'sentry.previous_trace': `${navigation1TraceId}-${navigation1TraceContext?.span_id}-1`, + }); + + expect(pageloadTraceId).not.toEqual(navigation1TraceId); + expect(navigation1TraceId).not.toEqual(navigation2TraceId); + expect(pageloadTraceId).not.toEqual(navigation2TraceId); +}); + +sentryTest("doesn't link between hard page reloads by default", async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await sentryTest.step('First pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + await page.goto(url); + const pageload1Event = envelopeRequestParser(await pageloadRequestPromise); + + expect(pageload1Event.contexts?.trace).toBeDefined(); + expect(pageload1Event.contexts?.trace?.links).toBeUndefined(); + }); + + await sentryTest.step('Second pageload', async () => { + const pageload2RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + await page.reload(); + const pageload2Event = envelopeRequestParser(await pageload2RequestPromise); + + expect(pageload2Event.contexts?.trace).toBeDefined(); + expect(pageload2Event.contexts?.trace?.links).toBeUndefined(); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/interaction-spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/interaction-spans/init.js similarity index 84% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/interaction-spans/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/interaction-spans/init.js index 812e8cb92dc2..2c3e9264d882 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/interaction-spans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/interaction-spans/init.js @@ -4,7 +4,8 @@ import { interactionsIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, - integrations: [Sentry.browserTracingIntegration(), interactionsIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration(), interactionsIntegration()], }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/interaction-spans/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/interaction-spans/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/interaction-spans/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/interaction-spans/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/interaction-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/interaction-spans/test.ts new file mode 100644 index 000000000000..24a1e2c59072 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/interaction-spans/test.ts @@ -0,0 +1,89 @@ +import { expect } from '@playwright/test'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; + +/* + This is quite peculiar behavior but it's a result of the route-based trace lifetime. + Once we shortened trace lifetime, this whole scenario will change as the interaction + spans will be their own trace. So most likely, we can replace this test with a new one + that covers the new default behavior. +*/ +sentryTest( + 'only the first root spans in the trace link back to the previous trace', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + await page.goto(url); + + const pageloadEvent = envelopeRequestParser(await pageloadRequestPromise); + const traceContext = pageloadEvent.contexts?.trace; + + expect(traceContext).toBeDefined(); + expect(traceContext?.links).toBeUndefined(); + + return traceContext; + }); + + await sentryTest.step('Click Before navigation', async () => { + const interactionRequestPromise = waitForTransactionRequest(page, evt => { + return evt.contexts?.trace?.op === 'ui.action.click'; + }); + await page.click('#btn'); + + const interactionEvent = envelopeRequestParser(await interactionRequestPromise); + const interactionTraceContext = interactionEvent.contexts?.trace; + + // sanity check: route-based trace lifetime means the trace_id should be the same + expect(interactionTraceContext?.trace_id).toBe(pageloadTraceContext?.trace_id); + + // no links yet as previous root span belonged to same trace + expect(interactionTraceContext?.links).toBeUndefined(); + }); + + const navigationTraceContext = await sentryTest.step('Navigation', async () => { + const navigationRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + await page.goto(`${url}#foo`); + const navigationEvent = envelopeRequestParser(await navigationRequestPromise); + + const traceContext = navigationEvent.contexts?.trace; + + expect(traceContext?.op).toBe('navigation'); + expect(traceContext?.links).toEqual([ + { + trace_id: pageloadTraceContext?.trace_id, + span_id: pageloadTraceContext?.span_id, + sampled: true, + attributes: { + [SENTRY_LINK_TYPE]: 'previous_trace', + }, + }, + ]); + + expect(traceContext?.trace_id).not.toEqual(traceContext?.links![0].trace_id); + return traceContext; + }); + + await sentryTest.step('Click After navigation', async () => { + const interactionRequestPromise = waitForTransactionRequest(page, evt => { + return evt.contexts?.trace?.op === 'ui.action.click'; + }); + await page.click('#btn'); + const interactionEvent = envelopeRequestParser(await interactionRequestPromise); + + const interactionTraceContext = interactionEvent.contexts?.trace; + + // sanity check: route-based trace lifetime means the trace_id should be the same + expect(interactionTraceContext?.trace_id).toBe(navigationTraceContext?.trace_id); + + // since this is the second root span in the trace, it doesn't link back to the previous trace + expect(interactionTraceContext?.links).toBeUndefined(); + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/meta/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/meta/init.js new file mode 100644 index 000000000000..bdd43f5ac757 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/meta/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, + debug: true, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/meta/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/meta/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/meta/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/meta/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/meta/test.ts new file mode 100644 index 000000000000..a232a5a0f6e7 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/meta/test.ts @@ -0,0 +1,54 @@ +import { expect } from '@playwright/test'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; + +sentryTest( + "links back to previous trace's local root span if continued from meta tags", + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const metaTagTraceId = '12345678901234567890123456789012'; + + const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + await page.goto(url); + const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); + + const traceContext = pageloadRequest.contexts?.trace; + + // sanity check + expect(traceContext?.trace_id).toBe(metaTagTraceId); + + expect(traceContext?.links).toBeUndefined(); + + return traceContext; + }); + + const navigationTraceContext = await sentryTest.step('Navigation', async () => { + const navigationRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + await page.goto(`${url}#foo`); + const navigationRequest = envelopeRequestParser(await navigationRequestPromise); + return navigationRequest.contexts?.trace; + }); + + const navigationTraceId = navigationTraceContext?.trace_id; + + expect(navigationTraceContext?.links).toEqual([ + { + trace_id: metaTagTraceId, + span_id: pageloadTraceContext?.span_id, + sampled: true, + attributes: { + [SENTRY_LINK_TYPE]: 'previous_trace', + }, + }, + ]); + + expect(navigationTraceId).not.toEqual(metaTagTraceId); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/negatively-sampled/init.js similarity index 88% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/negatively-sampled/init.js index 778092cf026b..79ca4d68e8ba 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/negatively-sampled/init.js @@ -3,9 +3,10 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', // We want to ignore redirects for this test - integrations: [Sentry.browserTracingIntegration({ detectRedirects: false }), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration({ detectRedirects: false })], tracesSampler: ctx => { if (ctx.attributes['sentry.origin'] === 'auto.pageload.browser') { return 0; diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/negatively-sampled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/negatively-sampled/test.ts new file mode 100644 index 000000000000..894fe0d5929a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/negatively-sampled/test.ts @@ -0,0 +1,42 @@ +import { expect } from '@playwright/test'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; + +sentryTest('includes a span link to a previously negatively sampled span', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await sentryTest.step('Initial pageload', async () => { + // No event to check for an event here because this pageload span is sampled negatively! + await page.goto(url); + }); + + await sentryTest.step('Navigation', async () => { + const navigationRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + await page.goto(`${url}#foo`); + const navigationEvent = envelopeRequestParser(await navigationRequestPromise); + const navigationTraceContext = navigationEvent.contexts?.trace; + + expect(navigationTraceContext?.op).toBe('navigation'); + expect(navigationTraceContext?.links).toEqual([ + { + trace_id: expect.stringMatching(/[a-f\d]{32}/), + span_id: expect.stringMatching(/[a-f\d]{16}/), + sampled: false, + attributes: { + [SENTRY_LINK_TYPE]: 'previous_trace', + }, + }, + ]); + + expect(navigationTraceContext?.data).toMatchObject({ + 'sentry.previous_trace': expect.stringMatching(/[a-f\d]{32}-[a-f\d]{16}-0/), + }); + + expect(navigationTraceContext?.trace_id).not.toEqual(navigationTraceContext?.links![0].trace_id); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/session-storage/init.js similarity index 56% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/session-storage/init.js index 749560a5c459..9b88e489ae07 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/session-storage/init.js @@ -3,8 +3,8 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration({ linkPreviousTrace: 'session-storage' })], tracesSampleRate: 1, - debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/session-storage/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/session-storage/test.ts new file mode 100644 index 000000000000..f7e0d288de5b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-static/session-storage/test.ts @@ -0,0 +1,39 @@ +import { expect } from '@playwright/test'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; + +sentryTest('adds link between hard page reloads when opting into sessionStorage', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageload1TraceContext = await sentryTest.step('First pageload', async () => { + const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + await page.goto(url); + const pageload1Event = envelopeRequestParser(await pageloadRequestPromise); + const pageload1TraceContext = pageload1Event.contexts?.trace; + expect(pageload1TraceContext).toBeDefined(); + expect(pageload1TraceContext?.links).toBeUndefined(); + return pageload1TraceContext; + }); + + const pageload2Event = await sentryTest.step('Hard page reload', async () => { + const pageload2RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + await page.reload(); + return envelopeRequestParser(await pageload2RequestPromise); + }); + + expect(pageload2Event.contexts?.trace?.links).toEqual([ + { + trace_id: pageload1TraceContext?.trace_id, + span_id: pageload1TraceContext?.span_id, + sampled: true, + attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, + }, + ]); + + expect(pageload1TraceContext?.trace_id).not.toEqual(pageload2Event.contexts?.trace?.trace_id); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/test.ts deleted file mode 100644 index 3ef7ddefca6d..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; -import { expect } from '@playwright/test'; -import { extractTraceparentData, parseBaggageHeader } from '@sentry/core'; -import { sentryTest } from '../../../../../../utils/fixtures'; -import { shouldSkipTracingTest, waitForTracingHeadersOnUrl } from '../../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpanEnvelope } from '../../../../../../utils/spanUtils'; - -sentryTest.describe('When `consistentTraceSampling` is `true`', () => { - sentryTest('continues sampling decision from initial pageload span', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const { pageloadSpan, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => { - const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), - ); - await page.goto(url); - - const envelope = await pageloadEnvelopePromise; - const pageloadSampleRand = Number(envelope[0].trace?.sample_rand); - const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - - expect(pageloadSpan.attributes['sentry.sample_rate']?.value).toBe(1); - expect(Number.isNaN(pageloadSampleRand)).toBe(false); - expect(pageloadSampleRand).toBeGreaterThanOrEqual(0); - expect(pageloadSampleRand).toBeLessThanOrEqual(1); - - return { pageloadSpan, pageloadSampleRand }; - }); - - const customTraceSpan = await sentryTest.step('Custom trace', async () => { - const customEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), - ); - await page.locator('#btn1').click(); - const envelope = await customEnvelopePromise; - const span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'custom')!; - - expect(span.trace_id).not.toEqual(pageloadSpan.trace_id); - // although we "continue the trace" from pageload, this is actually a root span, - // so there must not be a parent span id - expect(span.parent_span_id).toBeUndefined(); - - expect(Number(envelope[0].trace?.sample_rand)).toBe(pageloadSampleRand); - - return span; - }); - - await sentryTest.step('Navigation', async () => { - const navigationEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), - ); - await page.goto(`${url}#foo`); - const envelope = await navigationEnvelopePromise; - const navSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; - - expect(navSpan.trace_id).not.toEqual(customTraceSpan.trace_id); - expect(navSpan.trace_id).not.toEqual(pageloadSpan.trace_id); - - expect(navSpan.links).toEqual([ - { - trace_id: customTraceSpan.trace_id, - span_id: customTraceSpan.span_id, - sampled: true, - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, - }, - ]); - expect(navSpan.parent_span_id).toBeUndefined(); - - expect(Number(envelope[0].trace?.sample_rand)).toBe(pageloadSampleRand); - }); - }); - - sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const { pageloadSpan, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => { - const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), - ); - await page.goto(url); - - const envelope = await pageloadEnvelopePromise; - const pageloadSampleRand = Number(envelope[0].trace?.sample_rand); - - expect(Number(envelope[0].trace?.sample_rand)).toBe(pageloadSampleRand); - expect(pageloadSampleRand).toBeGreaterThanOrEqual(0); - expect(pageloadSampleRand).toBeLessThanOrEqual(1); - expect(Number.isNaN(pageloadSampleRand)).toBe(false); - - const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - - expect(pageloadSpan.attributes['sentry.sample_rate']?.value).toBe(1); - - return { pageloadSpan, pageloadSampleRand }; - }); - - await sentryTest.step('Make fetch request', async () => { - const fetchEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), - ); - const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'http://sentry-test-external.io'); - - await page.locator('#btn2').click(); - - const { baggage, sentryTrace } = await tracingHeadersPromise; - const fetchEnvelope = await fetchEnvelopePromise; - - const fetchTraceSampleRand = Number(fetchEnvelope[0].trace?.sample_rand); - const fetchTraceSpans = fetchEnvelope[1][0][1].items; - const fetchTraceSpan = fetchTraceSpans.find(s => getSpanOp(s) === 'custom')!; - const httpClientSpan = fetchTraceSpans.find(s => getSpanOp(s) === 'http.client'); - - expect(fetchTraceSampleRand).toBe(pageloadSampleRand); - - expect(fetchTraceSpan.attributes['sentry.sample_rate']?.value).toEqual( - pageloadSpan.attributes['sentry.sample_rate']?.value, - ); - expect(fetchTraceSpan.trace_id).not.toEqual(pageloadSpan.trace_id); - - expect(sentryTrace).toBeDefined(); - expect(baggage).toBeDefined(); - - expect(extractTraceparentData(sentryTrace)).toEqual({ - traceId: fetchTraceSpan.trace_id, - parentSpanId: httpClientSpan?.span_id, - parentSampled: true, - }); - - expect(parseBaggageHeader(baggage)).toEqual({ - 'sentry-environment': 'production', - 'sentry-public_key': 'public', - 'sentry-sample_rand': `${pageloadSampleRand}`, - 'sentry-sample_rate': '1', - 'sentry-sampled': 'true', - 'sentry-trace_id': fetchTraceSpan.trace_id, - 'sentry-transaction': 'custom root span 2', - }); - }); - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/test.ts deleted file mode 100644 index 2657bed50d61..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/test.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { expect } from '@playwright/test'; -import { - extractTraceparentData, - parseBaggageHeader, - SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE, -} from '@sentry/core'; -import { sentryTest } from '../../../../../../utils/fixtures'; -import { shouldSkipTracingTest, waitForTracingHeadersOnUrl } from '../../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpanEnvelope } from '../../../../../../utils/spanUtils'; - -const metaTagSampleRand = 0.051121; -const metaTagSampleRate = 0.2; - -sentryTest.describe('When `consistentTraceSampling` is `true` and page contains tags', () => { - sentryTest('Continues sampling decision across all traces from meta tag', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadSpan = await sentryTest.step('Initial pageload', async () => { - const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), - ); - - await page.goto(url); - - const envelope = await pageloadEnvelopePromise; - const span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - - expect(Number(envelope[0].trace?.sample_rand)).toBe(metaTagSampleRand); - expect(Number(envelope[0].trace?.sample_rate)).toBe(metaTagSampleRate); - - // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(span.attributes['sentry.sample_rate']).toBeUndefined(); - expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); - - return span; - }); - - const customTraceSpan = await sentryTest.step('Custom trace', async () => { - const customEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), - ); - - await page.locator('#btn1').click(); - - const envelope = await customEnvelopePromise; - const span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'custom')!; - - expect(span.trace_id).not.toEqual(pageloadSpan.trace_id); - expect(span.parent_span_id).toBeUndefined(); - - expect(Number(envelope[0].trace?.sample_rand)).toBe(metaTagSampleRand); - expect(Number(envelope[0].trace?.sample_rate)).toBe(metaTagSampleRate); - expect(envelope[0].trace?.sampled).toBe('true'); - - // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(span.attributes['sentry.sample_rate']).toBeUndefined(); - - // but we need to set this attribute to still be able to correctly add the sample rate to the DSC (checked above in trace header) - expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe(metaTagSampleRate); - - return span; - }); - - await sentryTest.step('Navigation', async () => { - const navigationEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), - ); - - await page.goto(`${url}#foo`); - - const envelope = await navigationEnvelopePromise; - const navSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; - - expect(navSpan.trace_id).not.toEqual(pageloadSpan.trace_id); - expect(navSpan.trace_id).not.toEqual(customTraceSpan.trace_id); - - expect(navSpan.parent_span_id).toBeUndefined(); - - expect(Number(envelope[0].trace?.sample_rand)).toEqual(metaTagSampleRand); - expect(Number(envelope[0].trace?.sample_rate)).toEqual(metaTagSampleRate); - expect(envelope[0].trace?.sampled).toEqual('true'); - - // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(navSpan.attributes['sentry.sample_rate']).toBeUndefined(); - - // but we need to set this attribute to still be able to correctly add the sample rate to the DSC (checked above in trace header) - expect(navSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe(metaTagSampleRate); - }); - }); - - sentryTest( - 'Propagates continued tag sampling decision to outgoing requests', - async ({ page, getLocalTestUrl }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadSpan = await sentryTest.step('Initial pageload', async () => { - const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), - ); - - await page.goto(url); - - const envelope = await pageloadEnvelopePromise; - const span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - - expect(Number(envelope[0].trace?.sample_rand)).toBe(metaTagSampleRand); - expect(Number(envelope[0].trace?.sample_rate)).toBe(metaTagSampleRate); - - // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(span.attributes['sentry.sample_rate']).toBeUndefined(); - expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); - - return span; - }); - - await sentryTest.step('Make fetch request', async () => { - const fetchEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), - ); - const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'http://sentry-test-external.io'); - - await page.locator('#btn2').click(); - - const { baggage, sentryTrace } = await tracingHeadersPromise; - const fetchEnvelope = await fetchEnvelopePromise; - - const fetchTraceSampleRand = Number(fetchEnvelope[0].trace?.sample_rand); - const fetchTraceSpans = fetchEnvelope[1][0][1].items; - const fetchTraceSpan = fetchTraceSpans.find(s => getSpanOp(s) === 'custom')!; - const httpClientSpan = fetchTraceSpans.find(s => getSpanOp(s) === 'http.client'); - - expect(fetchTraceSampleRand).toEqual(metaTagSampleRand); - - expect(fetchTraceSpan.attributes['sentry.sample_rate']).toBeUndefined(); - expect(fetchTraceSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe( - metaTagSampleRate, - ); - - expect(fetchTraceSpan.trace_id).not.toEqual(pageloadSpan.trace_id); - - expect(sentryTrace).toBeDefined(); - expect(baggage).toBeDefined(); - - expect(extractTraceparentData(sentryTrace)).toEqual({ - traceId: fetchTraceSpan.trace_id, - parentSpanId: httpClientSpan?.span_id, - parentSampled: true, - }); - - expect(parseBaggageHeader(baggage)).toEqual({ - 'sentry-environment': 'production', - 'sentry-public_key': 'public', - 'sentry-sample_rand': `${metaTagSampleRand}`, - 'sentry-sample_rate': `${metaTagSampleRate}`, - 'sentry-sampled': 'true', - 'sentry-trace_id': fetchTraceSpan.trace_id, - 'sentry-transaction': 'custom root span 2', - }); - }); - }, - ); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/custom-trace/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/custom-trace/test.ts deleted file mode 100644 index 7bb7a5546910..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/custom-trace/test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { expect } from '@playwright/test'; -import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; - -sentryTest('manually started custom traces are linked correctly in the chain', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadSpan = await sentryTest.step('Initial pageload', async () => { - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - return pageloadSpanPromise; - }); - - const customTraceSpan = await sentryTest.step('Custom trace', async () => { - const customSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'custom'); - await page.locator('#btn1').click(); - const span = await customSpanPromise; - - expect(span.trace_id).not.toEqual(pageloadSpan.trace_id); - expect(span.links).toEqual([ - { - trace_id: pageloadSpan.trace_id, - span_id: pageloadSpan.span_id, - sampled: true, - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, - }, - ]); - - return span; - }); - - await sentryTest.step('Navigation', async () => { - const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - await page.goto(`${url}#foo`); - const navSpan = await navigationSpanPromise; - - expect(navSpan.trace_id).not.toEqual(customTraceSpan.trace_id); - expect(navSpan.trace_id).not.toEqual(pageloadSpan.trace_id); - - expect(navSpan.links).toEqual([ - { - trace_id: customTraceSpan.trace_id, - span_id: customTraceSpan.span_id, - sampled: true, - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, - }, - ]); - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/default/test.ts deleted file mode 100644 index 5fb19844a3ce..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/default/test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { expect } from '@playwright/test'; -import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; - -sentryTest("navigation spans link back to previous trace's root span", async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - const pageloadSpan = await pageloadSpanPromise; - - const navigation1SpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - await page.goto(`${url}#foo`); - const navigation1Span = await navigation1SpanPromise; - - const navigation2SpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - await page.goto(`${url}#bar`); - const navigation2Span = await navigation2SpanPromise; - - const pageloadTraceId = pageloadSpan.trace_id; - const navigation1TraceId = navigation1Span.trace_id; - const navigation2TraceId = navigation2Span.trace_id; - - expect(pageloadSpan.links).toBeUndefined(); - - expect(navigation1Span.links).toEqual([ - { - trace_id: pageloadTraceId, - span_id: pageloadSpan.span_id, - sampled: true, - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, - }, - ]); - - expect(navigation1Span.attributes['sentry.previous_trace']).toEqual({ - type: 'string', - value: `${pageloadTraceId}-${pageloadSpan.span_id}-1`, - }); - - expect(navigation2Span.links).toEqual([ - { - trace_id: navigation1TraceId, - span_id: navigation1Span.span_id, - sampled: true, - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, - }, - ]); - - expect(navigation2Span.attributes['sentry.previous_trace']).toEqual({ - type: 'string', - value: `${navigation1TraceId}-${navigation1Span.span_id}-1`, - }); - - expect(pageloadTraceId).not.toEqual(navigation1TraceId); - expect(navigation1TraceId).not.toEqual(navigation2TraceId); - expect(pageloadTraceId).not.toEqual(navigation2TraceId); -}); - -sentryTest("doesn't link between hard page reloads by default", async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await sentryTest.step('First pageload', async () => { - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - const pageload1Span = await pageloadSpanPromise; - - expect(pageload1Span).toBeDefined(); - expect(pageload1Span.links).toBeUndefined(); - }); - - await sentryTest.step('Second pageload', async () => { - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.reload(); - const pageload2Span = await pageloadSpanPromise; - - expect(pageload2Span).toBeDefined(); - expect(pageload2Span.links).toBeUndefined(); - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/interaction-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/interaction-spans/test.ts deleted file mode 100644 index bfc974fc953a..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/interaction-spans/test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { expect } from '@playwright/test'; -import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; - -/* - This is quite peculiar behavior but it's a result of the route-based trace lifetime. - Once we shortened trace lifetime, this whole scenario will change as the interaction - spans will be their own trace. So most likely, we can replace this test with a new one - that covers the new default behavior. -*/ -sentryTest( - 'only the first root spans in the trace link back to the previous trace', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadSpan = await sentryTest.step('Initial pageload', async () => { - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - const span = await pageloadSpanPromise; - - expect(span).toBeDefined(); - expect(span.links).toBeUndefined(); - - return span; - }); - - await sentryTest.step('Click Before navigation', async () => { - const interactionSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.action.click'); - await page.click('#btn'); - const interactionSpan = await interactionSpanPromise; - - // sanity check: route-based trace lifetime means the trace_id should be the same - expect(interactionSpan.trace_id).toBe(pageloadSpan.trace_id); - - // no links yet as previous root span belonged to same trace - expect(interactionSpan.links).toBeUndefined(); - }); - - const navigationSpan = await sentryTest.step('Navigation', async () => { - const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - await page.goto(`${url}#foo`); - const span = await navigationSpanPromise; - - expect(getSpanOp(span)).toBe('navigation'); - expect(span.links).toEqual([ - { - trace_id: pageloadSpan.trace_id, - span_id: pageloadSpan.span_id, - sampled: true, - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, - }, - ]); - - expect(span.trace_id).not.toEqual(span.links![0].trace_id); - return span; - }); - - await sentryTest.step('Click After navigation', async () => { - const interactionSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.action.click'); - await page.click('#btn'); - const interactionSpan = await interactionSpanPromise; - - // sanity check: route-based trace lifetime means the trace_id should be the same - expect(interactionSpan.trace_id).toBe(navigationSpan.trace_id); - - // since this is the second root span in the trace, it doesn't link back to the previous trace - expect(interactionSpan.links).toBeUndefined(); - }); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/meta/test.ts deleted file mode 100644 index 732547954091..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/meta/test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { expect } from '@playwright/test'; -import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; - -sentryTest( - "links back to previous trace's local root span if continued from meta tags", - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const metaTagTraceId = '12345678901234567890123456789012'; - - const pageloadSpan = await sentryTest.step('Initial pageload', async () => { - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - const span = await pageloadSpanPromise; - - // sanity check - expect(span.trace_id).toBe(metaTagTraceId); - expect(span.links).toBeUndefined(); - - return span; - }); - - const navigationSpan = await sentryTest.step('Navigation', async () => { - const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - await page.goto(`${url}#foo`); - return navigationSpanPromise; - }); - - expect(navigationSpan.links).toEqual([ - { - trace_id: metaTagTraceId, - span_id: pageloadSpan.span_id, - sampled: true, - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, - }, - ]); - - expect(navigationSpan.trace_id).not.toEqual(metaTagTraceId); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/test.ts deleted file mode 100644 index 4bc05cb95737..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { expect } from '@playwright/test'; -import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; - -sentryTest('includes a span link to a previously negatively sampled span', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await sentryTest.step('Initial pageload', async () => { - // No span envelope expected here because this pageload span is sampled negatively! - await page.goto(url); - }); - - await sentryTest.step('Navigation', async () => { - const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - await page.goto(`${url}#foo`); - const navigationSpan = await navigationSpanPromise; - - expect(getSpanOp(navigationSpan)).toBe('navigation'); - expect(navigationSpan.links).toEqual([ - { - trace_id: expect.stringMatching(/[a-f\d]{32}/), - span_id: expect.stringMatching(/[a-f\d]{16}/), - sampled: false, - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, - }, - ]); - - expect(navigationSpan.attributes['sentry.previous_trace']).toEqual({ - type: 'string', - value: expect.stringMatching(/[a-f\d]{32}-[a-f\d]{16}-0/), - }); - - expect(navigationSpan.trace_id).not.toEqual(navigationSpan.links![0].trace_id); - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/session-storage/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/session-storage/init.js deleted file mode 100644 index e51af56c2a9d..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/session-storage/init.js +++ /dev/null @@ -1,12 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - Sentry.browserTracingIntegration({ linkPreviousTrace: 'session-storage' }), - Sentry.spanStreamingIntegration(), - ], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/session-storage/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/session-storage/test.ts deleted file mode 100644 index 81d1d31e10e3..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/session-storage/test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect } from '@playwright/test'; -import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; - -sentryTest('adds link between hard page reloads when opting into sessionStorage', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageload1Span = await sentryTest.step('First pageload', async () => { - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - const span = await pageloadSpanPromise; - expect(span).toBeDefined(); - expect(span.links).toBeUndefined(); - return span; - }); - - const pageload2Span = await sentryTest.step('Hard page reload', async () => { - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.reload(); - return pageloadSpanPromise; - }); - - expect(pageload2Span.links).toEqual([ - { - trace_id: pageload1Span.trace_id, - span_id: pageload1Span.span_id, - sampled: true, - attributes: { - [SENTRY_LINK_TYPE]: { - type: 'string', - value: 'previous_trace', - }, - }, - }, - ]); - - expect(pageload1Span.trace_id).not.toEqual(pageload2Span.trace_id); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/default/init.js index 04a299421004..2497d6da6b84 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/default/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/default/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/default/test.ts index 4d13371bc693..28c0b0ea1796 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/default/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/default/test.ts @@ -1,136 +1,140 @@ import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import { extractTraceparentData, parseBaggageHeader, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, extractTraceparentData, parseBaggageHeader } from '@sentry/core'; import { sentryTest } from '../../../../../../utils/fixtures'; -import { - eventAndTraceHeaderRequestParser, - shouldSkipTracingTest, - waitForTracingHeadersOnUrl, - waitForTransactionRequest, -} from '../../../../../../utils/helpers'; +import { shouldSkipTracingTest, waitForTracingHeadersOnUrl } from '../../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpanEnvelope } from '../../../../../../utils/spanUtils'; sentryTest.describe('When `consistentTraceSampling` is `true`', () => { - sentryTest('Continues sampling decision from initial pageload', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest('continues sampling decision from initial pageload span', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => { - return evt.contexts?.trace?.op === 'pageload'; - }); + const { pageloadSpan, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => { + const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), + ); await page.goto(url); - const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); - const pageloadSampleRand = Number(res[1]?.sample_rand); - const pageloadTraceContext = res[0].contexts?.trace; + const envelope = await pageloadEnvelopePromise; + const pageloadSampleRand = Number(envelope[0].trace?.sample_rand); + const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); + expect(pageloadSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); + expect(Number.isNaN(pageloadSampleRand)).toBe(false); expect(pageloadSampleRand).toBeGreaterThanOrEqual(0); + expect(pageloadSampleRand).toBeLessThanOrEqual(1); - return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand }; + return { pageloadSpan, pageloadSampleRand }; }); - const customTraceContext = await sentryTest.step('Custom trace', async () => { - const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); - await page.locator('#btn1').click(); - const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser( - await customTrace1RequestPromise, + const customTraceSpan = await sentryTest.step('Custom trace', async () => { + const customEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), ); + await page.locator('#btn1').click(); + const envelope = await customEnvelopePromise; + const span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'custom')!; - const customTraceContext = customTrace1Event.contexts?.trace; - - expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(span.trace_id).not.toEqual(pageloadSpan.trace_id); // although we "continue the trace" from pageload, this is actually a root span, // so there must not be a parent span id - expect(customTraceContext?.parent_span_id).toBeUndefined(); + expect(span.parent_span_id).toBeUndefined(); - expect(pageloadSampleRand).toEqual(Number(customTraceTraceHeader?.sample_rand)); + expect(Number(envelope[0].trace?.sample_rand)).toBe(pageloadSampleRand); - return customTraceContext; + return span; }); await sentryTest.step('Navigation', async () => { - const navigation1RequestPromise = waitForTransactionRequest( + const navigationEnvelopePromise = waitForStreamedSpanEnvelope( page, - evt => evt.contexts?.trace?.op === 'navigation', + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), ); await page.goto(`${url}#foo`); - const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser( - await navigation1RequestPromise, - ); - const navTraceContext = navigationEvent.contexts?.trace; + const envelope = await navigationEnvelopePromise; + const navSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; - expect(navTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id); - expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(navSpan.trace_id).not.toEqual(customTraceSpan.trace_id); + expect(navSpan.trace_id).not.toEqual(pageloadSpan.trace_id); - expect(navTraceContext?.links).toEqual([ + expect(navSpan.links).toEqual([ { - trace_id: customTraceContext?.trace_id, - span_id: customTraceContext?.span_id, + trace_id: customTraceSpan.trace_id, + span_id: customTraceSpan.span_id, sampled: true, attributes: { - [SENTRY_LINK_TYPE]: 'previous_trace', + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, }, }, ]); - expect(navTraceContext?.parent_span_id).toBeUndefined(); + expect(navSpan.parent_span_id).toBeUndefined(); - expect(pageloadSampleRand).toEqual(Number(navigationTraceHeader?.sample_rand)); + expect(Number(envelope[0].trace?.sample_rand)).toBe(pageloadSampleRand); }); }); sentryTest('Propagates continued sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const { pageloadTraceContext, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const { pageloadSpan, pageloadSampleRand } = await sentryTest.step('Initial pageload', async () => { + const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), + ); await page.goto(url); - const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); - const pageloadSampleRand = Number(res[1]?.sample_rand); + const envelope = await pageloadEnvelopePromise; + const pageloadSampleRand = Number(envelope[0].trace?.sample_rand); expect(pageloadSampleRand).toBeGreaterThanOrEqual(0); + expect(pageloadSampleRand).toBeLessThanOrEqual(1); + expect(Number.isNaN(pageloadSampleRand)).toBe(false); - const pageloadTraceContext = res[0].contexts?.trace; + const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); + expect(pageloadSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); - return { pageloadTraceContext: pageloadTraceContext, pageloadSampleRand }; + return { pageloadSpan, pageloadSampleRand }; }); await sentryTest.step('Make fetch request', async () => { - const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + const fetchEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), + ); const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'http://sentry-test-external.io'); await page.locator('#btn2').click(); const { baggage, sentryTrace } = await tracingHeadersPromise; + const fetchEnvelope = await fetchEnvelopePromise; - const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise); - - const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand); - const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace; - const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client'); + const fetchTraceSampleRand = Number(fetchEnvelope[0].trace?.sample_rand); + const fetchTraceSpans = fetchEnvelope[1][0][1].items; + const fetchTraceSpan = fetchTraceSpans.find(s => getSpanOp(s) === 'custom')!; + const httpClientSpan = fetchTraceSpans.find(s => getSpanOp(s) === 'http.client'); - expect(fetchTraceSampleRand).toEqual(pageloadSampleRand); + expect(fetchTraceSampleRand).toBe(pageloadSampleRand); - expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual( - pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE], + expect(fetchTraceSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toEqual( + pageloadSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value, ); - expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(fetchTraceSpan.trace_id).not.toEqual(pageloadSpan.trace_id); expect(sentryTrace).toBeDefined(); expect(baggage).toBeDefined(); expect(extractTraceparentData(sentryTrace)).toEqual({ - traceId: fetchTraceTraceContext?.trace_id, + traceId: fetchTraceSpan.trace_id, parentSpanId: httpClientSpan?.span_id, parentSampled: true, }); @@ -141,7 +145,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { 'sentry-sample_rand': `${pageloadSampleRand}`, 'sentry-sample_rate': '1', 'sentry-sampled': 'true', - 'sentry-trace_id': fetchTraceTraceContext?.trace_id, + 'sentry-trace_id': fetchTraceSpan.trace_id, 'sentry-transaction': 'custom root span 2', }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-negative/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-negative/init.js index 05c4ec8db85a..4cb0d3389fc2 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-negative/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-negative/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-negative/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-negative/test.ts index c8faee2f5feb..34401725079c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-negative/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-negative/test.ts @@ -1,15 +1,15 @@ import { expect } from '@playwright/test'; -import type { ClientReport } from '@sentry/core'; +import type { ClientReport, SerializedStreamedSpan } from '@sentry/core'; import { extractTraceparentData, parseBaggageHeader } from '@sentry/core'; import { sentryTest } from '../../../../../../utils/fixtures'; import { envelopeRequestParser, - getMultipleSentryEnvelopeRequests, hidePage, shouldSkipTracingTest, waitForClientReportRequest, waitForTracingHeadersOnUrl, } from '../../../../../../utils/helpers'; +import { observeStreamedSpan } from '../../../../../../utils/spanUtils'; const metaTagSampleRand = 0.9; const metaTagSampleRate = 0.2; @@ -19,34 +19,31 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains sentryTest( 'Continues negative sampling decision from meta tag across all traces and downstream propagations', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - let txnsReceived = 0; - // @ts-expect-error - no need to return something valid here - getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'transaction' }, () => { - ++txnsReceived; - return {}; + const spansReceived: SerializedStreamedSpan[] = []; + observeStreamedSpan(page, span => { + spansReceived.push(span); + return false; }); const clientReportPromise = waitForClientReportRequest(page); await sentryTest.step('Initial pageload', async () => { await page.goto(url); - expect(txnsReceived).toEqual(0); + expect(spansReceived).toHaveLength(0); }); await sentryTest.step('Custom instrumented button click', async () => { await page.locator('#btn1').click(); - expect(txnsReceived).toEqual(0); + expect(spansReceived).toHaveLength(0); }); await sentryTest.step('Navigation', async () => { await page.goto(`${url}#foo`); - expect(txnsReceived).toEqual(0); + expect(spansReceived).toHaveLength(0); }); await sentryTest.step('Make fetch request', async () => { @@ -73,6 +70,8 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains 'sentry-trace_id': expect.not.stringContaining(metaTagTraceId), 'sentry-transaction': 'custom root span 2', }); + + expect(spansReceived).toHaveLength(0); }); await sentryTest.step('Client report', async () => { @@ -82,19 +81,18 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains timestamp: expect.any(Number), discarded_events: [ { - category: 'transaction', - quantity: 4, + category: 'span', + quantity: expect.any(Number), reason: 'sample_rate', }, ], }); + // exact number depends on performance observer emissions + expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(10); }); - await sentryTest.step('Wait for transactions to be discarded', async () => { - // give it a little longer just in case a txn is pending to be sent - await page.waitForTimeout(1000); - expect(txnsReceived).toEqual(0); - }); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(spansReceived).toHaveLength(0); }, ); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/init.js index fbf897d47eab..c758d9875be1 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/test.ts index 3dab9594ba7c..cd64596f9bfd 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta-precedence/test.ts @@ -4,13 +4,12 @@ import { extractTraceparentData, parseBaggageHeader } from '@sentry/core'; import { sentryTest } from '../../../../../../utils/fixtures'; import { envelopeRequestParser, - eventAndTraceHeaderRequestParser, hidePage, shouldSkipTracingTest, waitForClientReportRequest, waitForTracingHeadersOnUrl, - waitForTransactionRequest, } from '../../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpanEnvelope } from '../../../../../../utils/spanUtils'; const metaTagSampleRand = 0.9; const metaTagSampleRate = 0.2; @@ -21,16 +20,14 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains sentryTest( 'meta tag decision has precedence over sampling decision from previous trace in session storage', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); const clientReportPromise = waitForClientReportRequest(page); await sentryTest.step('Initial pageload', async () => { - // negative sampling decision -> no pageload txn + // negative sampling decision -> no pageload span await page.goto(url); }); @@ -71,37 +68,49 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains timestamp: expect.any(Number), discarded_events: [ { - category: 'transaction', - quantity: 2, + category: 'span', + quantity: expect.any(Number), reason: 'sample_rate', }, ], }); + // exact number depends on performance observer emissions + expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(3); }); await sentryTest.step('Navigate to another page with meta tags', async () => { - const page1Pageload = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const page1PageloadEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload' && s.trace_id === metaTagTraceIdPage1), + ); await page.locator('a').click(); - const [pageloadEvent, pageloadTraceHeader] = eventAndTraceHeaderRequestParser(await page1Pageload); - const pageloadTraceContext = pageloadEvent.contexts?.trace; + const envelope = await page1PageloadEnvelopePromise; + const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - expect(Number(pageloadTraceHeader?.sample_rand)).toBe(0.12); - expect(Number(pageloadTraceHeader?.sample_rate)).toBe(0.2); - expect(pageloadTraceContext?.trace_id).toEqual(metaTagTraceIdPage1); + expect(Number(envelope[0].trace?.sample_rand)).toBe(0.12); + expect(Number(envelope[0].trace?.sample_rate)).toBe(0.2); + expect(pageloadSpan.trace_id).toEqual(metaTagTraceIdPage1); }); await sentryTest.step('Navigate to another page without meta tags', async () => { - const page2Pageload = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const page2PageloadEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => + !!env[1][0][1].items.find( + s => + getSpanOp(s) === 'pageload' && s.trace_id !== metaTagTraceIdPage1 && s.trace_id !== metaTagTraceIdIndex, + ), + ); await page.locator('a').click(); - const [pageloadEvent, pageloadTraceHeader] = eventAndTraceHeaderRequestParser(await page2Pageload); - const pageloadTraceContext = pageloadEvent.contexts?.trace; + const envelope = await page2PageloadEnvelopePromise; + const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - expect(Number(pageloadTraceHeader?.sample_rand)).toBe(0.12); - expect(Number(pageloadTraceHeader?.sample_rate)).toBe(0.2); - expect(pageloadTraceContext?.trace_id).not.toEqual(metaTagTraceIdPage1); - expect(pageloadTraceContext?.trace_id).not.toEqual(metaTagTraceIdIndex); + expect(Number(envelope[0].trace?.sample_rand)).toBe(0.12); + expect(Number(envelope[0].trace?.sample_rate)).toBe(0.2); + expect(pageloadSpan.trace_id).not.toEqual(metaTagTraceIdPage1); + expect(pageloadSpan.trace_id).not.toEqual(metaTagTraceIdIndex); }); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta/init.js index 61e2b2daa9ba..9aa63000ef2e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta/test.ts index ac2a865fb181..bc2eb2118a09 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/meta/test.ts @@ -1,162 +1,158 @@ import { expect } from '@playwright/test'; import { + SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, extractTraceparentData, parseBaggageHeader, SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE, - SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, } from '@sentry/core'; import { sentryTest } from '../../../../../../utils/fixtures'; -import { - eventAndTraceHeaderRequestParser, - shouldSkipTracingTest, - waitForTracingHeadersOnUrl, - waitForTransactionRequest, -} from '../../../../../../utils/helpers'; +import { shouldSkipTracingTest, waitForTracingHeadersOnUrl } from '../../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpanEnvelope } from '../../../../../../utils/spanUtils'; const metaTagSampleRand = 0.051121; const metaTagSampleRate = 0.2; sentryTest.describe('When `consistentTraceSampling` is `true` and page contains tags', () => { sentryTest('Continues sampling decision across all traces from meta tag', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const pageloadSpan = await sentryTest.step('Initial pageload', async () => { + const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), + ); await page.goto(url); - const [pageloadEvent, pageloadTraceHeader] = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); - const pageloadTraceContext = pageloadEvent.contexts?.trace; + const envelope = await pageloadEnvelopePromise; + const span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - expect(Number(pageloadTraceHeader?.sample_rand)).toBe(metaTagSampleRand); - expect(Number(pageloadTraceHeader?.sample_rate)).toBe(metaTagSampleRate); + expect(Number(envelope[0].trace?.sample_rand)).toBe(metaTagSampleRand); + expect(Number(envelope[0].trace?.sample_rate)).toBe(metaTagSampleRate); // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); - expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); - return pageloadTraceContext; + return span; }); - const customTraceContext = await sentryTest.step('Custom trace', async () => { - const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + const customTraceSpan = await sentryTest.step('Custom trace', async () => { + const customEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), + ); await page.locator('#btn1').click(); - const [customTrace1Event, customTraceTraceHeader] = eventAndTraceHeaderRequestParser( - await customTrace1RequestPromise, - ); + const envelope = await customEnvelopePromise; + const span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'custom')!; - const customTraceContext = customTrace1Event.contexts?.trace; + expect(span.trace_id).not.toEqual(pageloadSpan.trace_id); + expect(span.parent_span_id).toBeUndefined(); - expect(customTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); - expect(customTraceContext?.parent_span_id).toBeUndefined(); - - expect(Number(customTraceTraceHeader?.sample_rand)).toBe(metaTagSampleRand); - expect(Number(customTraceTraceHeader?.sample_rate)).toBe(metaTagSampleRate); - expect(Boolean(customTraceTraceHeader?.sampled)).toBe(true); + expect(Number(envelope[0].trace?.sample_rand)).toBe(metaTagSampleRand); + expect(Number(envelope[0].trace?.sample_rate)).toBe(metaTagSampleRate); + expect(envelope[0].trace?.sampled).toBe('true'); // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(customTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); // but we need to set this attribute to still be able to correctly add the sample rate to the DSC (checked above in trace header) - expect(customTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBe(metaTagSampleRate); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe(metaTagSampleRate); - return customTraceContext; + return span; }); await sentryTest.step('Navigation', async () => { - const navigation1RequestPromise = waitForTransactionRequest( + const navigationEnvelopePromise = waitForStreamedSpanEnvelope( page, - evt => evt.contexts?.trace?.op === 'navigation', + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), ); await page.goto(`${url}#foo`); - const [navigationEvent, navigationTraceHeader] = eventAndTraceHeaderRequestParser( - await navigation1RequestPromise, - ); - - const navigationTraceContext = navigationEvent.contexts?.trace; + const envelope = await navigationEnvelopePromise; + const navSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; - expect(navigationTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); - expect(navigationTraceContext?.trace_id).not.toEqual(customTraceContext?.trace_id); + expect(navSpan.trace_id).not.toEqual(pageloadSpan.trace_id); + expect(navSpan.trace_id).not.toEqual(customTraceSpan.trace_id); - expect(navigationTraceContext?.parent_span_id).toBeUndefined(); + expect(navSpan.parent_span_id).toBeUndefined(); - expect(Number(navigationTraceHeader?.sample_rand)).toEqual(metaTagSampleRand); - expect(Number(navigationTraceHeader?.sample_rate)).toEqual(metaTagSampleRate); - expect(Boolean(navigationTraceHeader?.sampled)).toEqual(true); + expect(Number(envelope[0].trace?.sample_rand)).toEqual(metaTagSampleRand); + expect(Number(envelope[0].trace?.sample_rate)).toEqual(metaTagSampleRate); + expect(envelope[0].trace?.sampled).toEqual('true'); // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(navigationTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + expect(navSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); // but we need to set this attribute to still be able to correctly add the sample rate to the DSC (checked above in trace header) - expect(navigationTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBe( - metaTagSampleRate, - ); + expect(navSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe(metaTagSampleRate); }); }); sentryTest( 'Propagates continued tag sampling decision to outgoing requests', async ({ page, getLocalTestUrl }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const pageloadSpan = await sentryTest.step('Initial pageload', async () => { + const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), + ); await page.goto(url); - const [pageloadEvent, pageloadTraceHeader] = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); - const pageloadTraceContext = pageloadEvent.contexts?.trace; + const envelope = await pageloadEnvelopePromise; + const span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - expect(Number(pageloadTraceHeader?.sample_rand)).toBe(metaTagSampleRand); - expect(Number(pageloadTraceHeader?.sample_rate)).toBe(metaTagSampleRate); + expect(Number(envelope[0].trace?.sample_rand)).toBe(metaTagSampleRand); + expect(Number(envelope[0].trace?.sample_rate)).toBe(metaTagSampleRate); // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); - expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); - return pageloadTraceContext; + return span; }); await sentryTest.step('Make fetch request', async () => { - const fetchTracePromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + const fetchEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), + ); const tracingHeadersPromise = waitForTracingHeadersOnUrl(page, 'http://sentry-test-external.io'); await page.locator('#btn2').click(); const { baggage, sentryTrace } = await tracingHeadersPromise; + const fetchEnvelope = await fetchEnvelopePromise; - const [fetchTraceEvent, fetchTraceTraceHeader] = eventAndTraceHeaderRequestParser(await fetchTracePromise); - - const fetchTraceSampleRand = Number(fetchTraceTraceHeader?.sample_rand); - const fetchTraceTraceContext = fetchTraceEvent.contexts?.trace; - const httpClientSpan = fetchTraceEvent.spans?.find(span => span.op === 'http.client'); + const fetchTraceSampleRand = Number(fetchEnvelope[0].trace?.sample_rand); + const fetchTraceSpans = fetchEnvelope[1][0][1].items; + const fetchTraceSpan = fetchTraceSpans.find(s => getSpanOp(s) === 'custom')!; + const httpClientSpan = fetchTraceSpans.find(s => getSpanOp(s) === 'http.client'); expect(fetchTraceSampleRand).toEqual(metaTagSampleRand); - expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); - expect(fetchTraceTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBe( + expect(fetchTraceSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBeUndefined(); + expect(fetchTraceSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe( metaTagSampleRate, ); - expect(fetchTraceTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(fetchTraceSpan.trace_id).not.toEqual(pageloadSpan.trace_id); expect(sentryTrace).toBeDefined(); expect(baggage).toBeDefined(); expect(extractTraceparentData(sentryTrace)).toEqual({ - traceId: fetchTraceTraceContext?.trace_id, + traceId: fetchTraceSpan.trace_id, parentSpanId: httpClientSpan?.span_id, parentSampled: true, }); @@ -167,7 +163,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains 'sentry-sample_rand': `${metaTagSampleRand}`, 'sentry-sample_rate': `${metaTagSampleRate}`, 'sentry-sampled': 'true', - 'sentry-trace_id': fetchTraceTraceContext?.trace_id, + 'sentry-trace_id': fetchTraceSpan.trace_id, 'sentry-transaction': 'custom root span 2', }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/tracesSampler-precedence/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/tracesSampler-precedence/init.js index e2efc43e50f2..e99cf357559a 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/tracesSampler-precedence/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/tracesSampler-precedence/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/tracesSampler-precedence/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/tracesSampler-precedence/test.ts index 2bb196c898fd..801f0a51d3bf 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/tracesSampler-precedence/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/consistent-sampling/tracesSampler-precedence/test.ts @@ -1,15 +1,17 @@ +import { PAGELOAD, NAVIGATION } from '@sentry/conventions/op'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; import type { ClientReport } from '@sentry/core'; + import { sentryTest } from '../../../../../../utils/fixtures'; import { envelopeRequestParser, - eventAndTraceHeaderRequestParser, hidePage, shouldSkipTracingTest, waitForClientReportRequest, - waitForTransactionRequest, } from '../../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpanEnvelope } from '../../../../../../utils/spanUtils'; /** * This test demonstrates that: @@ -18,26 +20,24 @@ import { */ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { sentryTest('explicit sampling decisions in `tracesSampler` have precedence', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const { pageloadTraceContext } = await sentryTest.step('Initial pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => { - return evt.contexts?.trace?.op === 'pageload'; - }); + const { pageloadSpan } = await sentryTest.step('Initial pageload', async () => { + const pageloadEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === PAGELOAD), + ); await page.goto(url); - const res = eventAndTraceHeaderRequestParser(await pageloadRequestPromise); - const pageloadSampleRand = Number(res[1]?.sample_rand); - const pageloadTraceContext = res[0].contexts?.trace; + const envelope = await pageloadEnvelopePromise; + const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === PAGELOAD)!; - expect(pageloadTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); - expect(pageloadSampleRand).toBeGreaterThanOrEqual(0); + expect(pageloadSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); + expect(Number(envelope[0].trace?.sample_rand)).toBeGreaterThanOrEqual(0); - return { pageloadTraceContext: res[0].contexts?.trace, pageloadSampleRand }; + return { pageloadSpan }; }); await sentryTest.step('Custom trace is sampled negatively (explicitly in tracesSampler)', async () => { @@ -45,7 +45,6 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { await page.locator('#btn1').click(); - await page.waitForTimeout(500); await hidePage(page); const clientReport = envelopeRequestParser(await clientReportPromise); @@ -54,12 +53,13 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { timestamp: expect.any(Number), discarded_events: [ { - category: 'transaction', - quantity: 1, + category: 'span', + quantity: expect.any(Number), reason: 'sample_rate', }, ], }); + expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(1); }); await sentryTest.step('Subsequent navigation trace is also sampled negatively', async () => { @@ -67,8 +67,6 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { await page.goto(`${url}#foo`); - await page.waitForTimeout(500); - await hidePage(page); const clientReport = envelopeRequestParser(await clientReportPromise); @@ -77,61 +75,76 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { timestamp: expect.any(Number), discarded_events: [ { - category: 'transaction', - quantity: 1, + category: 'span', + quantity: expect.any(Number), reason: 'sample_rate', }, ], }); + expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(1); }); - const { customTrace2Context } = await sentryTest.step( + const { customTrace2Span } = await sentryTest.step( 'Custom trace 2 is sampled positively (explicitly in tracesSampler)', async () => { - const customTrace2RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + const customEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'custom'), + ); await page.locator('#btn2').click(); - const [customTrace2Event] = eventAndTraceHeaderRequestParser(await customTrace2RequestPromise); + const envelope = await customEnvelopePromise; + const customTrace2Span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'custom')!; - const customTrace2Context = customTrace2Event.contexts?.trace; + expect(customTrace2Span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); + expect(customTrace2Span.trace_id).not.toEqual(pageloadSpan.trace_id); + expect(customTrace2Span.parent_span_id).toBeUndefined(); - expect(customTrace2Context?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); - expect(customTrace2Context?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); - expect(customTrace2Context?.parent_span_id).toBeUndefined(); - - expect(customTrace2Context?.links).toEqual([ + expect(customTrace2Span.links).toEqual([ { - attributes: { 'sentry.link.type': 'previous_trace' }, + attributes: { + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, + }, sampled: false, span_id: expect.stringMatching(/^[\da-f]{16}$/), trace_id: expect.stringMatching(/^[\da-f]{32}$/), }, ]); - return { customTrace2Context }; + return { customTrace2Span }; }, ); await sentryTest.step('Navigation trace is sampled positively (inherited from previous trace)', async () => { - const navigationRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + const navigationEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => env[0].trace?.sampled === 'true' && !!env[1][0][1].items.find(s => getSpanOp(s) === NAVIGATION), + ); await page.goto(`${url}#bar`); - const [navigationEvent] = eventAndTraceHeaderRequestParser(await navigationRequestPromise); - - const navigationTraceContext = navigationEvent.contexts?.trace; + const envelope = await navigationEnvelopePromise; + const navigationSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === NAVIGATION)!; - expect(navigationTraceContext?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toBe(1); - expect(navigationTraceContext?.trace_id).not.toEqual(customTrace2Context?.trace_id); - expect(navigationTraceContext?.parent_span_id).toBeUndefined(); + expect(navigationSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); + expect(navigationSpan.trace_id).not.toEqual(customTrace2Span.trace_id); + expect(navigationSpan.parent_span_id).toBeUndefined(); - expect(navigationTraceContext?.links).toEqual([ + expect(navigationSpan.links).toEqual([ { - attributes: { 'sentry.link.type': 'previous_trace' }, + attributes: { + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, + }, sampled: true, - span_id: customTrace2Context?.span_id, - trace_id: customTrace2Context?.trace_id, + span_id: customTrace2Span.span_id, + trace_id: customTrace2Span.trace_id, }, ]); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/custom-trace/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/custom-trace/test.ts index c4637dc25018..bdbdd1c78a90 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/custom-trace/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/custom-trace/test.ts @@ -1,60 +1,62 @@ -import { expect } from '@playwright/test'; import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; + import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest('manually started custom traces are linked correctly in the chain', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const pageloadSpan = await sentryTest.step('Initial pageload', async () => { + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.goto(url); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); - return pageloadRequest.contexts?.trace; + return pageloadSpanPromise; }); - const customTrace1Context = await sentryTest.step('Custom trace', async () => { - const customTrace1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'custom'); + const customTraceSpan = await sentryTest.step('Custom trace', async () => { + const customSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'custom'); await page.locator('#btn1').click(); - const customTrace1Event = envelopeRequestParser(await customTrace1RequestPromise); - - const customTraceCtx = customTrace1Event.contexts?.trace; + const span = await customSpanPromise; - expect(customTraceCtx?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); - expect(customTraceCtx?.links).toEqual([ + expect(span.trace_id).not.toEqual(pageloadSpan.trace_id); + expect(span.links).toEqual([ { - trace_id: pageloadTraceContext?.trace_id, - span_id: pageloadTraceContext?.span_id, + trace_id: pageloadSpan.trace_id, + span_id: pageloadSpan.span_id, sampled: true, attributes: { - [SENTRY_LINK_TYPE]: 'previous_trace', + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, }, }, ]); - return customTraceCtx; + return span; }); await sentryTest.step('Navigation', async () => { - const navigation1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); await page.goto(`${url}#foo`); - const navigationEvent = envelopeRequestParser(await navigation1RequestPromise); - const navTraceContext = navigationEvent.contexts?.trace; + const navSpan = await navigationSpanPromise; - expect(navTraceContext?.trace_id).not.toEqual(customTrace1Context?.trace_id); - expect(navTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(navSpan.trace_id).not.toEqual(customTraceSpan.trace_id); + expect(navSpan.trace_id).not.toEqual(pageloadSpan.trace_id); - expect(navTraceContext?.links).toEqual([ + expect(navSpan.links).toEqual([ { - trace_id: customTrace1Context?.trace_id, - span_id: customTrace1Context?.span_id, + trace_id: customTraceSpan.trace_id, + span_id: customTraceSpan.span_id, sampled: true, attributes: { - [SENTRY_LINK_TYPE]: 'previous_trace', + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, }, }, ]); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/default/test.ts index 7399c2bb26c3..ca42ae5e32a0 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/default/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/default/test.ts @@ -1,70 +1,69 @@ -import { expect } from '@playwright/test'; import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; + import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest("navigation spans link back to previous trace's root span", async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); - await page.goto(url); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); - return pageloadRequest.contexts?.trace; - }); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + const pageloadSpan = await pageloadSpanPromise; - const navigation1TraceContext = await sentryTest.step('First navigation', async () => { - const navigation1RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); - await page.goto(`${url}#foo`); - const navigation1Request = envelopeRequestParser(await navigation1RequestPromise); - return navigation1Request.contexts?.trace; - }); + const navigation1SpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); + await page.goto(`${url}#foo`); + const navigation1Span = await navigation1SpanPromise; - const navigation2TraceContext = await sentryTest.step('Second navigation', async () => { - const navigation2RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); - await page.goto(`${url}#bar`); - const navigation2Request = envelopeRequestParser(await navigation2RequestPromise); - return navigation2Request.contexts?.trace; - }); + const navigation2SpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); + await page.goto(`${url}#bar`); + const navigation2Span = await navigation2SpanPromise; - const pageloadTraceId = pageloadTraceContext?.trace_id; - const navigation1TraceId = navigation1TraceContext?.trace_id; - const navigation2TraceId = navigation2TraceContext?.trace_id; + const pageloadTraceId = pageloadSpan.trace_id; + const navigation1TraceId = navigation1Span.trace_id; + const navigation2TraceId = navigation2Span.trace_id; - expect(pageloadTraceContext?.links).toBeUndefined(); + expect(pageloadSpan.links).toBeUndefined(); - expect(navigation1TraceContext?.links).toEqual([ + expect(navigation1Span.links).toEqual([ { trace_id: pageloadTraceId, - span_id: pageloadTraceContext?.span_id, + span_id: pageloadSpan.span_id, sampled: true, attributes: { - [SENTRY_LINK_TYPE]: 'previous_trace', + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, }, }, ]); - expect(navigation1TraceContext?.data).toMatchObject({ - 'sentry.previous_trace': `${pageloadTraceId}-${pageloadTraceContext?.span_id}-1`, + expect(navigation1Span.attributes['sentry.previous_trace']).toEqual({ + type: 'string', + value: `${pageloadTraceId}-${pageloadSpan.span_id}-1`, }); - expect(navigation2TraceContext?.links).toEqual([ + expect(navigation2Span.links).toEqual([ { trace_id: navigation1TraceId, - span_id: navigation1TraceContext?.span_id, + span_id: navigation1Span.span_id, sampled: true, attributes: { - [SENTRY_LINK_TYPE]: 'previous_trace', + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, }, }, ]); - expect(navigation2TraceContext?.data).toMatchObject({ - 'sentry.previous_trace': `${navigation1TraceId}-${navigation1TraceContext?.span_id}-1`, + expect(navigation2Span.attributes['sentry.previous_trace']).toEqual({ + type: 'string', + value: `${navigation1TraceId}-${navigation1Span.span_id}-1`, }); expect(pageloadTraceId).not.toEqual(navigation1TraceId); @@ -73,27 +72,25 @@ sentryTest("navigation spans link back to previous trace's root span", async ({ }); sentryTest("doesn't link between hard page reloads by default", async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); await sentryTest.step('First pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.goto(url); - const pageload1Event = envelopeRequestParser(await pageloadRequestPromise); + const pageload1Span = await pageloadSpanPromise; - expect(pageload1Event.contexts?.trace).toBeDefined(); - expect(pageload1Event.contexts?.trace?.links).toBeUndefined(); + expect(pageload1Span).toBeDefined(); + expect(pageload1Span.links).toBeUndefined(); }); await sentryTest.step('Second pageload', async () => { - const pageload2RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.reload(); - const pageload2Event = envelopeRequestParser(await pageload2RequestPromise); + const pageload2Span = await pageloadSpanPromise; - expect(pageload2Event.contexts?.trace).toBeDefined(); - expect(pageload2Event.contexts?.trace?.links).toBeUndefined(); + expect(pageload2Span).toBeDefined(); + expect(pageload2Span.links).toBeUndefined(); }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/init.js index bdd43f5ac757..1ce4125ee422 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/interaction-spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/interaction-spans/init.js index 2c3e9264d882..96386e1d38d5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/interaction-spans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/interaction-spans/init.js @@ -4,7 +4,6 @@ import { interactionsIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, integrations: [Sentry.browserTracingIntegration(), interactionsIntegration()], diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/interaction-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/interaction-spans/test.ts index 24a1e2c59072..67e216567856 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/interaction-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/interaction-spans/test.ts @@ -1,7 +1,9 @@ -import { expect } from '@playwright/test'; import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; + import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; /* This is quite peculiar behavior but it's a result of the route-based trace lifetime. @@ -12,78 +14,67 @@ import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest sentryTest( 'only the first root spans in the trace link back to the previous trace', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const pageloadSpan = await sentryTest.step('Initial pageload', async () => { + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.goto(url); + const span = await pageloadSpanPromise; - const pageloadEvent = envelopeRequestParser(await pageloadRequestPromise); - const traceContext = pageloadEvent.contexts?.trace; - - expect(traceContext).toBeDefined(); - expect(traceContext?.links).toBeUndefined(); + expect(span).toBeDefined(); + expect(span.links).toBeUndefined(); - return traceContext; + return span; }); await sentryTest.step('Click Before navigation', async () => { - const interactionRequestPromise = waitForTransactionRequest(page, evt => { - return evt.contexts?.trace?.op === 'ui.action.click'; - }); + const interactionSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.action.click'); await page.click('#btn'); - - const interactionEvent = envelopeRequestParser(await interactionRequestPromise); - const interactionTraceContext = interactionEvent.contexts?.trace; + const interactionSpan = await interactionSpanPromise; // sanity check: route-based trace lifetime means the trace_id should be the same - expect(interactionTraceContext?.trace_id).toBe(pageloadTraceContext?.trace_id); + expect(interactionSpan.trace_id).toBe(pageloadSpan.trace_id); // no links yet as previous root span belonged to same trace - expect(interactionTraceContext?.links).toBeUndefined(); + expect(interactionSpan.links).toBeUndefined(); }); - const navigationTraceContext = await sentryTest.step('Navigation', async () => { - const navigationRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + const navigationSpan = await sentryTest.step('Navigation', async () => { + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); await page.goto(`${url}#foo`); - const navigationEvent = envelopeRequestParser(await navigationRequestPromise); - - const traceContext = navigationEvent.contexts?.trace; + const span = await navigationSpanPromise; - expect(traceContext?.op).toBe('navigation'); - expect(traceContext?.links).toEqual([ + expect(getSpanOp(span)).toBe('navigation'); + expect(span.links).toEqual([ { - trace_id: pageloadTraceContext?.trace_id, - span_id: pageloadTraceContext?.span_id, + trace_id: pageloadSpan.trace_id, + span_id: pageloadSpan.span_id, sampled: true, attributes: { - [SENTRY_LINK_TYPE]: 'previous_trace', + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, }, }, ]); - expect(traceContext?.trace_id).not.toEqual(traceContext?.links![0].trace_id); - return traceContext; + expect(span.trace_id).not.toEqual(span.links![0].trace_id); + return span; }); await sentryTest.step('Click After navigation', async () => { - const interactionRequestPromise = waitForTransactionRequest(page, evt => { - return evt.contexts?.trace?.op === 'ui.action.click'; - }); + const interactionSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.action.click'); await page.click('#btn'); - const interactionEvent = envelopeRequestParser(await interactionRequestPromise); - - const interactionTraceContext = interactionEvent.contexts?.trace; + const interactionSpan = await interactionSpanPromise; // sanity check: route-based trace lifetime means the trace_id should be the same - expect(interactionTraceContext?.trace_id).toBe(navigationTraceContext?.trace_id); + expect(interactionSpan.trace_id).toBe(navigationSpan.trace_id); // since this is the second root span in the trace, it doesn't link back to the previous trace - expect(interactionTraceContext?.links).toBeUndefined(); + expect(interactionSpan.links).toBeUndefined(); }); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/meta/test.ts index a232a5a0f6e7..3f8688a0bf4d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/meta/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/meta/test.ts @@ -1,54 +1,51 @@ -import { expect } from '@playwright/test'; import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; + import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( "links back to previous trace's local root span if continued from meta tags", async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); const metaTagTraceId = '12345678901234567890123456789012'; - const pageloadTraceContext = await sentryTest.step('Initial pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const pageloadSpan = await sentryTest.step('Initial pageload', async () => { + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.goto(url); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); - - const traceContext = pageloadRequest.contexts?.trace; + const span = await pageloadSpanPromise; // sanity check - expect(traceContext?.trace_id).toBe(metaTagTraceId); - - expect(traceContext?.links).toBeUndefined(); + expect(span.trace_id).toBe(metaTagTraceId); + expect(span.links).toBeUndefined(); - return traceContext; + return span; }); - const navigationTraceContext = await sentryTest.step('Navigation', async () => { - const navigationRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + const navigationSpan = await sentryTest.step('Navigation', async () => { + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); await page.goto(`${url}#foo`); - const navigationRequest = envelopeRequestParser(await navigationRequestPromise); - return navigationRequest.contexts?.trace; + return navigationSpanPromise; }); - const navigationTraceId = navigationTraceContext?.trace_id; - - expect(navigationTraceContext?.links).toEqual([ + expect(navigationSpan.links).toEqual([ { trace_id: metaTagTraceId, - span_id: pageloadTraceContext?.span_id, + span_id: pageloadSpan.span_id, sampled: true, attributes: { - [SENTRY_LINK_TYPE]: 'previous_trace', + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, }, }, ]); - expect(navigationTraceId).not.toEqual(metaTagTraceId); + expect(navigationSpan.trace_id).not.toEqual(metaTagTraceId); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/negatively-sampled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/negatively-sampled/init.js index 79ca4d68e8ba..a3b99e2e1dc3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/negatively-sampled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/negatively-sampled/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', // We want to ignore redirects for this test integrations: [Sentry.browserTracingIntegration({ detectRedirects: false })], diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/negatively-sampled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/negatively-sampled/test.ts index 894fe0d5929a..9fd3c0d2dd7d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/negatively-sampled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/negatively-sampled/test.ts @@ -1,42 +1,45 @@ -import { expect } from '@playwright/test'; import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; + import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest('includes a span link to a previously negatively sampled span', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); await sentryTest.step('Initial pageload', async () => { - // No event to check for an event here because this pageload span is sampled negatively! + // No span envelope expected here because this pageload span is sampled negatively! await page.goto(url); }); await sentryTest.step('Navigation', async () => { - const navigationRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); await page.goto(`${url}#foo`); - const navigationEvent = envelopeRequestParser(await navigationRequestPromise); - const navigationTraceContext = navigationEvent.contexts?.trace; + const navigationSpan = await navigationSpanPromise; - expect(navigationTraceContext?.op).toBe('navigation'); - expect(navigationTraceContext?.links).toEqual([ + expect(getSpanOp(navigationSpan)).toBe('navigation'); + expect(navigationSpan.links).toEqual([ { trace_id: expect.stringMatching(/[a-f\d]{32}/), span_id: expect.stringMatching(/[a-f\d]{16}/), sampled: false, attributes: { - [SENTRY_LINK_TYPE]: 'previous_trace', + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, }, }, ]); - expect(navigationTraceContext?.data).toMatchObject({ - 'sentry.previous_trace': expect.stringMatching(/[a-f\d]{32}-[a-f\d]{16}-0/), + expect(navigationSpan.attributes['sentry.previous_trace']).toEqual({ + type: 'string', + value: expect.stringMatching(/[a-f\d]{32}-[a-f\d]{16}-0/), }); - expect(navigationTraceContext?.trace_id).not.toEqual(navigationTraceContext?.links![0].trace_id); + expect(navigationSpan.trace_id).not.toEqual(navigationSpan.links![0].trace_id); }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/session-storage/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/session-storage/init.js index 9b88e489ae07..fc053206d9ed 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/session-storage/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/session-storage/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration({ linkPreviousTrace: 'session-storage' })], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/session-storage/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/session-storage/test.ts index f7e0d288de5b..57121c395b53 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/session-storage/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/session-storage/test.ts @@ -1,39 +1,43 @@ -import { expect } from '@playwright/test'; import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; + import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest('adds link between hard page reloads when opting into sessionStorage', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageload1TraceContext = await sentryTest.step('First pageload', async () => { - const pageloadRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const pageload1Span = await sentryTest.step('First pageload', async () => { + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.goto(url); - const pageload1Event = envelopeRequestParser(await pageloadRequestPromise); - const pageload1TraceContext = pageload1Event.contexts?.trace; - expect(pageload1TraceContext).toBeDefined(); - expect(pageload1TraceContext?.links).toBeUndefined(); - return pageload1TraceContext; + const span = await pageloadSpanPromise; + expect(span).toBeDefined(); + expect(span.links).toBeUndefined(); + return span; }); - const pageload2Event = await sentryTest.step('Hard page reload', async () => { - const pageload2RequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'pageload'); + const pageload2Span = await sentryTest.step('Hard page reload', async () => { + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.reload(); - return envelopeRequestParser(await pageload2RequestPromise); + return pageloadSpanPromise; }); - expect(pageload2Event.contexts?.trace?.links).toEqual([ + expect(pageload2Span.links).toEqual([ { - trace_id: pageload1TraceContext?.trace_id, - span_id: pageload1TraceContext?.span_id, + trace_id: pageload1Span.trace_id, + span_id: pageload1Span.span_id, sampled: true, - attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, + attributes: { + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, + }, }, ]); - expect(pageload1TraceContext?.trace_id).not.toEqual(pageload2Event.contexts?.trace?.trace_id); + expect(pageload1Span.trace_id).not.toEqual(pageload2Span.trace_id); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/init.js similarity index 90% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/init.js index d3c861a8ae26..a8a7fc20c7f9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/init.js @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ @@ -11,7 +12,6 @@ Sentry.init({ instrumentPageLoad: false, enableInp: false, }), - Sentry.spanStreamingIntegration(), ], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/test.ts new file mode 100644 index 000000000000..ca072e14b342 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-static/test.ts @@ -0,0 +1,34 @@ +import { NAVIGATION } from '@sentry/conventions/op'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest( + "doesn't capture long animation frame that starts before a navigation.", + async ({ browserName, getLocalTestUrl, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const navigationTransactionEventPromise = waitForTransactionRequest( + page, + event => event.contexts?.trace?.op === NAVIGATION, + ); + + await page.locator('#clickme').click(); + + const navigationTransactionEvent = envelopeRequestParser(await navigationTransactionEventPromise); + + expect(navigationTransactionEvent.contexts?.trace?.op).toBe(NAVIGATION); + + const loafSpans = navigationTransactionEvent.spans?.filter(s => s.op?.startsWith('ui.long_animation_frame')); + + expect(loafSpans?.length).toEqual(0); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/test.ts deleted file mode 100644 index bfda616223b4..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation-streamed/test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpans } from '../../../../utils/spanUtils'; - -sentryTest( - "doesn't capture long animation frame that starts before a navigation.", - async ({ browserName, getLocalTestUrl, page }) => { - // Long animation frames only work on chrome - sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const navigationSpansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'navigation')); - - await page.goto(url); - - await page.locator('#clickme').click(); - - const spans = await navigationSpansPromise; - - const loafSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); - expect(loafSpans).toHaveLength(0); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js index a8a7fc20c7f9..019eece018f6 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts index b7215249910f..031da81f9d78 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts @@ -1,30 +1,30 @@ +import { SENTRY_SEGMENT_ID } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( "doesn't capture long animation frame that starts before a navigation.", async ({ browserName, getLocalTestUrl, page }) => { // Long animation frames only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); + const allSpans = collectStreamedSpans(page); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - const navigationTransactionEventPromise = getFirstSentryEnvelopeRequest(page); + await page.goto(url); await page.locator('#clickme').click(); - const navigationTransactionEvent = await navigationTransactionEventPromise; - - expect(navigationTransactionEvent.contexts?.trace?.op).toBe('navigation'); - - const loafSpans = navigationTransactionEvent.spans?.filter(s => s.op?.startsWith('ui.long_animation_frame')); + const navigationRoot = await navigationSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + expect(getSpanOp(navigationRoot)).toBe('navigation'); + const spans = allSpans.filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === navigationRoot.span_id); - expect(loafSpans?.length).toEqual(0); + const loafSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); + expect(loafSpans).toHaveLength(0); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/assets/script.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/assets/script.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/assets/script.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/init.js similarity index 88% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/init.js index 3c004b952f80..250f2a72311b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/init.js @@ -3,13 +3,13 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ enableLongTask: false, enableLongAnimationFrame: false, }), - Sentry.spanStreamingIntegration(), ], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/test.ts new file mode 100644 index 000000000000..3d4ca79877b1 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-static/test.ts @@ -0,0 +1,26 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'should not capture long animation frame when flag is disabled.', + async ({ browserName, getLocalTestUrl, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + + expect(uiSpans?.length).toBe(0); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/test.ts deleted file mode 100644 index e03474070bb0..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpans } from '../../../../utils/spanUtils'; - -sentryTest( - 'does not capture long animation frame when flag is disabled.', - async ({ browserName, getLocalTestUrl, page }) => { - // Long animation frames only work on chrome - sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - - await page.route('**/path/to/script.js', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/script.js` }), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'pageload')); - - await page.goto(url); - - const spans = await spansPromise; - const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui')); - - expect(uiSpans.length).toBe(0); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js index 250f2a72311b..0339c3fa3dbf 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts index 3d4ca79877b1..7ea2ac55bb53 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts @@ -1,16 +1,14 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( - 'should not capture long animation frame when flag is disabled.', + 'does not capture long animation frame when flag is disabled.', async ({ browserName, getLocalTestUrl, page }) => { // Long animation frames only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` }), @@ -18,9 +16,15 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + const spans = collectStreamedSpans(page); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - expect(uiSpans?.length).toBe(0); + await page.goto(url); + + await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui')); + + expect(uiSpans.length).toBe(0); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/assets/script.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/assets/script.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/assets/script.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/init.js similarity index 88% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/init.js index 2be5f32985d7..cfa9cb5faa3f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/init.js @@ -3,13 +3,13 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ enableLongTask: false, enableLongAnimationFrame: true, }), - Sentry.spanStreamingIntegration(), ], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/test.ts new file mode 100644 index 000000000000..0a3f97f6a786 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-static/test.ts @@ -0,0 +1,116 @@ +import { PAGELOAD, UI_LONG_ANIMATION_FRAME } from '@sentry/conventions/op'; +import { + BROWSER_SCRIPT_INVOKER, + CODE_FILE_PATH, + BROWSER_SCRIPT_SOURCE_CHAR_POSITION, + BROWSER_SCRIPT_INVOKER_TYPE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest( + 'should capture long animation frame for top-level script.', + async ({ browserName, getLocalTestUrl, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const promise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + + await page.goto(url); + + const eventData = envelopeRequestParser(await promise); + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith(UI_LONG_ANIMATION_FRAME)); + + expect(uiSpans?.length).toBeGreaterThanOrEqual(1); + + const topLevelUISpan = (uiSpans || []).find( + span => span.data?.[BROWSER_SCRIPT_INVOKER] === 'https://sentry-test-site.example/path/to/script.js', + )!; + expect(topLevelUISpan).toEqual( + expect.objectContaining({ + op: UI_LONG_ANIMATION_FRAME, + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + [CODE_FILE_PATH]: 'https://sentry-test-site.example/path/to/script.js', + [BROWSER_SCRIPT_SOURCE_CHAR_POSITION]: 0, + [BROWSER_SCRIPT_INVOKER]: 'https://sentry-test-site.example/path/to/script.js', + [BROWSER_SCRIPT_INVOKER_TYPE]: 'classic-script', + [SENTRY_OP]: UI_LONG_ANIMATION_FRAME, + [SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = topLevelUISpan.start_timestamp ?? 0; + const end = topLevelUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); + +sentryTest( + 'should capture long animation frame for event listener.', + async ({ browserName, getLocalTestUrl, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const promise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + + await page.goto(url); + + // trigger long animation frame function + await page.getByRole('button').click(); + + const eventData = envelopeRequestParser(await promise); + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith(UI_LONG_ANIMATION_FRAME)) || []; + + expect(uiSpans.length).toBeGreaterThanOrEqual(2); + + const eventListenerUISpan = uiSpans.find(span => span.data[BROWSER_SCRIPT_INVOKER] === 'BUTTON#clickme.onclick')!; + + expect(eventListenerUISpan).toEqual( + expect.objectContaining({ + op: UI_LONG_ANIMATION_FRAME, + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + [BROWSER_SCRIPT_INVOKER]: 'BUTTON#clickme.onclick', + [BROWSER_SCRIPT_INVOKER_TYPE]: 'event-listener', + [CODE_FILE_PATH]: 'https://sentry-test-site.example/path/to/script.js', + [SENTRY_OP]: UI_LONG_ANIMATION_FRAME, + [SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = eventListenerUISpan.start_timestamp ?? 0; + const end = eventListenerUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/test.ts deleted file mode 100644 index f885fc4c3bde..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpans } from '../../../../utils/spanUtils'; - -sentryTest( - 'captures long animation frame span for top-level script.', - async ({ browserName, getLocalTestUrl, page }) => { - // Long animation frames only work on chrome - sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - - await page.route('**/path/to/script.js', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/script.js` }), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'pageload')); - - await page.goto(url); - - const spans = await spansPromise; - const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload')!; - - const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); - - expect(uiSpans.length).toBeGreaterThanOrEqual(1); - - const topLevelUISpan = uiSpans.find( - s => s.attributes['browser.script.invoker']?.value === 'https://sentry-test-site.example/path/to/script.js', - )!; - - expect(topLevelUISpan).toEqual( - expect.objectContaining({ - name: 'Main UI thread blocked', - parent_span_id: pageloadSpan.span_id, - attributes: expect.objectContaining({ - 'code.file.path': { type: 'string', value: 'https://sentry-test-site.example/path/to/script.js' }, - 'browser.script.source_char_position': expect.objectContaining({ value: 0 }), - 'browser.script.invoker': { - type: 'string', - value: 'https://sentry-test-site.example/path/to/script.js', - }, - 'browser.script.invoker_type': { type: 'string', value: 'classic-script' }, - [SENTRY_OP]: { type: 'string', value: 'ui.long_animation_frame' }, - [SENTRY_ORIGIN]: { type: 'string', value: 'auto.ui.browser.metrics' }, - }), - }), - ); - - const start = topLevelUISpan.start_timestamp ?? 0; - const end = topLevelUISpan.end_timestamp ?? 0; - const duration = end - start; - - expect(duration).toBeGreaterThanOrEqual(0.1); - expect(duration).toBeLessThanOrEqual(0.15); - }, -); - -sentryTest('captures long animation frame span for event listener.', async ({ browserName, getLocalTestUrl, page }) => { - // Long animation frames only work on chrome - sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - - await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'pageload')); - - await page.goto(url); - - // trigger long animation frame function - await page.getByRole('button').click(); - - const spans = await spansPromise; - const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload')!; - - const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); - - expect(uiSpans.length).toBeGreaterThanOrEqual(2); - - const eventListenerUISpan = uiSpans.find( - s => s.attributes['browser.script.invoker']?.value === 'BUTTON#clickme.onclick', - )!; - - expect(eventListenerUISpan).toEqual( - expect.objectContaining({ - name: 'Main UI thread blocked', - parent_span_id: pageloadSpan.span_id, - attributes: expect.objectContaining({ - 'browser.script.invoker': { type: 'string', value: 'BUTTON#clickme.onclick' }, - 'browser.script.invoker_type': { type: 'string', value: 'event-listener' }, - 'code.file.path': { type: 'string', value: 'https://sentry-test-site.example/path/to/script.js' }, - [SENTRY_OP]: { type: 'string', value: 'ui.long_animation_frame' }, - [SENTRY_ORIGIN]: { type: 'string', value: 'auto.ui.browser.metrics' }, - }), - }), - ); - - const start = eventListenerUISpan.start_timestamp ?? 0; - const end = eventListenerUISpan.end_timestamp ?? 0; - const duration = end - start; - - expect(duration).toBeGreaterThanOrEqual(0.1); - expect(duration).toBeLessThanOrEqual(0.15); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js index cfa9cb5faa3f..f16f7af33b5c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts index 937662e117a4..8a1cc8026aaa 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts @@ -1,17 +1,22 @@ -import { SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + BROWSER_SCRIPT_INVOKER, + CODE_FILE_PATH, + BROWSER_SCRIPT_SOURCE_CHAR_POSITION, + BROWSER_SCRIPT_INVOKER_TYPE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( - 'should capture long animation frame for top-level script.', + 'captures long animation frame span for top-level script.', async ({ browserName, getLocalTestUrl, page }) => { // Long animation frames only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` }), @@ -19,38 +24,43 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const promise = getFirstSentryEnvelopeRequest(page); + const spans = collectStreamedSpans(page); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.goto(url); - await new Promise(resolve => setTimeout(resolve, 1000)); + await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload')!; - const eventData = await promise; + const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long_animation_frame')); + expect(uiSpans.length).toBeGreaterThanOrEqual(1); - expect(uiSpans?.length).toBeGreaterThanOrEqual(1); - - const topLevelUISpan = (uiSpans || []).find( - span => span.data?.['browser.script.invoker'] === 'https://sentry-test-site.example/path/to/script.js', + const topLevelUISpan = uiSpans.find( + s => s.attributes[BROWSER_SCRIPT_INVOKER]?.value === 'https://sentry-test-site.example/path/to/script.js', )!; + expect(topLevelUISpan).toEqual( expect.objectContaining({ - op: 'ui.long_animation_frame', - description: 'Main UI thread blocked', - parent_span_id: eventData.contexts?.trace?.span_id, - data: { - 'code.file.path': 'https://sentry-test-site.example/path/to/script.js', - 'browser.script.source_char_position': 0, - 'browser.script.invoker': 'https://sentry-test-site.example/path/to/script.js', - 'browser.script.invoker_type': 'classic-script', - [SENTRY_OP]: 'ui.long_animation_frame', - [SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - }, + name: 'Main UI thread blocked', + parent_span_id: pageloadSpan.span_id, + attributes: expect.objectContaining({ + [CODE_FILE_PATH]: { type: 'string', value: 'https://sentry-test-site.example/path/to/script.js' }, + [BROWSER_SCRIPT_SOURCE_CHAR_POSITION]: expect.objectContaining({ value: 0 }), + [BROWSER_SCRIPT_INVOKER]: { + type: 'string', + value: 'https://sentry-test-site.example/path/to/script.js', + }, + [BROWSER_SCRIPT_INVOKER_TYPE]: { type: 'string', value: 'classic-script' }, + [SENTRY_OP]: { type: 'string', value: 'ui.long_animation_frame' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.ui.browser.metrics' }, + }), }), ); + const start = topLevelUISpan.start_timestamp ?? 0; - const end = topLevelUISpan.timestamp ?? 0; + const end = topLevelUISpan.end_timestamp ?? 0; const duration = end - start; expect(duration).toBeGreaterThanOrEqual(0.1); @@ -58,56 +68,51 @@ sentryTest( }, ); -sentryTest( - 'should capture long animation frame for event listener.', - async ({ browserName, getLocalTestUrl, page }) => { - // Long animation frames only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - await page.route('**/path/to/script.js', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/script.js` }), - ); +sentryTest('captures long animation frame span for event listener.', async ({ browserName, getLocalTestUrl, page }) => { + // Long animation frames only work on chrome + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - const url = await getLocalTestUrl({ testDir: __dirname }); + await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); - const promise = getFirstSentryEnvelopeRequest(page); + const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); + const spans = collectStreamedSpans(page); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - // trigger long animation frame function - await page.getByRole('button').click(); + await page.goto(url); - await new Promise(resolve => setTimeout(resolve, 1000)); + // trigger long animation frame function + await page.getByRole('button').click(); - const eventData = await promise; + const pageloadSpan = await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long_animation_frame')) || []; + const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); - expect(uiSpans.length).toBeGreaterThanOrEqual(2); + expect(uiSpans.length).toBeGreaterThanOrEqual(2); - const eventListenerUISpan = uiSpans.find(span => span.data['browser.script.invoker'] === 'BUTTON#clickme.onclick')!; + const eventListenerUISpan = uiSpans.find( + s => s.attributes[BROWSER_SCRIPT_INVOKER]?.value === 'BUTTON#clickme.onclick', + )!; - expect(eventListenerUISpan).toEqual( - expect.objectContaining({ - op: 'ui.long_animation_frame', - description: 'Main UI thread blocked', - parent_span_id: eventData.contexts?.trace?.span_id, - data: { - 'browser.script.invoker': 'BUTTON#clickme.onclick', - 'browser.script.invoker_type': 'event-listener', - 'code.file.path': 'https://sentry-test-site.example/path/to/script.js', - [SENTRY_OP]: 'ui.long_animation_frame', - [SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - }, + expect(eventListenerUISpan).toEqual( + expect.objectContaining({ + name: 'Main UI thread blocked', + parent_span_id: pageloadSpan.span_id, + attributes: expect.objectContaining({ + [BROWSER_SCRIPT_INVOKER]: { type: 'string', value: 'BUTTON#clickme.onclick' }, + [BROWSER_SCRIPT_INVOKER_TYPE]: { type: 'string', value: 'event-listener' }, + [CODE_FILE_PATH]: { type: 'string', value: 'https://sentry-test-site.example/path/to/script.js' }, + [SENTRY_OP]: { type: 'string', value: 'ui.long_animation_frame' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.ui.browser.metrics' }, }), - ); - const start = eventListenerUISpan.start_timestamp ?? 0; - const end = eventListenerUISpan.timestamp ?? 0; - const duration = end - start; + }), + ); - expect(duration).toBeGreaterThanOrEqual(0.1); - expect(duration).toBeLessThanOrEqual(0.15); - }, -); + const start = eventListenerUISpan.start_timestamp ?? 0; + const end = eventListenerUISpan.end_timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js index 37df798364a8..382115cbc8b9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts index 2c8f5427cc05..07d3a6cd313a 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts @@ -1,8 +1,8 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; sentryTest( 'should not capture long animation frame or long task when browser is non-chromium', @@ -18,8 +18,10 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + const spans = collectStreamedSpans(page); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url, span => getSpanOp(span) === 'pageload'); + await page.evaluate(() => (window as any).Sentry.flush()); + const uiSpans = spans?.filter(span => getSpanOp(span)?.startsWith('ui')); expect(uiSpans?.length).toBe(0); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/assets/script.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/assets/script.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/assets/script.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/init.js similarity index 88% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/init.js index b0dc0a8d7ff5..37df798364a8 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/init.js @@ -3,13 +3,13 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ enableLongTask: true, enableLongAnimationFrame: true, }), - Sentry.spanStreamingIntegration(), ], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/test.ts new file mode 100644 index 000000000000..fb252c80fa30 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/test.ts @@ -0,0 +1,118 @@ +import { PAGELOAD, UI_LONG_ANIMATION_FRAME } from '@sentry/conventions/op'; +import { + BROWSER_SCRIPT_INVOKER, + CODE_FILE_PATH, + BROWSER_SCRIPT_SOURCE_CHAR_POSITION, + BROWSER_SCRIPT_INVOKER_TYPE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest( + 'should capture long animation frame for top-level script.', + async ({ browserName, getLocalTestUrl, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + // Long animation frame should take priority over long tasks + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const promise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + + await page.goto(url); + + const eventData = envelopeRequestParser(await promise); + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith(UI_LONG_ANIMATION_FRAME)); + + expect(uiSpans?.length).toBeGreaterThanOrEqual(1); + + const topLevelUISpan = (uiSpans || []).find( + span => span.data?.[BROWSER_SCRIPT_INVOKER] === 'https://sentry-test-site.example/path/to/script.js', + )!; + expect(topLevelUISpan).toEqual( + expect.objectContaining({ + op: UI_LONG_ANIMATION_FRAME, + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + [CODE_FILE_PATH]: 'https://sentry-test-site.example/path/to/script.js', + [BROWSER_SCRIPT_SOURCE_CHAR_POSITION]: 0, + [BROWSER_SCRIPT_INVOKER]: 'https://sentry-test-site.example/path/to/script.js', + [BROWSER_SCRIPT_INVOKER_TYPE]: 'classic-script', + [SENTRY_OP]: UI_LONG_ANIMATION_FRAME, + [SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = topLevelUISpan.start_timestamp ?? 0; + const end = topLevelUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); + +sentryTest( + 'should capture long animation frame for event listener.', + async ({ browserName, getLocalTestUrl, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const promise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + + await page.goto(url); + + // trigger long animation frame function + await page.getByRole('button').click(); + + const eventData = envelopeRequestParser(await promise); + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith(UI_LONG_ANIMATION_FRAME)) || []; + + expect(uiSpans.length).toBeGreaterThanOrEqual(2); + + const eventListenerUISpan = uiSpans.find(span => span.data[BROWSER_SCRIPT_INVOKER] === 'BUTTON#clickme.onclick')!; + + expect(eventListenerUISpan).toEqual( + expect.objectContaining({ + op: UI_LONG_ANIMATION_FRAME, + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + [BROWSER_SCRIPT_INVOKER]: 'BUTTON#clickme.onclick', + [BROWSER_SCRIPT_INVOKER_TYPE]: 'event-listener', + [CODE_FILE_PATH]: 'https://sentry-test-site.example/path/to/script.js', + [SENTRY_OP]: UI_LONG_ANIMATION_FRAME, + [SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = eventListenerUISpan.start_timestamp ?? 0; + const end = eventListenerUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/test.ts deleted file mode 100644 index 94ece2b74e6f..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpans } from '../../../../utils/spanUtils'; - -sentryTest( - 'captures long animation frame span for top-level script.', - async ({ browserName, getLocalTestUrl, page }) => { - // Long animation frames only work on chrome - sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - - // Long animation frame should take priority over long tasks - - await page.route('**/path/to/script.js', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/script.js` }), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'pageload')); - - await page.goto(url); - - const spans = await spansPromise; - const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload')!; - - const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); - - expect(uiSpans.length).toBeGreaterThanOrEqual(1); - - const topLevelUISpan = uiSpans.find( - s => s.attributes['browser.script.invoker']?.value === 'https://sentry-test-site.example/path/to/script.js', - )!; - - expect(topLevelUISpan).toEqual( - expect.objectContaining({ - name: 'Main UI thread blocked', - parent_span_id: pageloadSpan.span_id, - attributes: expect.objectContaining({ - 'code.file.path': { type: 'string', value: 'https://sentry-test-site.example/path/to/script.js' }, - 'browser.script.source_char_position': expect.objectContaining({ value: 0 }), - 'browser.script.invoker': { - type: 'string', - value: 'https://sentry-test-site.example/path/to/script.js', - }, - 'browser.script.invoker_type': { type: 'string', value: 'classic-script' }, - [SENTRY_OP]: { type: 'string', value: 'ui.long_animation_frame' }, - [SENTRY_ORIGIN]: { type: 'string', value: 'auto.ui.browser.metrics' }, - }), - }), - ); - - const start = topLevelUISpan.start_timestamp ?? 0; - const end = topLevelUISpan.end_timestamp ?? 0; - const duration = end - start; - - expect(duration).toBeGreaterThanOrEqual(0.1); - expect(duration).toBeLessThanOrEqual(0.15); - }, -); - -sentryTest('captures long animation frame span for event listener.', async ({ browserName, getLocalTestUrl, page }) => { - // Long animation frames only work on chrome - sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - - await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'pageload')); - - await page.goto(url); - - // trigger long animation frame function - await page.getByRole('button').click(); - - const spans = await spansPromise; - const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload')!; - - const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); - - expect(uiSpans.length).toBeGreaterThanOrEqual(2); - - const eventListenerUISpan = uiSpans.find( - s => s.attributes['browser.script.invoker']?.value === 'BUTTON#clickme.onclick', - )!; - - expect(eventListenerUISpan).toEqual( - expect.objectContaining({ - name: 'Main UI thread blocked', - parent_span_id: pageloadSpan.span_id, - attributes: expect.objectContaining({ - 'browser.script.invoker': { type: 'string', value: 'BUTTON#clickme.onclick' }, - 'browser.script.invoker_type': { type: 'string', value: 'event-listener' }, - 'code.file.path': { type: 'string', value: 'https://sentry-test-site.example/path/to/script.js' }, - [SENTRY_OP]: { type: 'string', value: 'ui.long_animation_frame' }, - [SENTRY_ORIGIN]: { type: 'string', value: 'auto.ui.browser.metrics' }, - }), - }), - ); - - const start = eventListenerUISpan.start_timestamp ?? 0; - const end = eventListenerUISpan.end_timestamp ?? 0; - const duration = end - start; - - expect(duration).toBeGreaterThanOrEqual(0.1); - expect(duration).toBeLessThanOrEqual(0.15); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js index 37df798364a8..382115cbc8b9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts index b571a7d6a306..192956b477d9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts @@ -1,17 +1,22 @@ -import { SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + BROWSER_SCRIPT_INVOKER, + CODE_FILE_PATH, + BROWSER_SCRIPT_SOURCE_CHAR_POSITION, + BROWSER_SCRIPT_INVOKER_TYPE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( - 'should capture long animation frame for top-level script.', + 'captures long animation frame span for top-level script.', async ({ browserName, getLocalTestUrl, page }) => { // Long animation frames only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); // Long animation frame should take priority over long tasks @@ -21,38 +26,43 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const promise = getFirstSentryEnvelopeRequest(page); + const spans = collectStreamedSpans(page); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.goto(url); - await new Promise(resolve => setTimeout(resolve, 1000)); + await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload')!; - const eventData = await promise; + const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long_animation_frame')); + expect(uiSpans.length).toBeGreaterThanOrEqual(1); - expect(uiSpans?.length).toBeGreaterThanOrEqual(1); - - const topLevelUISpan = (uiSpans || []).find( - span => span.data?.['browser.script.invoker'] === 'https://sentry-test-site.example/path/to/script.js', + const topLevelUISpan = uiSpans.find( + s => s.attributes[BROWSER_SCRIPT_INVOKER]?.value === 'https://sentry-test-site.example/path/to/script.js', )!; + expect(topLevelUISpan).toEqual( expect.objectContaining({ - op: 'ui.long_animation_frame', - description: 'Main UI thread blocked', - parent_span_id: eventData.contexts?.trace?.span_id, - data: { - 'code.file.path': 'https://sentry-test-site.example/path/to/script.js', - 'browser.script.source_char_position': 0, - 'browser.script.invoker': 'https://sentry-test-site.example/path/to/script.js', - 'browser.script.invoker_type': 'classic-script', - [SENTRY_OP]: 'ui.long_animation_frame', - [SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - }, + name: 'Main UI thread blocked', + parent_span_id: pageloadSpan.span_id, + attributes: expect.objectContaining({ + [CODE_FILE_PATH]: { type: 'string', value: 'https://sentry-test-site.example/path/to/script.js' }, + [BROWSER_SCRIPT_SOURCE_CHAR_POSITION]: expect.objectContaining({ value: 0 }), + [BROWSER_SCRIPT_INVOKER]: { + type: 'string', + value: 'https://sentry-test-site.example/path/to/script.js', + }, + [BROWSER_SCRIPT_INVOKER_TYPE]: { type: 'string', value: 'classic-script' }, + [SENTRY_OP]: { type: 'string', value: 'ui.long_animation_frame' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.ui.browser.metrics' }, + }), }), ); + const start = topLevelUISpan.start_timestamp ?? 0; - const end = topLevelUISpan.timestamp ?? 0; + const end = topLevelUISpan.end_timestamp ?? 0; const duration = end - start; expect(duration).toBeGreaterThanOrEqual(0.1); @@ -60,56 +70,51 @@ sentryTest( }, ); -sentryTest( - 'should capture long animation frame for event listener.', - async ({ browserName, getLocalTestUrl, page }) => { - // Long animation frames only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - await page.route('**/path/to/script.js', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/script.js` }), - ); +sentryTest('captures long animation frame span for event listener.', async ({ browserName, getLocalTestUrl, page }) => { + // Long animation frames only work on chrome + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - const url = await getLocalTestUrl({ testDir: __dirname }); + await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); - const promise = getFirstSentryEnvelopeRequest(page); + const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); + const spans = collectStreamedSpans(page); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - // trigger long animation frame function - await page.getByRole('button').click(); + await page.goto(url); - await new Promise(resolve => setTimeout(resolve, 1000)); + // trigger long animation frame function + await page.getByRole('button').click(); - const eventData = await promise; + const pageloadSpan = await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long_animation_frame')) || []; + const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui.long_animation_frame')); - expect(uiSpans.length).toBeGreaterThanOrEqual(2); + expect(uiSpans.length).toBeGreaterThanOrEqual(2); - const eventListenerUISpan = uiSpans.find(span => span.data['browser.script.invoker'] === 'BUTTON#clickme.onclick')!; + const eventListenerUISpan = uiSpans.find( + s => s.attributes[BROWSER_SCRIPT_INVOKER]?.value === 'BUTTON#clickme.onclick', + )!; - expect(eventListenerUISpan).toEqual( - expect.objectContaining({ - op: 'ui.long_animation_frame', - description: 'Main UI thread blocked', - parent_span_id: eventData.contexts?.trace?.span_id, - data: { - 'browser.script.invoker': 'BUTTON#clickme.onclick', - 'browser.script.invoker_type': 'event-listener', - 'code.file.path': 'https://sentry-test-site.example/path/to/script.js', - [SENTRY_OP]: 'ui.long_animation_frame', - [SENTRY_ORIGIN]: 'auto.ui.browser.metrics', - }, + expect(eventListenerUISpan).toEqual( + expect.objectContaining({ + name: 'Main UI thread blocked', + parent_span_id: pageloadSpan.span_id, + attributes: expect.objectContaining({ + [BROWSER_SCRIPT_INVOKER]: { type: 'string', value: 'BUTTON#clickme.onclick' }, + [BROWSER_SCRIPT_INVOKER_TYPE]: { type: 'string', value: 'event-listener' }, + [CODE_FILE_PATH]: { type: 'string', value: 'https://sentry-test-site.example/path/to/script.js' }, + [SENTRY_OP]: { type: 'string', value: 'ui.long_animation_frame' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.ui.browser.metrics' }, }), - ); - const start = eventListenerUISpan.start_timestamp ?? 0; - const end = eventListenerUISpan.timestamp ?? 0; - const duration = end - start; + }), + ); - expect(duration).toBeGreaterThanOrEqual(0.1); - expect(duration).toBeLessThanOrEqual(0.15); - }, -); + const start = eventListenerUISpan.start_timestamp ?? 0; + const end = eventListenerUISpan.end_timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/init.js similarity index 90% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/init.js index 9f36511d8ff5..63abc00dea38 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/init.js @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ @@ -12,7 +13,6 @@ Sentry.init({ enableInp: false, enableLongTask: true, }), - Sentry.spanStreamingIntegration(), ], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/test.ts similarity index 50% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/test.ts index 7e544f27a107..5236717091f1 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation-static/test.ts @@ -1,29 +1,30 @@ import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpans } from '../../../../utils/spanUtils'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; sentryTest( "doesn't capture long task spans starting before a navigation in the navigation transaction", async ({ browserName, getLocalTestUrl, page }) => { // Long tasks only work on chrome - sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } const url = await getLocalTestUrl({ testDir: __dirname }); await page.route('**/path/to/script.js', route => route.fulfill({ path: `${__dirname}/assets/script.js` })); - const navigationSpansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'navigation')); - await page.goto(url); + const navigationTransactionEventPromise = getFirstSentryEnvelopeRequest(page); + await page.locator('#myButton').click(); - const spans = await navigationSpansPromise; + const navigationTransactionEvent = await navigationTransactionEventPromise; - const navigationSpan = spans.find(s => getSpanOp(s) === 'navigation'); - expect(navigationSpan).toBeDefined(); + expect(navigationTransactionEvent.contexts?.trace?.op).toBe('navigation'); - const longTaskSpans = spans.filter(s => getSpanOp(s) === 'ui.long_task'); + const longTaskSpans = navigationTransactionEvent?.spans?.filter(span => span.op === 'ui.long_task'); expect(longTaskSpans).toHaveLength(0); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js index 63abc00dea38..ccf669361e37 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts index 5236717091f1..fb458438ed43 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts @@ -1,30 +1,33 @@ +import { SENTRY_SEGMENT_ID } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( - "doesn't capture long task spans starting before a navigation in the navigation transaction", + "doesn't capture long task spans starting before a navigation in the navigation span", async ({ browserName, getLocalTestUrl, page }) => { // Long tasks only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.route('**/path/to/script.js', route => route.fulfill({ path: `${__dirname}/assets/script.js` })); - await page.goto(url); + const allSpans = collectStreamedSpans(page); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - const navigationTransactionEventPromise = getFirstSentryEnvelopeRequest(page); + await page.goto(url); await page.locator('#myButton').click(); - const navigationTransactionEvent = await navigationTransactionEventPromise; + const navigationRoot = await navigationSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const spans = allSpans.filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === navigationRoot.span_id); - expect(navigationTransactionEvent.contexts?.trace?.op).toBe('navigation'); + const navigationSpan = spans.find(s => getSpanOp(s) === 'navigation'); + expect(navigationSpan).toBeDefined(); - const longTaskSpans = navigationTransactionEvent?.spans?.filter(span => span.op === 'ui.long_task'); + const longTaskSpans = spans.filter(s => getSpanOp(s) === 'ui.long_task'); expect(longTaskSpans).toHaveLength(0); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/assets/script.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/assets/script.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/assets/script.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/init.js similarity index 88% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/init.js index 3c004b952f80..250f2a72311b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/init.js @@ -3,13 +3,13 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ enableLongTask: false, enableLongAnimationFrame: false, }), - Sentry.spanStreamingIntegration(), ], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/test.ts new file mode 100644 index 000000000000..bb32e21ee1d5 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-static/test.ts @@ -0,0 +1,21 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest('should not capture long task when flag is disabled.', async ({ browserName, getLocalTestUrl, page }) => { + // Long tasks only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + + expect(uiSpans?.length).toBe(0); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/test.ts deleted file mode 100644 index da7f44f05df7..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled-streamed/test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpans } from '../../../../utils/spanUtils'; - -sentryTest("doesn't capture long task spans when flag is disabled.", async ({ browserName, getLocalTestUrl, page }) => { - // Long tasks only work on chrome - sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - - await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'pageload')); - - await page.goto(url); - - const spans = await spansPromise; - const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui')); - - expect(uiSpans.length).toBe(0); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/init.js index 250f2a72311b..0339c3fa3dbf 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts index bb32e21ee1d5..6ebf0c018e6f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts @@ -1,21 +1,25 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; -sentryTest('should not capture long task when flag is disabled.', async ({ browserName, getLocalTestUrl, page }) => { +sentryTest("doesn't capture long task spans when flag is disabled.", async ({ browserName, getLocalTestUrl, page }) => { // Long tasks only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + const spans = collectStreamedSpans(page); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - expect(uiSpans?.length).toBe(0); + await page.goto(url); + + await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui')); + + expect(uiSpans.length).toBe(0); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/assets/script.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/assets/script.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/assets/script.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/init.js similarity index 87% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/init.js index 977ff9c6425e..73d20634b532 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/init.js @@ -3,12 +3,12 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ enableLongAnimationFrame: false, }), - Sentry.spanStreamingIntegration(), ], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/test.ts new file mode 100644 index 000000000000..e34b0d779f7e --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-static/test.ts @@ -0,0 +1,36 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest('should capture long task.', async ({ browserName, getLocalTestUrl, page }) => { + // Long tasks only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + + expect(uiSpans?.length).toBeGreaterThan(0); + + const [firstUISpan] = uiSpans || []; + expect(firstUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long_task', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + }), + ); + const start = firstUISpan.start_timestamp ?? 0; + const end = firstUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/test.ts deleted file mode 100644 index 1eeb65579b39..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled-streamed/test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpans } from '../../../../utils/spanUtils'; - -sentryTest('captures long task.', async ({ browserName, getLocalTestUrl, page }) => { - // Long tasks only work on chrome - sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); - - await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => getSpanOp(s) === 'pageload')); - - await page.goto(url); - - const spans = await spansPromise; - const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload')!; - - const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui')); - expect(uiSpans.length).toBeGreaterThan(0); - - const [firstUISpan] = uiSpans; - expect(firstUISpan).toEqual( - expect.objectContaining({ - name: 'Main UI thread blocked', - parent_span_id: pageloadSpan.span_id, - attributes: expect.objectContaining({ - 'sentry.op': { type: 'string', value: 'ui.long_task' }, - }), - }), - ); - - const start = firstUISpan.start_timestamp ?? 0; - const end = firstUISpan.end_timestamp ?? 0; - const duration = end - start; - - expect(duration).toBeGreaterThanOrEqual(0.1); - expect(duration).toBeLessThanOrEqual(0.15); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/init.js index 73d20634b532..47e9fd0b92d8 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts index e34b0d779f7e..add1a8130425 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts @@ -1,34 +1,42 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; -sentryTest('should capture long task.', async ({ browserName, getLocalTestUrl, page }) => { +sentryTest('captures long task.', async ({ browserName, getLocalTestUrl, page }) => { // Long tasks only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + const spans = collectStreamedSpans(page); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - expect(uiSpans?.length).toBeGreaterThan(0); + await page.goto(url); - const [firstUISpan] = uiSpans || []; + const pageloadSpan = await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + + const uiSpans = spans.filter(s => getSpanOp(s)?.startsWith('ui')); + expect(uiSpans.length).toBeGreaterThan(0); + + const [firstUISpan] = uiSpans; expect(firstUISpan).toEqual( expect.objectContaining({ - op: 'ui.long_task', - description: 'Main UI thread blocked', - parent_span_id: eventData.contexts?.trace?.span_id, + name: 'Main UI thread blocked', + parent_span_id: pageloadSpan.span_id, + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: 'ui.long_task' }, + }), }), ); + const start = firstUISpan.start_timestamp ?? 0; - const end = firstUISpan.timestamp ?? 0; + const end = firstUISpan.end_timestamp ?? 0; const duration = end - start; expect(duration).toBeGreaterThanOrEqual(0.1); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js index a7797f5dce06..723707736f2e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts index e34b0d779f7e..707a336d3df1 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts @@ -1,8 +1,9 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; sentryTest('should capture long task.', async ({ browserName, getLocalTestUrl, page }) => { // Long tasks only work on chrome @@ -14,21 +15,27 @@ sentryTest('should capture long task.', async ({ browserName, getLocalTestUrl, p const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + const spans = collectStreamedSpans(page); + const [pageloadSpan] = await waitForStreamedSpanAndTraceHeaderOnUrl( + page, + url, + span => getSpanOp(span) === 'pageload', + ); + await page.evaluate(() => (window as any).Sentry.flush()); + const uiSpans = spans.filter(span => getSpanOp(span)?.startsWith('ui')); - expect(uiSpans?.length).toBeGreaterThan(0); + expect(uiSpans.length).toBeGreaterThan(0); - const [firstUISpan] = uiSpans || []; + const [firstUISpan] = uiSpans; expect(firstUISpan).toEqual( expect.objectContaining({ - op: 'ui.long_task', - description: 'Main UI thread blocked', - parent_span_id: eventData.contexts?.trace?.span_id, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'ui.long_task' } }), + name: 'Main UI thread blocked', + parent_span_id: pageloadSpan.span_id, }), ); const start = firstUISpan.start_timestamp ?? 0; - const end = firstUISpan.timestamp ?? 0; + const end = firstUISpan.end_timestamp ?? 0; const duration = end - start; expect(duration).toBeGreaterThanOrEqual(0.1); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/meta/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/meta/init.js index 52fc529d095a..d4c7810ef518 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/meta/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/meta/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/meta/test.ts index 1d7a14a46055..86b7d100bb37 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/meta/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/meta/test.ts @@ -1,43 +1,31 @@ +import { SENTRY_SEGMENT_ID } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event, EventEnvelopeHeaders } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { - envelopeHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; - -sentryTest('should create a pageload transaction based on `sentry-trace` ', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; +sentryTest('creates a pageload span based on `sentry-trace` ', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.contexts?.trace).toMatchObject({ - op: 'pageload', + const spans = collectStreamedSpans(page); + const [pageload] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(getSpanOp(pageload)).toBe('pageload'); + expect(pageload).toMatchObject({ + is_segment: true, parent_span_id: '1121201211212012', trace_id: '12312012123120121231201212312012', }); - - expect(eventData.spans?.length).toBeGreaterThan(0); + expect(spans.filter(span => !span.is_segment)).not.toHaveLength(0); }); sentryTest( - 'should pick up `baggage` tag, propagate the content in transaction and not add own data', + 'propagates `baggage` in the span envelope without adding own data', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - - const envHeader = await getFirstSentryEnvelopeRequest(page, url, envelopeHeaderRequestParser); - - expect(envHeader.trace).toBeDefined(); - expect(envHeader.trace).toEqual({ + const [, trace] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + expect(trace).toEqual({ release: '2.1.12', sample_rate: '0.3232', trace_id: '123', @@ -47,47 +35,31 @@ sentryTest( }, ); -sentryTest( - "should create a navigation that's not influenced by `sentry-trace` ", - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadRequest = await getFirstSentryEnvelopeRequest(page, url); - const navigationRequest = await getFirstSentryEnvelopeRequest(page, `${url}#foo`); - - expect(pageloadRequest.contexts?.trace).toMatchObject({ - op: 'pageload', - parent_span_id: '1121201211212012', - trace_id: '12312012123120121231201212312012', - }); - - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); - expect(navigationRequest.contexts?.trace?.trace_id).toBeDefined(); - expect(navigationRequest.contexts?.trace?.trace_id).not.toBe(pageloadRequest.contexts?.trace?.trace_id); - - const pageloadSpans = pageloadRequest.spans; - const navigationSpans = navigationRequest.spans; - - const pageloadSpanId = pageloadRequest.contexts?.trace?.span_id; - const navigationSpanId = navigationRequest.contexts?.trace?.span_id; - - expect(pageloadSpanId).toBeDefined(); - expect(navigationSpanId).toBeDefined(); - - pageloadSpans?.forEach(span => - expect(span).toMatchObject({ - parent_span_id: pageloadSpanId, - }), - ); - - navigationSpans?.forEach(span => - expect(span).toMatchObject({ - parent_span_id: navigationSpanId, - }), - ); - }, -); +sentryTest("creates a navigation that's not influenced by `sentry-trace` ", async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + const url = await getLocalTestUrl({ testDir: __dirname }); + const spans = collectStreamedSpans(page); + const [pageload] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [navigation] = await waitForStreamedSpanAndTraceHeaderOnUrl( + page, + `${url}#foo`, + span => getSpanOp(span) === 'navigation', + ); + await page.evaluate(() => (window as any).Sentry.flush()); + + expect(getSpanOp(pageload)).toBe('pageload'); + expect(pageload).toMatchObject({ + parent_span_id: '1121201211212012', + trace_id: '12312012123120121231201212312012', + }); + expect(getSpanOp(navigation)).toBe('navigation'); + expect(navigation.trace_id).toBeDefined(); + expect(navigation.trace_id).not.toBe(pageload.trace_id); + expect(pageload.span_id).toBeDefined(); + expect(navigation.span_id).toBeDefined(); + for (const root of [pageload, navigation]) { + spans + .filter(span => !span.is_segment && span.attributes[SENTRY_SEGMENT_ID]?.value === root.span_id) + .forEach(span => expect(span.parent_span_id).toBe(root.span_id)); + } +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-aborting-pageload/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-aborting-pageload/init.js index f1e482cc6950..fcce12c3dca2 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-aborting-pageload/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-aborting-pageload/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-aborting-pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-aborting-pageload/test.ts index 829710ce5011..c62d4a2a7b3f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-aborting-pageload/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-aborting-pageload/test.ts @@ -1,69 +1,71 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + URL_PATH, + SENTRY_IDLE_SPAN_FINISH_REASON, + USER_AGENT_ORIGINAL, + URL_FULL, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; -sentryTest( - 'should create a navigation transaction that aborts an ongoing pageload', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest('should create a navigation span that aborts an ongoing pageload', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } - const url = await getLocalTestUrl({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( - page, - event => event.contexts?.trace?.op === 'navigation', - ); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - await page.goto(url); + await page.goto(url); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); - const navigationRequest = envelopeRequestParser(await navigationRequestPromise); + const pageloadSpan = await pageloadSpanPromise; + const navigationSpan = await navigationSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - expect(pageloadRequest.contexts?.trace?.op).toBe('pageload'); - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); + expect(getSpanOp(pageloadSpan)).toBe('pageload'); + expect(getSpanOp(navigationSpan)).toBe('navigation'); - expect(navigationRequest.transaction_info?.source).toEqual('url'); + expect(navigationSpan.attributes[SENTRY_SEGMENT_NAME_SOURCE]?.value).toEqual('url'); - const pageloadTraceId = pageloadRequest.contexts?.trace?.trace_id; - const navigationTraceId = navigationRequest.contexts?.trace?.trace_id; + const pageloadTraceId = pageloadSpan.trace_id; + const navigationTraceId = navigationSpan.trace_id; - expect(pageloadTraceId).toBeDefined(); - expect(navigationTraceId).toBeDefined(); - expect(pageloadTraceId).not.toEqual(navigationTraceId); + expect(pageloadTraceId).toBeDefined(); + expect(navigationTraceId).toBeDefined(); + expect(pageloadTraceId).not.toEqual(navigationTraceId); - expect(pageloadRequest.transaction).toEqual('/index.html'); - expect(navigationRequest.transaction).toEqual('/sub-page'); + expect(pageloadSpan.name).toEqual('Pageload'); + expect(pageloadSpan.attributes[URL_PATH]?.value).toEqual('/index.html'); + expect(navigationSpan.name).toEqual('Navigation'); + expect(navigationSpan.attributes[URL_PATH]?.value).toEqual('/sub-page'); - expect(pageloadRequest.contexts?.trace?.data).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'pageload', - ['sentry.idle_span_finish_reason']: 'cancelled', - }); - expect(navigationRequest.contexts?.trace?.data).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.navigation.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'navigation', - ['sentry.idle_span_finish_reason']: 'idleTimeout', - }); - expect(pageloadRequest.request).toEqual({ - headers: { - 'User-Agent': expect.any(String), - }, - url: 'http://sentry-test.io/index.html', - }); - expect(navigationRequest.request).toEqual({ - headers: { - 'User-Agent': expect.any(String), - }, - url: 'http://sentry-test.io/sub-page', - }); - }, -); + expect(pageloadSpan.attributes).toMatchObject({ + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'integer', value: 1 }, + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, + [SENTRY_OP]: { type: 'string', value: 'pageload' }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'cancelled' }, + }); + expect(navigationSpan.attributes).toMatchObject({ + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.navigation.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'integer', value: 1 }, + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, + [SENTRY_OP]: { type: 'string', value: 'navigation' }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'idleTimeout' }, + }); + expect(pageloadSpan.attributes).toMatchObject({ + [USER_AGENT_ORIGINAL]: { type: 'string', value: expect.any(String) }, + [URL_FULL]: { type: 'string', value: 'http://sentry-test.io/index.html' }, + }); + expect(navigationSpan.attributes).toMatchObject({ + [USER_AGENT_ORIGINAL]: { type: 'string', value: expect.any(String) }, + [URL_FULL]: { type: 'string', value: 'http://sentry-test.io/sub-page' }, + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/multiple-redirects/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/multiple-redirects/init.js index b538f322b360..484a12682a2d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/multiple-redirects/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/multiple-redirects/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/multiple-redirects/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/multiple-redirects/test.ts index caa2b6afb328..75f306ef5078 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/multiple-redirects/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/multiple-redirects/test.ts @@ -1,8 +1,13 @@ import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; import { + SENTRY_IDLE_SPAN_FINISH_REASON, + USER_AGENT_ORIGINAL, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, SENTRY_SEGMENT_NAME_SOURCE, URL_FULL, URL_PATH, @@ -17,66 +22,72 @@ sentryTest( sentryTest.skip(); } + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan( page, - event => event.contexts?.trace?.op === 'navigation' && event.transaction === '/next-page', + span => getSpanOp(span) === 'navigation' && span.attributes[URL_PATH]?.value === '/next-page', ); await page.goto(url); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); - const navigationRequest = envelopeRequestParser(await navigationRequestPromise); + const pageloadSpan = await pageloadSpanPromise; + const navigationSpan = await navigationSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - expect(pageloadRequest.contexts?.trace?.op).toBe('pageload'); + expect(getSpanOp(pageloadSpan)).toBe('pageload'); - expect(pageloadRequest.contexts?.trace?.data).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'pageload', - ['sentry.idle_span_finish_reason']: 'cancelled', + expect(pageloadSpan.attributes).toMatchObject({ + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'integer', value: 1 }, + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, + [SENTRY_OP]: { type: 'string', value: 'pageload' }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'cancelled' }, }); - expect(pageloadRequest.request).toEqual({ - headers: { - 'User-Agent': expect.any(String), - }, - url: 'http://sentry-test.io/index.html', + expect(pageloadSpan.attributes).toMatchObject({ + [USER_AGENT_ORIGINAL]: { type: 'string', value: expect.any(String) }, + [URL_FULL]: { type: 'string', value: 'http://sentry-test.io/index.html' }, }); - const spans = pageloadRequest.spans || []; + const spans = allSpans.filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === pageloadSpan.span_id); - const redirectSpans = spans.filter(span => span.op === 'navigation.redirect'); + const redirectSpans = spans.filter(span => getSpanOp(span) === 'navigation.redirect'); expect(redirectSpans).toHaveLength(3); redirectSpans.forEach(redirectSpan => { - expect(redirectSpan?.timestamp).toEqual(redirectSpan?.start_timestamp); - expect(redirectSpan).toEqual({ - data: { - 'sentry.op': 'navigation.redirect', - 'sentry.origin': 'auto.navigation.browser', - [URL_FULL]: expect.any(String), - [URL_PATH]: expect.any(String), + expect(redirectSpan?.end_timestamp).toEqual(redirectSpan?.start_timestamp); + expect(redirectSpan).toMatchObject({ + attributes: { + [SENTRY_OP]: { type: 'string', value: 'navigation.redirect' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.navigation.browser' }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: pageloadSpan.span_id }, + [SENTRY_SEGMENT_NAME]: { type: 'string', value: 'Pageload' }, + [URL_FULL]: { type: 'string', value: expect.any(String) }, + [URL_PATH]: { type: 'string', value: expect.any(String) }, }, - description: expect.stringContaining('/sub-page-redirect-'), - op: 'navigation.redirect', - origin: 'auto.navigation.browser', + name: 'Navigation', + is_segment: false, status: 'ok', - parent_span_id: pageloadRequest.contexts!.trace!.span_id, + parent_span_id: pageloadSpan.span_id, span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), }); }); - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); - expect(navigationRequest.transaction).toEqual('/next-page'); + expect(getSpanOp(navigationSpan)).toBe('navigation'); + expect(navigationSpan.name).toEqual('Navigation'); + expect(navigationSpan.attributes[URL_PATH]?.value).toEqual('/next-page'); // 2 subsequent redirects belonging to the navigation root span - expect(navigationRequest.spans?.filter(span => span.op === 'navigation.redirect')).toHaveLength(2); + expect( + allSpans + .filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === navigationSpan.span_id) + .filter(span => getSpanOp(span) === 'navigation.redirect'), + ).toHaveLength(2); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-click/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-click/init.js index b85590114a76..c3e90ce95430 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-click/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-click/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-click/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-click/test.ts index a88987209bcd..93e77f615917 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-click/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-click/test.ts @@ -1,6 +1,8 @@ +import { URL_PATH, SENTRY_SEGMENT_ID } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( 'creates navigation root span if click happened within 1.5s of the last navigation', @@ -9,39 +11,47 @@ sentryTest( sentryTest.skip(); } + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan( page, - event => event.contexts?.trace?.op === 'navigation' && event.transaction === '/sub-page', + span => getSpanOp(span) === 'navigation' && span.attributes[URL_PATH]?.value === '/sub-page', ); - const navigation2RequestPromise = waitForTransactionRequest( + const navigation2SpanPromise = waitForStreamedSpan( page, - event => event.contexts?.trace?.op === 'navigation' && event.transaction === '/sub-page-2', + span => getSpanOp(span) === 'navigation' && span.attributes[URL_PATH]?.value === '/sub-page-2', ); await page.goto(url); - await pageloadRequestPromise; + await pageloadSpanPromise; // Now trigger navigation (since no span is active), and then a redirect in the navigation, with await page.click('#btn1'); - const navigationRequest = envelopeRequestParser(await navigationRequestPromise); - const navigation2Request = envelopeRequestParser(await navigation2RequestPromise); + const navigationSpan = await navigationSpanPromise; + const navigation2Span = await navigation2SpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); - expect(navigationRequest.transaction).toEqual('/sub-page'); + expect(getSpanOp(navigationSpan)).toBe('navigation'); + expect(navigationSpan.name).toEqual('Navigation'); + expect(navigationSpan.attributes[URL_PATH]?.value).toEqual('/sub-page'); - const spans = (navigationRequest.spans || []).filter(s => s.op === 'navigation.redirect'); + const spans = allSpans + .filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === navigationSpan.span_id) + .filter(s => getSpanOp(s) === 'navigation.redirect'); expect(spans).toHaveLength(0); - expect(navigation2Request.contexts?.trace?.op).toBe('navigation'); - expect(navigation2Request.transaction).toEqual('/sub-page-2'); + expect(getSpanOp(navigation2Span)).toBe('navigation'); + expect(navigation2Span.name).toEqual('Navigation'); + expect(navigation2Span.attributes[URL_PATH]?.value).toEqual('/sub-page-2'); - const spans2 = (navigation2Request.spans || []).filter(s => s.op === 'navigation.redirect'); + const spans2 = allSpans + .filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === navigation2Span.span_id) + .filter(s => getSpanOp(s) === 'navigation.redirect'); expect(spans2).toHaveLength(0); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-keypress/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-keypress/init.js index b53ef315b481..f9d8db46d3d9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-keypress/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-keypress/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-keypress/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-keypress/test.ts index fe152feda462..fe01834e77cb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-keypress/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-navigation-keypress/test.ts @@ -1,6 +1,8 @@ +import { URL_PATH, SENTRY_SEGMENT_ID } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( 'creates a navigation root span if a keypress happened within the last 1.5s', @@ -9,22 +11,23 @@ sentryTest( sentryTest.skip(); } + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan( page, - event => event.contexts?.trace?.op === 'navigation' && event.transaction === '/sub-page', + span => getSpanOp(span) === 'navigation' && span.attributes[URL_PATH]?.value === '/sub-page', ); - const navigationRequest2Promise = waitForTransactionRequest( + const navigationSpan2Promise = waitForStreamedSpan( page, - event => event.contexts?.trace?.op === 'navigation' && event.transaction === '/sub-page-2', + span => getSpanOp(span) === 'navigation' && span.attributes[URL_PATH]?.value === '/sub-page-2', ); await page.goto(url); - await pageloadRequestPromise; + await pageloadSpanPromise; await page.focus('#btn1'); await page.keyboard.press('Enter'); @@ -34,21 +37,29 @@ sentryTest( await page.focus('#btn2'); await page.keyboard.press('Enter'); - const navigationRequest = envelopeRequestParser(await navigationRequestPromise); - const navigationRequest2 = envelopeRequestParser(await navigationRequest2Promise); + const navigationSpan = await navigationSpanPromise; + const navigationSpan2 = await navigationSpan2Promise; + await page.evaluate(() => (window as any).Sentry.flush()); - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); - expect(navigationRequest.transaction).toEqual('/sub-page'); + expect(getSpanOp(navigationSpan)).toBe('navigation'); + expect(navigationSpan.name).toEqual('Navigation'); + expect(navigationSpan.attributes[URL_PATH]?.value).toEqual('/sub-page'); - const redirectSpans = navigationRequest.spans?.filter(span => span.op === 'navigation.redirect') || []; + const redirectSpans = allSpans + .filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === navigationSpan.span_id) + .filter(span => getSpanOp(span) === 'navigation.redirect'); expect(redirectSpans).toHaveLength(1); - expect(redirectSpans[0].description).toEqual('/sub-page-redirect'); + expect(redirectSpans[0].name).toBe('Navigation'); + expect(redirectSpans[0].attributes[URL_PATH]?.value).toBe('/sub-page-redirect'); - expect(navigationRequest2.contexts?.trace?.op).toBe('navigation'); - expect(navigationRequest2.transaction).toEqual('/sub-page-2'); + expect(getSpanOp(navigationSpan2)).toBe('navigation'); + expect(navigationSpan2.name).toEqual('Navigation'); + expect(navigationSpan2.attributes[URL_PATH]?.value).toEqual('/sub-page-2'); - const redirectSpans2 = navigationRequest2.spans?.filter(span => span.op === 'navigation.redirect') || []; + const redirectSpans2 = allSpans + .filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === navigationSpan2.span_id) + .filter(span => getSpanOp(span) === 'navigation.redirect'); expect(redirectSpans2).toHaveLength(0); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-redirect/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-redirect/init.js index 1545e68dd89c..5cf45a1f0b06 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-redirect/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-redirect/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-redirect/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-redirect/test.ts index 71fe35c8bcc9..bab505425ba5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-redirect/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/navigation-redirect/test.ts @@ -1,6 +1,8 @@ +import { URL_PATH, SENTRY_SEGMENT_ID, SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( 'creates a navigation root span and redirect child span if no click happened within the last 1.5s', @@ -9,32 +11,35 @@ sentryTest( sentryTest.skip(); } + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( - page, - event => event.contexts?.trace?.op === 'navigation', - ); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); await page.goto(url); - await pageloadRequestPromise; + await pageloadSpanPromise; // Now trigger navigation (since no span is active), and then a redirect in the navigation, with await page.click('#btn1'); - const navigationRequest = envelopeRequestParser(await navigationRequestPromise); + const navigationSpan = await navigationSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); - expect(navigationRequest.transaction).toEqual('/sub-page'); + expect(getSpanOp(navigationSpan)).toBe('navigation'); + expect(navigationSpan.name).toEqual('Navigation'); + expect(navigationSpan.attributes[URL_PATH]?.value).toEqual('/sub-page'); - const spans = navigationRequest.spans || []; + const spans = allSpans.filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === navigationSpan.span_id); expect(spans).toContainEqual( expect.objectContaining({ - op: 'navigation.redirect', - description: '/sub-page-redirect', + name: 'Navigation', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: 'navigation.redirect' }, + [URL_PATH]: { type: 'string', value: '/sub-page-redirect' }, + }), }), ); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/opt-out/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/opt-out/init.js index bfefd3b54534..331024032a6f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/opt-out/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/opt-out/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/opt-out/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/opt-out/test.ts index a25cc01f086f..ba2c70c2b924 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/opt-out/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/opt-out/test.ts @@ -1,6 +1,8 @@ +import { SENTRY_SEGMENT_ID, SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( "doesn't create a navigation.redirect span if `detectRedirects` is set to false", @@ -9,25 +11,24 @@ sentryTest( sentryTest.skip(); } + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( - page, - event => event.contexts?.trace?.op === 'navigation', - ); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); await page.goto(url); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); + const pageloadSpan = await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); // Ensure a navigation span is sent, too - await navigationRequestPromise; + await navigationSpanPromise; - const spans = pageloadRequest.spans || []; + const spans = allSpans.filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === pageloadSpan.span_id); expect(spans).not.toContainEqual( expect.objectContaining({ - op: 'navigation.redirect', + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'navigation.redirect' } }), }), ); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-click/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-click/init.js index bdd43f5ac757..1ce4125ee422 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-click/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-click/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-click/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-click/test.ts index 771b07afaba6..a7e18331a79d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-click/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-click/test.ts @@ -1,6 +1,8 @@ +import { SENTRY_SEGMENT_ID, SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( "doesn't create a navigation.redirect span if a click happened before navigation", @@ -9,25 +11,24 @@ sentryTest( sentryTest.skip(); } + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( - page, - event => event.contexts?.trace?.op === 'navigation', - ); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); await page.goto(url); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); + const pageloadSpan = await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); // Ensure a navigation span is sent, too - await navigationRequestPromise; + await navigationSpanPromise; - const pageloadTxnSpans = pageloadRequest.spans || []; + const pageloadSpans = allSpans.filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === pageloadSpan.span_id); - expect(pageloadTxnSpans).not.toContainEqual( + expect(pageloadSpans).not.toContainEqual( expect.objectContaining({ - op: 'navigation.redirect', + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'navigation.redirect' } }), }), ); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-keypress/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-keypress/init.js index bdd43f5ac757..1ce4125ee422 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-keypress/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-keypress/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-keypress/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-keypress/test.ts index e77e08936466..e82f4f565293 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-keypress/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-keypress/test.ts @@ -1,6 +1,8 @@ +import { SENTRY_SEGMENT_ID, SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( "doesn't create a navigation.redirect span if a keypress happened before navigation", @@ -9,27 +11,26 @@ sentryTest( sentryTest.skip(); } + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( - page, - event => event.contexts?.trace?.op === 'navigation', - ); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); await page.goto(url); await page.focus('#btn1'); await page.keyboard.press('Enter'); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); + const pageloadSpan = await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); // Ensure a navigation span is sent, too - await navigationRequestPromise; + await navigationSpanPromise; - const spans = pageloadRequest.spans || []; + const spans = allSpans.filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === pageloadSpan.span_id); expect(spans).not.toContainEqual( expect.objectContaining({ - op: 'navigation.redirect', + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'navigation.redirect' } }), }), ); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-time/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-time/init.js index 4aecc7f5ef40..f317a550e2a7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-time/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-time/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-time/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-time/test.ts index 6b5925f52cd1..535bc77adaae 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-time/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-navigation-time/test.ts @@ -1,6 +1,8 @@ +import { SENTRY_SEGMENT_ID, SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( "doesn't create a navigation.redirect span if redirect happened more than 1.5s after pageload", @@ -9,25 +11,24 @@ sentryTest( sentryTest.skip(); } + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( - page, - event => event.contexts?.trace?.op === 'navigation', - ); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); await page.goto(url); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); + const pageloadSpan = await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); // Ensure a navigation span is sent, too - await navigationRequestPromise; + await navigationSpanPromise; - const spans = pageloadRequest.spans || []; + const spans = allSpans.filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === pageloadSpan.span_id); expect(spans).not.toContainEqual( expect.objectContaining({ - op: 'navigation.redirect', + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'navigation.redirect' } }), }), ); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-redirect/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-redirect/init.js index bb91be7762dc..cba0015b22c8 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-redirect/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-redirect/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-redirect/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-redirect/test.ts index fc126da536f3..866e09aa6551 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-redirect/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-redirect/pageload-redirect/test.ts @@ -1,8 +1,13 @@ import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; import { + SENTRY_IDLE_SPAN_FINISH_REASON, + USER_AGENT_ORIGINAL, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, SENTRY_SEGMENT_NAME_SOURCE, URL_FULL, URL_PATH, @@ -15,56 +20,57 @@ sentryTest('creates a pageload root span with navigation.redirect childspan', as sentryTest.skip(); } + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); await page.goto(url); - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); + const pageloadSpan = await pageloadSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - expect(pageloadRequest.contexts?.trace?.op).toBe('pageload'); + expect(getSpanOp(pageloadSpan)).toBe('pageload'); - expect(pageloadRequest.contexts?.trace?.data).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'pageload', - ['sentry.idle_span_finish_reason']: 'idleTimeout', + expect(pageloadSpan.attributes).toMatchObject({ + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'integer', value: 1 }, + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, + [SENTRY_OP]: { type: 'string', value: 'pageload' }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'idleTimeout' }, }); - expect(pageloadRequest.request).toEqual({ - headers: { - 'User-Agent': expect.any(String), - }, - url: 'http://sentry-test.io/index.html', + expect(pageloadSpan.attributes).toMatchObject({ + [USER_AGENT_ORIGINAL]: { type: 'string', value: expect.any(String) }, + [URL_FULL]: { type: 'string', value: 'http://sentry-test.io/index.html' }, }); - const spans = pageloadRequest.spans || []; + const spans = allSpans.filter(span => span.attributes[SENTRY_SEGMENT_ID]?.value === pageloadSpan.span_id); expect(spans).toContainEqual( expect.objectContaining({ - op: 'navigation.redirect', + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'navigation.redirect' } }), }), ); - const redirectSpan = spans.find(span => span.op === 'navigation.redirect'); - expect(redirectSpan?.timestamp).toEqual(redirectSpan?.start_timestamp); - expect(redirectSpan).toEqual({ - data: { - 'sentry.op': 'navigation.redirect', - 'sentry.origin': 'auto.navigation.browser', - [URL_FULL]: 'http://sentry-test.io/sub-page', - [URL_PATH]: '/sub-page', + const redirectSpan = spans.find(span => getSpanOp(span) === 'navigation.redirect'); + expect(redirectSpan?.end_timestamp).toEqual(redirectSpan?.start_timestamp); + expect(redirectSpan).toMatchObject({ + attributes: { + [SENTRY_OP]: { type: 'string', value: 'navigation.redirect' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.navigation.browser' }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: pageloadSpan.span_id }, + [SENTRY_SEGMENT_NAME]: { type: 'string', value: 'Pageload' }, + [URL_FULL]: { type: 'string', value: 'http://sentry-test.io/sub-page' }, + [URL_PATH]: { type: 'string', value: '/sub-page' }, }, - description: '/sub-page', - op: 'navigation.redirect', - origin: 'auto.navigation.browser', + name: 'Navigation', + is_segment: false, status: 'ok', - parent_span_id: pageloadRequest.contexts!.trace!.span_id, + parent_span_id: pageloadSpan.span_id, span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-static/init.js new file mode 100644 index 000000000000..bb6d4918d1bf --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-static/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-static/test.ts new file mode 100644 index 000000000000..bf3324b9809b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-static/test.ts @@ -0,0 +1,156 @@ +import { PAGELOAD, NAVIGATION } from '@sentry/conventions/op'; +import { + SENTRY_IDLE_SPAN_FINISH_REASON, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('should create a navigation transaction on page navigation', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + await page.goto(url); + const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise); + + const navigationRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === NAVIGATION); + await page.goto(`${url}#foo`); + const navigationRequest = envelopeRequestParser(await navigationRequestPromise); + + expect(pageloadRequest.contexts?.trace?.op).toBe(PAGELOAD); + expect(navigationRequest.contexts?.trace?.op).toBe(NAVIGATION); + + expect(navigationRequest.transaction_info?.source).toEqual('url'); + + const pageloadTraceId = pageloadRequest.contexts?.trace?.trace_id; + const navigationTraceId = navigationRequest.contexts?.trace?.trace_id; + + expect(pageloadTraceId).toBeDefined(); + expect(navigationTraceId).toBeDefined(); + expect(pageloadTraceId).not.toEqual(navigationTraceId); + + expect(pageloadRequest.transaction).toEqual('/index.html'); + // Fragment is not in transaction name + expect(navigationRequest.transaction).toEqual('/index.html'); + + expect(pageloadRequest.contexts?.trace?.data).toMatchObject({ + [SENTRY_ORIGIN]: 'auto.pageload.browser', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'url', + [SENTRY_OP]: PAGELOAD, + [SENTRY_IDLE_SPAN_FINISH_REASON]: 'idleTimeout', + }); + expect(navigationRequest.contexts?.trace?.data).toMatchObject({ + [SENTRY_ORIGIN]: 'auto.navigation.browser', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'url', + [SENTRY_OP]: NAVIGATION, + [SENTRY_IDLE_SPAN_FINISH_REASON]: 'idleTimeout', + }); + expect(pageloadRequest.request).toEqual({ + headers: { + 'User-Agent': expect.any(String), + }, + url: 'http://sentry-test.io/index.html', + }); + expect(navigationRequest.request).toEqual({ + headers: { + 'User-Agent': expect.any(String), + }, + url: 'http://sentry-test.io/index.html#foo', + }); + + const pageloadSpans = pageloadRequest.spans; + const navigationSpans = navigationRequest.spans; + + const pageloadSpanId = pageloadRequest.contexts?.trace?.span_id; + const navigationSpanId = navigationRequest.contexts?.trace?.span_id; + + expect(pageloadSpanId).toBeDefined(); + expect(navigationSpanId).toBeDefined(); + + pageloadSpans?.forEach(span => + expect(span).toMatchObject({ + parent_span_id: pageloadSpanId, + }), + ); + + navigationSpans?.forEach(span => + expect(span).toMatchObject({ + parent_span_id: navigationSpanId, + }), + ); + + expect(pageloadSpanId).not.toEqual(navigationSpanId); +}); + +// +sentryTest('should handle pushState with full URL', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + const navigationRequestPromise = waitForTransactionRequest( + page, + event => event.contexts?.trace?.op === NAVIGATION && event.transaction === '/sub-page', + ); + const navigationRequestPromise2 = waitForTransactionRequest( + page, + event => event.contexts?.trace?.op === NAVIGATION && event.transaction === '/sub-page-2', + ); + + await page.goto(url); + await pageloadRequestPromise; + + await page.evaluate("window.history.pushState({}, '', `${window.location.origin}/sub-page`);"); + + const navigationRequest = envelopeRequestParser(await navigationRequestPromise); + + expect(navigationRequest.transaction).toEqual('/sub-page'); + + expect(navigationRequest.contexts?.trace?.data).toMatchObject({ + [SENTRY_ORIGIN]: 'auto.navigation.browser', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'url', + [SENTRY_OP]: NAVIGATION, + [SENTRY_IDLE_SPAN_FINISH_REASON]: 'idleTimeout', + }); + expect(navigationRequest.request).toEqual({ + headers: { + 'User-Agent': expect.any(String), + }, + url: 'http://sentry-test.io/sub-page', + }); + + await page.evaluate("window.history.pushState({}, '', `${window.location.origin}/sub-page-2`);"); + + const navigationRequest2 = envelopeRequestParser(await navigationRequestPromise2); + + expect(navigationRequest2.transaction).toEqual('/sub-page-2'); + + expect(navigationRequest2.contexts?.trace?.data).toMatchObject({ + [SENTRY_ORIGIN]: 'auto.navigation.browser', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'url', + [SENTRY_OP]: NAVIGATION, + [SENTRY_IDLE_SPAN_FINISH_REASON]: 'idleTimeout', + }); + expect(navigationRequest2.request).toEqual({ + headers: { + 'User-Agent': expect.any(String), + }, + url: 'http://sentry-test.io/sub-page-2', + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-streamed/init.js deleted file mode 100644 index a93fc742bafb..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-streamed/init.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-streamed/test.ts deleted file mode 100644 index 62fcd88c337f..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-streamed/test.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { expect } from '@playwright/test'; -import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; -import { - SENTRY_SEGMENT_NAME_SOURCE, - SENTRY_TRACE_LIFECYCLE, - URL_FULL, - URL_PATH, - USER_AGENT_ORIGINAL, - SENTRY_ENVIRONMENT, - SENTRY_SDK_INTEGRATIONS, - SENTRY_OP, - SENTRY_ORIGIN, -} from '@sentry/conventions/attributes'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { - getSpanOp, - getSpansFromEnvelope, - waitForStreamedSpan, - waitForStreamedSpanEnvelope, -} from '../../../../utils/spanUtils'; - -sentryTest('starts a streamed navigation span on page navigation', async ({ browserName, getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - const navigationSpanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'navigation'), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const pageloadSpan = await pageloadSpanPromise; - - // simulate navigation - page.goto(`${url}#foo`); - - const navigationSpanEnvelope = await navigationSpanEnvelopePromise; - - const navigationSpanEnvelopeHeader = navigationSpanEnvelope[0]; - const navigationSpanEnvelopeItem = navigationSpanEnvelope[1]; - const navigationSpans = navigationSpanEnvelopeItem[0][1].items; - const navigationSpan = navigationSpans.find(s => getSpanOp(s) === 'navigation')!; - - expect(navigationSpanEnvelopeHeader).toEqual({ - sent_at: expect.any(String), - trace: { - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - environment: 'production', - public_key: 'public', - sample_rand: expect.any(String), - sample_rate: '1', - sampled: 'true', - }, - sdk: { - name: 'sentry.javascript.browser', - version: SDK_VERSION, - }, - }); - - const numericSampleRand = parseFloat(navigationSpanEnvelopeHeader.trace!.sample_rand!); - expect(Number.isNaN(numericSampleRand)).toBe(false); - - const pageloadTraceId = pageloadSpan.trace_id; - const navigationTraceId = navigationSpan.trace_id; - - expect(pageloadTraceId).toBeDefined(); - expect(navigationTraceId).toBeDefined(); - expect(pageloadTraceId).not.toEqual(navigationTraceId); - - expect(pageloadSpan.name).toEqual('Pageload'); - - expect(navigationSpan).toEqual({ - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - 'culture.calendar': { - type: 'string', - value: expect.any(String), - }, - 'culture.locale': { - type: 'string', - value: expect.any(String), - }, - 'culture.timezone': { - type: 'string', - value: expect.any(String), - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - [URL_FULL]: { - type: 'string', - value: expect.any(String), - }, - [URL_PATH]: { - type: 'string', - value: expect.any(String), - }, - 'device.processor_count': { - type: expect.stringMatching(/^(integer)|(double)$/), - value: expect.any(Number), - }, - ...(browserName !== 'webkit' && { - 'network.connection.effective_type': { - type: 'string', - value: expect.any(String), - }, - 'network.connection.rtt': { - type: expect.stringMatching(/^(integer)|(double)$/), - value: expect.any(Number), - }, - }), - 'sentry.idle_span_finish_reason': { - type: 'string', - value: 'idleTimeout', - }, - [SENTRY_OP]: { - type: 'string', - value: 'navigation', - }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'auto.navigation.browser', - }, - 'sentry.previous_trace': { - type: 'string', - value: `${pageloadTraceId}-${pageloadSpan.span_id}-1`, - }, - 'sentry.sample_rate': { - type: 'integer', - value: 1, - }, - 'sentry.sdk.name': { - type: 'string', - value: 'sentry.javascript.browser', - }, - 'sentry.sdk.version': { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SDK_INTEGRATIONS]: { - type: 'array', - value: expect.arrayContaining(['BrowserTracing', 'SpanStreaming']), - }, - 'sentry.segment.id': { - type: 'string', - value: navigationSpan.span_id, - }, - 'sentry.segment.name': { - type: 'string', - value: 'Navigation', - }, - 'sentry.segment.name.source': { - type: 'string', - value: 'url', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - }, - end_timestamp: expect.any(Number), - is_segment: true, - links: [ - { - attributes: { - 'sentry.link.type': { - type: 'string', - value: 'previous_trace', - }, - }, - sampled: true, - span_id: pageloadSpan.span_id, - trace_id: pageloadTraceId, - }, - ], - // The raw URL stays in `url.path`/`url.full`: with span streaming, a navigation span name is - // low cardinality and falls back to 'Navigation' when there is no parameterized route. - name: 'Navigation', - span_id: navigationSpan.span_id, - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: navigationTraceId, - }); -}); - -sentryTest('handles pushState with full URL', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - const navigationSpan1Promise = waitForStreamedSpan( - page, - // Matched on `url.path` rather than the span name, which is low cardinality. - span => getSpanOp(span) === 'navigation' && span.attributes?.[URL_PATH]?.value === '/sub-page', - ); - const navigationSpan2Promise = waitForStreamedSpan( - page, - span => getSpanOp(span) === 'navigation' && span.attributes?.[URL_PATH]?.value === '/sub-page-2', - ); - - await page.goto(url); - await pageloadSpanPromise; - - await page.evaluate("window.history.pushState({}, '', `${window.location.origin}/sub-page`);"); - - const navigationSpan1 = await navigationSpan1Promise; - - expect(navigationSpan1.name).toEqual('Navigation'); - - expect(navigationSpan1.attributes).toMatchObject({ - [URL_PATH]: { - type: 'string', - value: '/sub-page', - }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'auto.navigation.browser', - }, - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { - type: 'integer', - value: 1, - }, - [SENTRY_SEGMENT_NAME_SOURCE]: { - type: 'string', - value: 'url', - }, - [SENTRY_OP]: { - type: 'string', - value: 'navigation', - }, - }); - - await page.evaluate("window.history.pushState({}, '', `${window.location.origin}/sub-page-2`);"); - - const navigationSpan2 = await navigationSpan2Promise; - - expect(navigationSpan2.name).toEqual('Navigation'); - - expect(navigationSpan2.attributes).toMatchObject({ - [URL_PATH]: { - type: 'string', - value: '/sub-page-2', - }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'auto.navigation.browser', - }, - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { - type: 'integer', - value: 1, - }, - [SENTRY_SEGMENT_NAME_SOURCE]: { - type: 'string', - value: 'url', - }, - [SENTRY_OP]: { - type: 'string', - value: 'navigation', - }, - ['sentry.idle_span_finish_reason']: { - type: 'string', - value: 'idleTimeout', - }, - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation/init.js new file mode 100644 index 000000000000..83076460599f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation/init.js @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation/test.ts index ab8a3931e132..84eaf5b4943a 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation/test.ts @@ -1,150 +1,310 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; +import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; +import { + SENTRY_IDLE_SPAN_FINISH_REASON, + SENTRY_SEGMENT_ID, + SENTRY_IS_LOCALHOST, + CULTURE_CALENDAR, + CULTURE_LOCALE, + CULTURE_TIMEZONE, + DEVICE_PROCESSOR_COUNT, + NETWORK_CONNECTION_EFFECTIVE_TYPE, + NETWORK_CONNECTION_RTT, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_SEGMENT_NAME, + SENTRY_LINK_TYPE, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_TRACE_LIFECYCLE, + URL_FULL, + URL_PATH, + USER_AGENT_ORIGINAL, + SENTRY_ENVIRONMENT, + SENTRY_SDK_INTEGRATIONS, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import { sentryTest } from '../../../../utils/fixtures'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; import { - envelopeRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipTracingTest, - waitForTransactionRequest, -} from '../../../../utils/helpers'; - -sentryTest('should create a navigation transaction on page navigation', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + collectStreamedSpans, + getSpanOp, + getSpansFromEnvelope, + waitForStreamedSpan, + waitForStreamedSpanEnvelope, +} from '../../../../utils/spanUtils'; - const url = await getLocalTestUrl({ testDir: __dirname }); +sentryTest('starts a streamed navigation span on page navigation', async ({ browserName, getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); - const pageloadRequest = await getFirstSentryEnvelopeRequest(page, url); - const navigationRequest = await getFirstSentryEnvelopeRequest(page, `${url}#foo`); + const collectedSpans = collectStreamedSpans(page); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'navigation'), + ); - expect(pageloadRequest.contexts?.trace?.op).toBe('pageload'); - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); - expect(navigationRequest.transaction_info?.source).toEqual('url'); + const pageloadSpan = await pageloadSpanPromise; - const pageloadTraceId = pageloadRequest.contexts?.trace?.trace_id; - const navigationTraceId = navigationRequest.contexts?.trace?.trace_id; + await page.goto(`${url}#foo`); - expect(pageloadTraceId).toBeDefined(); - expect(navigationTraceId).toBeDefined(); - expect(pageloadTraceId).not.toEqual(navigationTraceId); + const navigationSpanEnvelope = await navigationSpanEnvelopePromise; - expect(pageloadRequest.transaction).toEqual('/index.html'); - // Fragment is not in transaction name - expect(navigationRequest.transaction).toEqual('/index.html'); + const navigationSpanEnvelopeHeader = navigationSpanEnvelope[0]; + const navigationSpanEnvelopeItem = navigationSpanEnvelope[1]; + const navigationSpans = navigationSpanEnvelopeItem[0][1].items; + const navigationSpan = navigationSpans.find(s => getSpanOp(s) === 'navigation')!; - expect(pageloadRequest.contexts?.trace?.data).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'pageload', - ['sentry.idle_span_finish_reason']: 'idleTimeout', - }); - expect(navigationRequest.contexts?.trace?.data).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.navigation.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'navigation', - ['sentry.idle_span_finish_reason']: 'idleTimeout', - }); - expect(pageloadRequest.request).toEqual({ - headers: { - 'User-Agent': expect.any(String), + expect(navigationSpanEnvelopeHeader).toEqual({ + sent_at: expect.any(String), + trace: { + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + environment: 'production', + public_key: 'public', + sample_rand: expect.any(String), + sample_rate: '1', + sampled: 'true', }, - url: 'http://sentry-test.io/index.html', - }); - expect(navigationRequest.request).toEqual({ - headers: { - 'User-Agent': expect.any(String), + sdk: { + name: 'sentry.javascript.browser', + version: SDK_VERSION, }, - url: 'http://sentry-test.io/index.html#foo', }); - const pageloadSpans = pageloadRequest.spans; - const navigationSpans = navigationRequest.spans; + const numericSampleRand = parseFloat(navigationSpanEnvelopeHeader.trace!.sample_rand!); + expect(Number.isNaN(numericSampleRand)).toBe(false); - const pageloadSpanId = pageloadRequest.contexts?.trace?.span_id; - const navigationSpanId = navigationRequest.contexts?.trace?.span_id; + const pageloadTraceId = pageloadSpan.trace_id; + const navigationTraceId = navigationSpan.trace_id; - expect(pageloadSpanId).toBeDefined(); - expect(navigationSpanId).toBeDefined(); - - pageloadSpans?.forEach(span => - expect(span).toMatchObject({ - parent_span_id: pageloadSpanId, - }), - ); + expect(pageloadTraceId).toBeDefined(); + expect(navigationTraceId).toBeDefined(); + expect(pageloadTraceId).not.toEqual(navigationTraceId); - navigationSpans?.forEach(span => - expect(span).toMatchObject({ - parent_span_id: navigationSpanId, - }), - ); + expect(pageloadSpan.name).toEqual('Pageload'); + expect(pageloadSpan.attributes).toMatchObject({ + [URL_PATH]: { type: 'string', value: '/index.html' }, + [URL_FULL]: { type: 'string', value: 'http://sentry-test.io/index.html' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'integer', value: 1 }, + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'idleTimeout' }, + }); + expect(pageloadSpan.span_id).not.toBe(navigationSpan.span_id); + await page.evaluate(() => (window as any).Sentry.flush()); + for (const root of [pageloadSpan, navigationSpan]) { + collectedSpans + .filter(span => !span.is_segment && span.attributes[SENTRY_SEGMENT_ID]?.value === root.span_id) + .forEach(span => expect(span.parent_span_id).toBe(root.span_id)); + } - expect(pageloadSpanId).not.toEqual(navigationSpanId); + expect(navigationSpan).toEqual({ + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + [CULTURE_CALENDAR]: { + type: 'string', + value: expect.any(String), + }, + [CULTURE_LOCALE]: { + type: 'string', + value: expect.any(String), + }, + [CULTURE_TIMEZONE]: { + type: 'string', + value: expect.any(String), + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + [URL_FULL]: { + type: 'string', + value: 'http://sentry-test.io/index.html#foo', + }, + [URL_PATH]: { + type: 'string', + value: '/index.html', + }, + [DEVICE_PROCESSOR_COUNT]: { + type: expect.stringMatching(/^(integer)|(double)$/), + value: expect.any(Number), + }, + ...(browserName !== 'webkit' && { + [NETWORK_CONNECTION_EFFECTIVE_TYPE]: { + type: 'string', + value: expect.any(String), + }, + [NETWORK_CONNECTION_RTT]: { + type: expect.stringMatching(/^(integer)|(double)$/), + value: expect.any(Number), + }, + }), + [SENTRY_IDLE_SPAN_FINISH_REASON]: { + type: 'string', + value: 'idleTimeout', + }, + [SENTRY_OP]: { + type: 'string', + value: 'navigation', + }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'auto.navigation.browser', + }, + 'sentry.previous_trace': { + type: 'string', + value: `${pageloadTraceId}-${pageloadSpan.span_id}-1`, + }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { + type: 'integer', + value: 1, + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SDK_INTEGRATIONS]: { + type: 'array', + value: expect.arrayContaining(['BrowserTracing', 'SpanStreaming']), + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: navigationSpan.span_id, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'Navigation', + }, + [SENTRY_SEGMENT_NAME_SOURCE]: { + type: 'string', + value: 'url', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + }, + end_timestamp: expect.any(Number), + is_segment: true, + links: [ + { + attributes: { + [SENTRY_LINK_TYPE]: { + type: 'string', + value: 'previous_trace', + }, + }, + sampled: true, + span_id: pageloadSpan.span_id, + trace_id: pageloadTraceId, + }, + ], + name: 'Navigation', + span_id: navigationSpan.span_id, + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: navigationTraceId, + }); }); -// -sentryTest('should handle pushState with full URL', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest('handles pushState with full URL', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const navigationRequestPromise = waitForTransactionRequest( + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpan1Promise = waitForStreamedSpan( page, - event => event.contexts?.trace?.op === 'navigation' && event.transaction === '/sub-page', + span => getSpanOp(span) === 'navigation' && span.attributes?.[URL_PATH]?.value === '/sub-page', ); - const navigationRequestPromise2 = waitForTransactionRequest( + const navigationSpan2Promise = waitForStreamedSpan( page, - event => event.contexts?.trace?.op === 'navigation' && event.transaction === '/sub-page-2', + span => getSpanOp(span) === 'navigation' && span.attributes?.[URL_PATH]?.value === '/sub-page-2', ); await page.goto(url); - await pageloadRequestPromise; + await pageloadSpanPromise; await page.evaluate("window.history.pushState({}, '', `${window.location.origin}/sub-page`);"); - const navigationRequest = envelopeRequestParser(await navigationRequestPromise); + const navigationSpan1 = await navigationSpan1Promise; - expect(navigationRequest.transaction).toEqual('/sub-page'); - - expect(navigationRequest.contexts?.trace?.data).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.navigation.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'navigation', - ['sentry.idle_span_finish_reason']: 'idleTimeout', + expect(navigationSpan1.name).toEqual('Navigation'); + expect(navigationSpan1.attributes).toMatchObject({ + [URL_FULL]: { type: 'string', value: 'http://sentry-test.io/sub-page' }, + [USER_AGENT_ORIGINAL]: { type: 'string', value: expect.any(String) }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'idleTimeout' }, }); - expect(navigationRequest.request).toEqual({ - headers: { - 'User-Agent': expect.any(String), + + expect(navigationSpan1.attributes).toMatchObject({ + [URL_PATH]: { + type: 'string', + value: '/sub-page', + }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'auto.navigation.browser', + }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { + type: 'integer', + value: 1, + }, + [SENTRY_SEGMENT_NAME_SOURCE]: { + type: 'string', + value: 'url', + }, + [SENTRY_OP]: { + type: 'string', + value: 'navigation', }, - url: 'http://sentry-test.io/sub-page', }); await page.evaluate("window.history.pushState({}, '', `${window.location.origin}/sub-page-2`);"); - const navigationRequest2 = envelopeRequestParser(await navigationRequestPromise2); - - expect(navigationRequest2.transaction).toEqual('/sub-page-2'); + const navigationSpan2 = await navigationSpan2Promise; - expect(navigationRequest2.contexts?.trace?.data).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.navigation.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'navigation', - ['sentry.idle_span_finish_reason']: 'idleTimeout', + expect(navigationSpan2.name).toEqual('Navigation'); + expect(navigationSpan2.attributes).toMatchObject({ + [URL_FULL]: { type: 'string', value: 'http://sentry-test.io/sub-page-2' }, + [USER_AGENT_ORIGINAL]: { type: 'string', value: expect.any(String) }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'idleTimeout' }, }); - expect(navigationRequest2.request).toEqual({ - headers: { - 'User-Agent': expect.any(String), + + expect(navigationSpan2.attributes).toMatchObject({ + [URL_PATH]: { + type: 'string', + value: '/sub-page-2', + }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'auto.navigation.browser', + }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { + type: 'integer', + value: 1, + }, + [SENTRY_SEGMENT_NAME_SOURCE]: { + type: 'string', + value: 'url', + }, + [SENTRY_OP]: { + type: 'string', + value: 'navigation', + }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { + type: 'string', + value: 'idleTimeout', }, - url: 'http://sentry-test.io/sub-page-2', }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-end/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-end/init.js index 25e1637ea608..207473572df5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-end/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-end/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-end/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-end/test.ts index 03bfc13814af..8fd42411f5a9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-end/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-end/test.ts @@ -1,7 +1,8 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; sentryTest('should call onRequestSpanEnd hook', async ({ browserName, getLocalTestUrl, page }) => { const supportedBrowsers = ['chromium', 'firefox']; @@ -35,26 +36,26 @@ sentryTest('should call onRequestSpanEnd hook', async ({ browserName, getLocalTe const url = await getLocalTestUrl({ testDir: __dirname }); - const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); + const spans = collectStreamedSpans(page); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url, span => getSpanOp(span) === 'pageload'); + await page.evaluate(() => (window as any).Sentry.flush()); - const tracingEvent = envelopes[envelopes.length - 1]; // last envelope contains tracing data on all browsers - - expect(tracingEvent.spans).toContainEqual( + expect(spans).toContainEqual( expect.objectContaining({ - op: 'http.client', - data: expect.objectContaining({ - type: 'xhr', - 'hook.called.response-type': 'xhr', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: 'http.client' }, + type: { type: 'string', value: 'xhr' }, + 'hook.called.response-type': { type: 'string', value: 'xhr' }, }), }), ); - expect(tracingEvent.spans).toContainEqual( + expect(spans).toContainEqual( expect.objectContaining({ - op: 'http.client', - data: expect.objectContaining({ - type: 'fetch', - 'hook.called.response-type': 'fetch', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: 'http.client' }, + type: { type: 'string', value: 'fetch' }, + 'hook.called.response-type': { type: 'string', value: 'fetch' }, }), }), ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-start/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-start/init.js index a24444de55b1..680dfd7304df 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-start/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-start/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-start/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-start/test.ts index d1d90a698a77..c21fa92e97a3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-start/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/on-request-span-start/test.ts @@ -1,7 +1,8 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; sentryTest('should call onRequestSpanStart hook', async ({ browserName, getLocalTestUrl, page }) => { const supportedBrowsers = ['chromium', 'firefox']; @@ -27,24 +28,24 @@ sentryTest('should call onRequestSpanStart hook', async ({ browserName, getLocal const url = await getLocalTestUrl({ testDir: __dirname }); - const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); + const spans = collectStreamedSpans(page); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url, span => getSpanOp(span) === 'pageload'); + await page.evaluate(() => (window as any).Sentry.flush()); - const tracingEvent = envelopes[envelopes.length - 1]; // last envelope contains tracing data on all browsers - - expect(tracingEvent.spans).toContainEqual( + expect(spans).toContainEqual( expect.objectContaining({ - op: 'http.client', - data: expect.objectContaining({ - 'hook.called.headers': 'xhr', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: 'http.client' }, + 'hook.called.headers': { type: 'string', value: 'xhr' }, }), }), ); - expect(tracingEvent.spans).toContainEqual( + expect(spans).toContainEqual( expect.objectContaining({ - op: 'http.client', - data: expect.objectContaining({ - 'hook.called.headers': 'fetch', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: 'http.client' }, + 'hook.called.headers': { type: 'string', value: 'fetch' }, }), }), ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-sensitive-query-params-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-sensitive-query-params-static/init.js new file mode 100644 index 000000000000..bb6d4918d1bf --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-sensitive-query-params-static/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-sensitive-query-params-static/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-sensitive-query-params-static/template.html new file mode 100644 index 000000000000..3d3b49a9951f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-sensitive-query-params-static/template.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-sensitive-query-params-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-sensitive-query-params-static/test.ts new file mode 100644 index 000000000000..8bfc5a6df874 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-sensitive-query-params-static/test.ts @@ -0,0 +1,32 @@ +import { BROWSER_REQUEST, PAGELOAD, RESOURCE_IMG } from '@sentry/conventions/op'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest( + 'filters sensitive query params in the pageload transaction URL and span descriptions', + async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + + await page.route('https://sentry-test-site.example/path/to/image.svg?*', route => + route.fulfill({ body: '', contentType: 'image/svg+xml' }), + ); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + await page.goto(`${url}?jwt=sensitive-jwt-value&page=1`); + const eventData = envelopeRequestParser(await pageloadRequestPromise); + + expect(eventData.request?.url).toBe(`${url}?jwt=[Filtered]&page=1`); + expect(eventData.spans?.find(({ op }) => op === BROWSER_REQUEST)?.description).toBe(`${url}?jwt=[Filtered]&page=1`); + expect(eventData.spans?.find(({ op }) => op === RESOURCE_IMG)?.description).toBe( + 'https://sentry-test-site.example/path/to/image.svg?token=[Filtered]&size=2', + ); + + const serializedEvent = JSON.stringify(eventData); + expect(serializedEvent).not.toContain('sensitive-jwt-value'); + expect(serializedEvent).not.toContain('sensitive-token-value'); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-static/init.js similarity index 65% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-static/init.js index bd3b6ed17872..b74e6633eadb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-static/init.js @@ -4,8 +4,8 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], - traceLifecycle: 'stream', + integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-static/test.ts new file mode 100644 index 000000000000..2f89771057f7 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-static/test.ts @@ -0,0 +1,43 @@ +import { PAGELOAD } from '@sentry/conventions/op'; +import { + SENTRY_IDLE_SPAN_FINISH_REASON, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('creates a pageload transaction with url as source', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + await page.goto(url); + const eventData = envelopeRequestParser(await pageloadRequestPromise); + const timeOrigin = await page.evaluate('window._testBaseTimestamp'); + + const { start_timestamp: startTimestamp } = eventData; + + const traceContextData = eventData.contexts?.trace?.data; + + expect(startTimestamp).toBeCloseTo(timeOrigin, 1); + + expect(traceContextData).toMatchObject({ + [SENTRY_ORIGIN]: 'auto.pageload.browser', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'url', + [SENTRY_OP]: PAGELOAD, + [SENTRY_IDLE_SPAN_FINISH_REASON]: 'idleTimeout', + }); + + expect(eventData.contexts?.trace?.op).toBe(PAGELOAD); + expect(eventData.spans?.length).toBeGreaterThan(0); + expect(eventData.transaction_info?.source).toEqual('url'); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-streamed/test.ts deleted file mode 100644 index 717011ffb9c3..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-streamed/test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { expect } from '@playwright/test'; -import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; -import { - BROWSER_NAVIGATION_TYPE, - SENTRY_SEGMENT_NAME_SOURCE, - SENTRY_SEGMENT_ID, - SENTRY_SEGMENT_NAME, - SENTRY_SDK_NAME, - SENTRY_SDK_VERSION, - SENTRY_TRACE_LIFECYCLE, - URL_FULL, - URL_PATH, - USER_AGENT_ORIGINAL, - SENTRY_ENVIRONMENT, - SENTRY_SDK_INTEGRATIONS, - SENTRY_OP, - SENTRY_ORIGIN, -} from '@sentry/conventions/attributes'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, getSpansFromEnvelope, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; - -sentryTest( - 'creates a pageload streamed span envelope with url as pageload span name source', - async ({ browserName, getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const spanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'pageload'), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const spanEnvelope = await spanEnvelopePromise; - const envelopeHeader = spanEnvelope[0]; - const envelopeItem = spanEnvelope[1]; - const spans = envelopeItem[0][1].items; - const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload'); - - const timeOrigin = await page.evaluate('window._testBaseTimestamp'); - - expect(envelopeHeader).toEqual({ - sdk: { - name: 'sentry.javascript.browser', - version: SDK_VERSION, - }, - sent_at: expect.any(String), - trace: { - environment: 'production', - public_key: 'public', - sample_rand: expect.any(String), - sample_rate: '1', - sampled: 'true', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - }, - }); - - const numericSampleRand = parseFloat(envelopeHeader.trace!.sample_rand!); - const traceId = envelopeHeader.trace!.trace_id; - - expect(Number.isNaN(numericSampleRand)).toBe(false); - - expect(envelopeItem[0][0].item_count).toBeGreaterThan(1); - - expect(pageloadSpan?.start_timestamp).toBeCloseTo(timeOrigin, 1); - - expect(pageloadSpan).toEqual({ - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - 'culture.calendar': { - type: 'string', - value: expect.any(String), - }, - 'culture.locale': { - type: 'string', - value: expect.any(String), - }, - 'culture.timezone': { - type: 'string', - value: expect.any(String), - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - [URL_FULL]: { - type: 'string', - value: expect.any(String), - }, - [URL_PATH]: { - type: 'string', - value: expect.any(String), - }, - // formerly known as 'hardwareConcurrency' - 'device.processor_count': { - type: expect.stringMatching(/^(integer)|(double)$/), - value: expect.any(Number), - }, - 'browser.performance.navigation.activation_start': { - type: expect.stringMatching(/^(integer)|(double)$/), - value: expect.any(Number), - }, - 'browser.performance.time_origin': { - type: expect.stringMatching(/^(integer)|(double)$/), - value: expect.any(Number), - }, - 'browser.web_vital.ttfb.request_time': { - type: expect.stringMatching(/^(integer)|(double)$/), - value: expect.any(Number), - }, - ...(browserName !== 'webkit' && { - // formerly known as 'effectiveConnectionType' - 'network.connection.effective_type': { - type: 'string', - value: expect.any(String), - }, - 'network.connection.rtt': { - type: expect.stringMatching(/^(integer)|(double)$/), - value: expect.any(Number), - }, - 'browser.web_vital.ttfb.value': { - type: expect.stringMatching(/^(integer)|(double)$/), - value: expect.any(Number), - }, - [BROWSER_NAVIGATION_TYPE]: { - type: 'string', - value: 'navigate', - }, - }), - 'sentry.idle_span_finish_reason': { - type: 'string', - value: 'idleTimeout', - }, - [SENTRY_OP]: { - type: 'string', - value: 'pageload', - }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'auto.pageload.browser', - }, - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { - type: 'integer', - value: 1, - }, - [SENTRY_SDK_NAME]: { - type: 'string', - value: 'sentry.javascript.browser', - }, - [SENTRY_SDK_VERSION]: { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SDK_INTEGRATIONS]: { - type: 'array', - value: expect.arrayContaining(['BrowserTracing', 'SpanStreaming']), - }, - [SENTRY_SEGMENT_ID]: { - type: 'string', - value: pageloadSpan?.span_id, - }, - [SENTRY_SEGMENT_NAME]: { - type: 'string', - value: 'Pageload', - }, - [SENTRY_SEGMENT_NAME_SOURCE]: { - type: 'string', - value: 'url', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - }, - end_timestamp: expect.any(Number), - is_segment: true, - // The raw URL stays in `url.path`/`url.full`: with span streaming, a pageload span name is - // low cardinality and falls back to 'Pageload' when there is no parameterized route. - name: 'Pageload', - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: traceId, - }); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-update-txn-name/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-update-txn-name/init.js index b74e6633eadb..1f0b64911a75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-update-txn-name/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-update-txn-name/init.js @@ -4,7 +4,6 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-update-txn-name/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-update-txn-name/test.ts index 0776668fb9e8..232565e32f9f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-update-txn-name/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-update-txn-name/test.ts @@ -1,12 +1,13 @@ import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; -import { type Event, SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; sentryTest( - 'sets the source to custom when updating the transaction name with `span.updateName`', + 'sets the source to custom when updating the span name with `span.updateName`', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -14,25 +15,31 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const spans = collectStreamedSpans(page); + const [pageloadSpan] = await waitForStreamedSpanAndTraceHeaderOnUrl( + page, + url, + span => getSpanOp(span) === 'pageload', + ); + await page.evaluate(() => (window as any).Sentry.flush()); - const traceContextData = eventData.contexts?.trace?.data; + const attributes = pageloadSpan.attributes; - expect(traceContextData).toBeDefined(); + expect(attributes).toBeDefined(); - expect(eventData.transaction).toBe('new name'); + expect(pageloadSpan.name).toBe('new name'); - expect(traceContextData).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'custom', - [SENTRY_OP]: 'pageload', + expect(pageloadSpan.attributes).toMatchObject({ + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'integer', value: 1 }, + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'custom' }, + [SENTRY_OP]: { type: 'string', value: 'pageload' }, }); - expect(traceContextData![SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined(); + expect(attributes[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined(); - expect(eventData.contexts?.trace?.op).toBe('pageload'); - expect(eventData.spans?.length).toBeGreaterThan(0); - expect(eventData.transaction_info?.source).toEqual('custom'); + expect(getSpanOp(pageloadSpan)).toBe('pageload'); + expect(spans.filter(span => !span.is_segment).length).toBeGreaterThan(0); + expect(pageloadSpan.attributes[SENTRY_SEGMENT_NAME_SOURCE]?.value).toEqual('custom'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName-static/init.js similarity index 65% rename from dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName-static/init.js index bd3b6ed17872..b74e6633eadb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName-static/init.js @@ -4,8 +4,8 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], - traceLifecycle: 'stream', + integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName-static/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName-static/subject.js new file mode 100644 index 000000000000..7f0ad0fea340 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName-static/subject.js @@ -0,0 +1,4 @@ +const activeSpan = Sentry.getActiveSpan(); +const rootSpan = activeSpan && Sentry.getRootSpan(activeSpan); + +Sentry.updateSpanName(rootSpan, 'new name'); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName-static/test.ts new file mode 100644 index 000000000000..ab1b1ed20c46 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName-static/test.ts @@ -0,0 +1,38 @@ +import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; +import { type Event, SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'sets the source to custom when updating the transaction name with Sentry.updateSpanName', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + const traceContextData = eventData.contexts?.trace?.data; + + expect(traceContextData).toBeDefined(); + + expect(traceContextData).toMatchObject({ + [SENTRY_ORIGIN]: 'auto.pageload.browser', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'custom', + [SENTRY_OP]: 'pageload', + }); + + expect(traceContextData![SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined(); + + expect(eventData.transaction).toBe('new name'); + + expect(eventData.contexts?.trace?.op).toBe('pageload'); + expect(eventData.spans?.length).toBeGreaterThan(0); + expect(eventData.transaction_info?.source).toEqual('custom'); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName/init.js index b74e6633eadb..1f0b64911a75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName/init.js @@ -4,7 +4,6 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName/test.ts index ab1b1ed20c46..b62585b2f881 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload-updateSpanName/test.ts @@ -1,12 +1,13 @@ import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; -import { type Event, SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; sentryTest( - 'sets the source to custom when updating the transaction name with Sentry.updateSpanName', + 'sets the source to custom when updating the span name with Sentry.updateSpanName', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -14,25 +15,31 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const spans = collectStreamedSpans(page); + const [pageloadSpan] = await waitForStreamedSpanAndTraceHeaderOnUrl( + page, + url, + span => getSpanOp(span) === 'pageload', + ); + await page.evaluate(() => (window as any).Sentry.flush()); - const traceContextData = eventData.contexts?.trace?.data; + const attributes = pageloadSpan.attributes; - expect(traceContextData).toBeDefined(); + expect(attributes).toBeDefined(); - expect(traceContextData).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'custom', - [SENTRY_OP]: 'pageload', + expect(pageloadSpan.attributes).toMatchObject({ + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'integer', value: 1 }, + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'custom' }, + [SENTRY_OP]: { type: 'string', value: 'pageload' }, }); - expect(traceContextData![SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined(); + expect(attributes[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined(); - expect(eventData.transaction).toBe('new name'); + expect(pageloadSpan.name).toBe('new name'); - expect(eventData.contexts?.trace?.op).toBe('pageload'); - expect(eventData.spans?.length).toBeGreaterThan(0); - expect(eventData.transaction_info?.source).toEqual('custom'); + expect(getSpanOp(pageloadSpan)).toBe('pageload'); + expect(spans.filter(span => !span.is_segment).length).toBeGreaterThan(0); + expect(pageloadSpan.attributes[SENTRY_SEGMENT_NAME_SOURCE]?.value).toEqual('custom'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload/init.js index b74e6633eadb..1f0b64911a75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload/init.js @@ -4,7 +4,6 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload/test.ts index 8e7c3a4b2480..83e6991b114c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageload/test.ts @@ -1,35 +1,208 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; -import type { Event } from '@sentry/core'; +import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; +import { + SENTRY_IS_LOCALHOST, + CULTURE_CALENDAR, + CULTURE_LOCALE, + CULTURE_TIMEZONE, + DEVICE_PROCESSOR_COUNT, + BROWSER_PERFORMANCE_NAVIGATION_ACTIVATION_START, + BROWSER_PERFORMANCE_TIME_ORIGIN, + BROWSER_WEB_VITAL_TTFB_REQUEST_TIME, + NETWORK_CONNECTION_EFFECTIVE_TYPE, + NETWORK_CONNECTION_RTT, + BROWSER_WEB_VITAL_TTFB_VALUE, + SENTRY_IDLE_SPAN_FINISH_REASON, + BROWSER_NAVIGATION_TYPE, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_TRACE_LIFECYCLE, + URL_FULL, + URL_PATH, + USER_AGENT_ORIGINAL, + SENTRY_ENVIRONMENT, + SENTRY_SDK_INTEGRATIONS, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { + collectStreamedSpans, + getSpanOp, + getSpansFromEnvelope, + waitForStreamedSpanEnvelope, +} from '../../../../utils/spanUtils'; -sentryTest('creates a pageload transaction with url as source', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest( + 'creates a pageload streamed span envelope with url as pageload span name source', + async ({ browserName, getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); - const url = await getLocalTestUrl({ testDir: __dirname }); + const collectedSpans = collectStreamedSpans(page); + const spanEnvelopePromise = waitForStreamedSpanEnvelope( + page, + env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'pageload'), + ); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const timeOrigin = await page.evaluate('window._testBaseTimestamp'); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); - const { start_timestamp: startTimestamp } = eventData; + const spanEnvelope = await spanEnvelopePromise; + const envelopeHeader = spanEnvelope[0]; + const envelopeItem = spanEnvelope[1]; + const spans = envelopeItem[0][1].items; + const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload'); - const traceContextData = eventData.contexts?.trace?.data; + const timeOrigin = await page.evaluate('window._testBaseTimestamp'); - expect(startTimestamp).toBeCloseTo(timeOrigin, 1); + expect(envelopeHeader).toEqual({ + sdk: { + name: 'sentry.javascript.browser', + version: SDK_VERSION, + }, + sent_at: expect.any(String), + trace: { + environment: 'production', + public_key: 'public', + sample_rand: expect.any(String), + sample_rate: '1', + sampled: 'true', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + }, + }); - expect(traceContextData).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'pageload', - ['sentry.idle_span_finish_reason']: 'idleTimeout', - }); + const numericSampleRand = parseFloat(envelopeHeader.trace!.sample_rand!); + const traceId = envelopeHeader.trace!.trace_id; - expect(eventData.contexts?.trace?.op).toBe('pageload'); - expect(eventData.spans?.length).toBeGreaterThan(0); - expect(eventData.transaction_info?.source).toEqual('url'); -}); + expect(Number.isNaN(numericSampleRand)).toBe(false); + + expect(envelopeItem[0][0].item_count).toBe(spans.length); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(collectedSpans.filter(span => !span.is_segment)).not.toHaveLength(0); + + expect(pageloadSpan?.start_timestamp).toBeCloseTo(timeOrigin, 1); + + expect(pageloadSpan).toEqual({ + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [CULTURE_CALENDAR]: { + type: 'string', + value: expect.any(String), + }, + [CULTURE_LOCALE]: { + type: 'string', + value: expect.any(String), + }, + [CULTURE_TIMEZONE]: { + type: 'string', + value: expect.any(String), + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + [URL_FULL]: { + type: 'string', + value: 'http://sentry-test.io/index.html', + }, + [URL_PATH]: { + type: 'string', + value: '/index.html', + }, + [DEVICE_PROCESSOR_COUNT]: { + type: expect.stringMatching(/^(integer)|(double)$/), + value: expect.any(Number), + }, + [BROWSER_PERFORMANCE_NAVIGATION_ACTIVATION_START]: { + type: expect.stringMatching(/^(integer)|(double)$/), + value: expect.any(Number), + }, + [BROWSER_PERFORMANCE_TIME_ORIGIN]: { + type: expect.stringMatching(/^(integer)|(double)$/), + value: expect.any(Number), + }, + [BROWSER_WEB_VITAL_TTFB_REQUEST_TIME]: { + type: expect.stringMatching(/^(integer)|(double)$/), + value: expect.any(Number), + }, + ...(browserName !== 'webkit' && { + [NETWORK_CONNECTION_EFFECTIVE_TYPE]: { + type: 'string', + value: expect.any(String), + }, + [NETWORK_CONNECTION_RTT]: { + type: expect.stringMatching(/^(integer)|(double)$/), + value: expect.any(Number), + }, + [BROWSER_WEB_VITAL_TTFB_VALUE]: { + type: expect.stringMatching(/^(integer)|(double)$/), + value: expect.any(Number), + }, + [BROWSER_NAVIGATION_TYPE]: { + type: 'string', + value: 'navigate', + }, + }), + [SENTRY_IDLE_SPAN_FINISH_REASON]: { + type: 'string', + value: 'idleTimeout', + }, + [SENTRY_OP]: { + type: 'string', + value: 'pageload', + }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'auto.pageload.browser', + }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { + type: 'integer', + value: 1, + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SDK_INTEGRATIONS]: { + type: 'array', + value: expect.arrayContaining(['BrowserTracing', 'SpanStreaming']), + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: pageloadSpan?.span_id, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'Pageload', + }, + [SENTRY_SEGMENT_NAME_SOURCE]: { + type: 'string', + value: 'url', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + }, + end_timestamp: expect.any(Number), + is_segment: true, + name: 'Pageload', + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: traceId, + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadDelayed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadDelayed/init.js index ba739b4b8cc5..2c5a44a7f76d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadDelayed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadDelayed/init.js @@ -6,7 +6,6 @@ window._testBaseTimestamp = performance.timeOrigin / 1000; setTimeout(() => { window._testTimeoutTimestamp = (performance.timeOrigin + performance.now()) / 1000; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadDelayed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadDelayed/test.ts index 0d608a782314..2b09200481b7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadDelayed/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadDelayed/test.ts @@ -1,25 +1,32 @@ +import { SENTRY_SEGMENT_NAME_SOURCE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; -sentryTest('should create a pageload transaction when initialized delayed', async ({ getLocalTestUrl, page }) => { +sentryTest('should create a pageload span when initialized delayed', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const spans = collectStreamedSpans(page); + const [pageloadSpan] = await waitForStreamedSpanAndTraceHeaderOnUrl( + page, + url, + span => getSpanOp(span) === 'pageload', + ); + await page.evaluate(() => (window as any).Sentry.flush()); const timeOrigin = await page.evaluate('window._testBaseTimestamp'); const timeoutTimestamp = await page.evaluate('window._testTimeoutTimestamp'); - const { start_timestamp: startTimestamp } = eventData; + const { start_timestamp: startTimestamp } = pageloadSpan; expect(startTimestamp).toBeCloseTo(timeOrigin, 1); expect(startTimestamp).toBeLessThan(timeoutTimestamp); - expect(eventData.contexts?.trace?.op).toBe('pageload'); - expect(eventData.spans?.length).toBeGreaterThan(0); - expect(eventData.transaction_info?.source).toEqual('url'); + expect(getSpanOp(pageloadSpan)).toBe('pageload'); + expect(spans.filter(span => !span.is_segment).length).toBeGreaterThan(0); + expect(pageloadSpan.attributes[SENTRY_SEGMENT_NAME_SOURCE]?.value).toEqual('url'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithChildSpanTimeout/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithChildSpanTimeout/init.js index b2820c740c2b..229372e215c7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithChildSpanTimeout/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithChildSpanTimeout/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithChildSpanTimeout/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithChildSpanTimeout/test.ts index 16f5a2f7f539..daf8048d6622 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithChildSpanTimeout/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithChildSpanTimeout/test.ts @@ -1,28 +1,33 @@ +import { SENTRY_IDLE_SPAN_FINISH_REASON } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { - envelopeRequestParser, - shouldSkipTracingTest, - waitForTransactionRequestOnUrl, -} from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; -// This tests asserts that the pageload span will finish itself after the child span timeout if it -// has a child span without adding any additional ones or finishing any of them finishing. All of the child spans that -// are still running should have the status "cancelled". sentryTest('should send a pageload span terminated via child span timeout', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } const url = await getLocalTestUrl({ testDir: __dirname }); - const req = await waitForTransactionRequestOnUrl(page, url); + const spans = collectStreamedSpans(page); + const [pageloadSpan] = await waitForStreamedSpanAndTraceHeaderOnUrl( + page, + url, + span => getSpanOp(span) === 'pageload', + ); + await page.evaluate(() => (window as any).Sentry.flush()); - const eventData = envelopeRequestParser(req); - - expect(eventData.contexts?.trace?.op).toBe('pageload'); - expect(eventData.contexts?.trace?.data?.['sentry.idle_span_discarded_spans']).toBeUndefined(); - expect(eventData.spans?.length).toBeGreaterThanOrEqual(1); - const testSpan = eventData.spans?.find(span => span.description === 'pageload-child-span'); + expect(getSpanOp(pageloadSpan)).toBe('pageload'); + expect(pageloadSpan.attributes['sentry.idle_span_discarded_spans']).toBeUndefined(); + expect(spans.length).toBeGreaterThanOrEqual(2); + const testSpan = spans.find(span => span.name === 'pageload-child-span'); expect(testSpan).toBeDefined(); - expect(testSpan?.status).toBe('cancelled'); + expect(testSpan?.status).toBe('ok'); + expect(pageloadSpan.attributes[SENTRY_IDLE_SPAN_FINISH_REASON]?.value).toBe('heartbeatFailed'); + expect(testSpan).toMatchObject({ + parent_span_id: pageloadSpan.span_id, + trace_id: pageloadSpan.trace_id, + end_timestamp: pageloadSpan.end_timestamp, + }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/default/init.js similarity index 86% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/default/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/default/init.js index ded3ca204b6b..054a40d79664 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/default/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/default/init.js @@ -4,8 +4,9 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true }), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true })], tracesSampleRate: 1, debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/default/test.ts similarity index 50% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/default/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/default/test.ts index 3c4a37bc9de3..a20d10297c4b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/default/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/default/test.ts @@ -7,30 +7,32 @@ import { import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; sentryTest( 'waits for Sentry.reportPageLoaded() to be called when `enableReportPageLoaded` is true', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } - const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadEventPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const pageloadSpan = await pageloadSpanPromise; + const eventData = envelopeRequestParser(await pageloadEventPromise); - const spanDurationSeconds = pageloadSpan.end_timestamp - pageloadSpan.start_timestamp; + const traceContextData = eventData.contexts?.trace?.data; + const spanDurationSeconds = eventData.timestamp! - eventData.start_timestamp!; - expect(pageloadSpan.attributes).toMatchObject({ - [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: expect.objectContaining({ value: 1 }), - [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, - [SENTRY_OP]: { type: 'string', value: 'pageload' }, - [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'reportPageLoaded' }, + expect(traceContextData).toMatchObject({ + [SENTRY_ORIGIN]: 'auto.pageload.browser', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'url', + [SENTRY_OP]: 'pageload', + [SENTRY_IDLE_SPAN_FINISH_REASON]: 'reportPageLoaded', }); // We wait for 2.5 seconds before calling Sentry.reportPageLoaded(). The span starts at navigation start, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/finalTimeout/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/finalTimeout/init.js similarity index 65% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/finalTimeout/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/finalTimeout/init.js index b1c19f779713..2f633a9c6f1e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/finalTimeout/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/finalTimeout/init.js @@ -4,11 +4,9 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - Sentry.browserTracingIntegration({ enableReportPageLoaded: true, finalTimeout: 3000 }), - Sentry.spanStreamingIntegration(), - ], + integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true, finalTimeout: 3000 })], tracesSampleRate: 1, debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/finalTimeout/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/finalTimeout/test.ts new file mode 100644 index 000000000000..8244b6f83c7a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/finalTimeout/test.ts @@ -0,0 +1,43 @@ +import { + SENTRY_IDLE_SPAN_FINISH_REASON, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; + +sentryTest( + 'final timeout cancels the pageload span even if `enableReportPageLoaded` is true', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const pageloadEventPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const eventData = envelopeRequestParser(await pageloadEventPromise); + + const traceContextData = eventData.contexts?.trace?.data; + const spanDurationSeconds = eventData.timestamp! - eventData.start_timestamp!; + + expect(traceContextData).toMatchObject({ + [SENTRY_ORIGIN]: 'auto.pageload.browser', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'url', + [SENTRY_OP]: 'pageload', + [SENTRY_IDLE_SPAN_FINISH_REASON]: 'finalTimeout', + }); + + // We wait for 3 seconds before calling Sentry.reportPageLoaded() + // the margins are to account for timing weirdness in CI to avoid flakes + expect(spanDurationSeconds).toBeGreaterThan(2.5); + expect(spanDurationSeconds).toBeLessThan(3.5); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/navigation/init.js similarity index 71% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/navigation/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/navigation/init.js index ac42880742a3..3e937c873954 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/navigation/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/navigation/init.js @@ -4,11 +4,9 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - Sentry.browserTracingIntegration({ enableReportPageLoaded: true, instrumentNavigation: false }), - Sentry.spanStreamingIntegration(), - ], + integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true, instrumentNavigation: false })], tracesSampleRate: 1, debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/navigation/test.ts new file mode 100644 index 000000000000..376c35d25a7f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-static/navigation/test.ts @@ -0,0 +1,41 @@ +import { + SENTRY_IDLE_SPAN_FINISH_REASON, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; +import { sentryTest } from '../../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; + +sentryTest( + 'starting a navigation span cancels the pageload span even if `enableReportPageLoaded` is true', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const pageloadEventPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const eventData = envelopeRequestParser(await pageloadEventPromise); + + const traceContextData = eventData.contexts?.trace?.data; + const spanDurationSeconds = eventData.timestamp! - eventData.start_timestamp!; + + expect(traceContextData).toMatchObject({ + [SENTRY_ORIGIN]: 'auto.pageload.browser', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'url', + [SENTRY_OP]: 'pageload', + [SENTRY_IDLE_SPAN_FINISH_REASON]: 'cancelled', + }); + + // ending span after 1s but adding a margin of 0.5s to account for timing weirdness in CI to avoid flakes + expect(spanDurationSeconds).toBeLessThan(1.5); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/finalTimeout/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/finalTimeout/test.ts deleted file mode 100644 index 8ea2ea1ca842..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/finalTimeout/test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; -import { expect } from '@playwright/test'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; - -sentryTest( - 'final timeout cancels the pageload span even if `enableReportPageLoaded` is true', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - - await page.goto(url); - - const pageloadSpan = await pageloadSpanPromise; - - const spanDurationSeconds = pageloadSpan.end_timestamp - pageloadSpan.start_timestamp; - - expect(pageloadSpan.attributes).toMatchObject({ - [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: expect.objectContaining({ value: 1 }), - [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, - [SENTRY_OP]: { type: 'string', value: 'pageload' }, - 'sentry.idle_span_finish_reason': { type: 'string', value: 'finalTimeout' }, - }); - - // We wait for 3 seconds before calling Sentry.reportPageLoaded() - // the margins are to account for timing weirdness in CI to avoid flakes - expect(spanDurationSeconds).toBeGreaterThan(2.5); - expect(spanDurationSeconds).toBeLessThan(3.5); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/navigation/test.ts deleted file mode 100644 index db6f6737ccd6..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded-streamed/navigation/test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; -import { expect } from '@playwright/test'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; - -sentryTest( - 'starting a navigation span cancels the pageload span even if `enableReportPageLoaded` is true', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - - await page.goto(url); - - const pageloadSpan = await pageloadSpanPromise; - - const spanDurationSeconds = pageloadSpan.end_timestamp - pageloadSpan.start_timestamp; - - expect(pageloadSpan.attributes).toMatchObject({ - [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: expect.objectContaining({ value: 1 }), - [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, - [SENTRY_OP]: { type: 'string', value: 'pageload' }, - 'sentry.idle_span_finish_reason': { type: 'string', value: 'cancelled' }, - }); - - // ending span after 1s but adding a margin of 0.5s to account for timing weirdness in CI to avoid flakes - expect(spanDurationSeconds).toBeLessThan(1.5); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/default/init.js index 054a40d79664..39a212ae5fa5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/default/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/default/init.js @@ -4,7 +4,6 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true })], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/default/test.ts index bca0d6052b95..52fb62fbec10 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/default/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/default/test.ts @@ -1,33 +1,37 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_IDLE_SPAN_FINISH_REASON, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; + import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( 'waits for Sentry.reportPageLoaded() to be called when `enableReportPageLoaded` is true', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const pageloadEventPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); - const eventData = envelopeRequestParser(await pageloadEventPromise); + const pageloadSpan = await pageloadSpanPromise; - const traceContextData = eventData.contexts?.trace?.data; - const spanDurationSeconds = eventData.timestamp! - eventData.start_timestamp!; + const spanDurationSeconds = pageloadSpan.end_timestamp - pageloadSpan.start_timestamp; - expect(traceContextData).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'pageload', - ['sentry.idle_span_finish_reason']: 'reportPageLoaded', + expect(pageloadSpan.attributes).toMatchObject({ + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: expect.objectContaining({ value: 1 }), + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, + [SENTRY_OP]: { type: 'string', value: 'pageload' }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'reportPageLoaded' }, }); // We wait for 2.5 seconds before calling Sentry.reportPageLoaded(). The span starts at navigation start, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/finalTimeout/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/finalTimeout/init.js index 2f633a9c6f1e..7ec015be44dd 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/finalTimeout/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/finalTimeout/init.js @@ -4,7 +4,6 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true, finalTimeout: 3000 })], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/finalTimeout/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/finalTimeout/test.ts index ae6ffdec21b3..aaef5b809ed7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/finalTimeout/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/finalTimeout/test.ts @@ -1,33 +1,36 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + SENTRY_IDLE_SPAN_FINISH_REASON, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( 'final timeout cancels the pageload span even if `enableReportPageLoaded` is true', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const pageloadEventPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); - const eventData = envelopeRequestParser(await pageloadEventPromise); + const pageloadSpan = await pageloadSpanPromise; - const traceContextData = eventData.contexts?.trace?.data; - const spanDurationSeconds = eventData.timestamp! - eventData.start_timestamp!; + const spanDurationSeconds = pageloadSpan.end_timestamp - pageloadSpan.start_timestamp; - expect(traceContextData).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'pageload', - ['sentry.idle_span_finish_reason']: 'finalTimeout', + expect(pageloadSpan.attributes).toMatchObject({ + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: expect.objectContaining({ value: 1 }), + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, + [SENTRY_OP]: { type: 'string', value: 'pageload' }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'finalTimeout' }, }); // We wait for 3 seconds before calling Sentry.reportPageLoaded() diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/navigation/init.js index 3e937c873954..65e9938f7985 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/navigation/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/navigation/init.js @@ -4,7 +4,6 @@ window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true, instrumentNavigation: false })], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/navigation/test.ts index 9e947592a576..b67276f0bcd0 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/reportPageLoaded/navigation/test.ts @@ -1,33 +1,36 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + SENTRY_IDLE_SPAN_FINISH_REASON, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; import { sentryTest } from '../../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest( 'starting a navigation span cancels the pageload span even if `enableReportPageLoaded` is true', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const pageloadEventPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); - const eventData = envelopeRequestParser(await pageloadEventPromise); + const pageloadSpan = await pageloadSpanPromise; - const traceContextData = eventData.contexts?.trace?.data; - const spanDurationSeconds = eventData.timestamp! - eventData.start_timestamp!; + const spanDurationSeconds = pageloadSpan.end_timestamp - pageloadSpan.start_timestamp; - expect(traceContextData).toMatchObject({ - [SENTRY_ORIGIN]: 'auto.pageload.browser', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'url', - [SENTRY_OP]: 'pageload', - ['sentry.idle_span_finish_reason']: 'cancelled', + expect(pageloadSpan.attributes).toMatchObject({ + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.pageload.browser' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: expect.objectContaining({ value: 1 }), + [SENTRY_SEGMENT_NAME_SOURCE]: { type: 'string', value: 'url' }, + [SENTRY_OP]: { type: 'string', value: 'pageload' }, + [SENTRY_IDLE_SPAN_FINISH_REASON]: { type: 'string', value: 'cancelled' }, }); // ending span after 1s but adding a margin of 0.5s to account for timing weirdness in CI to avoid flakes diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/init.js index 0eb1efc7a5a4..3a4c2cb1cdf4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/test.ts index 4bc9621f8395..282625e0d8b7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/test.ts @@ -1,8 +1,8 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; sentryTest('should allow specific types of resource spans to be ignored.', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -13,8 +13,10 @@ sentryTest('should allow specific types of resource spans to be ignored.', async const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const allSpans = eventData.spans?.filter(({ op }) => op?.startsWith('resource.script')); + const spans = collectStreamedSpans(page); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url, span => getSpanOp(span) === 'pageload'); + await page.evaluate(() => (window as any).Sentry.flush()); + const allSpans = spans?.filter(span => getSpanOp(span)?.startsWith('resource.script')); expect(allSpans?.length).toBe(0); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/init.js similarity index 90% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/init.js index 53029a40b668..f2b876b3bf7d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/init.js @@ -4,13 +4,13 @@ import { interactionsIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ enableLongTask: false, }), interactionsIntegration(), - Sentry.spanStreamingIntegration(), Sentry.spotlightBrowserIntegration(), ], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/test.ts new file mode 100644 index 000000000000..ae6640054caa --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-static/test.ts @@ -0,0 +1,54 @@ +import { PAGELOAD, UI_ACTION_CLICK, UI_INTERACTION_CLICK } from '@sentry/conventions/op'; +import { expect } from '@playwright/test'; +import type { TransactionEvent } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + envelopeRequestParser, + shouldSkipCdnBundleTest, + shouldSkipTracingTest, + waitForTransactionRequest, +} from '../../../../utils/helpers'; + +sentryTest( + 'filters ui.interaction.click spans for spotlight elements via ignoreSpans', + async ({ getLocalTestUrl, page }) => { + // spotlightBrowserIntegration is not available in CDN bundles + if (shouldSkipTracingTest() || shouldSkipCdnBundleTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadPromise = waitForTransactionRequest(page, txn => txn.contexts?.trace?.op === PAGELOAD); + await page.goto(url); + await pageloadPromise; + + // Click on the spotlight element — interaction span should be filtered + const spotlightTxnPromise = waitForTransactionRequest(page, txn => txn.contexts?.trace?.op === UI_ACTION_CLICK); + await page.locator('[data-test-id=spotlight-button]').click(); + await page.locator('.clicked[data-test-id=spotlight-button]').isVisible(); + const spotlightTransaction = envelopeRequestParser(await spotlightTxnPromise); + + expect(spotlightTransaction.contexts?.trace?.op).toBe(UI_ACTION_CLICK); + + const spotlightInteractionSpans = spotlightTransaction.spans?.filter(span => span.op === UI_INTERACTION_CLICK); + expect(spotlightInteractionSpans).toHaveLength(0); + + // Click on the regular button — wait specifically for a transaction that contains + // a ui.interaction.click child span, since the PerformanceObserver may deliver + // the event entry asynchronously + const regularTxnPromise = waitForTransactionRequest( + page, + txn => + txn.contexts?.trace?.op === UI_ACTION_CLICK && + (txn.spans?.some(span => span.op === UI_INTERACTION_CLICK) ?? false), + ); + await page.locator('[data-test-id=regular-button]').click(); + await page.locator('.clicked[data-test-id=regular-button]').isVisible(); + const regularTransaction = envelopeRequestParser(await regularTxnPromise); + + const regularInteractionSpans = regularTransaction.spans?.filter(span => span.op === UI_INTERACTION_CLICK); + expect(regularInteractionSpans?.length).toBeGreaterThanOrEqual(1); + expect(regularInteractionSpans![0]!.description).toContain('button'); + expect(regularInteractionSpans![0]!.description).not.toContain('#sentry-spotlight'); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/test.ts deleted file mode 100644 index 0a646701fde8..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter-streamed/test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipCdnBundleTest, shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, observeStreamedSpan, waitForStreamedSpan, waitForStreamedSpans } from '../../../../utils/spanUtils'; - -// Interaction spans are named after the component, so the element is on `browser.web_vital.inp.target`. -// The click handler adds the `clicked` class before the event timing entry is recorded. -const SPOTLIGHT_BUTTON = 'body > div#sentry-spotlight > button.clicked'; -const REGULAR_BUTTON = 'body > button.clicked'; - -sentryTest( - 'filters ui.interaction.click spans for spotlight elements via ignoreSpans in streaming mode', - async ({ getLocalTestUrl, page }) => { - // spotlightBrowserIntegration is not available in CDN bundles - if (shouldSkipTracingTest() || shouldSkipCdnBundleTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - // Set up an observer that fails if a spotlight interaction span is ever sent - let sawSpotlightInteractionSpan = false; - await observeStreamedSpan(page, span => { - if ( - getSpanOp(span) === 'ui.interaction.click' && - span.attributes['browser.web_vital.inp.target']?.value === SPOTLIGHT_BUTTON - ) { - sawSpotlightInteractionSpan = true; - return true; - } - return false; - }); - - await page.goto(url); - - // Wait for pageload to finish before clicking - await waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - - // Click on the spotlight element — its ui.interaction.click child should be filtered - await page.locator('[data-test-id=spotlight-button]').click(); - await page.locator('.clicked[data-test-id=spotlight-button]').isVisible(); - - // Wait for the spotlight click's segment span to arrive - await waitForStreamedSpans(page, spans => - spans.some(span => span.is_segment && getSpanOp(span) === 'ui.action.click'), - ); - - // Click on the regular button — its ui.interaction.click child should be kept - const regularInteractionSpansPromise = waitForStreamedSpans(page, spans => - spans.some(span => span.attributes['browser.web_vital.inp.target']?.value === REGULAR_BUTTON), - ); - - await page.locator('[data-test-id=regular-button]').click(); - await page.locator('.clicked[data-test-id=regular-button]').isVisible(); - - const regularSpans = await regularInteractionSpansPromise; - const regularInteractionSpan = regularSpans.find( - span => span.attributes['browser.web_vital.inp.target']?.value === REGULAR_BUTTON, - ); - expect(getSpanOp(regularInteractionSpan!)).toBe('ui.interaction.click'); - - // Verify no spotlight interaction span was ever sent - expect(sawSpotlightInteractionSpan).toBe(false); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter/init.js index f2b876b3bf7d..652e289ca5f7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter/init.js @@ -4,7 +4,6 @@ import { interactionsIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter/test.ts index d0480062c10a..26d8b21cc6b3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/spotlight-interaction-filter/test.ts @@ -1,57 +1,50 @@ +import { BROWSER_WEB_VITAL_INP_TARGET } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { - envelopeRequestParser, - shouldSkipCdnBundleTest, - shouldSkipTracingTest, - waitForTransactionRequest, -} from '../../../../utils/helpers'; - -sentryTest( - 'filters ui.interaction.click spans for spotlight elements via ignoreSpans', - async ({ getLocalTestUrl, page }) => { - // spotlightBrowserIntegration is not available in CDN bundles - if (shouldSkipTracingTest() || shouldSkipCdnBundleTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - // Wait for the pageload transaction to complete - await waitForTransactionRequest(page); - - // Click on the spotlight element — interaction span should be filtered - const spotlightTxnPromise = waitForTransactionRequest(page, txn => txn.contexts?.trace?.op === 'ui.action.click'); - await page.locator('[data-test-id=spotlight-button]').click(); - await page.locator('.clicked[data-test-id=spotlight-button]').isVisible(); - const spotlightTransaction = envelopeRequestParser(await spotlightTxnPromise); - - expect(spotlightTransaction.contexts?.trace?.op).toBe('ui.action.click'); - - const spotlightInteractionSpans = spotlightTransaction.spans?.filter(span => span.op === 'ui.interaction.click'); - expect(spotlightInteractionSpans).toHaveLength(0); - - // Let the first idle span fully settle before clicking again - await page.waitForTimeout(1000); - - // Click on the regular button — wait specifically for a transaction that contains - // a ui.interaction.click child span, since the PerformanceObserver may deliver - // the event entry asynchronously - const regularTxnPromise = waitForTransactionRequest( - page, - txn => - txn.contexts?.trace?.op === 'ui.action.click' && - (txn.spans?.some(span => span.op === 'ui.interaction.click') ?? false), - ); - await page.locator('[data-test-id=regular-button]').click(); - await page.locator('.clicked[data-test-id=regular-button]').isVisible(); - const regularTransaction = envelopeRequestParser(await regularTxnPromise); - - const regularInteractionSpans = regularTransaction.spans?.filter(span => span.op === 'ui.interaction.click'); - expect(regularInteractionSpans?.length).toBeGreaterThanOrEqual(1); - expect(regularInteractionSpans![0]!.description).toContain('button'); - expect(regularInteractionSpans![0]!.description).not.toContain('#sentry-spotlight'); - }, -); +import { shouldSkipCdnBundleTest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; + +// The click handler adds the class before the event timing entry records the target. +const SPOTLIGHT_BUTTON = 'body > div#sentry-spotlight > button.clicked'; +const REGULAR_BUTTON = 'body > button.clicked'; + +sentryTest('filters spotlight interaction spans via ignoreSpans', async ({ getLocalTestUrl, page }) => { + // spotlightBrowserIntegration is not available in CDN bundles. + sentryTest.skip(shouldSkipTracingTest() || shouldSkipCdnBundleTest()); + const url = await getLocalTestUrl({ testDir: __dirname }); + const spans = collectStreamedSpans(page); + const pageloadPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + await pageloadPromise; + + const spotlightPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === 'ui.action.click'); + await page.locator('[data-test-id=spotlight-button]').click(); + await expect(page.locator('.clicked[data-test-id=spotlight-button]')).toBeVisible(); + const spotlight = await spotlightPromise; + expect(getSpanOp(spotlight)).toBe('ui.action.click'); + + const regularPromise = waitForStreamedSpan( + page, + span => span.is_segment && getSpanOp(span) === 'ui.action.click' && span.span_id !== spotlight.span_id, + ); + const regularInteractionPromise = waitForStreamedSpan( + page, + span => span.attributes[BROWSER_WEB_VITAL_INP_TARGET]?.value === REGULAR_BUTTON, + ); + await page.locator('[data-test-id=regular-button]').click(); + await expect(page.locator('.clicked[data-test-id=regular-button]')).toBeVisible(); + const [regular, interaction] = await Promise.all([regularPromise, regularInteractionPromise]); + await page.evaluate(() => (window as any).Sentry.flush()); + + expect(getSpanOp(interaction)).toBe('ui.interaction.click'); + expect(interaction.parent_span_id).toBe(regular.span_id); + expect(interaction.trace_id).toBe(regular.trace_id); + expect(interaction.attributes[BROWSER_WEB_VITAL_INP_TARGET]?.value).not.toContain('#sentry-spotlight'); + expect( + spans.filter( + span => + getSpanOp(span) === 'ui.interaction.click' && + span.attributes[BROWSER_WEB_VITAL_INP_TARGET]?.value === SPOTLIGHT_BUTTON, + ), + ).toHaveLength(0); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargets/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargets/init.js index 65cfbab96ee2..b2280b70e307 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargets/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargets/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/init.js index bb6d4918d1bf..83076460599f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/init.js index 238d85509b25..d7b0a648a2de 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/init.js index 17b2b0a13c6b..c026daa1eed9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors-meta/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: integrations => { integrations.push(Sentry.browserTracingIntegration()); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/init.js index 17b2b0a13c6b..c026daa1eed9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/twp-errors/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: integrations => { integrations.push(Sentry.browserTracingIntegration()); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js index fdca84ba5c42..3595ea724749 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', sampleRate: 1, integrations: [Sentry.browserTracingIntegration()], diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/init.js b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/init.js index 65cfbab96ee2..b2280b70e307 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/test.ts b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/test.ts index f53e4ef0615f..ecca9689b964 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/test.ts @@ -1,13 +1,15 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; import { eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, shouldSkipTracingTest, waitForErrorRequest, - waitForTransactionRequest, } from '../../../../utils/helpers'; +import { + collectStreamedSpans, + waitForStreamedSpanAndTraceHeader, + waitForStreamedSpanAndTraceHeaderOnUrl, +} from '../../../../utils/spanUtils'; const SAMPLED_TRACE_ID = '12345678901234567890123456789012'; const SAMPLED_SPAN_ID = '1234567890123456'; @@ -28,26 +30,24 @@ sentryTest( return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - // Discard the initial pageload transaction. - await getFirstSentryEnvelopeRequest(page, url, eventAndTraceHeaderRequestParser); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - const transactionPromise = waitForTransactionRequest( + const spanPromise = waitForStreamedSpanAndTraceHeader( page, - event => event.contexts?.trace?.trace_id === SAMPLED_TRACE_ID, + span => span.is_segment && span.name === 'continued-sampled', ); await page.locator('#sampled').click(); - const req = await transactionPromise; - const transaction = eventAndTraceHeaderRequestParser(req); - const traceContext = transaction[0].contexts?.trace; + const span = await spanPromise; + const traceContext = span[0]; - expect(traceContext?.trace_id).toBe(SAMPLED_TRACE_ID); - expect(traceContext?.parent_span_id).toBe(SAMPLED_SPAN_ID); - expect(transaction[0].transaction).toBe('continued-sampled'); + expect(traceContext.trace_id).toBe(SAMPLED_TRACE_ID); + expect(traceContext.parent_span_id).toBe(SAMPLED_SPAN_ID); + expect(span[0].name).toBe('continued-sampled'); // The incoming positive sampling decision is honored regardless of local config. - expect(transaction[1]?.sampled).toBe('true'); + expect(span[1]?.sampled).toBe('true'); // Outgoing request carries the continued trace. const outgoingRequest = await outgoingRequestPromise; @@ -58,7 +58,7 @@ sentryTest( ); sentryTest( - 'continueTrace continues an unsampled incoming trace without emitting a transaction', + 'continueTrace continues an unsampled incoming trace without emitting a span', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -71,11 +71,11 @@ sentryTest( return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - await getFirstSentryEnvelopeRequest(page, url, eventAndTraceHeaderRequestParser); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - // The captured error carries the continued (unsampled) trace even though no transaction is sent. const errorPromise = waitForErrorRequest(page); + const spans = collectStreamedSpans(page); await page.locator('#unsampled').click(); const [errorEvent] = eventAndTraceHeaderRequestParser(await errorPromise); @@ -85,6 +85,8 @@ sentryTest( const outgoingRequest = await outgoingRequestPromise; const headers = await outgoingRequest.allHeaders(); expect(headers['sentry-trace']).toMatch(new RegExp(`^${UNSAMPLED_TRACE_ID}-[a-f0-9]{16}-0$`)); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(spans.some(span => span.trace_id === UNSAMPLED_TRACE_ID)).toBe(false); }, ); @@ -101,20 +103,18 @@ sentryTest( return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - await getFirstSentryEnvelopeRequest(page, url, eventAndTraceHeaderRequestParser); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - // With tracesSampleRate=1 the deferred decision resolves to sampled, so a transaction is emitted - // on the continued trace id. - const transactionPromise = waitForTransactionRequest( + const spanPromise = waitForStreamedSpanAndTraceHeader( page, - event => event.contexts?.trace?.trace_id === DEFERRED_TRACE_ID, + span => span.is_segment && span.name === 'continued-deferred', ); await page.locator('#deferred').click(); - const transaction = eventAndTraceHeaderRequestParser(await transactionPromise); - expect(transaction[0].contexts?.trace?.trace_id).toBe(DEFERRED_TRACE_ID); - expect(transaction[0].transaction).toBe('continued-deferred'); + const span = await spanPromise; + expect(span[0].trace_id).toBe(DEFERRED_TRACE_ID); + expect(span[0].name).toBe('continued-deferred'); }, ); @@ -129,21 +129,20 @@ sentryTest('continueTrace with no incoming trace starts a fresh trace', async ({ return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - const [pageloadEvent] = await getFirstSentryEnvelopeRequest( + const [pageloadEvent] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const pageloadTraceId = pageloadEvent.trace_id; + + const spanPromise = waitForStreamedSpanAndTraceHeader( page, - url, - eventAndTraceHeaderRequestParser, + span => span.is_segment && span.name === 'continued-noTrace', ); - const pageloadTraceId = pageloadEvent.contexts?.trace?.trace_id; - - const transactionPromise = waitForTransactionRequest(page, event => event.transaction === 'continued-noTrace'); await page.locator('#noTrace').click(); - const transaction = eventAndTraceHeaderRequestParser(await transactionPromise); - const traceId = transaction[0].contexts?.trace?.trace_id; + const span = await spanPromise; + const traceId = span[0].trace_id; expect(traceId).toMatch(/^[a-f0-9]{32}$/); expect(traceId).not.toBe(pageloadTraceId); - expect(transaction[0].contexts?.trace).not.toHaveProperty('parent_span_id'); + expect(span[0]).not.toHaveProperty('parent_span_id'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/init.js b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/init.js index 06a40a8e55ec..5e9c86105ae7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/test.ts b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/test.ts index 77956fb134bf..e7321abb38ee 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/test.ts @@ -4,8 +4,8 @@ import { eventAndTraceHeaderRequestParser, shouldSkipTracingTest, waitForErrorRequest, - waitForTransactionRequest, } from '../../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpanAndTraceHeader } from '../../../../utils/spanUtils'; const SAMPLED_TRACE_ID = '12345678901234567890123456789012'; const SAMPLED_SPAN_ID = '1234567890123456'; @@ -26,18 +26,17 @@ sentryTest( return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - // With tracesSampleRate=0 there is no pageload transaction, so we only wait for the continued one. - const transactionPromise = waitForTransactionRequest( + const spanPromise = waitForStreamedSpanAndTraceHeader( page, - event => event.contexts?.trace?.trace_id === SAMPLED_TRACE_ID, + span => span.is_segment && span.name === 'continued-sampled', ); await page.goto(url); await page.locator('#sampled').click(); - const transaction = eventAndTraceHeaderRequestParser(await transactionPromise); - expect(transaction[0].contexts?.trace?.trace_id).toBe(SAMPLED_TRACE_ID); - expect(transaction[0].contexts?.trace?.parent_span_id).toBe(SAMPLED_SPAN_ID); + const span = await spanPromise; + expect(span[0].trace_id).toBe(SAMPLED_TRACE_ID); + expect(span[0].parent_span_id).toBe(SAMPLED_SPAN_ID); const outgoingRequest = await outgoingRequestPromise; const headers = await outgoingRequest.allHeaders(); @@ -46,7 +45,7 @@ sentryTest( ); sentryTest( - 'continueTrace does not emit a transaction for a deferred decision with tracesSampleRate=0', + 'continueTrace does not emit a span for a deferred decision with tracesSampleRate=0', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -61,9 +60,9 @@ sentryTest( await page.goto(url); - // The captured error carries the continued trace even though no transaction is sent. const errorPromise = waitForErrorRequest(page); + const spans = collectStreamedSpans(page); await page.locator('#deferred').click(); const [errorEvent] = eventAndTraceHeaderRequestParser(await errorPromise); @@ -73,6 +72,8 @@ sentryTest( const outgoingRequest = await outgoingRequestPromise; const headers = await outgoingRequest.allHeaders(); expect(headers['sentry-trace']).toMatch(new RegExp(`^${DEFERRED_TRACE_ID}-[a-f0-9]{16}-0$`)); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(spans.some(span => span.trace_id === DEFERRED_TRACE_ID)).toBe(false); }, ); @@ -94,6 +95,7 @@ sentryTest( const errorPromise = waitForErrorRequest(page); + const spans = collectStreamedSpans(page); await page.locator('#unsampled').click(); const [errorEvent] = eventAndTraceHeaderRequestParser(await errorPromise); @@ -102,5 +104,7 @@ sentryTest( const outgoingRequest = await outgoingRequestPromise; const headers = await outgoingRequest.allHeaders(); expect(headers['sentry-trace']).toMatch(new RegExp(`^${UNSAMPLED_TRACE_ID}-[a-f0-9]{16}-0$`)); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(spans.some(span => span.trace_id === UNSAMPLED_TRACE_ID)).toBe(false); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/init.js b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/init.js index 9eb615a288dd..79d41d4ce43c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/init.js @@ -5,7 +5,6 @@ window.Sentry = Sentry; // In the browser, "tracing without performance" (TwP) means enabling `browserTracingIntegration` // but not setting `tracesSampleRate`. Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/test.ts b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/test.ts index e9ebd9dcec57..bbf746fdc4df 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/test.ts @@ -10,7 +10,7 @@ const SAMPLED_TRACE_ID = '12345678901234567890123456789012'; const UNSAMPLED_TRACE_ID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; const DEFERRED_TRACE_ID = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; -// In TwP mode no transactions are emitted, so each variant is observed via the captured error event +// In TwP mode no spans are emitted, so each variant is observed via the captured error event // (which carries the continued trace on its `contexts.trace`) and the outgoing request headers. const VARIANTS = [ { button: 'sampled', traceId: SAMPLED_TRACE_ID, sampledFlag: '-1' }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/init.js b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/init.js index 01c1dc121daf..b6c2bad12033 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration({ instrumentNavigation: false, instrumentPageLoad: false })], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/test.ts b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/test.ts index 7d33497a988b..23ec0ce9edda 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/test.ts @@ -8,6 +8,7 @@ import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest, } from '../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeader } from '../../../utils/spanUtils'; sentryTest('updates the DSC when the txn name is updated and high-quality', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -38,7 +39,7 @@ sentryTest('updates the DSC when the txn name is updated and high-quality', asyn 8. Make request and check that baggage has updated HQ txn name 9. Capture error and check that envelope trace header has updated HQ txn name 10. End span and check that envelope trace header has updated HQ txn name - 11. Make another request and check that there's no span information in baggage + 11. Make another request and check that it starts its own segment in the same trace 12. Capture an error and check that envelope trace header has no span information */ @@ -133,16 +134,9 @@ sentryTest('updates the DSC when the txn name is updated and high-quality', asyn }); // 10 - const txnEventPromise = getMultipleSentryEnvelopeRequests( - page, - 1, - { envelopeType: 'transaction' }, - eventAndTraceHeaderRequestParser, - ); - + const spanPromise = waitForStreamedSpanAndTraceHeader(page, span => span.name === 'updated-root-span-2'); await page.locator('#btnEndSpan').click(); - - const [txnEvent, txnEnvelopeTraceHeader] = (await txnEventPromise)[0]; + const [span, txnEnvelopeTraceHeader] = await spanPromise; expect(txnEnvelopeTraceHeader).toEqual({ environment: 'production', public_key: 'public', @@ -154,16 +148,28 @@ sentryTest('updates the DSC when the txn name is updated and high-quality', asyn sample_rand: expect.any(String), }); - expect(txnEvent.transaction).toEqual('updated-root-span-2'); + expect(span.name).toEqual('updated-root-span-2'); // 11 + const requestSpanPromise = waitForStreamedSpanAndTraceHeader( + page, + span => span.name === 'GET sentry-test-site.example', + ); const baggageItemsAfterEnd = await makeRequestAndGetBaggageItems(page); expect(baggageItemsAfterEnd).toEqual([ 'sentry-environment=production', 'sentry-public_key=public', 'sentry-release=1.1.1', + expect.stringMatching(/sentry-sample_rand=0\.\d+/), + 'sentry-sample_rate=1', + 'sentry-sampled=true', `sentry-trace_id=${traceId}`, + 'sentry-transaction=GET%20sentry-test-site.example', ]); + const [requestSpan] = await requestSpanPromise; + expect(requestSpan.is_segment).toBe(true); + expect(requestSpan.trace_id).toBe(traceId); + expect(requestSpan).not.toHaveProperty('parent_span_id'); // 12 const errorEnvelopeTraceHeaderAfterEnd = await captureErrorAndGetEnvelopeTraceHeader(page); diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js index 66fe57da4d6f..376c14f7b559 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js @@ -4,7 +4,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], environment: 'production', diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts index 23043ebc770d..9e146d46bfac 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts @@ -1,11 +1,7 @@ import { expect } from '@playwright/test'; -import type { EventEnvelopeHeaders } from '@sentry/core'; import { sentryTest } from '../../../utils/fixtures'; -import { - envelopeHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipTracingTest, -} from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../utils/spanUtils'; sentryTest( 'should only include transaction name if source is better than an unparameterized URL', @@ -16,13 +12,13 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const envHeader = await getFirstSentryEnvelopeRequest(page, url, envelopeHeaderRequestParser); + const [, traceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(envHeader.trace).toBeDefined(); - expect(envHeader.trace).toEqual({ + expect(traceHeader).toBeDefined(); + expect(traceHeader).toEqual({ environment: 'production', sample_rate: '1', - transaction: expect.stringContaining('/index.html'), + transaction: 'Pageload', trace_id: expect.stringMatching(/[a-f\d]{32}/), public_key: 'public', sampled: 'true', diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js index b90217ce3c39..79b4b0c89555 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: [/.*/], diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts index 7617b498efb2..242bf9ff462d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts @@ -1,14 +1,10 @@ import { expect } from '@playwright/test'; -import type { EventEnvelopeHeaders } from '@sentry/core'; import { sentryTest } from '../../../utils/fixtures'; -import { - envelopeHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipTracingTest, -} from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../utils/spanUtils'; sentryTest( - 'should send dynamic sampling context data in trace envelope header of a transaction envelope', + 'should send dynamic sampling context data in trace envelope header of a streamed span envelope', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -16,14 +12,14 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const envHeader = await getFirstSentryEnvelopeRequest(page, url, envelopeHeaderRequestParser); + const [, traceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); // In this test, we don't expect trace.transaction to be present because without a custom routing instrumentation // we for now don't have parameterization. This might change in the future but for now the only way of having // transaction in DSC with the default browserTracingIntegration is when the transaction name is set manually. // This scenario is covered in another integration test (envelope-header-transaction-name). - expect(envHeader.trace).toBeDefined(); - expect(envHeader.trace).toEqual({ + expect(traceHeader).toBeDefined(); + expect(traceHeader).toEqual({ environment: 'production', sample_rate: '1', trace_id: expect.stringMatching(/[a-f\d]{32}/), diff --git a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/init.js similarity index 54% rename from dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/http-client-span/init.js index 94db849f5fde..10e5ac1b84eb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/init.js @@ -4,10 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - Sentry.browserTracingIntegration({ instrumentPageLoad: false, instrumentNavigation: false }), - Sentry.spanStreamingIntegration(), - ], + integrations: [Sentry.browserTracingIntegration({ instrumentPageLoad: false, instrumentNavigation: false })], tracesSampleRate: 1, sendClientReports: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/http-client-span/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/test.ts similarity index 63% rename from dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/http-client-span/test.ts index 81d41ada7ccc..634068358415 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/test.ts @@ -1,10 +1,11 @@ +import { SENTRY_ORIGIN, SENTRY_OP, URL_DOMAIN } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../utils/helpers'; import { getSpanOp, waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest( - 'sends http.client span for fetch requests without an active span when span streaming is enabled', + 'sends http.client span for fetch requests without an active span with default span streaming', async ({ getLocalTestUrl, page }) => { sentryTest.skip(shouldSkipTracingTest()); @@ -24,9 +25,11 @@ sentryTest( const span = await spanPromise; + expect(span.is_segment).toBe(true); + expect(span).not.toHaveProperty('parent_span_id'); expect(span.name).toBe('GET sentry-test-site.example'); - expect(span.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser' }); - expect(span.attributes['sentry.op']).toEqual({ type: 'string', value: 'http.client' }); - expect(span.attributes['url.domain']).toEqual({ type: 'string', value: 'sentry-test-site.example' }); + expect(span.attributes[SENTRY_ORIGIN]).toEqual({ type: 'string', value: 'auto.http.browser' }); + expect(span.attributes[SENTRY_OP]).toEqual({ type: 'string', value: 'http.client' }); + expect(span.attributes[URL_DOMAIN]).toEqual({ type: 'string', value: 'sentry-test-site.example' }); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/init.js new file mode 100644 index 000000000000..9e5c6fa2f2e9 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/init.js @@ -0,0 +1,50 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 3000, + finalTimeout: 3000, + childSpanTimeout: 3000, + }), + ], + ignoreSpans: [/ignore/], + tracesSampleRate: 1, + debug: true, +}); + +const waitFor = time => new Promise(resolve => setTimeout(resolve, time)); + +Sentry.startSpanManual( + { + name: 'take-me', + }, + async span => { + await waitFor(500); + span.end(); + }, +); + +Sentry.startSpanManual( + { + name: 'ignore-me', + }, + async span => { + await waitFor(1500); + span.end(); + }, +); + +Sentry.startSpanManual( + { + name: 'ignore-me-too', + }, + async span => { + await waitFor(2500); + span.end(); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/test.ts new file mode 100644 index 000000000000..bc752c9cdf41 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/test.ts @@ -0,0 +1,29 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; + +sentryTest( + 'adjusts the end timestamp of the root idle span if child spans are ignored', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const eventData = envelopeRequestParser(await pageloadRequestPromise); + + const { start_timestamp: startTimestamp, timestamp: endTimestamp } = eventData; + const durationSeconds = endTimestamp! - startTimestamp!; + + const spans = eventData.spans || []; + + expect(durationSeconds).toBeGreaterThan(0); + expect(durationSeconds).toBeLessThan(1.5); + + expect(spans.some(span => span.description === 'take-me')).toBe(true); + expect(spans.some(span => span.description?.includes('ignore-me'))).toBe(false); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/init.js similarity index 82% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/init.js index f57cb5bfaa57..d4000b23ae24 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], + integrations: [], ignoreSpans: [{ attributes: { 'http.response.status_code': 200 } }], tracesSampleRate: 1, debug: true, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/init.js similarity index 82% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/init.js index 086383ccdd54..4346f1d8f878 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], + integrations: [], ignoreSpans: [/ignore/, { op: 'ignored-op' }], parentSpanIsAlwaysRootSpan: false, tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/init.js similarity index 76% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/init.js index 588769fbea94..df2361332e75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration()], ignoreSpans: [/ignored-click-listener/], parentSpanIsAlwaysRootSpan: false, tracePropagationTargets: ['sentry-test-external.io'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/template.html b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/template.html rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/init.js similarity index 76% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/init.js index 788e05781740..2bd0d43829a3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration()], ignoreSpans: [{ attributes: { 'sentry.op': 'http.client' } }], tracePropagationTargets: ['sentry-test-external.io'], tracesSampleRate: 0, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/template.html b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/template.html rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/init.js similarity index 74% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/init.js index 616ae3d942f6..8233baa577e3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration()], ignoreSpans: [{ op: 'pageload' }], tracePropagationTargets: ['sentry-test-external.io'], tracesSampleRate: 0, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/template.html b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/template.html rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js index 9e5c6fa2f2e9..385a1cdf1df5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/init.js similarity index 79% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/init.js index 3a710324f0e1..6f5aa2608b79 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], + integrations: [], ignoreSpans: [/ignore/], tracesSampleRate: 1, debug: true, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts index bc752c9cdf41..aca9af0055e6 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts @@ -1,6 +1,7 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest( 'adjusts the end timestamp of the root idle span if child spans are ignored', @@ -9,21 +10,22 @@ sentryTest( sentryTest.skip(); } - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + const spans = collectStreamedSpans(page); + const pageloadRequestPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === 'pageload'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const eventData = envelopeRequestParser(await pageloadRequestPromise); + const eventData = await pageloadRequestPromise; - const { start_timestamp: startTimestamp, timestamp: endTimestamp } = eventData; + const { start_timestamp: startTimestamp, end_timestamp: endTimestamp } = eventData; const durationSeconds = endTimestamp! - startTimestamp!; - const spans = eventData.spans || []; + await page.evaluate(() => (window as any).Sentry.flush()); expect(durationSeconds).toBeGreaterThan(0); expect(durationSeconds).toBeLessThan(1.5); - expect(spans.some(span => span.description === 'take-me')).toBe(true); - expect(spans.some(span => span.description?.includes('ignore-me'))).toBe(false); + expect(spans.some(span => span.name === 'take-me')).toBe(true); + expect(spans.some(span => span.name?.includes('ignore-me'))).toBe(false); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/interactions/disabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/interactions/disabled/init.js index bb6d4918d1bf..83076460599f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/interactions/disabled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/interactions/disabled/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/interactions/disabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/interactions/disabled/test.ts index 5a163c067969..400a19d98011 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/interactions/disabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/interactions/disabled/test.ts @@ -1,19 +1,19 @@ +import { SENTRY_ORIGIN } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event as SentryEvent } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { countEnvelopes, getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { hidePage, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('does not capture interaction spans without the integration', async ({ getLocalTestUrl, page }) => { sentryTest.skip(shouldSkipTracingTest()); - + const spans = collectStreamedSpans(page); + const pageloadPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === 'pageload'); const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - await getFirstSentryEnvelopeRequest(page); - - const countPromise = countEnvelopes(page, { envelopeType: 'transaction', timeout: 2000 }); - + await pageloadPromise; await page.locator('[data-test-id=interaction-button]').click(); + await hidePage(page); + await page.evaluate(() => (window as any).Sentry.flush()); - expect(await countPromise).toBe(0); + expect(spans.filter(span => span.attributes[SENTRY_ORIGIN]?.value === 'auto.browser.interactions')).toHaveLength(0); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/interactions/navigation-click/init.js b/dev-packages/browser-integration-tests/suites/tracing/interactions/navigation-click/init.js index 2c3e9264d882..96386e1d38d5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/interactions/navigation-click/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/interactions/navigation-click/init.js @@ -4,7 +4,6 @@ import { interactionsIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, integrations: [Sentry.browserTracingIntegration(), interactionsIntegration()], diff --git a/dev-packages/browser-integration-tests/suites/tracing/interactions/navigation-click/test.ts b/dev-packages/browser-integration-tests/suites/tracing/interactions/navigation-click/test.ts index 4931aaa4b172..b9913d3a5739 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/interactions/navigation-click/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/interactions/navigation-click/test.ts @@ -1,39 +1,28 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; - -sentryTest( - 'click-triggered navigation should produce a root navigation transaction', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.goto(url); - await waitForTransactionRequest(page); // "pageload" root span - - const interactionRequestPromise = waitForTransactionRequest( - page, - evt => evt.contexts?.trace?.op === 'ui.action.click', - ); - const navigationRequestPromise = waitForTransactionRequest(page, evt => evt.contexts?.trace?.op === 'navigation'); - - await page.locator('[data-test-id=navigate-button]').click(); - - const interactionEvent = envelopeRequestParser(await interactionRequestPromise); - const navigationEvent = envelopeRequestParser(await navigationRequestPromise); - - // Navigation is root span, not a child span on the interaction - expect(interactionEvent.contexts?.trace?.op).toBe('ui.action.click'); - expect(navigationEvent.contexts?.trace?.op).toBe('navigation'); - - expect(interactionEvent.contexts?.trace?.trace_id).not.toEqual(navigationEvent.contexts?.trace?.trace_id); - - // does not contain a child navigation span - const interactionSpans = interactionEvent.spans || []; - const hasNavigationChild = interactionSpans.some(span => span.op === 'navigation' || span.op === 'http.server'); - expect(hasNavigationChild).toBeFalsy(); - }, -); +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; + +sentryTest('starts a root navigation segment when a click triggers navigation', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + const spans = collectStreamedSpans(page); + const pageloadPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === 'pageload'); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + await pageloadPromise; + + const interactionPromise = waitForStreamedSpan( + page, + span => span.is_segment && getSpanOp(span) === 'ui.action.click', + ); + const navigationPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === 'navigation'); + await page.locator('[data-test-id=navigate-button]').click(); + const [interaction, navigation] = await Promise.all([interactionPromise, navigationPromise]); + await page.evaluate(() => (window as any).Sentry.flush()); + + expect(navigation.is_segment).toBe(true); + expect(navigation).not.toHaveProperty('parent_span_id'); + expect(navigation.trace_id).not.toBe(interaction.trace_id); + const children = spans.filter(span => span.parent_span_id === interaction.span_id); + expect(children.filter(span => ['navigation', 'http.server'].includes(getSpanOp(span) ?? ''))).toHaveLength(0); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/interactions/spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/interactions/spans/init.js index af4b7b3e3483..c2d6aa640c21 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/interactions/spans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/interactions/spans/init.js @@ -5,6 +5,6 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), interactionsIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration(), interactionsIntegration()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/interactions/spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/interactions/spans/test.ts index 0870974ba825..2a83365805f8 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/interactions/spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/interactions/spans/test.ts @@ -1,6 +1,12 @@ import { expect } from '@playwright/test'; import { SDK_VERSION } from '@sentry/core'; import { + SENTRY_IS_LOCALHOST, + CULTURE_CALENDAR, + CULTURE_LOCALE, + CULTURE_TIMEZONE, + URL_FULL, + BROWSER_WEB_VITAL_INP_TARGET, SENTRY_IDLE_SPAN_FINISH_REASON, SENTRY_SEGMENT_ID, SENTRY_SEGMENT_NAME, @@ -17,7 +23,7 @@ import { } from '@sentry/conventions/attributes'; import { sentryTest } from '../../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan, waitForStreamedSpans } from '../../../../utils/spanUtils'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('captures streamed interaction span tree. @firefox', async ({ browserName, getLocalTestUrl, page }) => { const supportedBrowsers = ['chromium', 'firefox']; @@ -25,8 +31,10 @@ sentryTest('captures streamed interaction span tree. @firefox', async ({ browser sentryTest.skip(shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)); const url = await getLocalTestUrl({ testDir: __dirname }); - const interactionSpansPromise = waitForStreamedSpans(page, spans => - spans.some(span => getSpanOp(span) === 'ui.action.click'), + const spans = collectStreamedSpans(page); + const interactionPromise = waitForStreamedSpan( + page, + span => span.is_segment && getSpanOp(span) === 'ui.action.click', ); const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); @@ -39,26 +47,30 @@ sentryTest('captures streamed interaction span tree. @firefox', async ({ browser await page.locator('[data-test-id=interaction-button]').click(); await page.locator('.clicked[data-test-id=interaction-button]').isVisible(); - const interactionSpanTree = await interactionSpansPromise; - - const interactionSegmentSpan = interactionSpanTree.find(span => !!span.is_segment); + const interactionSegmentSpan = await interactionPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const interactionSpanTree = spans.filter( + span => + span.span_id === interactionSegmentSpan.span_id || + span.attributes[SENTRY_SEGMENT_ID]?.value === interactionSegmentSpan.span_id, + ); expect(interactionSegmentSpan).toEqual({ attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, [SENTRY_TRACE_LIFECYCLE]: { type: 'string', value: 'stream', }, - 'culture.calendar': { + [CULTURE_CALENDAR]: { type: 'string', value: expect.any(String), }, - 'culture.locale': { + [CULTURE_LOCALE]: { type: 'string', value: expect.any(String), }, - 'culture.timezone': { + [CULTURE_TIMEZONE]: { type: 'string', value: expect.any(String), }, @@ -66,7 +78,7 @@ sentryTest('captures streamed interaction span tree. @firefox', async ({ browser type: 'string', value: expect.any(String), }, - 'url.full': { + [URL_FULL]: { type: 'string', value: expect.any(String), }, @@ -130,7 +142,7 @@ sentryTest('captures streamed interaction span tree. @firefox', async ({ browser const interactionSpan = interactionSpanTree.find(span => getSpanOp(span) === 'ui.interaction.click'); expect(interactionSpan).toEqual({ attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, [SENTRY_TRACE_LIFECYCLE]: { type: 'string', value: 'stream', @@ -167,7 +179,7 @@ sentryTest('captures streamed interaction span tree. @firefox', async ({ browser type: 'string', value: 'production', }, - 'browser.web_vital.inp.target': { + [BROWSER_WEB_VITAL_INP_TARGET]: { type: 'string', value: 'body > button.clicked', }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js similarity index 75% rename from dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js index 9afcee48dc4a..bd0421b32761 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/test.ts new file mode 100644 index 000000000000..063569d95996 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/test.ts @@ -0,0 +1,77 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import type { SpanJSON, TransactionEvent } from '@sentry/core'; +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; + +sentryTest('should link spans with addLink() in trace context', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); + const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); + const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); + + const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; + const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; + + expect(rootSpan1.transaction).toBe('rootSpan1'); + expect(rootSpan1.spans).toEqual([]); + + expect(rootSpan2.transaction).toBe('rootSpan2'); + expect(rootSpan2.spans).toEqual([]); + + expect(rootSpan2.contexts?.trace?.links?.length).toBe(1); + expect(rootSpan2.contexts?.trace?.links?.[0]).toMatchObject({ + attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, + sampled: true, + span_id: rootSpan1_spanId, + trace_id: rootSpan1_traceId, + }); +}); + +sentryTest('should link spans with addLink() in nested startSpan() calls', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); + const rootSpan3Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan3'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); + const rootSpan3 = envelopeRequestParser(await rootSpan3Promise); + + const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; + const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; + + const [childSpan_3_1, childSpan_3_2] = rootSpan3.spans as [SpanJSON, SpanJSON]; + const rootSpan3_traceId = rootSpan3.contexts?.trace?.trace_id as string; + const rootSpan3_spanId = rootSpan3.contexts?.trace?.span_id as string; + + expect(rootSpan3.transaction).toBe('rootSpan3'); + + expect(childSpan_3_1.description).toBe('childSpan3.1'); + expect(childSpan_3_1.links?.length).toBe(1); + expect(childSpan_3_1.links?.[0]).toMatchObject({ + attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, + sampled: true, + span_id: rootSpan1_spanId, + trace_id: rootSpan1_traceId, + }); + + expect(childSpan_3_2.description).toBe('childSpan3.2'); + expect(childSpan_3_2.links?.[0]).toMatchObject({ + sampled: true, + span_id: rootSpan3_spanId, + trace_id: rootSpan3_traceId, + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/test.ts deleted file mode 100644 index bafa95aee03f..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../utils/helpers'; -import { waitForStreamedSpan, waitForStreamedSpans } from '../../../utils/spanUtils'; - -sentryTest('links spans with addLink() in trace context', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const rootSpan1Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan1' && !!s.is_segment); - const rootSpan2Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan2' && !!s.is_segment); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const rootSpan1 = await rootSpan1Promise; - const rootSpan2 = await rootSpan2Promise; - - expect(rootSpan1.name).toBe('rootSpan1'); - expect(rootSpan1.links).toBeUndefined(); - - expect(rootSpan2.name).toBe('rootSpan2'); - expect(rootSpan2.links).toHaveLength(1); - expect(rootSpan2.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': { type: 'string', value: 'previous_trace' } }, - sampled: true, - span_id: rootSpan1.span_id, - trace_id: rootSpan1.trace_id, - }); -}); - -sentryTest('links spans with addLink() in nested startSpan() calls', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const rootSpan1Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan1' && !!s.is_segment); - const rootSpan3SpansPromise = waitForStreamedSpans(page, spans => - spans.some(s => s.name === 'rootSpan3' && s.is_segment), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const rootSpan1 = await rootSpan1Promise; - const rootSpan3Spans = await rootSpan3SpansPromise; - - const rootSpan3 = rootSpan3Spans.find(s => s.name === 'rootSpan3')!; - const childSpan1 = rootSpan3Spans.find(s => s.name === 'childSpan3.1')!; - const childSpan2 = rootSpan3Spans.find(s => s.name === 'childSpan3.2')!; - - expect(rootSpan3.name).toBe('rootSpan3'); - - expect(childSpan1.name).toBe('childSpan3.1'); - expect(childSpan1.links).toHaveLength(1); - expect(childSpan1.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': { type: 'string', value: 'previous_trace' } }, - sampled: true, - span_id: rootSpan1.span_id, - trace_id: rootSpan1.trace_id, - }); - - expect(childSpan2.name).toBe('childSpan3.2'); - expect(childSpan2.links?.[0]).toMatchObject({ - sampled: true, - span_id: rootSpan3.span_id, - trace_id: rootSpan3.trace_id, - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/init.js index 848d72e6bfdc..3ec6adbbdb75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/test.ts index 9c556335651e..e7f009c0e911 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/test.ts @@ -1,76 +1,66 @@ -import { expect } from '@playwright/test'; -import type { SpanJSON, TransactionEvent } from '@sentry/core'; -import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; - -sentryTest('should link spans with addLink() in trace context', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +sentryTest('links spans with addLink() in trace context', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); - const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); - const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); + const rootSpan1Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan1' && !!s.is_segment); + const rootSpan2Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan2' && !!s.is_segment); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); - const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); - - const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; - const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; - - expect(rootSpan1.transaction).toBe('rootSpan1'); - expect(rootSpan1.spans).toEqual([]); + const rootSpan1 = await rootSpan1Promise; + const rootSpan2 = await rootSpan2Promise; - expect(rootSpan2.transaction).toBe('rootSpan2'); - expect(rootSpan2.spans).toEqual([]); + expect(rootSpan1.name).toBe('rootSpan1'); + expect(rootSpan1.links).toBeUndefined(); - expect(rootSpan2.contexts?.trace?.links?.length).toBe(1); - expect(rootSpan2.contexts?.trace?.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': 'previous_trace' }, + expect(rootSpan2.name).toBe('rootSpan2'); + expect(rootSpan2.links).toHaveLength(1); + expect(rootSpan2.links?.[0]).toMatchObject({ + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, - span_id: rootSpan1_spanId, - trace_id: rootSpan1_traceId, + span_id: rootSpan1.span_id, + trace_id: rootSpan1.trace_id, }); }); +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../utils/fixtures'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../utils/spanUtils'; -sentryTest('should link spans with addLink() in nested startSpan() calls', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest('links spans with addLink() in nested startSpan() calls', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); - const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); - const rootSpan3Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan3'); + const rootSpan1Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan1' && !!s.is_segment); + const rootSpan3Promise = waitForStreamedSpan(page, span => span.name === 'rootSpan3' && span.is_segment); + const childSpan1Promise = waitForStreamedSpan(page, span => span.name === 'childSpan3.1'); + const childSpan2Promise = waitForStreamedSpan(page, span => span.name === 'childSpan3.2'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); - const rootSpan3 = envelopeRequestParser(await rootSpan3Promise); - - const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; - const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; - - const [childSpan_3_1, childSpan_3_2] = rootSpan3.spans as [SpanJSON, SpanJSON]; - const rootSpan3_traceId = rootSpan3.contexts?.trace?.trace_id as string; - const rootSpan3_spanId = rootSpan3.contexts?.trace?.span_id as string; + const rootSpan1 = await rootSpan1Promise; + const [rootSpan3, childSpan1, childSpan2] = await Promise.all([ + rootSpan3Promise, + childSpan1Promise, + childSpan2Promise, + ]); - expect(rootSpan3.transaction).toBe('rootSpan3'); + expect(rootSpan3.name).toBe('rootSpan3'); - expect(childSpan_3_1.description).toBe('childSpan3.1'); - expect(childSpan_3_1.links?.length).toBe(1); - expect(childSpan_3_1.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': 'previous_trace' }, + expect(childSpan1.name).toBe('childSpan3.1'); + expect(childSpan1.links).toHaveLength(1); + expect(childSpan1.links?.[0]).toMatchObject({ + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, - span_id: rootSpan1_spanId, - trace_id: rootSpan1_traceId, + span_id: rootSpan1.span_id, + trace_id: rootSpan1.trace_id, }); - expect(childSpan_3_2.description).toBe('childSpan3.2'); - expect(childSpan_3_2.links?.[0]).toMatchObject({ + expect(childSpan2.name).toBe('childSpan3.2'); + expect(childSpan2.links?.[0]).toMatchObject({ sampled: true, - span_id: rootSpan3_spanId, - trace_id: rootSpan3_traceId, + span_id: rootSpan3.span_id, + trace_id: rootSpan3.trace_id, }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/init.js index 848d72e6bfdc..3ec6adbbdb75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/test.ts index 529eae04ae03..56aaebf93cde 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/test.ts @@ -1,47 +1,50 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { SpanJSON, TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest('should link spans with addLinks() in trace context', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } - const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); - const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); - const rootSpan3Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan3'); + const rootSpan1Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan1'); + const rootSpan2Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan2'); + const rootSpan3Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan3'); + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); - const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); - const rootSpan3 = envelopeRequestParser(await rootSpan3Promise); + const rootSpan1 = await rootSpan1Promise; + const rootSpan2 = await rootSpan2Promise; + const rootSpan3 = await rootSpan3Promise; - const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; - const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; + const rootSpan1_traceId = rootSpan1.trace_id as string; + const rootSpan1_spanId = rootSpan1.span_id as string; - expect(rootSpan1.transaction).toBe('rootSpan1'); - expect(rootSpan1.spans).toEqual([]); + expect(rootSpan1.name).toBe('rootSpan1'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan1.span_id)).toEqual([]); - const rootSpan2_traceId = rootSpan2.contexts?.trace?.trace_id as string; - const rootSpan2_spanId = rootSpan2.contexts?.trace?.span_id as string; + const rootSpan2_traceId = rootSpan2.trace_id as string; + const rootSpan2_spanId = rootSpan2.span_id as string; - expect(rootSpan2.transaction).toBe('rootSpan2'); - expect(rootSpan2.spans).toEqual([]); + expect(rootSpan2.name).toBe('rootSpan2'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan2.span_id)).toEqual([]); - expect(rootSpan3.transaction).toBe('rootSpan3'); - expect(rootSpan3.spans).toEqual([]); - expect(rootSpan3.contexts?.trace?.links?.length).toBe(2); - expect(rootSpan3.contexts?.trace?.links).toEqual([ + expect(rootSpan3.name).toBe('rootSpan3'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan3.span_id)).toEqual([]); + expect(rootSpan3.links?.length).toBe(2); + expect(rootSpan3.links).toEqual([ { + attributes: {}, sampled: true, span_id: rootSpan1_spanId, trace_id: rootSpan1_traceId, }, { - attributes: { 'sentry.link.type': 'previous_trace' }, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, span_id: rootSpan2_spanId, trace_id: rootSpan2_traceId, @@ -54,37 +57,40 @@ sentryTest('should link spans with addLinks() in nested startSpan() calls', asyn sentryTest.skip(); } - const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); - const rootSpan4Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan4'); + const rootSpan2Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan2'); + const rootSpan4Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan4'); + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); - const rootSpan4 = envelopeRequestParser(await rootSpan4Promise); + const rootSpan2 = await rootSpan2Promise; + const rootSpan4 = await rootSpan4Promise; - const rootSpan2_traceId = rootSpan2.contexts?.trace?.trace_id as string; - const rootSpan2_spanId = rootSpan2.contexts?.trace?.span_id as string; + const rootSpan2_traceId = rootSpan2.trace_id as string; + const rootSpan2_spanId = rootSpan2.span_id as string; - const [childSpan_4_1, childSpan_4_2] = rootSpan4.spans as [SpanJSON, SpanJSON]; - const rootSpan4_traceId = rootSpan4.contexts?.trace?.trace_id as string; - const rootSpan4_spanId = rootSpan4.contexts?.trace?.span_id as string; + const childSpan_4_1 = allSpans.find(span => span.name === 'childSpan4.1')!; + const childSpan_4_2 = allSpans.find(span => span.name === 'childSpan4.2')!; + const rootSpan4_traceId = rootSpan4.trace_id as string; + const rootSpan4_spanId = rootSpan4.span_id as string; - expect(rootSpan4.transaction).toBe('rootSpan4'); + expect(rootSpan4.name).toBe('rootSpan4'); - expect(childSpan_4_1.description).toBe('childSpan4.1'); + expect(childSpan_4_1.name).toBe('childSpan4.1'); expect(childSpan_4_1.links).toBe(undefined); - expect(childSpan_4_2.description).toBe('childSpan4.2'); + expect(childSpan_4_2.name).toBe('childSpan4.2'); expect(childSpan_4_2.links?.length).toBe(2); expect(childSpan_4_2.links).toEqual([ { + attributes: {}, sampled: true, span_id: rootSpan4_spanId, trace_id: rootSpan4_traceId, }, { - attributes: { 'sentry.link.type': 'previous_trace' }, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, span_id: rootSpan2_spanId, trace_id: rootSpan2_traceId, diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/init.js index 848d72e6bfdc..3ec6adbbdb75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/test.ts index c2a2ed02f0e6..c07214170d5c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/test.ts @@ -1,45 +1,48 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest('should link spans by adding "links" to span options', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } - const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); - const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); - const rootSpan3Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan3'); + const rootSpan1Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan1'); + const rootSpan2Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan2'); + const rootSpan3Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan3'); + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); - const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); - const rootSpan3 = envelopeRequestParser(await rootSpan3Promise); + const rootSpan1 = await rootSpan1Promise; + const rootSpan2 = await rootSpan2Promise; + const rootSpan3 = await rootSpan3Promise; - const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; - const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; - const rootSpan2_traceId = rootSpan2.contexts?.trace?.trace_id as string; - const rootSpan2_spanId = rootSpan2.contexts?.trace?.span_id as string; + const rootSpan1_traceId = rootSpan1.trace_id as string; + const rootSpan1_spanId = rootSpan1.span_id as string; + const rootSpan2_traceId = rootSpan2.trace_id as string; + const rootSpan2_spanId = rootSpan2.span_id as string; - expect(rootSpan1.transaction).toBe('rootSpan1'); - expect(rootSpan1.spans).toEqual([]); + expect(rootSpan1.name).toBe('rootSpan1'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan1.span_id)).toEqual([]); - expect(rootSpan3.transaction).toBe('rootSpan3'); - expect(rootSpan3.spans?.length).toBe(1); - expect(rootSpan3.spans?.[0].description).toBe('childSpan3.1'); + expect(rootSpan3.name).toBe('rootSpan3'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan3.span_id)?.length).toBe(1); + expect(allSpans.find(span => span.name === 'childSpan3.1')?.name).toBe('childSpan3.1'); - expect(rootSpan3.contexts?.trace?.links?.length).toBe(2); - expect(rootSpan3.contexts?.trace?.links).toEqual([ + expect(rootSpan3.links?.length).toBe(2); + expect(rootSpan3.links).toEqual([ { + attributes: {}, sampled: true, span_id: rootSpan1_spanId, trace_id: rootSpan1_traceId, }, { - attributes: { 'sentry.link.type': 'previous_trace' }, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, span_id: rootSpan2_spanId, trace_id: rootSpan2_traceId, diff --git a/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/init.js new file mode 100644 index 000000000000..d4062aa6eb8f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/init.js @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [], + tracesSampleRate: 1, +}); + +Sentry.startSpan({ name: 'parent' }, () => { + for (let i = 0; i < 5000; i++) { + Sentry.startInactiveSpan({ name: `child ${i}` }).end(); + } +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/test.ts new file mode 100644 index 000000000000..5a5c6f851c69 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/test.ts @@ -0,0 +1,16 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequestOnUrl } from '../../../utils/helpers'; + +sentryTest('limits static transactions to 1000 child spans', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForTransactionRequestOnUrl(page, url); + const transaction = envelopeRequestParser(req); + + expect(transaction.spans).toHaveLength(1000); + expect(transaction.spans).toContainEqual(expect.objectContaining({ description: 'child 0' })); + expect(transaction.spans).toContainEqual(expect.objectContaining({ description: 'child 999' })); + expect(transaction.spans).not.toContainEqual(expect.objectContaining({ description: 'child 1000' })); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/maxSpans/init.js b/dev-packages/browser-integration-tests/suites/tracing/maxSpans/init.js index d4062aa6eb8f..162bc4a04cd9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/maxSpans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/maxSpans/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/maxSpans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/maxSpans/test.ts index 328c5c364c87..f5a05ad0854c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/maxSpans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/maxSpans/test.ts @@ -1,20 +1,20 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequestOnUrl } from '../../../utils/helpers'; - -sentryTest('it limits spans to 1000', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpan } from '../../../utils/spanUtils'; +sentryTest('streams all children without the static 1000-span limit', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + const spans = collectStreamedSpans(page); + const rootPromise = waitForStreamedSpan(page, span => span.name === 'parent'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); + const root = await rootPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - const req = await waitForTransactionRequestOnUrl(page, url); - const transaction = envelopeRequestParser(req); - - expect(transaction.spans).toHaveLength(1000); - expect(transaction.spans).toContainEqual(expect.objectContaining({ description: 'child 0' })); - expect(transaction.spans).toContainEqual(expect.objectContaining({ description: 'child 999' })); - expect(transaction.spans).not.toContainEqual(expect.objectContaining({ description: 'child 1000' })); + const children = spans.filter(span => span.parent_span_id === root.span_id); + expect(children).toHaveLength(5000); + expect(children).toContainEqual(expect.objectContaining({ name: 'child 0' })); + expect(children).toContainEqual(expect.objectContaining({ name: 'child 999' })); + expect(children).toContainEqual(expect.objectContaining({ name: 'child 4999' })); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt-navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt-navigation/init.js index 7ebcfe76faab..3e7a3d67bff0 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt-navigation/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt-navigation/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt-navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt-navigation/test.ts index c071cf089d71..ef019fa0dc56 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt-navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt-navigation/test.ts @@ -1,37 +1,24 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; +import { + BROWSER_WEB_VITAL_FCP_VALUE, + BROWSER_WEB_VITAL_TTFB_VALUE, + NETWORK_CONNECTION_RTT, +} from '@sentry/conventions/attributes'; -sentryTest.beforeEach(({ browserName }) => { - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } -}); - -// `connection.rtt` is recorded as a measurement, which is only flushed on the pageload -// transaction. It must not leak onto navigation transactions. sentryTest( - 'records `connection.rtt` as a measurement on pageload but not on navigation transactions', - async ({ getLocalTestUrl, page }) => { - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + 'records connection RTT on pageload and navigation spans', + async ({ getLocalTestUrl, page, browserName }) => { + sentryTest.skip(shouldSkipTracingTest() || browserName !== 'chromium'); const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise) as Event; - - const navigationRequestPromise = waitForTransactionRequest( - page, - event => event.contexts?.trace?.op === 'navigation', - ); - await page.goto(`${url}#foo`); - - const navigationRequest = envelopeRequestParser(await navigationRequestPromise) as Event; - - expect(pageloadRequest.contexts?.trace?.op).toBe('pageload'); - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); + const [pageload] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [navigation] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, `${url}#foo`); - expect(pageloadRequest.measurements?.['connection.rtt']?.value).toBeDefined(); - expect(navigationRequest.measurements?.['connection.rtt']).toBeUndefined(); + expect(pageload.attributes[NETWORK_CONNECTION_RTT]).toEqual({ type: 'integer', value: 0 }); + expect(navigation.attributes[NETWORK_CONNECTION_RTT]).toEqual(pageload.attributes[NETWORK_CONNECTION_RTT]); + expect(navigation.attributes[BROWSER_WEB_VITAL_FCP_VALUE]).toBeUndefined(); + expect(navigation.attributes[BROWSER_WEB_VITAL_TTFB_VALUE]).toBeUndefined(); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt/test.ts index 6f4b885e71d1..492027a2fead 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/connection-rtt/test.ts @@ -1,8 +1,9 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; +import { NETWORK_CONNECTION_RTT } from '@sentry/conventions/attributes'; sentryTest.beforeEach(({ browserName }) => { if (shouldSkipTracingTest() || browserName !== 'chromium') { @@ -22,55 +23,42 @@ async function createSessionWithLatency(page: Page, latency: number) { return session; } -sentryTest('should capture a `connection.rtt` metric. xxx', async ({ getLocalTestUrl, page }) => { +sentryTest('captures connection RTT without emulation', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url).then(([span]) => span); - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.['connection.rtt']?.value).toBe(0); + expect(eventData.attributes[NETWORK_CONNECTION_RTT]?.value).toBe(0); }); -sentryTest( - 'should capture a `connection.rtt` metric with emulated value 200ms on Chromium.', - async ({ getLocalTestUrl, page }) => { - const session = await createSessionWithLatency(page, 200); +sentryTest('captures connection RTT with emulated value 200ms on Chromium.', async ({ getLocalTestUrl, page }) => { + const session = await createSessionWithLatency(page, 200); - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const url = await getLocalTestUrl({ testDir: __dirname }); + const eventData = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url).then(([span]) => span); - await session.detach(); + await session.detach(); - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.['connection.rtt']?.value).toBe(200); - }, -); + expect(eventData.attributes[NETWORK_CONNECTION_RTT]?.value).toBe(200); +}); -sentryTest( - 'should capture a `connection.rtt` metric with emulated value 100ms on Chromium.', - async ({ getLocalTestUrl, page }) => { - const session = await createSessionWithLatency(page, 100); +sentryTest('captures connection RTT with emulated value 100ms on Chromium.', async ({ getLocalTestUrl, page }) => { + const session = await createSessionWithLatency(page, 100); - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const url = await getLocalTestUrl({ testDir: __dirname }); + const eventData = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url).then(([span]) => span); - await session.detach(); + await session.detach(); - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.['connection.rtt']?.value).toBe(100); - }, -); + expect(eventData.attributes[NETWORK_CONNECTION_RTT]?.value).toBe(100); +}); -sentryTest( - 'should capture a `connection.rtt` metric with emulated value 50ms on Chromium.', - async ({ getLocalTestUrl, page }) => { - const session = await createSessionWithLatency(page, 50); +sentryTest('captures connection RTT with emulated value 50ms on Chromium.', async ({ getLocalTestUrl, page }) => { + const session = await createSessionWithLatency(page, 50); - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const url = await getLocalTestUrl({ testDir: __dirname }); + const eventData = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url).then(([span]) => span); - await session.detach(); + await session.detach(); - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.['connection.rtt']?.value).toBe(50); - }, -); + expect(eventData.attributes[NETWORK_CONNECTION_RTT]?.value).toBe(50); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/init.js index 8c8e5e033d47..40253c296af1 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', debug: true, dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration(), Sentry.elementTimingIntegration()], diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/init.js new file mode 100644 index 000000000000..05b690d85393 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/init.js @@ -0,0 +1,30 @@ +import * as Sentry from '@sentry/browser'; +import { addLcpInstrumentationHandler } from '@sentry/browser-utils'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, +}); + +// Registered in the bundle of the SDK, so the handlers share its LCP observer. A second bundle gets +// a copy of the instrumentation with an observer of its own, whose view of the LCP candidates can +// differ from the SDK's (e.g. a different first-hidden time), which made this test flaky. +addLcpInstrumentationHandler(({ metric }) => { + const entry = metric.entries[metric.entries.length - 1]; + window._LCP = entry.size; +}); + +addLcpInstrumentationHandler(({ metric }) => { + const entry = metric.entries[metric.entries.length - 1]; + window._LCP2 = entry.size; +}); + +window.ADD_HANDLER = () => { + addLcpInstrumentationHandler(({ metric }) => { + const entry = metric.entries[metric.entries.length - 1]; + window._LCP3 = entry.size; + }); +}; diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/subject.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/subject.js index 838e6e864414..d57faa5bc8b1 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/subject.js @@ -1,18 +1 @@ -import { addLcpInstrumentationHandler } from '@sentry/browser-utils'; - -addLcpInstrumentationHandler(({ metric }) => { - const entry = metric.entries[metric.entries.length - 1]; - window._LCP = entry.size; -}); - -addLcpInstrumentationHandler(({ metric }) => { - const entry = metric.entries[metric.entries.length - 1]; - window._LCP2 = entry.size; -}); - -window.ADD_HANDLER = () => { - addLcpInstrumentationHandler(({ metric }) => { - const entry = metric.entries[metric.entries.length - 1]; - window._LCP3 = entry.size; - }); -}; +// The handlers live in `init.js`, see there. diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/template.html index a8ba98c05ad2..ff4754b3ebb6 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/template.html +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/template.html @@ -6,6 +6,6 @@
- + diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/test.ts index b40899e37521..be7a0585ffcf 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/test.ts @@ -1,8 +1,13 @@ +import { + BROWSER_WEB_VITAL_LCP_VALUE, + BROWSER_WEB_VITAL_LCP_ELEMENT, + BROWSER_WEB_VITAL_LCP_SIZE, +} from '@sentry/conventions/attributes'; import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest, waitForLcpCandidate } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; const bundle = process.env.PW_BUNDLE || ''; @@ -20,38 +25,21 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const [eventData] = await Promise.all([ - getFirstSentryEnvelopeRequest(page), - page.goto(url), - page.locator('button').click(), - ]); - - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.lcp?.value).toBeDefined(); - - // This should be body > img, but it can be flakey as sometimes it will report - // the button as LCP. - expect(eventData.contexts?.trace?.data?.['lcp.element'].startsWith('body >')).toBe(true); - - // Working around flakiness - // Only testing this when the LCP element is an image, not a button - if (eventData.contexts?.trace?.data?.['lcp.element'] === 'body > img') { - expect(eventData.contexts?.trace?.data?.['lcp.size']).toBe(107400); - - const lcp = await (await page.waitForFunction('window._LCP')).jsonValue(); - const lcp2 = await (await page.waitForFunction('window._LCP2')).jsonValue(); - const lcp3 = await page.evaluate('window._LCP3'); - - expect(lcp).toEqual(107400); - expect(lcp2).toEqual(107400); - // this has not been triggered yet - expect(lcp3).toEqual(undefined); - - // Adding a handler after LCP is completed still triggers the handler - await page.evaluate('window.ADD_HANDLER()'); - const lcp3_2 = await (await page.waitForFunction('window._LCP3')).jsonValue(); - - expect(lcp3_2).toEqual(107400); - } + const lcpPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.webvital.lcp'); + await page.goto(url); + await waitForLcpCandidate(page, 'img'); + await page.locator('button').click(); + const lcpSpan = await lcpPromise; + + expect(lcpSpan.attributes[BROWSER_WEB_VITAL_LCP_VALUE]?.value).toBeGreaterThan(0); + expect(lcpSpan.attributes[BROWSER_WEB_VITAL_LCP_ELEMENT]).toEqual({ type: 'string', value: 'body > img' }); + expect(lcpSpan.attributes[BROWSER_WEB_VITAL_LCP_SIZE]).toEqual({ type: 'integer', value: 107400 }); + expect(await page.evaluate('window._LCP')).toBe(107400); + expect(await page.evaluate('window._LCP2')).toBe(107400); + expect(await page.evaluate('window._LCP3')).toBeUndefined(); + + await page.evaluate('window.ADD_HANDLER()'); + await page.waitForFunction('window._LCP3 === 107400'); + expect(await page.evaluate('window._LCP3')).toBe(107400); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js index bb6d4918d1bf..83076460599f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts index 5e85698e709d..8be9de6373d1 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts @@ -1,29 +1,31 @@ +import { PAGELOAD, BROWSER, BROWSER_LOAD_EVENT, BROWSER_REQUEST, BROWSER_RESPONSE } from '@sentry/conventions/op'; +import { URL_FULL } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should add browser-related spans to pageload transaction', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; +sentryTest('adds browser performance spans to the pageload segment', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + const spans = collectStreamedSpans(page); + const pageloadPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === PAGELOAD); const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + const pageload = await pageloadPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const browserSpans = eventData.spans?.filter(({ op }) => op?.startsWith('browser')); - - // Spans `dom_content_loaded_event`, `connect`, `cache` and `dns` are not - // always inside `pageload` transaction. - expect(browserSpans?.length).toBeGreaterThanOrEqual(4); - - ['load_event', 'request', 'response'].forEach(eventDesc => - expect(browserSpans).toContainEqual( - expect.objectContaining({ - op: `browser.${eventDesc}`, - description: page.url(), - parent_span_id: eventData.contexts?.trace?.span_id, - }), - ), - ); + const browserSpans = spans.filter(span => getSpanOp(span)?.startsWith(BROWSER)); + expect(browserSpans.length).toBeGreaterThanOrEqual(4); + [ + { op: BROWSER_LOAD_EVENT, name: 'Load event' }, + { op: BROWSER_REQUEST, name: 'Request' }, + { op: BROWSER_RESPONSE, name: 'Response' }, + ].forEach(({ op, name }) => { + const matches = browserSpans.filter(span => getSpanOp(span) === op); + expect(matches).toHaveLength(1); + expect(matches[0].name).toBe(name); + expect(matches[0].attributes[URL_FULL]).toEqual({ type: 'string', value: page.url() }); + expect(matches[0].parent_span_id).toBe(pageload.span_id); + expect(matches[0].trace_id).toBe(pageload.trace_id); + }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/assets/image.svg b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/assets/image.svg new file mode 100644 index 000000000000..3e8fcacea683 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/assets/image.svg @@ -0,0 +1 @@ + diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/assets/script.js new file mode 100644 index 000000000000..1ebec8919f7f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/assets/script.js @@ -0,0 +1 @@ +(() => {})(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/assets/style.css b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/assets/style.css new file mode 100644 index 000000000000..3e9fdb9de409 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/assets/style.css @@ -0,0 +1,4 @@ +p { + color: red; + text-align: center; +} diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/init.js new file mode 100644 index 000000000000..bb6d4918d1bf --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/test.ts new file mode 100644 index 000000000000..c15ef574991a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-static/test.ts @@ -0,0 +1,254 @@ +import { PAGELOAD, RESOURCE, RESOURCE_SCRIPT, RESOURCE_LINK, RESOURCE_IMG } from '@sentry/conventions/op'; +import { + HTTP_REQUEST_SAME_ORIGIN, + URL_FULL, + HTTP_RESPONSE_BODY_DECODED_SIZE, + HTTP_RESPONSE_BODY_SIZE, + HTTP_RESPONSE_SIZE, + HTTP_REQUEST_CONNECT_START, + HTTP_REQUEST_CONNECTION_END, + HTTP_REQUEST_DOMAIN_LOOKUP_END, + HTTP_REQUEST_DOMAIN_LOOKUP_START, + HTTP_REQUEST_FETCH_START, + HTTP_REQUEST_REDIRECT_END, + HTTP_REQUEST_REDIRECT_START, + HTTP_REQUEST_REQUEST_START, + HTTP_REQUEST_SECURE_CONNECTION_START, + HTTP_REQUEST_WORKER_START, + HTTP_REQUEST_RESPONSE_END, + HTTP_REQUEST_RESPONSE_START, + HTTP_REQUEST_TIME_TO_FIRST_BYTE, + NETWORK_PROTOCOL_NAME, + NETWORK_PROTOCOL_VERSION, + SERVER_ADDRESS, + URL_DOMAIN, + URL_SCHEME, + HTTP_RESPONSE_STATUS_CODE, + RESOURCE_RENDER_BLOCKING_STATUS, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import { type Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('adds resource spans to pageload transaction', async ({ getLocalTestUrl, page, browserName }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const isWebkitRun = browserName === 'webkit'; + + // Intercepting asset requests to avoid network-related flakiness and random retries (on Firefox). + await page.route('https://sentry-test-site.example/path/to/image.svg', (route: Route) => + route.fulfill({ + path: `${__dirname}/assets/image.svg`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'image/svg+xml', + }, + }), + ); + await page.route('https://sentry-test-site.example/path/to/script.js', (route: Route) => + route.fulfill({ + path: `${__dirname}/assets/script.js`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'application/javascript', + }, + }), + ); + await page.route('https://sentry-test-site.example/path/to/style.css', (route: Route) => + route.fulfill({ + path: `${__dirname}/assets/style.css`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'text/css', + }, + }), + ); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const pageloadPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + await page.goto(url); + const eventData = envelopeRequestParser(await pageloadPromise); + const resourceSpans = eventData.spans?.filter(({ op }) => op?.startsWith(RESOURCE)); + + const scriptSpans = resourceSpans?.filter(({ op }) => op === RESOURCE_SCRIPT); + const linkSpan = resourceSpans?.filter(({ op }) => op === RESOURCE_LINK)[0]; + const imgSpan = resourceSpans?.filter(({ op }) => op === RESOURCE_IMG)[0]; + + const spanId = eventData.contexts?.trace?.span_id; + const traceId = eventData.contexts?.trace?.trace_id; + + expect(spanId).toBeDefined(); + expect(traceId).toBeDefined(); + + const hasCdnBundle = (process.env.PW_BUNDLE || '').startsWith('bundle'); + + const expectedScripts = ['/init.bundle.js', 'https://sentry-test-site.example/path/to/script.js']; + if (hasCdnBundle) { + expectedScripts.unshift('/cdn.bundle.js'); + } + + expect(scriptSpans?.map(({ description }) => description).sort()).toEqual(expectedScripts); + expect(scriptSpans?.map(({ parent_span_id }) => parent_span_id)).toEqual(expectedScripts.map(() => spanId)); + + // The init bundle script is served from the test origin: its description is origin-relative, + // but `url.full` retains the full absolute URL (needed for span description inference). + const sameOriginScriptSpan = scriptSpans?.find(({ description }) => description === '/init.bundle.js'); + expect(sameOriginScriptSpan?.data?.[HTTP_REQUEST_SAME_ORIGIN]).toBe(true); + expect(sameOriginScriptSpan?.data?.[URL_FULL]).toMatch(/^https?:\/\/.+\/init\.bundle\.js$/); + + const customScriptSpan = scriptSpans?.find( + ({ description }) => description === 'https://sentry-test-site.example/path/to/script.js', + ); + + expect(imgSpan).toEqual({ + data: { + [HTTP_RESPONSE_BODY_DECODED_SIZE]: expect.any(Number), + [HTTP_RESPONSE_BODY_SIZE]: expect.any(Number), + [HTTP_RESPONSE_SIZE]: expect.any(Number), + [HTTP_REQUEST_CONNECT_START]: expect.any(Number), + [HTTP_REQUEST_CONNECTION_END]: expect.any(Number), + [HTTP_REQUEST_DOMAIN_LOOKUP_END]: expect.any(Number), + [HTTP_REQUEST_DOMAIN_LOOKUP_START]: expect.any(Number), + [HTTP_REQUEST_FETCH_START]: expect.any(Number), + [HTTP_REQUEST_REDIRECT_END]: expect.any(Number), + [HTTP_REQUEST_REDIRECT_START]: expect.any(Number), + [HTTP_REQUEST_REQUEST_START]: expect.any(Number), + [HTTP_REQUEST_SECURE_CONNECTION_START]: expect.any(Number), + [HTTP_REQUEST_WORKER_START]: expect.any(Number), + [HTTP_REQUEST_RESPONSE_END]: expect.any(Number), + [HTTP_REQUEST_RESPONSE_START]: expect.any(Number), + [HTTP_REQUEST_TIME_TO_FIRST_BYTE]: expect.any(Number), + [NETWORK_PROTOCOL_NAME]: '', + [NETWORK_PROTOCOL_VERSION]: 'unknown', + [SENTRY_OP]: RESOURCE_IMG, + [SENTRY_ORIGIN]: 'auto.resource.browser.metrics', + [SERVER_ADDRESS]: 'sentry-test-site.example', + [URL_DOMAIN]: 'sentry-test-site.example', + [HTTP_REQUEST_SAME_ORIGIN]: false, + [URL_SCHEME]: 'https', + [URL_FULL]: 'https://sentry-test-site.example/path/to/image.svg', + // WebKit reports `deliveryType` as of Playwright 1.63's build, but still no response status + // or render blocking status. + 'http.response_delivery_type': '', + ...(!isWebkitRun && { + [HTTP_RESPONSE_STATUS_CODE]: expect.any(Number), + [RESOURCE_RENDER_BLOCKING_STATUS]: 'non-blocking', + }), + }, + description: 'https://sentry-test-site.example/path/to/image.svg', + op: RESOURCE_IMG, + origin: 'auto.resource.browser.metrics', + status: 'ok', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f\d]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); + + // range check: TTFB is reasonably <10 seconds, which is really a check that we report it in + // seconds rather than milliseconds. WebKit resolves these intercepted routes without measurable + // delay, so only the other engines are held to a non-zero value. + const imgSpanTtfb = imgSpan?.data[HTTP_REQUEST_TIME_TO_FIRST_BYTE]; + expect(imgSpanTtfb).toBeGreaterThan(isWebkitRun ? -1 : 0); + expect(imgSpanTtfb).toBeLessThan(10); + + expect(linkSpan).toEqual({ + data: { + [HTTP_RESPONSE_BODY_DECODED_SIZE]: expect.any(Number), + [HTTP_RESPONSE_BODY_SIZE]: expect.any(Number), + [HTTP_RESPONSE_SIZE]: expect.any(Number), + [HTTP_REQUEST_CONNECT_START]: expect.any(Number), + [HTTP_REQUEST_CONNECTION_END]: expect.any(Number), + [HTTP_REQUEST_DOMAIN_LOOKUP_END]: expect.any(Number), + [HTTP_REQUEST_DOMAIN_LOOKUP_START]: expect.any(Number), + [HTTP_REQUEST_FETCH_START]: expect.any(Number), + [HTTP_REQUEST_REDIRECT_END]: expect.any(Number), + [HTTP_REQUEST_REDIRECT_START]: expect.any(Number), + [HTTP_REQUEST_REQUEST_START]: expect.any(Number), + [HTTP_REQUEST_SECURE_CONNECTION_START]: expect.any(Number), + [HTTP_REQUEST_WORKER_START]: expect.any(Number), + [HTTP_REQUEST_RESPONSE_END]: expect.any(Number), + [HTTP_REQUEST_RESPONSE_START]: expect.any(Number), + [HTTP_REQUEST_TIME_TO_FIRST_BYTE]: expect.any(Number), + [NETWORK_PROTOCOL_NAME]: '', + [NETWORK_PROTOCOL_VERSION]: 'unknown', + [SENTRY_OP]: RESOURCE_LINK, + [SENTRY_ORIGIN]: 'auto.resource.browser.metrics', + [SERVER_ADDRESS]: 'sentry-test-site.example', + [URL_DOMAIN]: 'sentry-test-site.example', + [HTTP_REQUEST_SAME_ORIGIN]: false, + [URL_SCHEME]: 'https', + [URL_FULL]: 'https://sentry-test-site.example/path/to/style.css', + // WebKit reports `deliveryType` as of Playwright 1.63's build, but still no response status + // or render blocking status. + 'http.response_delivery_type': '', + ...(!isWebkitRun && { + [HTTP_RESPONSE_STATUS_CODE]: expect.any(Number), + [RESOURCE_RENDER_BLOCKING_STATUS]: 'non-blocking', + }), + }, + description: 'https://sentry-test-site.example/path/to/style.css', + op: RESOURCE_LINK, + origin: 'auto.resource.browser.metrics', + status: 'ok', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f\d]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); + + expect(customScriptSpan).toEqual({ + data: { + [HTTP_RESPONSE_BODY_DECODED_SIZE]: expect.any(Number), + [HTTP_RESPONSE_BODY_SIZE]: expect.any(Number), + [HTTP_RESPONSE_SIZE]: expect.any(Number), + [HTTP_REQUEST_CONNECTION_END]: expect.any(Number), + [HTTP_REQUEST_CONNECT_START]: expect.any(Number), + [HTTP_REQUEST_DOMAIN_LOOKUP_END]: expect.any(Number), + [HTTP_REQUEST_DOMAIN_LOOKUP_START]: expect.any(Number), + [HTTP_REQUEST_FETCH_START]: expect.any(Number), + [HTTP_REQUEST_REDIRECT_END]: expect.any(Number), + [HTTP_REQUEST_REDIRECT_START]: expect.any(Number), + [HTTP_REQUEST_REQUEST_START]: expect.any(Number), + [HTTP_REQUEST_SECURE_CONNECTION_START]: expect.any(Number), + [HTTP_REQUEST_WORKER_START]: expect.any(Number), + [HTTP_REQUEST_RESPONSE_END]: expect.any(Number), + [HTTP_REQUEST_RESPONSE_START]: expect.any(Number), + [HTTP_REQUEST_TIME_TO_FIRST_BYTE]: expect.any(Number), + [NETWORK_PROTOCOL_NAME]: '', + [NETWORK_PROTOCOL_VERSION]: 'unknown', + [SENTRY_OP]: RESOURCE_SCRIPT, + [SENTRY_ORIGIN]: 'auto.resource.browser.metrics', + [SERVER_ADDRESS]: 'sentry-test-site.example', + [URL_DOMAIN]: 'sentry-test-site.example', + [HTTP_REQUEST_SAME_ORIGIN]: false, + [URL_SCHEME]: 'https', + [URL_FULL]: 'https://sentry-test-site.example/path/to/script.js', + // WebKit reports `deliveryType` as of Playwright 1.63's build, but still no response status + // or render blocking status. + 'http.response_delivery_type': '', + ...(!isWebkitRun && { + [HTTP_RESPONSE_STATUS_CODE]: expect.any(Number), + [RESOURCE_RENDER_BLOCKING_STATUS]: 'non-blocking', + }), + }, + description: 'https://sentry-test-site.example/path/to/script.js', + op: RESOURCE_SCRIPT, + origin: 'auto.resource.browser.metrics', + status: 'ok', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f\d]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-streamed/init.js deleted file mode 100644 index 4deba4c452dd..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-streamed/init.js +++ /dev/null @@ -1,10 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], - traceLifecycle: 'stream', - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-streamed/test.ts deleted file mode 100644 index a0a1e863137c..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans-streamed/test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, getSpansFromEnvelope, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; - -const assetsDir = `${__dirname}/../pageload-resource-spans/assets`; - -sentryTest('names streamed resource spans after the resource domain', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - // Intercepting asset requests to avoid network-related flakiness and random retries (on Firefox). - await page.route('https://sentry-test-site.example/path/to/image.svg', (route: Route) => - route.fulfill({ - path: `${assetsDir}/image.svg`, - headers: { - 'Timing-Allow-Origin': '*', - 'Content-Type': 'image/svg+xml', - }, - }), - ); - await page.route('https://sentry-test-site.example/path/to/script.js', (route: Route) => - route.fulfill({ - path: `${assetsDir}/script.js`, - headers: { - 'Timing-Allow-Origin': '*', - 'Content-Type': 'application/javascript', - }, - }), - ); - await page.route('https://sentry-test-site.example/path/to/style.css', (route: Route) => - route.fulfill({ - path: `${assetsDir}/style.css`, - headers: { - 'Timing-Allow-Origin': '*', - 'Content-Type': 'text/css', - }, - }), - ); - - const spanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'resource.img'), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const spans = getSpansFromEnvelope(await spanEnvelopePromise); - - const imgSpan = spans.find(s => getSpanOp(s) === 'resource.img'); - const linkSpan = spans.find(s => getSpanOp(s) === 'resource.link'); - - expect(imgSpan?.name).toBe('sentry-test-site.example'); - expect(imgSpan?.attributes['url.domain']).toEqual({ type: 'string', value: 'sentry-test-site.example' }); - expect(imgSpan?.attributes['url.full']).toEqual({ - type: 'string', - value: 'https://sentry-test-site.example/path/to/image.svg', - }); - - expect(linkSpan?.name).toBe('sentry-test-site.example'); - - // Same-origin resources used to be named by their origin-relative path, they now carry the test host. - const sameOriginScriptSpan = spans.find( - s => getSpanOp(s) === 'resource.script' && s.name !== 'sentry-test-site.example', - ); - expect(sameOriginScriptSpan?.name).toBe('sentry-test.io'); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/init.js new file mode 100644 index 000000000000..83076460599f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/init.js @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts index 276234385fae..f9b35fa67c6a 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts @@ -1,223 +1,291 @@ -import { SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + URL_DOMAIN, + URL_FULL, + HTTP_REQUEST_SAME_ORIGIN, + HTTP_RESPONSE_BODY_DECODED_SIZE, + HTTP_RESPONSE_BODY_SIZE, + HTTP_RESPONSE_SIZE, + HTTP_REQUEST_CONNECT_START, + HTTP_REQUEST_CONNECTION_END, + HTTP_REQUEST_DOMAIN_LOOKUP_END, + HTTP_REQUEST_DOMAIN_LOOKUP_START, + HTTP_REQUEST_FETCH_START, + HTTP_REQUEST_REDIRECT_END, + HTTP_REQUEST_REDIRECT_START, + HTTP_REQUEST_REQUEST_START, + HTTP_REQUEST_SECURE_CONNECTION_START, + HTTP_REQUEST_WORKER_START, + HTTP_REQUEST_RESPONSE_END, + HTTP_REQUEST_RESPONSE_START, + HTTP_REQUEST_TIME_TO_FIRST_BYTE, + NETWORK_PROTOCOL_NAME, + NETWORK_PROTOCOL_VERSION, + SERVER_ADDRESS, + URL_SCHEME, + HTTP_RESPONSE_STATUS_CODE, + RESOURCE_RENDER_BLOCKING_STATUS, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import { type Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; -sentryTest('adds resource spans to pageload transaction', async ({ getLocalTestUrl, page, browserName }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +const assetsDir = `${__dirname}/assets`; - const isWebkitRun = browserName === 'webkit'; +sentryTest( + 'names streamed resource spans after the resource domain', + async ({ getLocalTestUrl, page, browserName }) => { + sentryTest.skip(shouldSkipTracingTest()); - // Intercepting asset requests to avoid network-related flakiness and random retries (on Firefox). - await page.route('https://sentry-test-site.example/path/to/image.svg', (route: Route) => - route.fulfill({ - path: `${__dirname}/assets/image.svg`, - headers: { - 'Timing-Allow-Origin': '*', - 'Content-Type': 'image/svg+xml', - }, - }), - ); - await page.route('https://sentry-test-site.example/path/to/script.js', (route: Route) => - route.fulfill({ - path: `${__dirname}/assets/script.js`, - headers: { - 'Timing-Allow-Origin': '*', - 'Content-Type': 'application/javascript', - }, - }), - ); - await page.route('https://sentry-test-site.example/path/to/style.css', (route: Route) => - route.fulfill({ - path: `${__dirname}/assets/style.css`, - headers: { - 'Timing-Allow-Origin': '*', - 'Content-Type': 'text/css', - }, - }), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const resourceSpans = eventData.spans?.filter(({ op }) => op?.startsWith('resource')); - - const scriptSpans = resourceSpans?.filter(({ op }) => op === 'resource.script'); - const linkSpan = resourceSpans?.filter(({ op }) => op === 'resource.link')[0]; - const imgSpan = resourceSpans?.filter(({ op }) => op === 'resource.img')[0]; - - const spanId = eventData.contexts?.trace?.span_id; - const traceId = eventData.contexts?.trace?.trace_id; - - expect(spanId).toBeDefined(); - expect(traceId).toBeDefined(); - - const hasCdnBundle = (process.env.PW_BUNDLE || '').startsWith('bundle'); - - const expectedScripts = ['/init.bundle.js', 'https://sentry-test-site.example/path/to/script.js']; - if (hasCdnBundle) { - expectedScripts.unshift('/cdn.bundle.js'); - } - - expect(scriptSpans?.map(({ description }) => description).sort()).toEqual(expectedScripts); - expect(scriptSpans?.map(({ parent_span_id }) => parent_span_id)).toEqual(expectedScripts.map(() => spanId)); + // Intercepting asset requests to avoid network-related flakiness and random retries (on Firefox). + await page.route('https://sentry-test-site.example/path/to/image.svg', (route: Route) => + route.fulfill({ + path: `${assetsDir}/image.svg`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'image/svg+xml', + }, + }), + ); + await page.route('https://sentry-test-site.example/path/to/script.js', (route: Route) => + route.fulfill({ + path: `${assetsDir}/script.js`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'application/javascript', + }, + }), + ); + await page.route('https://sentry-test-site.example/path/to/style.css', (route: Route) => + route.fulfill({ + path: `${assetsDir}/style.css`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'text/css', + }, + }), + ); - // The init bundle script is served from the test origin: its description is origin-relative, - // but `url.full` retains the full absolute URL (needed for span description inference). - const sameOriginScriptSpan = scriptSpans?.find(({ description }) => description === '/init.bundle.js'); - expect(sameOriginScriptSpan?.data?.['http.request.same_origin']).toBe(true); - expect(sameOriginScriptSpan?.data?.['url.full']).toMatch(/^https?:\/\/.+\/init\.bundle\.js$/); + const spans = collectStreamedSpans(page); + const pageloadPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === 'pageload'); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + const pageload = await pageloadPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const externalResources = spans.filter(span => span.attributes[URL_DOMAIN]?.value === 'sentry-test-site.example'); + expect(externalResources).toHaveLength(3); + externalResources.forEach(span => { + expect(span.parent_span_id).toBe(pageload.span_id); + expect(span.trace_id).toBe(pageload.trace_id); + }); - const customScriptSpan = scriptSpans?.find( - ({ description }) => description === 'https://sentry-test-site.example/path/to/script.js', - ); + const isWebkitRun = browserName === 'webkit'; + const resourceSpans = spans.filter(span => getSpanOp(span)?.startsWith('resource')); + const scriptSpans = resourceSpans.filter(span => getSpanOp(span) === 'resource.script'); + const imgSpan = resourceSpans.find(span => getSpanOp(span) === 'resource.img'); + const linkSpan = resourceSpans.find(span => getSpanOp(span) === 'resource.link'); + const customScriptSpan = scriptSpans.find( + span => span.attributes[URL_DOMAIN]?.value === 'sentry-test-site.example', + ); + const sameOriginScriptSpan = scriptSpans.find( + span => span.attributes[URL_FULL]?.value === new URL('init.bundle.js', url).href, + ); + const spanId = pageload.span_id; + const traceId = pageload.trace_id; + const expectedScripts = [new URL('init.bundle.js', url).href, 'https://sentry-test-site.example/path/to/script.js']; + if ((process.env.PW_BUNDLE || '').startsWith('bundle')) { + expectedScripts.push(new URL('cdn.bundle.js', url).href); + } + expect(scriptSpans.map(span => span.attributes[URL_FULL]?.value).sort()).toEqual(expectedScripts.sort()); + expect(scriptSpans.map(span => span.parent_span_id)).toEqual(expectedScripts.map(() => spanId)); + expect(sameOriginScriptSpan?.name).toBe('sentry-test.io'); + expect(sameOriginScriptSpan?.attributes[HTTP_REQUEST_SAME_ORIGIN]).toEqual({ type: 'boolean', value: true }); - expect(imgSpan).toEqual({ - data: { - 'http.response.body.decoded_size': expect.any(Number), - 'http.response.body.size': expect.any(Number), - 'http.response.size': expect.any(Number), - 'http.request.connect_start': expect.any(Number), - 'http.request.connection_end': expect.any(Number), - 'http.request.domain_lookup_end': expect.any(Number), - 'http.request.domain_lookup_start': expect.any(Number), - 'http.request.fetch_start': expect.any(Number), - 'http.request.redirect_end': expect.any(Number), - 'http.request.redirect_start': expect.any(Number), - 'http.request.request_start': expect.any(Number), - 'http.request.secure_connection_start': expect.any(Number), - 'http.request.worker_start': expect.any(Number), - 'http.request.response_end': expect.any(Number), - 'http.request.response_start': expect.any(Number), - 'http.request.time_to_first_byte': expect.any(Number), - 'network.protocol.name': '', - 'network.protocol.version': 'unknown', - [SENTRY_OP]: 'resource.img', - [SENTRY_ORIGIN]: 'auto.resource.browser.metrics', - 'server.address': 'sentry-test-site.example', - 'url.domain': 'sentry-test-site.example', - 'http.request.same_origin': false, - 'url.scheme': 'https', - 'url.full': 'https://sentry-test-site.example/path/to/image.svg', - // WebKit reports `deliveryType` as of Playwright 1.63's build, but still no response status - // or render blocking status. - 'http.response_delivery_type': '', - ...(!isWebkitRun && { - 'http.response.status_code': expect.any(Number), - 'resource.render_blocking_status': 'non-blocking', - }), - }, - description: 'https://sentry-test-site.example/path/to/image.svg', - op: 'resource.img', - origin: 'auto.resource.browser.metrics', - status: 'ok', - parent_span_id: spanId, - span_id: expect.stringMatching(/^[a-f\d]{16}$/), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - trace_id: traceId, - }); + expect(imgSpan).toMatchObject({ + attributes: { + [HTTP_RESPONSE_BODY_DECODED_SIZE]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_RESPONSE_BODY_SIZE]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_RESPONSE_SIZE]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_CONNECT_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_CONNECTION_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_DOMAIN_LOOKUP_END]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_DOMAIN_LOOKUP_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_FETCH_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_REDIRECT_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_REDIRECT_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_REQUEST_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_SECURE_CONNECTION_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_WORKER_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_RESPONSE_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_RESPONSE_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_TIME_TO_FIRST_BYTE]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [NETWORK_PROTOCOL_NAME]: { type: 'string', value: '' }, + [NETWORK_PROTOCOL_VERSION]: { type: 'string', value: 'unknown' }, + [SENTRY_OP]: { type: 'string', value: 'resource.img' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.resource.browser.metrics' }, + [SERVER_ADDRESS]: { type: 'string', value: 'sentry-test-site.example' }, + [URL_DOMAIN]: { type: 'string', value: 'sentry-test-site.example' }, + [HTTP_REQUEST_SAME_ORIGIN]: { type: 'boolean', value: false }, + [URL_SCHEME]: { type: 'string', value: 'https' }, + [URL_FULL]: { type: 'string', value: 'https://sentry-test-site.example/path/to/image.svg' }, + // WebKit reports `deliveryType` as of Playwright 1.63's build, but still no response status + // or render blocking status. + 'http.response_delivery_type': { type: 'string', value: '' }, + ...(!isWebkitRun && { + [HTTP_RESPONSE_STATUS_CODE]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [RESOURCE_RENDER_BLOCKING_STATUS]: { type: 'string', value: 'non-blocking' }, + }), + }, + name: 'sentry-test-site.example', + status: 'ok', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f\d]{16}$/), + start_timestamp: expect.any(Number), + end_timestamp: expect.any(Number), + trace_id: traceId, + }); - // range check: TTFB is reasonably <10 seconds, which is really a check that we report it in - // seconds rather than milliseconds. WebKit resolves these intercepted routes without measurable - // delay, so only the other engines are held to a non-zero value. - const imgSpanTtfb = imgSpan?.data['http.request.time_to_first_byte']; - expect(imgSpanTtfb).toBeGreaterThan(isWebkitRun ? -1 : 0); - expect(imgSpanTtfb).toBeLessThan(10); + // range check: TTFB is reasonably <10 seconds, which is really a check that we report it in + // seconds rather than milliseconds. WebKit resolves these intercepted routes without measurable + // delay, so only the other engines are held to a non-zero value. + const imgSpanTtfb = imgSpan?.attributes[HTTP_REQUEST_TIME_TO_FIRST_BYTE]?.value; + expect(imgSpanTtfb).toBeGreaterThan(isWebkitRun ? -1 : 0); + expect(imgSpanTtfb).toBeLessThan(10); - expect(linkSpan).toEqual({ - data: { - 'http.response.body.decoded_size': expect.any(Number), - 'http.response.body.size': expect.any(Number), - 'http.response.size': expect.any(Number), - 'http.request.connect_start': expect.any(Number), - 'http.request.connection_end': expect.any(Number), - 'http.request.domain_lookup_end': expect.any(Number), - 'http.request.domain_lookup_start': expect.any(Number), - 'http.request.fetch_start': expect.any(Number), - 'http.request.redirect_end': expect.any(Number), - 'http.request.redirect_start': expect.any(Number), - 'http.request.request_start': expect.any(Number), - 'http.request.secure_connection_start': expect.any(Number), - 'http.request.worker_start': expect.any(Number), - 'http.request.response_end': expect.any(Number), - 'http.request.response_start': expect.any(Number), - 'http.request.time_to_first_byte': expect.any(Number), - 'network.protocol.name': '', - 'network.protocol.version': 'unknown', - [SENTRY_OP]: 'resource.link', - [SENTRY_ORIGIN]: 'auto.resource.browser.metrics', - 'server.address': 'sentry-test-site.example', - 'url.domain': 'sentry-test-site.example', - 'http.request.same_origin': false, - 'url.scheme': 'https', - 'url.full': 'https://sentry-test-site.example/path/to/style.css', - // WebKit reports `deliveryType` as of Playwright 1.63's build, but still no response status - // or render blocking status. - 'http.response_delivery_type': '', - ...(!isWebkitRun && { - 'http.response.status_code': expect.any(Number), - 'resource.render_blocking_status': 'non-blocking', - }), - }, - description: 'https://sentry-test-site.example/path/to/style.css', - op: 'resource.link', - origin: 'auto.resource.browser.metrics', - status: 'ok', - parent_span_id: spanId, - span_id: expect.stringMatching(/^[a-f\d]{16}$/), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - trace_id: traceId, - }); + expect(linkSpan).toMatchObject({ + attributes: { + [HTTP_RESPONSE_BODY_DECODED_SIZE]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_RESPONSE_BODY_SIZE]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_RESPONSE_SIZE]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_CONNECT_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_CONNECTION_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_DOMAIN_LOOKUP_END]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_DOMAIN_LOOKUP_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_FETCH_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_REDIRECT_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_REDIRECT_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_REQUEST_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_SECURE_CONNECTION_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_WORKER_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_RESPONSE_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_RESPONSE_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_TIME_TO_FIRST_BYTE]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [NETWORK_PROTOCOL_NAME]: { type: 'string', value: '' }, + [NETWORK_PROTOCOL_VERSION]: { type: 'string', value: 'unknown' }, + [SENTRY_OP]: { type: 'string', value: 'resource.link' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.resource.browser.metrics' }, + [SERVER_ADDRESS]: { type: 'string', value: 'sentry-test-site.example' }, + [URL_DOMAIN]: { type: 'string', value: 'sentry-test-site.example' }, + [HTTP_REQUEST_SAME_ORIGIN]: { type: 'boolean', value: false }, + [URL_SCHEME]: { type: 'string', value: 'https' }, + [URL_FULL]: { type: 'string', value: 'https://sentry-test-site.example/path/to/style.css' }, + // WebKit reports `deliveryType` as of Playwright 1.63's build, but still no response status + // or render blocking status. + 'http.response_delivery_type': { type: 'string', value: '' }, + ...(!isWebkitRun && { + [HTTP_RESPONSE_STATUS_CODE]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [RESOURCE_RENDER_BLOCKING_STATUS]: { type: 'string', value: 'non-blocking' }, + }), + }, + name: 'sentry-test-site.example', + status: 'ok', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f\d]{16}$/), + start_timestamp: expect.any(Number), + end_timestamp: expect.any(Number), + trace_id: traceId, + }); - expect(customScriptSpan).toEqual({ - data: { - 'http.response.body.decoded_size': expect.any(Number), - 'http.response.body.size': expect.any(Number), - 'http.response.size': expect.any(Number), - 'http.request.connection_end': expect.any(Number), - 'http.request.connect_start': expect.any(Number), - 'http.request.domain_lookup_end': expect.any(Number), - 'http.request.domain_lookup_start': expect.any(Number), - 'http.request.fetch_start': expect.any(Number), - 'http.request.redirect_end': expect.any(Number), - 'http.request.redirect_start': expect.any(Number), - 'http.request.request_start': expect.any(Number), - 'http.request.secure_connection_start': expect.any(Number), - 'http.request.worker_start': expect.any(Number), - 'http.request.response_end': expect.any(Number), - 'http.request.response_start': expect.any(Number), - 'http.request.time_to_first_byte': expect.any(Number), - 'network.protocol.name': '', - 'network.protocol.version': 'unknown', - 'sentry.op': 'resource.script', - 'sentry.origin': 'auto.resource.browser.metrics', - 'server.address': 'sentry-test-site.example', - 'url.domain': 'sentry-test-site.example', - 'http.request.same_origin': false, - 'url.scheme': 'https', - 'url.full': 'https://sentry-test-site.example/path/to/script.js', - // WebKit reports `deliveryType` as of Playwright 1.63's build, but still no response status - // or render blocking status. - 'http.response_delivery_type': '', - ...(!isWebkitRun && { - 'http.response.status_code': expect.any(Number), - 'resource.render_blocking_status': 'non-blocking', - }), - }, - description: 'https://sentry-test-site.example/path/to/script.js', - op: 'resource.script', - origin: 'auto.resource.browser.metrics', - status: 'ok', - parent_span_id: spanId, - span_id: expect.stringMatching(/^[a-f\d]{16}$/), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - trace_id: traceId, - }); -}); + expect(customScriptSpan).toMatchObject({ + attributes: { + [HTTP_RESPONSE_BODY_DECODED_SIZE]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_RESPONSE_BODY_SIZE]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_RESPONSE_SIZE]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_CONNECTION_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_CONNECT_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_DOMAIN_LOOKUP_END]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_DOMAIN_LOOKUP_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_FETCH_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_REDIRECT_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_REDIRECT_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_REQUEST_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_SECURE_CONNECTION_START]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [HTTP_REQUEST_WORKER_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_RESPONSE_END]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_RESPONSE_START]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [HTTP_REQUEST_TIME_TO_FIRST_BYTE]: { + type: expect.stringMatching(/^(integer|double)$/), + value: expect.any(Number), + }, + [NETWORK_PROTOCOL_NAME]: { type: 'string', value: '' }, + [NETWORK_PROTOCOL_VERSION]: { type: 'string', value: 'unknown' }, + [SENTRY_OP]: { type: 'string', value: 'resource.script' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.resource.browser.metrics' }, + [SERVER_ADDRESS]: { type: 'string', value: 'sentry-test-site.example' }, + [URL_DOMAIN]: { type: 'string', value: 'sentry-test-site.example' }, + [HTTP_REQUEST_SAME_ORIGIN]: { type: 'boolean', value: false }, + [URL_SCHEME]: { type: 'string', value: 'https' }, + [URL_FULL]: { type: 'string', value: 'https://sentry-test-site.example/path/to/script.js' }, + // WebKit reports `deliveryType` as of Playwright 1.63's build, but still no response status + // or render blocking status. + 'http.response_delivery_type': { type: 'string', value: '' }, + ...(!isWebkitRun && { + [HTTP_RESPONSE_STATUS_CODE]: { type: expect.stringMatching(/^(integer|double)$/), value: expect.any(Number) }, + [RESOURCE_RENDER_BLOCKING_STATUS]: { type: 'string', value: 'non-blocking' }, + }), + }, + name: 'sentry-test-site.example', + status: 'ok', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f\d]{16}$/), + start_timestamp: expect.any(Number), + end_timestamp: expect.any(Number), + trace_id: traceId, + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/init.js similarity index 72% rename from dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/init.js rename to dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/init.js index 8be8d8420f85..38429010914d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/init.js @@ -1,15 +1,14 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; -window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ idleTimeout: 5000, }), - Sentry.spanStreamingIntegration(), ], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/subject.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/subject.js new file mode 100644 index 000000000000..b33aea72d7c9 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/subject.js @@ -0,0 +1,9 @@ +import { simulateCLS } from '../../../../utils/web-vitals/cls.ts'; + +// Getting expected CLS parameter from URL hash +const expectedCLS = Number(location.hash.slice(1)); + +// CLS lands on the pageload span when it ends on the idle timeout, so nothing has to force the +// page away to finalize it. Reloading here used to do that, but it raced the envelope: on a fast +// browser the reload cancelled the in-flight send before it left the page. +simulateCLS(expectedCLS); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/template.html similarity index 84% rename from dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/template.html rename to dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/template.html index 10e2e22f7d6a..72c22be8e49a 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/template.html +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/template.html @@ -5,6 +5,5 @@
-

Some content

diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/test.ts new file mode 100644 index 000000000000..c51b0c98687c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-static/test.ts @@ -0,0 +1,29 @@ +import { PAGELOAD } from '@sentry/conventions/op'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest.beforeEach(async ({ browserName, page }) => { + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.setViewportSize({ width: 800, height: 1200 }); +}); + +sentryTest('records CLS as a pageload measurement with its source', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + await page.goto(`${url}#0.05`); + const eventData = envelopeRequestParser(await pageloadPromise); + + expect(eventData.measurements).toBeDefined(); + expect(eventData.measurements?.cls?.value).toBeDefined(); + + // Flakey value dependent on timings -> we check for a range + expect(eventData.measurements?.cls?.value).toBeGreaterThan(0.03); + expect(eventData.measurements?.cls?.value).toBeLessThan(0.07); + + expect(eventData.contexts?.trace?.data?.['cls.source.1']).toBe('body > div#content > p#partial'); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/init.js deleted file mode 100644 index bd3b6ed17872..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/init.js +++ /dev/null @@ -1,11 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; -window._testBaseTimestamp = performance.timeOrigin / 1000; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], - traceLifecycle: 'stream', - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/subject.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/subject.js deleted file mode 100644 index 9742a4a5cc29..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/subject.js +++ /dev/null @@ -1,17 +0,0 @@ -import { simulateCLS } from '../../../../utils/web-vitals/cls.ts'; - -// Simulate Layout shift right at the beginning of the page load, depending on the URL hash -// don't run if expected CLS is NaN -const expectedCLS = Number(location.hash.slice(1)); -if (expectedCLS && expectedCLS >= 0) { - simulateCLS(expectedCLS).then(() => window.dispatchEvent(new Event('cls-done'))); -} - -// Simulate layout shift whenever the trigger-cls event is dispatched -// Cannot trigger via a button click because expected layout shift after -// an interaction doesn't contribute to CLS. -window.addEventListener('trigger-cls', () => { - simulateCLS(0.1).then(() => { - window.dispatchEvent(new Event('cls-done')); - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/test.ts deleted file mode 100644 index 02ce7b981ed9..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { Page } from '@playwright/test'; -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { hidePage, shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; - -sentryTest.beforeEach(async ({ browserName, page }) => { - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - await page.setViewportSize({ width: 800, height: 1200 }); -}); - -function waitForLayoutShift(page: Page): Promise { - return page.evaluate(() => { - return new Promise(resolve => { - window.addEventListener('cls-done', () => resolve()); - }); - }); -} - -sentryTest('captures CLS as a streamed span with source attributes', async ({ getLocalTestUrl, page }) => { - const url = await getLocalTestUrl({ testDir: __dirname }); - - const clsSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.webvital.cls'); - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - - await page.goto(`${url}#0.15`); - await waitForLayoutShift(page); - await hidePage(page); - - const clsSpan = await clsSpanPromise; - const pageloadSpan = await pageloadSpanPromise; - - expect(clsSpan.attributes['sentry.op']).toEqual({ type: 'string', value: 'ui.webvital.cls' }); - expect(clsSpan.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser.cls' }); - expect(clsSpan.attributes['sentry.exclusive_time']).toEqual({ type: 'integer', value: 0 }); - expect(clsSpan.attributes['user_agent.original']?.value).toEqual(expect.stringContaining('Chrome')); - - // Check the CLS span carries the segment name it belongs to - expect(clsSpan.attributes['sentry.segment.name']).toEqual({ type: 'string', value: 'Pageload' }); - - // Check browser.web_vital.cls.source attributes - expect(clsSpan.attributes['browser.web_vital.cls.source.1']?.value).toEqual( - expect.stringContaining('body > div#content > p'), - ); - - // Check pageload span id is present - expect(clsSpan.attributes['sentry.pageload.span_id']?.value).toBe(pageloadSpan.span_id); - - // CLS is a point-in-time metric - expect(clsSpan.start_timestamp).toEqual(clsSpan.end_timestamp); - - expect(clsSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(clsSpan.trace_id).toMatch(/^[\da-f]{32}$/); - - expect(clsSpan.parent_span_id).toBe(pageloadSpan.span_id); - expect(clsSpan.trace_id).toBe(pageloadSpan.trace_id); -}); - -sentryTest('CLS streamed span has web vital value attribute', async ({ getLocalTestUrl, page }) => { - const url = await getLocalTestUrl({ testDir: __dirname }); - - const clsSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.webvital.cls'); - - await page.goto(`${url}#0.1`); - await waitForLayoutShift(page); - await hidePage(page); - - const clsSpan = await clsSpanPromise; - - // The CLS value should be set as a browser.web_vital.cls.value attribute - expect(clsSpan.attributes['browser.web_vital.cls.value']?.type).toBe('double'); - // Flakey value dependent on timings -> we check for a range - const clsValue = clsSpan.attributes['browser.web_vital.cls.value']?.value as number; - expect(clsValue).toBeGreaterThan(0.05); - expect(clsValue).toBeLessThan(0.15); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/init.js index 38429010914d..1f0b64911a75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/init.js @@ -1,14 +1,10 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; +window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - Sentry.browserTracingIntegration({ - idleTimeout: 5000, - }), - ], + integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/subject.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/subject.js index b33aea72d7c9..9742a4a5cc29 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/subject.js @@ -1,9 +1,17 @@ import { simulateCLS } from '../../../../utils/web-vitals/cls.ts'; -// Getting expected CLS parameter from URL hash +// Simulate Layout shift right at the beginning of the page load, depending on the URL hash +// don't run if expected CLS is NaN const expectedCLS = Number(location.hash.slice(1)); +if (expectedCLS && expectedCLS >= 0) { + simulateCLS(expectedCLS).then(() => window.dispatchEvent(new Event('cls-done'))); +} -// CLS lands on the pageload span when it ends on the idle timeout, so nothing has to force the -// page away to finalize it. Reloading here used to do that, but it raced the envelope: on a fast -// browser the reload cancelled the in-flight send before it left the page. -simulateCLS(expectedCLS); +// Simulate layout shift whenever the trigger-cls event is dispatched +// Cannot trigger via a button click because expected layout shift after +// an interaction doesn't contribute to CLS. +window.addEventListener('trigger-cls', () => { + simulateCLS(0.1).then(() => { + window.dispatchEvent(new Event('cls-done')); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/template.html index 72c22be8e49a..10e2e22f7d6a 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/template.html +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/template.html @@ -5,5 +5,6 @@
+

Some content

diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/test.ts index f00a2b60f100..22e494e73447 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls/test.ts @@ -1,7 +1,17 @@ +import { + SENTRY_OP, + SENTRY_ORIGIN, + SENTRY_EXCLUSIVE_TIME, + USER_AGENT_ORIGINAL, + SENTRY_SEGMENT_NAME, + SENTRY_PAGELOAD_SPAN_ID, + BROWSER_WEB_VITAL_CLS_VALUE, +} from '@sentry/conventions/attributes'; +import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { hidePage, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest.beforeEach(async ({ browserName, page }) => { if (shouldSkipTracingTest() || browserName !== 'chromium') { @@ -11,43 +21,70 @@ sentryTest.beforeEach(async ({ browserName, page }) => { await page.setViewportSize({ width: 800, height: 1200 }); }); -sentryTest('should capture a "GOOD" CLS vital with its source(s).', async ({ getLocalTestUrl, page }) => { +function waitForLayoutShift(page: Page): Promise { + return page.evaluate(() => { + return new Promise(resolve => { + window.addEventListener('cls-done', () => resolve()); + }); + }); +} + +sentryTest('captures CLS as a streamed span with source attributes', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, `${url}#0.05`); - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.cls?.value).toBeDefined(); + const clsSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.webvital.cls'); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - // Flakey value dependent on timings -> we check for a range - expect(eventData.measurements?.cls?.value).toBeGreaterThan(0.03); - expect(eventData.measurements?.cls?.value).toBeLessThan(0.07); + await page.goto(`${url}#0.15`); + await waitForLayoutShift(page); + await hidePage(page); - expect(eventData.contexts?.trace?.data?.['cls.source.1']).toBe('body > div#content > p#partial'); -}); + const clsSpan = await clsSpanPromise; + const pageloadSpan = await pageloadSpanPromise; -sentryTest('should capture a "MEH" CLS vital with its source(s).', async ({ getLocalTestUrl, page }) => { - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, `${url}#0.21`); + expect(clsSpan.attributes[SENTRY_OP]).toEqual({ type: 'string', value: 'ui.webvital.cls' }); + expect(clsSpan.attributes[SENTRY_ORIGIN]).toEqual({ type: 'string', value: 'auto.http.browser.cls' }); + expect(clsSpan.attributes[SENTRY_EXCLUSIVE_TIME]).toEqual({ type: 'integer', value: 0 }); + expect(clsSpan.attributes[USER_AGENT_ORIGINAL]?.value).toEqual(expect.stringContaining('Chrome')); - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.cls?.value).toBeDefined(); + // Check the CLS span carries the segment name it belongs to + expect(clsSpan.attributes[SENTRY_SEGMENT_NAME]).toEqual({ type: 'string', value: 'Pageload' }); - // Flakey value dependent on timings -> we check for a range - expect(eventData.measurements?.cls?.value).toBeGreaterThan(0.18); - expect(eventData.measurements?.cls?.value).toBeLessThan(0.23); + // Check browser.web_vital.cls.source attributes + expect(clsSpan.attributes['browser.web_vital.cls.source.1']?.value).toEqual( + expect.stringContaining('body > div#content > p'), + ); - expect(eventData.contexts?.trace?.data?.['cls.source.1']).toBe('body > div#content > p'); -}); + // Check pageload span id is present + expect(clsSpan.attributes[SENTRY_PAGELOAD_SPAN_ID]?.value).toBe(pageloadSpan.span_id); -sentryTest('should capture a "POOR" CLS vital with its source(s).', async ({ getLocalTestUrl, page }) => { - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, `${url}#0.35`); + // CLS is a point-in-time metric + expect(clsSpan.start_timestamp).toEqual(clsSpan.end_timestamp); + + expect(clsSpan.span_id).toMatch(/^[\da-f]{16}$/); + expect(clsSpan.trace_id).toMatch(/^[\da-f]{32}$/); + + expect(clsSpan.parent_span_id).toBe(pageloadSpan.span_id); + expect(clsSpan.trace_id).toBe(pageloadSpan.trace_id); +}); - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.cls?.value).toBeDefined(); +[ + { cls: 0.05, min: 0.03, max: 0.07, source: 'body > div#content > p#partial' }, + { cls: 0.21, min: 0.18, max: 0.23, source: 'body > div#content > p' }, + { cls: 0.35, min: 0.34, max: 0.36, source: 'body > div#content > p' }, +].forEach(({ cls, min, max, source }) => { + sentryTest(`captures CLS ${cls} with its source`, async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + const clsPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.webvital.cls'); + await page.goto(`${url}#${cls}`); + await waitForLayoutShift(page); + await hidePage(page); + const span = await clsPromise; - // Flakey value dependent on timings -> we check for a range - expect(eventData.measurements?.cls?.value).toBeGreaterThan(0.34); - expect(eventData.measurements?.cls?.value).toBeLessThan(0.36); - expect(eventData.contexts?.trace?.data?.['cls.source.1']).toBe('body > div#content > p'); + expect(span.attributes[BROWSER_WEB_VITAL_CLS_VALUE]?.type).toBe('double'); + const value = span.attributes[BROWSER_WEB_VITAL_CLS_VALUE]?.value; + expect(value).toBeGreaterThan(min); + expect(value).toBeLessThan(max); + expect(span.attributes['browser.web_vital.cls.source.1']).toEqual({ type: 'string', value: source }); + }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp-static/init.js new file mode 100644 index 000000000000..bb6d4918d1bf --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp-static/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp-static/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp-static/template.html new file mode 100644 index 000000000000..b81f11e967e3 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp-static/template.html @@ -0,0 +1,9 @@ + + + + + + +
Rendered
+ + diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp-static/test.ts new file mode 100644 index 000000000000..6148701b53ba --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp-static/test.ts @@ -0,0 +1,46 @@ +import { PAGELOAD } from '@sentry/conventions/op'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('should capture FP vital.', async ({ browserName, getLocalTestUrl, page }) => { + // FP is not generated on webkit or firefox + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + await page.goto(url); + const eventData = envelopeRequestParser(await pageloadPromise); + + expect(eventData.measurements).toBeDefined(); + expect(eventData.measurements?.fp?.value).toBeDefined(); + + const fpSpan = eventData.spans?.filter(({ description }) => description === 'first-paint')[0]; + + expect(fpSpan).toBeDefined(); + expect(fpSpan?.op).toBe('browser.paint'); + expect(fpSpan?.parent_span_id).toBe(eventData.contexts?.trace?.span_id); +}); + +sentryTest('should capture FCP vital.', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === PAGELOAD); + await page.goto(url); + const eventData = envelopeRequestParser(await pageloadPromise); + + expect(eventData.measurements).toBeDefined(); + expect(eventData.measurements?.fcp?.value).toBeDefined(); + + const fcpSpan = eventData.spans?.filter(({ description }) => description === 'first-contentful-paint')[0]; + + expect(fcpSpan).toBeDefined(); + expect(fcpSpan?.op).toBe('browser.paint'); + expect(fcpSpan?.parent_span_id).toBe(eventData.contexts?.trace?.span_id); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts index cd63b699cc06..f3769c45992e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts @@ -1,41 +1,28 @@ +import { PAGELOAD, BROWSER_PAINT } from '@sentry/conventions/op'; +import { BROWSER_WEB_VITAL_FP_VALUE, BROWSER_WEB_VITAL_FCP_VALUE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should capture FP vital.', async ({ browserName, getLocalTestUrl, page }) => { - // FP is not generated on webkit or firefox - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.fp?.value).toBeDefined(); - - const fpSpan = eventData.spans?.filter(({ description }) => description === 'first-paint')[0]; - - expect(fpSpan).toBeDefined(); - expect(fpSpan?.op).toBe('browser.paint'); - expect(fpSpan?.parent_span_id).toBe(eventData.contexts?.trace?.span_id); -}); - -sentryTest('should capture FCP vital.', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.fcp?.value).toBeDefined(); - - const fcpSpan = eventData.spans?.filter(({ description }) => description === 'first-contentful-paint')[0]; - - expect(fcpSpan).toBeDefined(); - expect(fcpSpan?.op).toBe('browser.paint'); - expect(fcpSpan?.parent_span_id).toBe(eventData.contexts?.trace?.span_id); +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; + +[ + { vital: 'fp', attribute: BROWSER_WEB_VITAL_FP_VALUE, name: 'first-paint', chromiumOnly: true }, + { vital: 'fcp', attribute: BROWSER_WEB_VITAL_FCP_VALUE, name: 'first-contentful-paint', chromiumOnly: false }, +].forEach(({ vital, attribute, name, chromiumOnly }) => { + sentryTest(`captures ${vital.toUpperCase()} and its paint span`, async ({ getLocalTestUrl, page, browserName }) => { + sentryTest.skip(shouldSkipTracingTest() || (chromiumOnly && browserName !== 'chromium')); + const spans = collectStreamedSpans(page); + const pageloadPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === PAGELOAD); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + const pageload = await pageloadPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + + expect(pageload.attributes[attribute]?.value).toBeGreaterThan(0); + const paintSpans = spans.filter(span => span.name === name); + expect(paintSpans).toHaveLength(1); + expect(getSpanOp(paintSpans[0])).toBe(BROWSER_PAINT); + expect(paintSpans[0].parent_span_id).toBe(pageload.span_id); + expect(paintSpans[0].trace_id).toBe(pageload.trace_id); + }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/init.js new file mode 100644 index 000000000000..5dd891ccc2f8 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/init.js @@ -0,0 +1,39 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 4000, + enableLongTask: false, + enableInp: true, + instrumentPageLoad: false, + instrumentNavigation: false, + }), + ], + tracesSampleRate: 1, + // A plain (non-streamed) `beforeSendSpan` operates on the v1 `SpanJSON`. INP is sent as a v2 span, + // so this verifies the static callback still runs and its changes are carried into the v2 span. + beforeSendSpan: Sentry.withStaticSpan(span => { + if (span.op === 'ui.interaction.click') { + span.description = 'scrubbed'; + span.data['custom.attribute'] = 'from-before-send-span'; + } + + return span; + }), + debug: true, +}); + +const client = Sentry.getClient(); + +// Force page load transaction name to a testable value +Sentry.startBrowserTracingPageLoadSpan(client, { + name: 'test-url', + attributes: { + ['sentry.segment.name.source']: 'url', + }, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/subject.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/subject.js similarity index 70% rename from dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/subject.js index f4ea1cd46d67..64524952dfa7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/subject.js @@ -3,17 +3,18 @@ const blockUI = e => { const startTime = Date.now(); - function getElapsed() { + function getElasped() { const time = Date.now(); return time - startTime; } - while (getElapsed() < delay) { + while (getElasped() < delay) { // } e.target.classList.add('clicked'); }; +document.querySelector('[data-test-id=not-so-slow-button]').addEventListener('click', blockUI(300)); document.querySelector('[data-test-id=slow-button]').addEventListener('click', blockUI(450)); document.querySelector('[data-test-id=normal-button]').addEventListener('click', blockUI()); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/template.html similarity index 65% rename from dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/template.html rename to dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/template.html index d5f28c7c8847..437426e9ab01 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/template.html +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/template.html @@ -4,7 +4,9 @@ +
Rendered Before Long Task
+ diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/test.ts new file mode 100644 index 000000000000..6368c5df996f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span-static/test.ts @@ -0,0 +1,48 @@ +import { UI_INTERACTION_CLICK } from '@sentry/conventions/op'; +import { BROWSER_WEB_VITAL_INP_VALUE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { hidePage, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { + collectStreamedSpans, + getSpanOp, + getSpansFromEnvelope, + waitForStreamedSpanEnvelope, +} from '../../../../utils/spanUtils'; + +sentryTest('runs a static `beforeSendSpan` for the INP span', async ({ browserName, getLocalTestUrl, page }) => { + const supportedBrowsers = ['chromium']; + + if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) { + sentryTest.skip(); + } + + const spans = collectStreamedSpans(page); + const url = await getLocalTestUrl({ testDir: __dirname }); + + const spanEnvelopePromise = waitForStreamedSpanEnvelope(page, env => + getSpansFromEnvelope(env).some(span => getSpanOp(span) === UI_INTERACTION_CLICK), + ); + + await page.goto(url); + + await page.locator('[data-test-id=normal-button]').click(); + await expect(page.locator('.clicked[data-test-id=normal-button]')).toBeVisible(); + + // Page hide to trigger INP + await hidePage(page); + + await spanEnvelopePromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const inpSpans = spans.filter(span => getSpanOp(span) === UI_INTERACTION_CLICK); + expect(inpSpans).toHaveLength(1); + const [inpSpan] = inpSpans; + + // The callback rewrote the name and added a custom attribute. + expect(inpSpan.name).toBe('scrubbed'); + expect(inpSpan.attributes['custom.attribute']).toEqual({ value: 'from-before-send-span', type: 'string' }); + + // The span is still a valid v2 INP span carrying its web vital value. + const inpValue = inpSpan.attributes[BROWSER_WEB_VITAL_INP_VALUE]?.value as number; + expect(inpValue).toBeGreaterThan(0); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span/init.js index 5dd891ccc2f8..b320fb9f5984 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ @@ -15,16 +14,14 @@ Sentry.init({ }), ], tracesSampleRate: 1, - // A plain (non-streamed) `beforeSendSpan` operates on the v1 `SpanJSON`. INP is sent as a v2 span, - // so this verifies the static callback still runs and its changes are carried into the v2 span. - beforeSendSpan: Sentry.withStaticSpan(span => { - if (span.op === 'ui.interaction.click') { - span.description = 'scrubbed'; - span.data['custom.attribute'] = 'from-before-send-span'; + beforeSendSpan: span => { + if (span.attributes['sentry.op'] === 'ui.interaction.click') { + span.name = 'scrubbed'; + span.attributes['custom.attribute'] = 'from-before-send-span'; } return span; - }), + }, debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span/test.ts index add8a0ac71c3..e65c3e9b90de 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-before-send-span/test.ts @@ -1,44 +1,48 @@ +import { UI_INTERACTION_CLICK } from '@sentry/conventions/op'; +import { BROWSER_WEB_VITAL_INP_VALUE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; import { hidePage, shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, getSpansFromEnvelope, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; - -// This app does not enable span streaming (`traceLifecycle: 'static'`) and defines a plain, non-streamed -// `beforeSendSpan` callback (operating on the v1 `SpanJSON`). INP is still emitted as a v2 span, so this -// verifies the static callback runs for INP and its modifications are carried into the v2 span. - -sentryTest('runs a non-streamed `beforeSendSpan` for the INP span', async ({ browserName, getLocalTestUrl, page }) => { +import { + collectStreamedSpans, + getSpanOp, + getSpansFromEnvelope, + waitForStreamedSpanEnvelope, +} from '../../../../utils/spanUtils'; + +sentryTest('runs `beforeSendSpan` for the streamed INP span', async ({ browserName, getLocalTestUrl, page }) => { const supportedBrowsers = ['chromium']; if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) { sentryTest.skip(); } + const spans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const spanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'ui.interaction.click'), + const spanEnvelopePromise = waitForStreamedSpanEnvelope(page, env => + getSpansFromEnvelope(env).some(span => getSpanOp(span) === UI_INTERACTION_CLICK), ); await page.goto(url); await page.locator('[data-test-id=normal-button]').click(); - await page.locator('.clicked[data-test-id=normal-button]').isVisible(); - - await page.waitForTimeout(500); + await expect(page.locator('.clicked[data-test-id=normal-button]')).toBeVisible(); // Page hide to trigger INP await hidePage(page); - const spanEnvelope = await spanEnvelopePromise; - const inpSpan = getSpansFromEnvelope(spanEnvelope).find(s => getSpanOp(s) === 'ui.interaction.click')!; + await spanEnvelopePromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const inpSpans = spans.filter(span => getSpanOp(span) === UI_INTERACTION_CLICK); + expect(inpSpans).toHaveLength(1); + const [inpSpan] = inpSpans; // The callback rewrote the name and added a custom attribute. expect(inpSpan.name).toBe('scrubbed'); expect(inpSpan.attributes['custom.attribute']).toEqual({ value: 'from-before-send-span', type: 'string' }); // The span is still a valid v2 INP span carrying its web vital value. - const inpValue = inpSpan.attributes['browser.web_vital.inp.value']?.value as number; + const inpValue = inpSpan.attributes[BROWSER_WEB_VITAL_INP_VALUE]?.value as number; expect(inpValue).toBeGreaterThan(0); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/init.js index 398edbd14fe2..9ae2938e2969 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts index 10073a27e212..63f6f5ca17a6 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts @@ -1,8 +1,34 @@ +import { UI_INTERACTION_CLICK } from '@sentry/conventions/op'; +import { + BROWSER_WEB_VITAL_INP_VALUE, + SENTRY_IS_LOCALHOST, + SENTRY_ORIGIN, + SENTRY_OP, + UI_COMPONENT_NAME, + BROWSER_WEB_VITAL_INP_TARGET, + SENTRY_EXCLUSIVE_TIME, + BROWSER_WEB_VITAL_INP_INTERACTION_TYPE, + BROWSER_NAVIGATION_TYPE, + SENTRY_TRANSACTION, + SENTRY_SEGMENT_NAME, + USER_AGENT_ORIGINAL, + SENTRY_PAGELOAD_SPAN_ID, + SENTRY_TRACE_LIFECYCLE, + SENTRY_SEGMENT_ID, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_ENVIRONMENT, +} from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SDK_VERSION } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { hidePage, shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, getSpansFromEnvelope, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; +import { + collectStreamedSpans, + getSpanOp, + getSpansFromEnvelope, + waitForStreamedSpanEnvelope, +} from '../../../../utils/spanUtils'; sentryTest( 'captures an INP click as a streamed span after pageload', @@ -13,19 +39,17 @@ sentryTest( sentryTest.skip(); } + const spans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const spanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'ui.interaction.click'), + const spanEnvelopePromise = waitForStreamedSpanEnvelope(page, env => + getSpansFromEnvelope(env).some(span => getSpanOp(span) === UI_INTERACTION_CLICK), ); await page.goto(url); await page.locator('[data-test-id=normal-button]').click(); - await page.locator('.clicked[data-test-id=normal-button]').isVisible(); - - await page.waitForTimeout(500); + await expect(page.locator('.clicked[data-test-id=normal-button]')).toBeVisible(); // Page hide to trigger INP await hidePage(page); @@ -33,7 +57,10 @@ sentryTest( const spanEnvelope = await spanEnvelopePromise; const envelopeHeader = spanEnvelope[0]; const itemHeader = spanEnvelope[1][0][0]; - const inpSpan = getSpansFromEnvelope(spanEnvelope).find(s => getSpanOp(s) === 'ui.interaction.click')!; + await page.evaluate(() => (window as any).Sentry.flush()); + const inpSpans = spans.filter(span => getSpanOp(span) === UI_INTERACTION_CLICK); + expect(inpSpans).toHaveLength(1); + const [inpSpan] = inpSpans; const traceId = envelopeHeader.trace!.trace_id; expect(traceId).toMatch(/^[\da-f]{32}$/); @@ -54,17 +81,17 @@ sentryTest( expect(itemHeader).toEqual({ type: 'span', - item_count: 1, + item_count: getSpansFromEnvelope(spanEnvelope).length, content_type: 'application/vnd.sentry.items.span.v2+json', }); - const inpValue = inpSpan.attributes['browser.web_vital.inp.value']?.value as number; + const inpValue = inpSpan.attributes[BROWSER_WEB_VITAL_INP_VALUE]?.value as number; expect(inpValue).toBeGreaterThan(0); const pageloadSpanId = inpSpan.parent_span_id; expect(inpSpan).toEqual({ - name: 'body > NormalButton', + name: 'NormalButton', span_id: expect.stringMatching(/^[\da-f]{16}$/), trace_id: traceId, parent_span_id: expect.stringMatching(/^[\da-f]{16}$/), @@ -73,24 +100,24 @@ sentryTest( is_segment: false, status: 'ok', attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - 'sentry.origin': { value: 'auto.http.browser.inp', type: 'string' }, - 'sentry.op': { value: 'ui.interaction.click', type: 'string' }, - 'ui.component_name': { value: 'NormalButton', type: 'string' }, - 'browser.web_vital.inp.target': { value: 'body > NormalButton', type: 'string' }, - 'sentry.exclusive_time': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, - 'browser.web_vital.inp.value': { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, - 'browser.web_vital.inp.interaction_type': { value: 'click', type: 'string' }, - 'browser.navigation.type': { value: 'navigate', type: 'string' }, - 'sentry.transaction': { value: 'test-url', type: 'string' }, - 'sentry.segment.name': { value: 'test-url', type: 'string' }, - 'user_agent.original': { value: expect.stringContaining('Chrome'), type: 'string' }, - 'sentry.pageload.span_id': { value: pageloadSpanId, type: 'string' }, - 'sentry.trace_lifecycle': { value: 'stream', type: 'string' }, - 'sentry.segment.id': { value: pageloadSpanId, type: 'string' }, - 'sentry.sdk.name': { value: 'sentry.javascript.browser', type: 'string' }, - 'sentry.sdk.version': { value: SDK_VERSION, type: 'string' }, - 'sentry.environment': { value: 'production', type: 'string' }, + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [SENTRY_ORIGIN]: { value: 'auto.http.browser.inp', type: 'string' }, + [SENTRY_OP]: { value: UI_INTERACTION_CLICK, type: 'string' }, + [UI_COMPONENT_NAME]: { value: 'NormalButton', type: 'string' }, + [BROWSER_WEB_VITAL_INP_TARGET]: { value: 'body > NormalButton', type: 'string' }, + [SENTRY_EXCLUSIVE_TIME]: { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, + [BROWSER_WEB_VITAL_INP_VALUE]: { value: inpValue, type: expect.stringMatching(/^(integer)|(double)$/) }, + [BROWSER_WEB_VITAL_INP_INTERACTION_TYPE]: { value: 'click', type: 'string' }, + [BROWSER_NAVIGATION_TYPE]: { value: 'navigate', type: 'string' }, + [SENTRY_TRANSACTION]: { value: 'test-url', type: 'string' }, + [SENTRY_SEGMENT_NAME]: { value: 'test-url', type: 'string' }, + [USER_AGENT_ORIGINAL]: { value: expect.stringContaining('Chrome'), type: 'string' }, + [SENTRY_PAGELOAD_SPAN_ID]: { value: pageloadSpanId, type: 'string' }, + [SENTRY_TRACE_LIFECYCLE]: { value: 'stream', type: 'string' }, + [SENTRY_SEGMENT_ID]: { value: pageloadSpanId, type: 'string' }, + [SENTRY_SDK_NAME]: { value: 'sentry.javascript.browser', type: 'string' }, + [SENTRY_SDK_VERSION]: { value: SDK_VERSION, type: 'string' }, + [SENTRY_ENVIRONMENT]: { value: 'production', type: 'string' }, }, }); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/init.js index 822c40214adb..cf36be344e01 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/test.ts index c7a29f406c1e..8cdafe4870c0 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-navigate/test.ts @@ -1,8 +1,34 @@ +import { UI_INTERACTION_CLICK } from '@sentry/conventions/op'; +import { + BROWSER_WEB_VITAL_INP_VALUE, + SENTRY_IS_LOCALHOST, + SENTRY_ORIGIN, + SENTRY_OP, + UI_COMPONENT_NAME, + BROWSER_WEB_VITAL_INP_TARGET, + SENTRY_EXCLUSIVE_TIME, + BROWSER_WEB_VITAL_INP_INTERACTION_TYPE, + BROWSER_NAVIGATION_TYPE, + SENTRY_TRANSACTION, + SENTRY_SEGMENT_NAME, + USER_AGENT_ORIGINAL, + SENTRY_PAGELOAD_SPAN_ID, + SENTRY_TRACE_LIFECYCLE, + SENTRY_SEGMENT_ID, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_ENVIRONMENT, +} from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { SDK_VERSION } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { hidePage, shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, getSpansFromEnvelope, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; +import { + collectStreamedSpans, + getSpanOp, + getSpansFromEnvelope, + waitForStreamedSpanEnvelope, +} from '../../../../utils/spanUtils'; const supportedBrowsers = ['chromium']; @@ -13,20 +39,18 @@ sentryTest( sentryTest.skip(); } + const spans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const spanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'ui.interaction.click'), + const spanEnvelopePromise = waitForStreamedSpanEnvelope(page, env => + getSpansFromEnvelope(env).some(span => getSpanOp(span) === UI_INTERACTION_CLICK), ); await page.goto(url); // Simulating route change (keeping