Skip to content

Commit 4d8581a

Browse files
feat(browser): Add CLS sources to span attributes (#16710)
resolves #16707 The session focused on enhancing CLS (Cumulative Layout Shift) spans by adding attributes detailing the elements that caused layout shifts. * In `packages/browser-utils/src/metrics/cls.ts`, the `sendStandaloneClsSpan` function was updated. It now iterates over `LayoutShift` entry sources and adds them as `cls.source.N` attributes to the span, converting DOM nodes to readable CSS selectors using `htmlTreeAsString()`. This aligns standalone CLS spans with the existing implementation for regular pageload spans. * Test expectations in `dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts` were updated to assert the presence of these new `cls.source.N` attributes on the captured CLS spans. * `yarn.lock` was updated to reflect changes in dependency resolutions, likely due to package installations during the session. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent ebdd485 commit 4d8581a

File tree

3 files changed

+28
-17
lines changed
  • dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans
  • packages/browser-utils/src/metrics

3 files changed

+28
-17
lines changed

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ sentryTest('captures a "GOOD" CLS vital with its source as a standalone span', a
6868
transaction: expect.stringContaining('index.html'),
6969
'user_agent.original': expect.stringContaining('Chrome'),
7070
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
71+
'cls.source.1': expect.stringContaining('body > div#content > p'),
7172
},
7273
description: expect.stringContaining('body > div#content > p'),
7374
exclusive_time: 0,
@@ -136,6 +137,7 @@ sentryTest('captures a "MEH" CLS vital with its source as a standalone span', as
136137
transaction: expect.stringContaining('index.html'),
137138
'user_agent.original': expect.stringContaining('Chrome'),
138139
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
140+
'cls.source.1': expect.stringContaining('body > div#content > p'),
139141
},
140142
description: expect.stringContaining('body > div#content > p'),
141143
exclusive_time: 0,
@@ -202,6 +204,7 @@ sentryTest('captures a "POOR" CLS vital with its source as a standalone span.',
202204
transaction: expect.stringContaining('index.html'),
203205
'user_agent.original': expect.stringContaining('Chrome'),
204206
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
207+
'cls.source.1': expect.stringContaining('body > div#content > p'),
205208
},
206209
description: expect.stringContaining('body > div#content > p'),
207210
exclusive_time: 0,

packages/browser-utils/src/metrics/cls.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined,
106106
'sentry.pageload.span_id': pageloadSpanId,
107107
};
108108

109+
// Add CLS sources as span attributes to help with debugging layout shifts
110+
// See: https://developer.mozilla.org/en-US/docs/Web/API/LayoutShift/sources
111+
if (entry?.sources) {
112+
entry.sources.forEach((source, index) => {
113+
attributes[`cls.source.${index + 1}`] = htmlTreeAsString(source.node);
114+
});
115+
}
116+
109117
const span = startStandaloneWebVitalSpan({
110118
name,
111119
transaction: routeName,

yarn.lock

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27038,7 +27038,7 @@ string-template@~0.2.1:
2703827038
is-fullwidth-code-point "^3.0.0"
2703927039
strip-ansi "^6.0.1"
2704027040

27041-
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
27041+
string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
2704227042
version "4.2.3"
2704327043
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
2704427044
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -27148,6 +27148,13 @@ stringify-object@^3.2.1:
2714827148
dependencies:
2714927149
ansi-regex "^5.0.1"
2715027150

27151+
strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
27152+
version "6.0.1"
27153+
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
27154+
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
27155+
dependencies:
27156+
ansi-regex "^5.0.1"
27157+
2715127158
strip-ansi@^3.0.0:
2715227159
version "3.0.1"
2715327160
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -27169,13 +27176,6 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0:
2716927176
dependencies:
2717027177
ansi-regex "^4.1.0"
2717127178

27172-
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
27173-
version "6.0.1"
27174-
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
27175-
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
27176-
dependencies:
27177-
ansi-regex "^5.0.1"
27178-
2717927179
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
2718027180
version "7.1.0"
2718127181
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -27311,7 +27311,7 @@ stylus@0.59.0, stylus@^0.59.0:
2731127311
sax "~1.2.4"
2731227312
source-map "^0.7.3"
2731327313

27314-
sucrase@^3.27.0, sucrase@^3.35.0:
27314+
sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills:
2731527315
version "3.36.0"
2731627316
resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061"
2731727317
dependencies:
@@ -29955,19 +29955,19 @@ wrangler@^3.67.1:
2995529955
string-width "^4.1.0"
2995629956
strip-ansi "^6.0.0"
2995729957

29958-
wrap-ansi@^6.0.1:
29959-
version "6.2.0"
29960-
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
29961-
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
29958+
wrap-ansi@7.0.0, wrap-ansi@^7.0.0:
29959+
version "7.0.0"
29960+
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
29961+
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
2996229962
dependencies:
2996329963
ansi-styles "^4.0.0"
2996429964
string-width "^4.1.0"
2996529965
strip-ansi "^6.0.0"
2996629966

29967-
wrap-ansi@^7.0.0:
29968-
version "7.0.0"
29969-
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
29970-
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
29967+
wrap-ansi@^6.0.1:
29968+
version "6.2.0"
29969+
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
29970+
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
2997129971
dependencies:
2997229972
ansi-styles "^4.0.0"
2997329973
string-width "^4.1.0"

0 commit comments

Comments
 (0)