-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(browser): Emit web vitals as streamed spans #19827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
logaretm
wants to merge
15
commits into
develop
Choose a base branch
from
awad/js-17931-webvitals-v2-spans
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
597d120
feat(browser-utils): Add FCP instrumentation handler and export INP_E…
logaretm ff68317
feat(browser): Emit web vitals as streamed spans when span streaming …
logaretm 03f912b
test(browser): Add integration tests for streamed web vital spans
logaretm bf8a48f
fix(browser): Only emit LCP, CLS, INP as streamed spans; disable stan…
logaretm e9a7881
fix(browser): Add MAX_PLAUSIBLE_INP_DURATION check to streamed INP sp…
logaretm 3028afc
fix(browser): Prevent duplicate INP spans when span streaming is enabled
logaretm 0009ee7
fix(browser-utils): Remove dead FCP instrumentation code
logaretm 69a8add
fix(browser-utils): Add fallback for browserPerformanceTimeOrigin in …
logaretm dad3db4
fix(browser-utils): Cache browserPerformanceTimeOrigin call in _sendL…
logaretm b4ffde3
fix(browser): Skip INP interaction listeners when span streaming is e…
logaretm a52c543
fix(browser): Skip CLS/LCP measurements on pageload span when streaming
logaretm fa7b1d4
refactor(browser-utils): Share MAX_PLAUSIBLE_INP_DURATION between INP…
logaretm aa40c14
fix(browser): Fix ReferenceError for spanStreamingEnabled in afterAll…
logaretm 43438d6
fix(browser): Skip redundant CLS/LCP handlers when span streaming is …
logaretm 1695674
send wv spans as child spans of pageload/inp root span
Lms24 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
...es/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/init.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| 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, | ||
| }); |
17 changes: 17 additions & 0 deletions
17
...browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/subject.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| 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')); | ||
| }); | ||
| }); |
10 changes: 10 additions & 0 deletions
10
...wser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/template.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <!doctype html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| </head> | ||
| <body> | ||
| <div id="content"></div> | ||
| <p>Some content</p> | ||
| </body> | ||
| </html> |
76 changes: 76 additions & 0 deletions
76
...es/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import type { Page } from '@playwright/test'; | ||
| import { expect } from '@playwright/test'; | ||
| import { sentryTest } from '../../../../utils/fixtures'; | ||
| import { hidePage, shouldSkipTracingTest, testingCdnBundle } from '../../../../utils/helpers'; | ||
| import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; | ||
|
|
||
| sentryTest.beforeEach(async ({ browserName, page }) => { | ||
| if (shouldSkipTracingTest() || testingCdnBundle() || browserName !== 'chromium') { | ||
| sentryTest.skip(); | ||
| } | ||
|
|
||
| await page.setViewportSize({ width: 800, height: 1200 }); | ||
| }); | ||
|
|
||
| function waitForLayoutShift(page: Page): Promise<void> { | ||
| 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 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); | ||
| }); | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
13 changes: 13 additions & 0 deletions
13
...es/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/init.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| 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({ idleTimeout: 4000 }), | ||
| Sentry.spanStreamingIntegration(), | ||
| ], | ||
| tracesSampleRate: 1, | ||
| }); |
19 changes: 19 additions & 0 deletions
19
...browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/subject.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| const blockUI = | ||
| (delay = 70) => | ||
| e => { | ||
| const startTime = Date.now(); | ||
|
|
||
| function getElapsed() { | ||
| const time = Date.now(); | ||
| return time - startTime; | ||
| } | ||
|
|
||
| while (getElapsed() < delay) { | ||
| // | ||
| } | ||
|
|
||
| e.target.classList.add('clicked'); | ||
| }; | ||
|
|
||
| document.querySelector('[data-test-id=slow-button]').addEventListener('click', blockUI(450)); | ||
| document.querySelector('[data-test-id=normal-button]').addEventListener('click', blockUI()); |
10 changes: 10 additions & 0 deletions
10
...wser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/template.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <!doctype html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| </head> | ||
| <body> | ||
| <button data-test-id="slow-button" data-sentry-element="SlowButton">Slow</button> | ||
| <button data-test-id="normal-button" data-sentry-element="NormalButton">Click Me</button> | ||
| </body> | ||
| </html> |
79 changes: 79 additions & 0 deletions
79
...es/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { expect } from '@playwright/test'; | ||
| import { sentryTest } from '../../../../utils/fixtures'; | ||
| import { hidePage, shouldSkipTracingTest, testingCdnBundle } from '../../../../utils/helpers'; | ||
| import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; | ||
|
|
||
| sentryTest.beforeEach(async ({ browserName }) => { | ||
| if (shouldSkipTracingTest() || testingCdnBundle() || browserName !== 'chromium') { | ||
| sentryTest.skip(); | ||
| } | ||
| }); | ||
|
|
||
| sentryTest('captures INP click as a streamed span', async ({ getLocalTestUrl, page }) => { | ||
| const url = await getLocalTestUrl({ testDir: __dirname }); | ||
|
|
||
| const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); | ||
| const inpSpanPromise = waitForStreamedSpan(page, 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 hidePage(page); | ||
|
|
||
| const inpSpan = await inpSpanPromise; | ||
| const pageloadSpan = await pageloadSpanPromise; | ||
|
|
||
| expect(inpSpan.attributes?.['sentry.op']).toEqual({ type: 'string', value: 'ui.interaction.click' }); | ||
| expect(inpSpan.attributes?.['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser.inp' }); | ||
| expect(inpSpan.attributes?.['user_agent.original']?.value).toEqual(expect.stringContaining('Chrome')); | ||
|
|
||
| const inpValue = inpSpan.attributes?.['browser.web_vital.inp.value']?.value as number; | ||
| expect(inpValue).toBeGreaterThan(0); | ||
|
|
||
| expect(inpSpan.attributes?.['sentry.exclusive_time']?.value).toBeGreaterThan(0); | ||
|
|
||
| expect(inpSpan.name).toBe('body > NormalButton'); | ||
|
|
||
| expect(inpSpan.end_timestamp).toBeGreaterThan(inpSpan.start_timestamp); | ||
|
|
||
| expect(inpSpan.span_id).toMatch(/^[\da-f]{16}$/); | ||
| expect(inpSpan.trace_id).toMatch(/^[\da-f]{32}$/); | ||
|
|
||
| expect(inpSpan.parent_span_id).toBe(pageloadSpan.span_id); | ||
| expect(inpSpan.trace_id).toBe(pageloadSpan.trace_id); | ||
| }); | ||
|
|
||
| sentryTest('captures the slowest interaction as streamed INP span', async ({ getLocalTestUrl, page }) => { | ||
| const url = await getLocalTestUrl({ testDir: __dirname }); | ||
|
|
||
| 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); | ||
|
|
||
| const inpSpanPromise = waitForStreamedSpan(page, span => { | ||
| const op = getSpanOp(span); | ||
| return op === 'ui.interaction.click'; | ||
| }); | ||
|
|
||
| await page.locator('[data-test-id=slow-button]').click(); | ||
| await page.locator('.clicked[data-test-id=slow-button]').isVisible(); | ||
|
|
||
| await page.waitForTimeout(500); | ||
|
|
||
| await hidePage(page); | ||
|
|
||
| const inpSpan = await inpSpanPromise; | ||
|
|
||
| expect(inpSpan.name).toBe('body > SlowButton'); | ||
| expect(inpSpan.attributes?.['sentry.exclusive_time']?.value).toBeGreaterThan(400); | ||
|
|
||
| const inpValue = inpSpan.attributes?.['browser.web_vital.inp.value']?.value as number; | ||
| expect(inpValue).toBeGreaterThan(400); | ||
| }); |
Binary file added
BIN
+15.7 KB
...es/tracing/metrics/web-vitals-lcp-streamed-spans/assets/sentry-logo-600x179.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions
11
...es/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/init.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| 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, | ||
| }); |
10 changes: 10 additions & 0 deletions
10
...wser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/template.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| <!doctype html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| </head> | ||
| <body> | ||
| <div id="content"></div> | ||
| <img src="https://sentry-test-site.example/my/image.png" /> | ||
| </body> | ||
| </html> |
65 changes: 65 additions & 0 deletions
65
...es/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import type { Route } from '@playwright/test'; | ||
| import { expect } from '@playwright/test'; | ||
| import { sentryTest } from '../../../../utils/fixtures'; | ||
| import { hidePage, shouldSkipTracingTest, testingCdnBundle } from '../../../../utils/helpers'; | ||
| import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; | ||
|
|
||
| sentryTest.beforeEach(async ({ browserName, page }) => { | ||
| if (shouldSkipTracingTest() || testingCdnBundle() || browserName !== 'chromium') { | ||
| sentryTest.skip(); | ||
| } | ||
|
|
||
| await page.setViewportSize({ width: 800, height: 1200 }); | ||
| }); | ||
|
|
||
| sentryTest('captures LCP as a streamed span with element attributes', 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 lcpSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'ui.webvital.lcp'); | ||
| const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); | ||
|
|
||
| await page.goto(url); | ||
|
|
||
| // Wait for LCP to be captured | ||
| await page.waitForTimeout(1000); | ||
Lms24 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| await hidePage(page); | ||
|
|
||
| const lcpSpan = await lcpSpanPromise; | ||
| const pageloadSpan = await pageloadSpanPromise; | ||
|
|
||
| expect(lcpSpan.attributes?.['sentry.op']).toEqual({ type: 'string', value: 'ui.webvital.lcp' }); | ||
| expect(lcpSpan.attributes?.['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser.lcp' }); | ||
| expect(lcpSpan.attributes?.['sentry.exclusive_time']).toEqual({ type: 'integer', value: 0 }); | ||
| expect(lcpSpan.attributes?.['user_agent.original']?.value).toEqual(expect.stringContaining('Chrome')); | ||
|
|
||
| // Check browser.web_vital.lcp.* attributes | ||
| expect(lcpSpan.attributes?.['browser.web_vital.lcp.element']?.value).toEqual(expect.stringContaining('body > img')); | ||
| expect(lcpSpan.attributes?.['browser.web_vital.lcp.url']?.value).toBe( | ||
| 'https://sentry-test-site.example/my/image.png', | ||
| ); | ||
| expect(lcpSpan.attributes?.['browser.web_vital.lcp.size']?.value).toEqual(expect.any(Number)); | ||
|
|
||
| // Check web vital value attribute | ||
| expect(lcpSpan.attributes?.['browser.web_vital.lcp.value']?.type).toMatch(/^(double)|(integer)$/); | ||
| expect(lcpSpan.attributes?.['browser.web_vital.lcp.value']?.value).toBeGreaterThan(0); | ||
|
|
||
| // Check pageload span id is present | ||
| expect(lcpSpan.attributes?.['sentry.pageload.span_id']?.value).toBe(pageloadSpan.span_id); | ||
|
|
||
| // Span should have meaningful duration (navigation start -> LCP event) | ||
| expect(lcpSpan.end_timestamp).toBeGreaterThan(lcpSpan.start_timestamp); | ||
|
|
||
| expect(lcpSpan.span_id).toMatch(/^[\da-f]{16}$/); | ||
| expect(lcpSpan.trace_id).toMatch(/^[\da-f]{32}$/); | ||
|
|
||
| expect(lcpSpan.parent_span_id).toBe(pageloadSpan.span_id); | ||
| expect(lcpSpan.trace_id).toBe(pageloadSpan.trace_id); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l: can we also wait on the pageload span and assert that both have the same traceId? same for the LCP test