diff --git a/packages/core/src/utils/browser.ts b/packages/core/src/utils/browser.ts index c051cd70f234..d8e8ff13f48e 100644 --- a/packages/core/src/utils/browser.ts +++ b/packages/core/src/utils/browser.ts @@ -11,8 +11,9 @@ type SimpleNode = { /** * Given a child DOM element, returns a query-selector statement describing that - * and its ancestors + * and its ancestors, optionally prefixed with data-sentry-label if found on an ancestor * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz] + * e.g. [HTMLElement] => [data-sentry-label="MyLabel"] div.container > button * @returns generated DOM path */ export function htmlTreeAsString( @@ -30,7 +31,7 @@ export function htmlTreeAsString( try { let currentElem = elem as SimpleNode; const MAX_TRAVERSE_HEIGHT = 5; - const out = []; + const out: string[] = []; let height = 0; let len = 0; const separator = ' > '; @@ -55,7 +56,18 @@ export function htmlTreeAsString( currentElem = currentElem.parentNode; } - return out.reverse().join(separator); + const cssSelector = out.reverse().join(separator); + + if (cssSelector.includes('[data-sentry-label="')) { + return cssSelector; + } + + const sentryLabel = _getSentryLabel(elem); + if (sentryLabel) { + return `[data-sentry-label="${sentryLabel}"] ${cssSelector}`; + } + + return cssSelector; } catch { return ''; } @@ -84,6 +96,9 @@ function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string { if (WINDOW.HTMLElement) { // If using the component name annotation plugin, this value may be available on the DOM node if (elem instanceof HTMLElement && elem.dataset) { + if (elem.dataset['sentryLabel']) { + return `[data-sentry-label="${elem.dataset['sentryLabel']}"]`; + } if (elem.dataset['sentryComponent']) { return elem.dataset['sentryComponent']; } @@ -128,6 +143,27 @@ function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string { return out.join(''); } +/** + * Searches for the data-sentry-label attribute up the DOM tree. + * @returns The value of the first data-sentry-label found, or null if not found + */ +function _getSentryLabel(elem: unknown): string | null { + const MAX_LABEL_TRAVERSE_HEIGHT = 15; + let labelElem = elem as SimpleNode; + + for (let i = 0; i < MAX_LABEL_TRAVERSE_HEIGHT && labelElem; i++) { + // @ts-expect-error WINDOW has HTMLElement + if (WINDOW.HTMLElement && labelElem instanceof HTMLElement && labelElem.dataset) { + if (labelElem.dataset['sentryLabel']) { + return labelElem.dataset['sentryLabel']; + } + } + labelElem = labelElem.parentNode; + } + + return null; +} + /** * A safe form of location.href */ diff --git a/packages/core/test/lib/utils/browser.test.ts b/packages/core/test/lib/utils/browser.test.ts index 0bcf71884482..5f7330014c90 100644 --- a/packages/core/test/lib/utils/browser.test.ts +++ b/packages/core/test/lib/utils/browser.test.ts @@ -5,7 +5,7 @@ import { htmlTreeAsString } from '../../../src/utils/browser'; beforeAll(() => { const dom = new JSDOM(); global.document = dom.window.document; - global.HTMLElement = new JSDOM().window.HTMLElement; + global.HTMLElement = dom.window.HTMLElement; }); describe('htmlTreeAsString', () => { @@ -73,4 +73,83 @@ describe('htmlTreeAsString', () => { 'div#main-cta > div.container > button.bg-blue-500.hover:bg-blue-700.text-white.hover:text-blue-100', ); }); + + describe('data-sentry-label support', () => { + it('returns data-sentry-label when element has the attribute directly', () => { + const el = document.createElement('div'); + el.innerHTML = '