diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index e267bcd64c1c..49a52ac5a7bf 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -14,7 +14,7 @@ import { /** JSDoc */ interface BreadcrumbsOptions { console: boolean; - dom: boolean | { serializeAttribute: string }; + dom: boolean | { serializeAttribute: string | string[] }; fetch: boolean; history: boolean; sentry: boolean; @@ -162,13 +162,17 @@ export class Breadcrumbs implements Integration { // eslint-disable-next-line @typescript-eslint/no-explicit-any private _domBreadcrumb(handlerData: { [key: string]: any }): void { let target; - const keyAttr = typeof this._options.dom === 'object' ? this._options.dom.serializeAttribute : undefined; + let keyAttrs = typeof this._options.dom === 'object' ? this._options.dom.serializeAttribute : undefined; + + if (typeof keyAttrs === 'string') { + keyAttrs = [keyAttrs]; + } // Accessing event.target can throw (see getsentry/raven-js#838, #768) try { target = handlerData.event.target - ? htmlTreeAsString(handlerData.event.target as Node, keyAttr) - : htmlTreeAsString((handlerData.event as unknown) as Node, keyAttr); + ? htmlTreeAsString(handlerData.event.target as Node, keyAttrs) + : htmlTreeAsString((handlerData.event as unknown) as Node, keyAttrs); } catch (e) { target = ''; } diff --git a/packages/utils/src/browser.ts b/packages/utils/src/browser.ts index cafd3da01af9..c776796512a2 100644 --- a/packages/utils/src/browser.ts +++ b/packages/utils/src/browser.ts @@ -6,7 +6,7 @@ import { isString } from './is'; * e.g. [HTMLElement] => body > div > input#foo.btn[name=baz] * @returns generated DOM path */ -export function htmlTreeAsString(elem: unknown, keyAttr?: string): string { +export function htmlTreeAsString(elem: unknown, keyAttrs?: string[]): string { type SimpleNode = { parentNode: SimpleNode; } | null; @@ -28,7 +28,7 @@ export function htmlTreeAsString(elem: unknown, keyAttr?: string): string { // eslint-disable-next-line no-plusplus while (currentElem && height++ < MAX_TRAVERSE_HEIGHT) { - nextStr = _htmlElementAsString(currentElem, keyAttr); + nextStr = _htmlElementAsString(currentElem, keyAttrs); // bail out if // - nextStr is the 'html' element // - the length of the string that would be created exceeds MAX_OUTPUT_LEN @@ -54,7 +54,7 @@ export function htmlTreeAsString(elem: unknown, keyAttr?: string): string { * e.g. [HTMLElement] => input#foo.btn[name=baz] * @returns generated DOM path */ -function _htmlElementAsString(el: unknown, keyAttr?: string): string { +function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string { const elem = el as { tagName?: string; id?: string; @@ -75,9 +75,15 @@ function _htmlElementAsString(el: unknown, keyAttr?: string): string { out.push(elem.tagName.toLowerCase()); - const keyAttrValue = keyAttr ? elem.getAttribute(keyAttr) : null; - if (keyAttrValue) { - out.push(`[${keyAttr}="${keyAttrValue}"]`); + // Pairs of attribute keys defined in `serializeAttribute` and their values on element. + const keyAttrPairs = keyAttrs?.length + ? keyAttrs.filter(keyAttr => elem.getAttribute(keyAttr)).map(keyAttr => [keyAttr, elem.getAttribute(keyAttr)]) + : null; + + if (keyAttrPairs?.length) { + keyAttrPairs.forEach(keyAttrPair => { + out.push(`[${keyAttrPair[0]}="${keyAttrPair[1]}"]`); + }); } else { if (elem.id) { out.push(`#${elem.id}`); diff --git a/packages/utils/test/browser.test.ts b/packages/utils/test/browser.test.ts index ad3f0d129497..1b7978b75ba6 100644 --- a/packages/utils/test/browser.test.ts +++ b/packages/utils/test/browser.test.ts @@ -40,7 +40,7 @@ describe('htmlTreeAsString', () => { `; document.body.appendChild(el); - expect(htmlTreeAsString(document.getElementById('cat-2'), 'test-id')).toBe( + expect(htmlTreeAsString(document.getElementById('cat-2'), ['test-id'])).toBe( 'body > ul > li.li-class[title="li-title"] > img[test-id="cat-2-test-id"]', ); });