diff --git a/packages/browser-integration-tests/suites/replay/customEvents/template.html b/packages/browser-integration-tests/suites/replay/customEvents/template.html index 56a956a95d24..e988c5ed1666 100644 --- a/packages/browser-integration-tests/suites/replay/customEvents/template.html +++ b/packages/browser-integration-tests/suites/replay/customEvents/template.html @@ -5,13 +5,9 @@
An Error
- - + diff --git a/packages/browser-integration-tests/suites/replay/customEvents/test.ts b/packages/browser-integration-tests/suites/replay/customEvents/test.ts index 585266746365..92bcd081ea8f 100644 --- a/packages/browser-integration-tests/suites/replay/customEvents/test.ts +++ b/packages/browser-integration-tests/suites/replay/customEvents/test.ts @@ -135,16 +135,15 @@ sentryTest( expect.arrayContaining([ { ...expectedClickBreadcrumb, - message: 'body > button > img#img[alt="Alt Text"]', + message: 'body > button[title="Button title"]', data: { nodeId: expect.any(Number), node: { attributes: { - alt: 'Alt Text', - id: 'img', + title: '****** *****', }, id: expect.any(Number), - tagName: 'img', + tagName: 'button', textContent: '', }, }, diff --git a/packages/replay/src/coreHandlers/handleDom.ts b/packages/replay/src/coreHandlers/handleDom.ts index 8878f2b71966..f98a92725861 100644 --- a/packages/replay/src/coreHandlers/handleDom.ts +++ b/packages/replay/src/coreHandlers/handleDom.ts @@ -8,7 +8,7 @@ import { createBreadcrumb } from '../util/createBreadcrumb'; import { addBreadcrumbEvent } from './util/addBreadcrumbEvent'; import { getAttributesToRecord } from './util/getAttributesToRecord'; -interface DomHandlerData { +export interface DomHandlerData { name: string; event: Node | { target: Node }; } @@ -31,15 +31,18 @@ export const handleDomListener: (replay: ReplayContainer) => (handlerData: DomHa /** * An event handler to react to DOM events. + * Exported for tests only. */ -function handleDom(handlerData: DomHandlerData): Breadcrumb | null { +export function handleDom(handlerData: DomHandlerData): Breadcrumb | null { let target; let targetNode: Node | INode | undefined; + const isClick = handlerData.name === 'click'; + // Accessing event.target can throw (see getsentry/raven-js#838, #768) try { - targetNode = getTargetNode(handlerData); - target = htmlTreeAsString(targetNode); + targetNode = isClick ? getClickTargetNode(handlerData.event) : getTargetNode(handlerData.event); + target = htmlTreeAsString(targetNode, { maxStringLength: 200 }); } catch (e) { target = ''; } @@ -73,12 +76,29 @@ function handleDom(handlerData: DomHandlerData): Breadcrumb | null { }); } -function getTargetNode(handlerData: DomHandlerData): Node { - if (isEventWithTarget(handlerData.event)) { - return handlerData.event.target; +function getTargetNode(event: DomHandlerData['event']): Node { + if (isEventWithTarget(event)) { + return event.target; + } + + return event; +} + +const INTERACTIVE_SELECTOR = 'button,a'; + +// For clicks, we check if the target is inside of a button or link +// If so, we use this as the target instead +// This is useful because if you click on the image in , +// The target will be the image, not the button, which we don't want here +function getClickTargetNode(event: DomHandlerData['event']): Node { + const target = getTargetNode(event); + + if (!target || !(target instanceof Element)) { + return target; } - return handlerData.event; + const closestInteractive = target.closest(INTERACTIVE_SELECTOR); + return closestInteractive || target; } function isEventWithTarget(event: unknown): event is { target: Node } { diff --git a/packages/replay/test/unit/coreHandlers/handleDom.test.ts b/packages/replay/test/unit/coreHandlers/handleDom.test.ts new file mode 100644 index 000000000000..dc1fff0b5ff2 --- /dev/null +++ b/packages/replay/test/unit/coreHandlers/handleDom.test.ts @@ -0,0 +1,130 @@ +import type { DomHandlerData } from '../../../src/coreHandlers/handleDom'; +import { handleDom } from '../../../src/coreHandlers/handleDom'; + +describe('Unit | coreHandlers | handleDom', () => { + test('it works with a basic click event on a div', () => { + const parent = document.createElement('body'); + const target = document.createElement('div'); + target.classList.add('my-class', 'other-class'); + parent.appendChild(target); + + const handlerData: DomHandlerData = { + name: 'click', + event: { + target, + }, + }; + const actual = handleDom(handlerData); + expect(actual).toEqual({ + category: 'ui.click', + data: {}, + message: 'body > div.my-class.other-class', + timestamp: expect.any(Number), + type: 'default', + }); + }); + + test('it works with a basic click event on a button', () => { + const parent = document.createElement('body'); + const target = document.createElement('button'); + target.classList.add('my-class', 'other-class'); + parent.appendChild(target); + + const handlerData: DomHandlerData = { + name: 'click', + event: { + target, + }, + }; + const actual = handleDom(handlerData); + expect(actual).toEqual({ + category: 'ui.click', + data: {}, + message: 'body > button.my-class.other-class', + timestamp: expect.any(Number), + type: 'default', + }); + }); + + test('it works with a basic click event on a span inside of