diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/init.js
deleted file mode 100644
index dce8cd2508fd..000000000000
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/init.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import * as Sentry from '@sentry/browser';
-
-window.Sentry = Sentry;
-
-Sentry.init({
- dsn: 'https://public@dsn.ingest.sentry.io/1337',
- integrations: [
- Sentry.browserTracingIntegration({
- idleTimeout: 5000,
- _experiments: {
- enableStandaloneClsSpans: true,
- },
- }),
- ],
- tracesSampleRate: 1,
-});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/subject.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/subject.js
deleted file mode 100644
index ed1b9b790bb9..000000000000
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-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 cia 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-standalone-spans/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/template.html
deleted file mode 100644
index 10e2e22f7d6a..000000000000
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/template.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
- Some content
-
-
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts
deleted file mode 100644
index fd4b3b8fa06b..000000000000
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts
+++ /dev/null
@@ -1,516 +0,0 @@
-import type { Page } from '@playwright/test';
-import { expect } from '@playwright/test';
-import type { Event as SentryEvent, EventEnvelope, SpanEnvelope } from '@sentry/core';
-import { sentryTest } from '../../../../utils/fixtures';
-import {
- getFirstSentryEnvelopeRequest,
- getMultipleSentryEnvelopeRequests,
- properFullEnvelopeRequestParser,
- shouldSkipTracingTest,
-} from '../../../../utils/helpers';
-
-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());
- });
- });
-}
-
-function triggerAndWaitForLayoutShift(page: Page): Promise {
- return page.evaluate(() => {
- window.dispatchEvent(new CustomEvent('trigger-cls'));
- return new Promise(resolve => {
- window.addEventListener('cls-done', () => resolve());
- });
- });
-}
-
-function hidePage(page: Page): Promise {
- return page.evaluate(() => {
- window.dispatchEvent(new Event('pagehide'));
- });
-}
-
-sentryTest('captures a "GOOD" CLS vital with its source as a standalone span', async ({ getLocalTestUrl, page }) => {
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- const url = await getLocalTestUrl({ testDir: __dirname });
- await page.goto(`${url}#0.05`);
-
- await waitForLayoutShift(page);
-
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
-
- const spanEnvelopeHeaders = spanEnvelope[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
-
- expect(spanEnvelopeItem).toEqual({
- data: {
- 'sentry.exclusive_time': 0,
- 'sentry.op': 'ui.webvital.cls',
- 'sentry.origin': 'auto.http.browser.cls',
- 'sentry.report_event': 'pagehide',
- transaction: expect.stringContaining('index.html'),
- 'user_agent.original': expect.stringContaining('Chrome'),
- 'sentry.pageload.span_id': expect.stringMatching(/[a-f\d]{16}/),
- 'cls.source.1': expect.stringContaining('body > div#content > p'),
- },
- description: expect.stringContaining('body > div#content > p'),
- exclusive_time: 0,
- measurements: {
- cls: {
- unit: '',
- value: expect.any(Number), // better check below,
- },
- },
- op: 'ui.webvital.cls',
- origin: 'auto.http.browser.cls',
- parent_span_id: expect.stringMatching(/[a-f\d]{16}/),
- span_id: expect.stringMatching(/[a-f\d]{16}/),
- segment_id: expect.stringMatching(/[a-f\d]{16}/),
- start_timestamp: expect.any(Number),
- timestamp: spanEnvelopeItem.start_timestamp,
- trace_id: expect.stringMatching(/[a-f\d]{32}/),
- });
-
- // Flakey value dependent on timings -> we check for a range
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0.03);
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeLessThan(0.07);
-
- expect(spanEnvelopeHeaders).toEqual({
- sent_at: expect.any(String),
- trace: {
- environment: 'production',
- public_key: 'public',
- sample_rate: '1',
- sampled: 'true',
- trace_id: spanEnvelopeItem.trace_id,
- sample_rand: expect.any(String),
- // no transaction, because span source is URL
- },
- });
-});
-
-sentryTest('captures a "MEH" CLS vital with its source as a standalone span', async ({ getLocalTestUrl, page }) => {
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- const url = await getLocalTestUrl({ testDir: __dirname });
- await page.goto(`${url}#0.21`);
-
- await waitForLayoutShift(page);
-
- // Page hide to trigger CLS emission
- await page.evaluate(() => {
- window.dispatchEvent(new Event('pagehide'));
- });
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
-
- const spanEnvelopeHeaders = spanEnvelope[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
-
- expect(spanEnvelopeItem).toEqual({
- data: {
- 'sentry.exclusive_time': 0,
- 'sentry.op': 'ui.webvital.cls',
- 'sentry.origin': 'auto.http.browser.cls',
- 'sentry.report_event': 'pagehide',
- transaction: expect.stringContaining('index.html'),
- 'user_agent.original': expect.stringContaining('Chrome'),
- 'sentry.pageload.span_id': expect.stringMatching(/[a-f\d]{16}/),
- 'cls.source.1': expect.stringContaining('body > div#content > p'),
- },
- description: expect.stringContaining('body > div#content > p'),
- exclusive_time: 0,
- measurements: {
- cls: {
- unit: '',
- value: expect.any(Number), // better check below,
- },
- },
- op: 'ui.webvital.cls',
- origin: 'auto.http.browser.cls',
- parent_span_id: expect.stringMatching(/[a-f\d]{16}/),
- span_id: expect.stringMatching(/[a-f\d]{16}/),
- segment_id: expect.stringMatching(/[a-f\d]{16}/),
- start_timestamp: expect.any(Number),
- timestamp: spanEnvelopeItem.start_timestamp,
- trace_id: expect.stringMatching(/[a-f\d]{32}/),
- });
-
- // Flakey value dependent on timings -> we check for a range
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0.18);
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeLessThan(0.23);
-
- expect(spanEnvelopeHeaders).toEqual({
- sent_at: expect.any(String),
- trace: {
- environment: 'production',
- public_key: 'public',
- sample_rate: '1',
- sampled: 'true',
- trace_id: spanEnvelopeItem.trace_id,
- sample_rand: expect.any(String),
- // no transaction, because span source is URL
- },
- });
-});
-
-sentryTest('captures a "POOR" CLS vital with its source as a standalone span.', async ({ getLocalTestUrl, page }) => {
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- const url = await getLocalTestUrl({ testDir: __dirname });
- await page.goto(`${url}#0.35`);
-
- await waitForLayoutShift(page);
-
- // Page hide to trigger CLS emission
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
-
- const spanEnvelopeHeaders = spanEnvelope[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
-
- expect(spanEnvelopeItem).toEqual({
- data: {
- 'sentry.exclusive_time': 0,
- 'sentry.op': 'ui.webvital.cls',
- 'sentry.origin': 'auto.http.browser.cls',
- 'sentry.report_event': 'pagehide',
- transaction: expect.stringContaining('index.html'),
- 'user_agent.original': expect.stringContaining('Chrome'),
- 'sentry.pageload.span_id': expect.stringMatching(/[a-f\d]{16}/),
- 'cls.source.1': expect.stringContaining('body > div#content > p'),
- },
- description: expect.stringContaining('body > div#content > p'),
- exclusive_time: 0,
- measurements: {
- cls: {
- unit: '',
- value: expect.any(Number), // better check below,
- },
- },
- op: 'ui.webvital.cls',
- origin: 'auto.http.browser.cls',
- parent_span_id: expect.stringMatching(/[a-f\d]{16}/),
- span_id: expect.stringMatching(/[a-f\d]{16}/),
- segment_id: expect.stringMatching(/[a-f\d]{16}/),
- start_timestamp: expect.any(Number),
- timestamp: spanEnvelopeItem.start_timestamp,
- trace_id: expect.stringMatching(/[a-f\d]{32}/),
- });
-
- // Flakey value dependent on timings -> we check for a range
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0.33);
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeLessThan(0.38);
-
- expect(spanEnvelopeHeaders).toEqual({
- sent_at: expect.any(String),
- trace: {
- environment: 'production',
- public_key: 'public',
- sample_rate: '1',
- sampled: 'true',
- trace_id: spanEnvelopeItem.trace_id,
- sample_rand: expect.any(String),
- // no transaction, because span source is URL
- },
- });
-});
-
-sentryTest(
- 'captures a 0 CLS vital as a standalone span if no layout shift occurred',
- async ({ getLocalTestUrl, page }) => {
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- const url = await getLocalTestUrl({ testDir: __dirname });
- await page.goto(url);
-
- await page.waitForTimeout(1000);
-
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
-
- const spanEnvelopeHeaders = spanEnvelope[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
-
- expect(spanEnvelopeItem).toEqual({
- data: {
- 'sentry.exclusive_time': 0,
- 'sentry.op': 'ui.webvital.cls',
- 'sentry.origin': 'auto.http.browser.cls',
- 'sentry.report_event': 'pagehide',
- transaction: expect.stringContaining('index.html'),
- 'user_agent.original': expect.stringContaining('Chrome'),
- 'sentry.pageload.span_id': expect.stringMatching(/[a-f\d]{16}/),
- },
- description: 'Layout shift',
- exclusive_time: 0,
- measurements: {
- cls: {
- unit: '',
- value: 0,
- },
- },
- op: 'ui.webvital.cls',
- origin: 'auto.http.browser.cls',
- parent_span_id: expect.stringMatching(/[a-f\d]{16}/),
- span_id: expect.stringMatching(/[a-f\d]{16}/),
- segment_id: expect.stringMatching(/[a-f\d]{16}/),
- start_timestamp: expect.any(Number),
- timestamp: spanEnvelopeItem.start_timestamp,
- trace_id: expect.stringMatching(/[a-f\d]{32}/),
- });
-
- expect(spanEnvelopeHeaders).toEqual({
- sent_at: expect.any(String),
- trace: {
- environment: 'production',
- public_key: 'public',
- sample_rate: '1',
- sampled: 'true',
- trace_id: spanEnvelopeItem.trace_id,
- sample_rand: expect.any(String),
- // no transaction, because span source is URL
- },
- });
- },
-);
-
-sentryTest(
- 'captures CLS increases after the pageload span ended, when page is hidden',
- async ({ getLocalTestUrl, page }) => {
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const eventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(eventData.type).toBe('transaction');
- expect(eventData.contexts?.trace?.op).toBe('pageload');
-
- const pageloadSpanId = eventData.contexts?.trace?.span_id;
- const pageloadTraceId = eventData.contexts?.trace?.trace_id;
-
- expect(pageloadSpanId).toMatch(/[a-f\d]{16}/);
- expect(pageloadTraceId).toMatch(/[a-f\d]{32}/);
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- await triggerAndWaitForLayoutShift(page);
-
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
- // Flakey value dependent on timings -> we check for a range
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0.05);
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeLessThan(0.15);
-
- // Ensure the CLS span is connected to the pageload span and trace
- expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadSpanId);
- expect(spanEnvelopeItem.trace_id).toEqual(pageloadTraceId);
-
- expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('pagehide');
- },
-);
-
-sentryTest('sends CLS of the initial page when soft-navigating to a new page', async ({ getLocalTestUrl, page }) => {
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const pageloadEventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(pageloadEventData.type).toBe('transaction');
- expect(pageloadEventData.contexts?.trace?.op).toBe('pageload');
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- await triggerAndWaitForLayoutShift(page);
-
- await page.goto(`${url}#soft-navigation`);
-
- const pageloadTraceId = pageloadEventData.contexts?.trace?.trace_id;
- expect(pageloadTraceId).toMatch(/[a-f\d]{32}/);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
- // Flakey value dependent on timings -> we check for a range
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0.05);
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeLessThan(0.15);
- expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadEventData.contexts?.trace?.span_id);
- expect(spanEnvelopeItem.trace_id).toEqual(pageloadTraceId);
-
- expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation');
-});
-
-sentryTest("doesn't send further CLS after the first navigation", async ({ getLocalTestUrl, page }) => {
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const eventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(eventData.type).toBe('transaction');
- expect(eventData.contexts?.trace?.op).toBe('pageload');
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- await triggerAndWaitForLayoutShift(page);
-
- await page.goto(`${url}#soft-navigation`);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0);
- expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation');
-
- getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'span' }, () => {
- throw new Error('Unexpected span - This should not happen!');
- });
-
- const navigationTxnPromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'transaction' },
- properFullEnvelopeRequestParser,
- );
-
- // activate both CLS emission triggers:
- await page.goto(`${url}#soft-navigation-2`);
- await hidePage(page);
-
- // assumption: If we would send another CLS span on the 2nd navigation, it would be sent before the navigation
- // transaction ends. This isn't 100% safe to ensure we don't send something but otherwise we'd need to wait for
- // a timeout or something similar.
- await navigationTxnPromise;
-});
-
-sentryTest("doesn't send further CLS after the first page hide", async ({ getLocalTestUrl, page }) => {
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const eventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(eventData.type).toBe('transaction');
- expect(eventData.contexts?.trace?.op).toBe('pageload');
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- await triggerAndWaitForLayoutShift(page);
-
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
- expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0);
- expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('pagehide');
-
- getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'span' }, () => {
- throw new Error('Unexpected span - This should not happen!');
- });
-
- const navigationTxnPromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'transaction' },
- properFullEnvelopeRequestParser,
- );
-
- // activate both CLS emission triggers:
- await page.goto(`${url}#soft-navigation-2`);
- await hidePage(page);
-
- // assumption: If we would send another CLS span on the 2nd navigation, it would be sent before the navigation
- // transaction ends. This isn't 100% safe to ensure we don't send something but otherwise we'd need to wait for
- // a timeout or something similar.
- await navigationTxnPromise;
-});
-
-sentryTest('CLS span timestamps are set correctly', async ({ getLocalTestUrl, page }) => {
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const eventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(eventData.type).toBe('transaction');
- expect(eventData.contexts?.trace?.op).toBe('pageload');
- expect(eventData.timestamp).toBeDefined();
-
- const pageloadEndTimestamp = eventData.timestamp!;
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- await triggerAndWaitForLayoutShift(page);
-
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
-
- expect(spanEnvelopeItem.start_timestamp).toBeDefined();
- expect(spanEnvelopeItem.timestamp).toBeDefined();
-
- const clsSpanStartTimestamp = spanEnvelopeItem.start_timestamp!;
- const clsSpanEndTimestamp = spanEnvelopeItem.timestamp!;
-
- // CLS performance entries have no duration ==> start and end timestamp should be the same
- expect(clsSpanStartTimestamp).toEqual(clsSpanEndTimestamp);
-
- // We don't really care that they are very close together but rather about the order of magnitude
- // Previously, we had a bug where the timestamps would be significantly off (by multiple hours)
- // so we only ensure that this bug is fixed. 60 seconds should be more than enough.
- expect(clsSpanStartTimestamp - pageloadEndTimestamp).toBeLessThan(60);
- expect(clsSpanStartTimestamp).toBeGreaterThan(pageloadEndTimestamp);
-});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/assets/sentry-logo-600x179.png b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/assets/sentry-logo-600x179.png
deleted file mode 100644
index 353b7233d6bf..000000000000
Binary files a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/assets/sentry-logo-600x179.png and /dev/null differ
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/init.js
deleted file mode 100644
index d09eeab5f565..000000000000
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/init.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import * as Sentry from '@sentry/browser';
-
-window.Sentry = Sentry;
-
-Sentry.init({
- dsn: 'https://public@dsn.ingest.sentry.io/1337',
- integrations: [
- Sentry.browserTracingIntegration({
- idleTimeout: 5000,
- _experiments: {
- enableStandaloneLcpSpans: true,
- },
- }),
- ],
- tracesSampleRate: 1,
- debug: true,
-});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/template.html
deleted file mode 100644
index b613a556aca4..000000000000
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/template.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/test.ts
deleted file mode 100644
index e2b8a3e66e44..000000000000
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-standalone-spans/test.ts
+++ /dev/null
@@ -1,369 +0,0 @@
-import type { Page, Route } from '@playwright/test';
-import { expect } from '@playwright/test';
-import type { Event as SentryEvent, EventEnvelope, SpanEnvelope } from '@sentry/core';
-import { sentryTest } from '../../../../utils/fixtures';
-import {
- envelopeRequestParser,
- getFirstSentryEnvelopeRequest,
- getMultipleSentryEnvelopeRequests,
- properFullEnvelopeRequestParser,
- shouldSkipTracingTest,
- waitForTransactionRequest,
-} from '../../../../utils/helpers';
-
-sentryTest.beforeEach(async ({ browserName, page }) => {
- if (shouldSkipTracingTest() || browserName !== 'chromium') {
- sentryTest.skip();
- }
-
- await page.setViewportSize({ width: 800, height: 1200 });
-});
-
-function hidePage(page: Page): Promise {
- return page.evaluate(() => {
- window.dispatchEvent(new Event('pagehide'));
- });
-}
-
-sentryTest('captures LCP vital as a standalone span', async ({ getLocalTestUrl, page }) => {
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- const pageloadEnvelopePromise = waitForTransactionRequest(page, e => e.contexts?.trace?.op === 'pageload');
-
- page.route('**', route => route.continue());
- page.route('**/my/image.png', async (route: Route) => {
- return route.fulfill({
- path: `${__dirname}/assets/sentry-logo-600x179.png`,
- });
- });
-
- const url = await getLocalTestUrl({ testDir: __dirname });
- await page.goto(url);
-
- // Wait for LCP to be captured
- await page.waitForTimeout(1000);
-
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const pageloadTransactionEvent = envelopeRequestParser(await pageloadEnvelopePromise);
-
- const spanEnvelopeHeaders = spanEnvelope[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
-
- const pageloadTraceId = pageloadTransactionEvent.contexts?.trace?.trace_id;
- expect(pageloadTraceId).toMatch(/[a-f\d]{32}/);
-
- expect(spanEnvelopeItem).toEqual({
- data: {
- 'sentry.exclusive_time': 0,
- 'sentry.op': 'ui.webvital.lcp',
- 'sentry.origin': 'auto.http.browser.lcp',
- 'sentry.report_event': 'pagehide',
- transaction: expect.stringContaining('index.html'),
- 'user_agent.original': expect.stringContaining('Chrome'),
- 'sentry.pageload.span_id': expect.stringMatching(/[a-f\d]{16}/),
- 'lcp.element': 'body > img',
- 'lcp.loadTime': expect.any(Number),
- 'lcp.renderTime': expect.any(Number),
- 'lcp.size': expect.any(Number),
- 'lcp.url': 'https://sentry-test-site.example/my/image.png',
- },
- description: expect.stringContaining('body > img'),
- exclusive_time: 0,
- measurements: {
- lcp: {
- unit: 'millisecond',
- value: expect.any(Number),
- },
- },
- op: 'ui.webvital.lcp',
- origin: 'auto.http.browser.lcp',
- parent_span_id: expect.stringMatching(/[a-f\d]{16}/),
- span_id: expect.stringMatching(/[a-f\d]{16}/),
- segment_id: expect.stringMatching(/[a-f\d]{16}/),
- start_timestamp: expect.any(Number),
- timestamp: spanEnvelopeItem.start_timestamp, // LCP is a point-in-time metric
- trace_id: pageloadTraceId,
- });
-
- // LCP value should be greater than 0
- expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0);
-
- expect(spanEnvelopeHeaders).toEqual({
- sent_at: expect.any(String),
- trace: {
- environment: 'production',
- public_key: 'public',
- sample_rate: '1',
- sampled: 'true',
- trace_id: spanEnvelopeItem.trace_id,
- sample_rand: expect.any(String),
- },
- });
-});
-
-sentryTest('LCP span is linked to pageload transaction', async ({ getLocalTestUrl, page }) => {
- page.route('**', route => route.continue());
- page.route('**/my/image.png', async (route: Route) => {
- return route.fulfill({
- path: `${__dirname}/assets/sentry-logo-600x179.png`,
- });
- });
-
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const eventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(eventData.type).toBe('transaction');
- expect(eventData.contexts?.trace?.op).toBe('pageload');
-
- const pageloadSpanId = eventData.contexts?.trace?.span_id;
- const pageloadTraceId = eventData.contexts?.trace?.trace_id;
-
- expect(pageloadSpanId).toMatch(/[a-f\d]{16}/);
- expect(pageloadTraceId).toMatch(/[a-f\d]{32}/);
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- // Wait for LCP to be captured
- await page.waitForTimeout(1000);
-
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
-
- // Ensure the LCP span is connected to the pageload span and trace
- expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadSpanId);
- expect(spanEnvelopeItem.trace_id).toEqual(pageloadTraceId);
- expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0);
-});
-
-sentryTest('sends LCP of the initial page when soft-navigating to a new page', async ({ getLocalTestUrl, page }) => {
- page.route('**', route => route.continue());
- page.route('**/my/image.png', async (route: Route) => {
- return route.fulfill({
- path: `${__dirname}/assets/sentry-logo-600x179.png`,
- });
- });
-
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const pageloadEventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(pageloadEventData.type).toBe('transaction');
- expect(pageloadEventData.contexts?.trace?.op).toBe('pageload');
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- // Wait for LCP to be captured
- await page.waitForTimeout(1000);
-
- await page.goto(`${url}#soft-navigation`);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
-
- expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0);
- expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadEventData.contexts?.trace?.span_id);
- expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation');
- expect(spanEnvelopeItem.trace_id).toBe(pageloadEventData.contexts?.trace?.trace_id);
-});
-
-sentryTest("doesn't send further LCP after the first navigation", async ({ getLocalTestUrl, page }) => {
- page.route('**', route => route.continue());
- page.route('**/my/image.png', async (route: Route) => {
- return route.fulfill({
- path: `${__dirname}/assets/sentry-logo-600x179.png`,
- });
- });
-
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const pageloadEventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(pageloadEventData.type).toBe('transaction');
- expect(pageloadEventData.contexts?.trace?.op).toBe('pageload');
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- // Wait for LCP to be captured
- await page.waitForTimeout(1000);
-
- await page.goto(`${url}#soft-navigation`);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
- expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0);
- expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('navigation');
- expect(spanEnvelopeItem.trace_id).toBe(pageloadEventData.contexts?.trace?.trace_id);
-
- getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'span' }, () => {
- throw new Error('Unexpected span - This should not happen!');
- });
-
- const navigationTxnPromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'transaction' },
- properFullEnvelopeRequestParser,
- );
-
- // activate both LCP emission triggers:
- await page.goto(`${url}#soft-navigation-2`);
- await hidePage(page);
-
- // assumption: If we would send another LCP span on the 2nd navigation, it would be sent before the navigation
- // transaction ends. This isn't 100% safe to ensure we don't send something but otherwise we'd need to wait for
- // a timeout or something similar.
- await navigationTxnPromise;
-});
-
-sentryTest("doesn't send further LCP after the first page hide", async ({ getLocalTestUrl, page }) => {
- page.route('**', route => route.continue());
- page.route('**/my/image.png', async (route: Route) => {
- return route.fulfill({
- path: `${__dirname}/assets/sentry-logo-600x179.png`,
- });
- });
-
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const pageloadEventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(pageloadEventData.type).toBe('transaction');
- expect(pageloadEventData.contexts?.trace?.op).toBe('pageload');
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- // Wait for LCP to be captured
- await page.waitForTimeout(1000);
-
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
- expect(spanEnvelopeItem.measurements?.lcp?.value).toBeGreaterThan(0);
- expect(spanEnvelopeItem.data?.['sentry.report_event']).toBe('pagehide');
- expect(spanEnvelopeItem.trace_id).toBe(pageloadEventData.contexts?.trace?.trace_id);
-
- getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'span' }, () => {
- throw new Error('Unexpected span - This should not happen!');
- });
-
- const navigationTxnPromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'transaction' },
- properFullEnvelopeRequestParser,
- );
-
- // activate both LCP emission triggers:
- await page.goto(`${url}#soft-navigation-2`);
- await hidePage(page);
-
- // assumption: If we would send another LCP span on the 2nd navigation, it would be sent before the navigation
- // transaction ends. This isn't 100% safe to ensure we don't send something but otherwise we'd need to wait for
- // a timeout or something similar.
- await navigationTxnPromise;
-});
-
-sentryTest('LCP span timestamps are set correctly', async ({ getLocalTestUrl, page }) => {
- page.route('**', route => route.continue());
- page.route('**/my/image.png', async (route: Route) => {
- return route.fulfill({
- path: `${__dirname}/assets/sentry-logo-600x179.png`,
- });
- });
-
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- const eventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(eventData.type).toBe('transaction');
- expect(eventData.contexts?.trace?.op).toBe('pageload');
- expect(eventData.timestamp).toBeDefined();
-
- const pageloadEndTimestamp = eventData.timestamp!;
-
- const spanEnvelopePromise = getMultipleSentryEnvelopeRequests(
- page,
- 1,
- { envelopeType: 'span' },
- properFullEnvelopeRequestParser,
- );
-
- // Wait for LCP to be captured
- await page.waitForTimeout(1000);
-
- await hidePage(page);
-
- const spanEnvelope = (await spanEnvelopePromise)[0];
- const spanEnvelopeItem = spanEnvelope[1][0][1];
-
- expect(spanEnvelopeItem.start_timestamp).toBeDefined();
- expect(spanEnvelopeItem.timestamp).toBeDefined();
-
- const lcpSpanStartTimestamp = spanEnvelopeItem.start_timestamp!;
- const lcpSpanEndTimestamp = spanEnvelopeItem.timestamp!;
-
- // LCP is a point-in-time metric ==> start and end timestamp should be the same
- expect(lcpSpanStartTimestamp).toEqual(lcpSpanEndTimestamp);
-
- // We don't really care that they are very close together but rather about the order of magnitude
- // Previously, we had a bug where the timestamps would be significantly off (by multiple hours)
- // so we only ensure that this bug is fixed. 60 seconds should be more than enough.
- expect(lcpSpanStartTimestamp - pageloadEndTimestamp).toBeLessThan(60);
-});
-
-sentryTest(
- 'pageload transaction does not contain LCP measurement when standalone spans are enabled',
- async ({ getLocalTestUrl, page }) => {
- page.route('**', route => route.continue());
- page.route('**/my/image.png', async (route: Route) => {
- return route.fulfill({
- path: `${__dirname}/assets/sentry-logo-600x179.png`,
- });
- });
-
- const url = await getLocalTestUrl({ testDir: __dirname });
- const eventData = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(eventData.type).toBe('transaction');
- expect(eventData.contexts?.trace?.op).toBe('pageload');
-
- // LCP measurement should NOT be present on the pageload transaction when standalone spans are enabled
- expect(eventData.measurements?.lcp).toBeUndefined();
-
- // LCP attributes should also NOT be present on the pageload transaction when standalone spans are enabled
- // because the LCP data is sent as a standalone span instead
- expect(eventData.contexts?.trace?.data?.['lcp.element']).toBeUndefined();
- expect(eventData.contexts?.trace?.data?.['lcp.size']).toBeUndefined();
- },
-);
diff --git a/packages/browser/src/integrations/webVitals.ts b/packages/browser/src/integrations/webVitals.ts
index b7b325f81f8f..8f4d98affb13 100644
--- a/packages/browser/src/integrations/webVitals.ts
+++ b/packages/browser/src/integrations/webVitals.ts
@@ -18,14 +18,6 @@ export interface WebVitalsOptions {
* Web vitals to skip.
*/
disable?: WebVitalName[];
-
- /**
- * @experimental
- */
- _experiments?: Partial<{
- enableStandaloneClsSpans: boolean;
- enableStandaloneLcpSpans: boolean;
- }>;
}
const collectWebVitalsCallbacks = new WeakMap void>();
@@ -48,15 +40,12 @@ export const webVitalsIntegration = defineIntegration((options: WebVitalsOptions
name: WEB_VITALS_INTEGRATION_NAME,
setup(client) {
const spanStreamingEnabled = hasSpanStreamingEnabled(client);
- const { enableStandaloneClsSpans, enableStandaloneLcpSpans } = options._experiments ?? {};
collectWebVitalsCallbacks.set(
client,
startTrackingWebVitals({
- recordClsStandaloneSpans:
- spanStreamingEnabled || disabled.has('cls') ? undefined : enableStandaloneClsSpans || false,
- recordLcpStandaloneSpans:
- spanStreamingEnabled || disabled.has('lcp') ? undefined : enableStandaloneLcpSpans || false,
+ recordClsStandaloneSpans: spanStreamingEnabled || disabled.has('cls') ? undefined : false,
+ recordLcpStandaloneSpans: spanStreamingEnabled || disabled.has('lcp') ? undefined : false,
client,
}),
);
diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts
index 1f6094a37fdc..debf2bac6626 100644
--- a/packages/browser/src/tracing/browserTracingIntegration.ts
+++ b/packages/browser/src/tracing/browserTracingIntegration.ts
@@ -303,8 +303,6 @@ export interface BrowserTracingOptions {
*/
_experiments: Partial<{
enableInteractions: boolean;
- enableStandaloneClsSpans: boolean;
- enableStandaloneLcpSpans: boolean;
}>;
/**
@@ -386,7 +384,7 @@ export const browserTracingIntegration = ((options: Partial {
expect(mockTrackInpAsSpan).not.toHaveBeenCalled();
});
- it('keeps standalone LCP and CLS experiments working', () => {
- const client = { getOptions: () => ({}) };
- const integration = webVitalsIntegration({
- _experiments: {
- enableStandaloneClsSpans: true,
- enableStandaloneLcpSpans: true,
- },
- });
-
- integration.setup?.(client as never);
-
- expect(mockStartTrackingWebVitals).toHaveBeenCalledWith({
- recordClsStandaloneSpans: true,
- recordLcpStandaloneSpans: true,
- client,
- });
- });
-
it('tracks LCP, CLS and INP as streamed spans when span streaming is enabled', () => {
const client = { getOptions: () => ({ traceLifecycle: 'stream' }) };
const integration = webVitalsIntegration();