Skip to content

Commit 0c1ce6c

Browse files
authored
fix: Get string className for SVG elements (#1423)
1 parent f808447 commit 0c1ce6c

File tree

5 files changed

+210
-146
lines changed

5 files changed

+210
-146
lines changed

package-lock.json

+3-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/common/dom/selector-path.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@ export const generateSelectorPath = (elem, targetFields = []) => {
3434
try {
3535
while (elem?.tagName) {
3636
const { id, localName } = elem
37-
targetFields.forEach(field => {
38-
nearestFields[nearestAttrName(field)] ||= elem[field]
39-
})
37+
targetFields.forEach(field => { nearestFields[nearestAttrName(field)] ||= (elem[field]?.baseVal || elem[field]) })
4038
const selector = [
4139
localName,
4240
id ? `#${id}` : '',

tests/assets/svg.html

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>SVG Test</title>
5+
{init} {config} {loader}
6+
</head>
7+
<body>
8+
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
9+
<rect id="testRect" class="test-class" width="50" height="50" fill="blue"></rect>
10+
</svg>
11+
<!-- <script>
12+
const rect = document.getElementById('testRect');
13+
14+
rect.addEventListener('click', () => {
15+
console.log('Rect was clicked!');
16+
});
17+
18+
const clickEvent = new MouseEvent('click', {
19+
bubbles: true,
20+
cancelable: true,
21+
view: window
22+
});
23+
rect.dispatchEvent(clickEvent);
24+
</script> -->
25+
</body>
26+
</html>

tests/specs/ins/harvesting.e2e.js

-139
Original file line numberDiff line numberDiff line change
@@ -75,145 +75,6 @@ describe('ins harvesting', () => {
7575
expect(customEventsHarvest.length).toEqual(0)
7676
})
7777

78-
it('should submit UserAction (when enabled)', async () => {
79-
const testUrl = await browser.testHandle.assetURL('user-actions.html', getInsInit({ user_actions: { enabled: true } }))
80-
await browser.url(testUrl).then(() => browser.waitForAgentLoad())
81-
82-
const [insHarvests] = await Promise.all([
83-
insightsCapture.waitForResult({ timeout: 7500 }),
84-
$('#textbox').click().then(async () => {
85-
// rage click
86-
await browser.execute(function () {
87-
for (let i = 0; i < 5; i++) {
88-
document.querySelector('span').click()
89-
}
90-
})
91-
// stop aggregating clicks
92-
await $('body').click()
93-
})
94-
])
95-
96-
const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins) // firefox sends a window focus event on load, so we may end up with 2 harvests
97-
const clickUAs = userActionsHarvest.filter(ua => ua.action === 'click')
98-
expect(clickUAs.length).toBeGreaterThanOrEqual(2)
99-
expect(clickUAs[0]).toMatchObject({
100-
eventType: 'UserAction',
101-
action: 'click',
102-
actionCount: 1,
103-
actionDuration: 0,
104-
actionMs: '[0]',
105-
target: 'html>body>div>input#textbox:nth-of-type(1)',
106-
targetId: 'textbox',
107-
targetTag: 'INPUT',
108-
targetType: 'text',
109-
pageUrl: expect.any(String),
110-
timestamp: expect.any(Number)
111-
})
112-
expect(clickUAs[1]).toMatchObject({
113-
eventType: 'UserAction',
114-
action: 'click',
115-
actionCount: 5,
116-
actionDuration: expect.any(Number),
117-
actionMs: expect.any(String),
118-
nearestId: 'pay-btn',
119-
nearestTag: 'SPAN',
120-
nearestType: 'submit',
121-
nearestClass: 'btn-cart-add flex-grow container',
122-
rageClick: true,
123-
target: 'html>body>div>button#pay-btn>span:nth-of-type(1)',
124-
targetTag: 'SPAN',
125-
pageUrl: expect.any(String),
126-
timestamp: expect.any(Number)
127-
})
128-
expect(clickUAs[1].actionDuration).toBeGreaterThanOrEqual(0)
129-
expect(clickUAs[1].actionMs).toEqual(expect.stringMatching(/^\[\d+(,\d+){4}\]$/))
130-
})
131-
132-
// firefox generates a focus event when the page loads, which makes testing this easier
133-
it.withBrowsersMatching(onlyFirefox)('should ignore window attributes on UserAction blur and focus', async () => {
134-
const testUrl = await browser.testHandle.assetURL('user-actions-modified-window.html', { init: { user_actions: { enabled: true } } })
135-
await browser.url(testUrl).then(() => browser.waitForAgentLoad())
136-
137-
const [insHarvests] = await Promise.all([
138-
insightsCapture.waitForResult({ timeout: 7500 }),
139-
$('#pay-btn').click().then(async () => {
140-
// rage click
141-
await browser.execute(function () {
142-
for (let i = 0; i < 5; i++) {
143-
document.querySelector('#textbox').click()
144-
}
145-
})
146-
// stop aggregating textbox clicks
147-
await $('body').click()
148-
})
149-
])
150-
151-
const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins) // firefox sends a window focus event on load, so we may end up with 2 harvests
152-
const focusBlurUAs = userActionsHarvest.filter(ua => ua.action === 'focus' || ua.action === 'blur')
153-
expect(focusBlurUAs.length).toBeGreaterThan(0)
154-
expect(focusBlurUAs[0].targetType).toBeUndefined()
155-
})
156-
157-
it('should detect iframes on UserActions if agent is running inside iframe', async () => {
158-
const testUrl = await browser.testHandle.assetURL('iframe/same-origin.html', getInsInit({ user_actions: { enabled: true } }))
159-
await browser.url(testUrl).then(() => browser.pause(2000))
160-
161-
const [insHarvests] = await Promise.all([
162-
insightsCapture.waitForResult({ timeout: 5000 }),
163-
browser.execute(function () {
164-
const frame = document.querySelector('iframe')
165-
const frameBody = frame.contentWindow.document.querySelector('body')
166-
frame.focus()
167-
frameBody.click()
168-
window.focus()
169-
window.location.reload()
170-
})
171-
172-
])
173-
174-
const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins) // firefox sends a window focus event on load, so we may end up with 2 harvests
175-
expect(userActionsHarvest.length).toBeGreaterThanOrEqual(3) // 3 page events above, plus the occasional window focus event mentioned above
176-
userActionsHarvest.forEach(ua => {
177-
expect(ua.eventType).toEqual('UserAction')
178-
expect(ua.iframe).toEqual(true)
179-
})
180-
})
181-
182-
it('should only report duplicative focus and blur events once', async () => {
183-
const testUrl = await browser.testHandle.assetURL('user-actions.html', getInsInit({ user_actions: { enabled: true } }))
184-
await browser.url(testUrl).then(() => browser.pause(2000))
185-
186-
const [insHarvests] = await Promise.all([
187-
insightsCapture.waitForResult({ timeout: 5000 }),
188-
browser.execute(function () {
189-
let i = 0; while (i++ < 10) {
190-
window.dispatchEvent(new Event('focus'))
191-
window.dispatchEvent(new Event('blur'))
192-
}
193-
}).then(() => $('body').click()) // stop aggregating the blur events
194-
])
195-
196-
const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins) // firefox sends a window focus event on load, so we may end up with 2 harvests
197-
const focusEvents = userActionsHarvest.filter(ua => ua.action === 'focus')
198-
const blurEvents = userActionsHarvest.filter(ua => ua.action === 'blur')
199-
200-
// firefox generates a focus event when the page loads, which is reported within the time gap between the page load and the first user action. This
201-
// leads to two focus events for firefox and one for the others, but very inconsistently since it could be triggered in the time it takes to debounce
202-
const isFirefox = browserMatch(onlyFirefox)
203-
if (isFirefox) {
204-
expect(focusEvents.length).toBeLessThanOrEqual(2)
205-
expect(JSON.parse(focusEvents[0].actionMs).length).toBeLessThanOrEqual(2)
206-
expect(focusEvents[0].actionCount).toBeLessThanOrEqual(2)
207-
} else {
208-
expect(focusEvents.length).toEqual(1)
209-
expect(JSON.parse(focusEvents[0].actionMs).length).toEqual(1)
210-
expect(focusEvents[0].actionCount).toEqual(1)
211-
}
212-
expect(blurEvents.length).toEqual(1)
213-
expect(JSON.parse(blurEvents[0].actionMs).length).toEqual(1)
214-
expect(blurEvents[0].actionCount).toEqual(1)
215-
})
216-
21778
;[
21879
[getInsInit({ performance: { capture_marks: true } }), 'enabled'],
21980
[getInsInit({ performance: { capture_marks: false }, feature_flags: [FEATURE_FLAGS.MARKS] }), 'feature flag']

tests/specs/ins/user-actions.e2e.js

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { testInsRequest } from '../../../tools/testing-server/utils/expect-tests'
2+
import { onlyFirefox } from '../../../tools/browser-matcher/common-matchers.mjs'
3+
4+
const init = {
5+
user_actions: { enabled: true },
6+
page_actions: { enabled: false },
7+
performance: {
8+
capture_marks: false,
9+
capture_measures: false,
10+
resources: { enabled: false, ignore_newrelic: true, asset_types: [], first_party_domains: [] }
11+
}
12+
}
13+
14+
describe('UserAction events', () => {
15+
let insightsCapture
16+
beforeEach(async () => {
17+
insightsCapture = await browser.testHandle.createNetworkCaptures('bamServer', { test: testInsRequest })
18+
})
19+
20+
it('are sent when enabled', async () => {
21+
const testUrl = await browser.testHandle.assetURL('user-actions.html', { init })
22+
await browser.url(testUrl).then(() => browser.waitForAgentLoad())
23+
24+
const [insHarvests] = await Promise.all([
25+
insightsCapture.waitForResult({ timeout: 7500 }),
26+
$('#textbox').click().then(async () => {
27+
// rage click
28+
await browser.execute(function () {
29+
for (let i = 0; i < 5; i++) {
30+
document.querySelector('span').click()
31+
}
32+
})
33+
// stop aggregating clicks
34+
await $('body').click()
35+
})
36+
])
37+
38+
const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins) // firefox sends a window focus event on load, so we may end up with 2 harvests
39+
const clickUAs = userActionsHarvest.filter(ua => ua.action === 'click')
40+
expect(clickUAs.length).toBeGreaterThanOrEqual(2)
41+
expect(clickUAs[0]).toMatchObject({
42+
eventType: 'UserAction',
43+
action: 'click',
44+
actionCount: 1,
45+
actionDuration: 0,
46+
actionMs: '[0]',
47+
target: 'html>body>div>input#textbox:nth-of-type(1)',
48+
targetId: 'textbox',
49+
targetTag: 'INPUT',
50+
targetType: 'text',
51+
pageUrl: expect.any(String),
52+
timestamp: expect.any(Number)
53+
})
54+
expect(clickUAs[1]).toMatchObject({
55+
eventType: 'UserAction',
56+
action: 'click',
57+
actionCount: 5,
58+
actionDuration: expect.any(Number),
59+
actionMs: expect.any(String),
60+
nearestId: 'pay-btn',
61+
nearestTag: 'SPAN',
62+
nearestType: 'submit',
63+
nearestClass: 'btn-cart-add flex-grow container',
64+
rageClick: true,
65+
target: 'html>body>div>button#pay-btn>span:nth-of-type(1)',
66+
targetTag: 'SPAN',
67+
pageUrl: expect.any(String),
68+
timestamp: expect.any(Number)
69+
})
70+
expect(clickUAs[1].actionDuration).toBeGreaterThanOrEqual(0)
71+
expect(clickUAs[1].actionMs).toEqual(expect.stringMatching(/^\[\d+(,\d+){4}\]$/))
72+
})
73+
74+
// firefox generates a focus event when the page loads, which makes testing this easier
75+
it.withBrowsersMatching(onlyFirefox)('ignore attributes on window blur and focus', async () => {
76+
const testUrl = await browser.testHandle.assetURL('user-actions-modified-window.html', { init })
77+
await browser.url(testUrl).then(() => browser.waitForAgentLoad())
78+
79+
const [insHarvests] = await Promise.all([
80+
insightsCapture.waitForResult({ timeout: 7500 }),
81+
$('#pay-btn').click().then(async () => {
82+
// rage click
83+
await browser.execute(function () {
84+
for (let i = 0; i < 5; i++) {
85+
document.querySelector('#textbox').click()
86+
}
87+
})
88+
// stop aggregating textbox clicks
89+
await $('body').click()
90+
})
91+
])
92+
93+
const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins) // firefox sends a window focus event on load, so we may end up with 2 harvests
94+
const focusBlurUAs = userActionsHarvest.filter(ua => ua.action === 'focus' || ua.action === 'blur')
95+
expect(focusBlurUAs.length).toBeGreaterThan(0)
96+
expect(focusBlurUAs[0].targetType).toBeUndefined()
97+
})
98+
99+
it('report duplicative focus and blur events once', async () => {
100+
const testUrl = await browser.testHandle.assetURL('user-actions.html', { init })
101+
await browser.url(testUrl).then(() => browser.pause(2000))
102+
103+
const [insHarvests] = await Promise.all([
104+
insightsCapture.waitForResult({ timeout: 5000 }),
105+
browser.execute(function () {
106+
let i = 0; while (i++ < 10) {
107+
window.dispatchEvent(new Event('focus'))
108+
window.dispatchEvent(new Event('blur'))
109+
}
110+
}).then(() => $('body').click()) // stop aggregating the blur events
111+
])
112+
113+
const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins) // firefox sends a window focus event on load, so we may end up with 2 harvests
114+
const focusEvents = userActionsHarvest.filter(ua => ua.action === 'focus')
115+
const blurEvents = userActionsHarvest.filter(ua => ua.action === 'blur')
116+
117+
// firefox generates a focus event when the page loads, which is reported within the time gap between the page load and the first user action. This
118+
// leads to two focus events for firefox and one for the others, but very inconsistently since it could be triggered in the time it takes to debounce
119+
const isFirefox = browserMatch(onlyFirefox)
120+
if (isFirefox) {
121+
expect(focusEvents.length).toBeLessThanOrEqual(2)
122+
expect(JSON.parse(focusEvents[0].actionMs).length).toBeLessThanOrEqual(2)
123+
expect(focusEvents[0].actionCount).toBeLessThanOrEqual(2)
124+
} else {
125+
expect(focusEvents.length).toEqual(1)
126+
expect(JSON.parse(focusEvents[0].actionMs).length).toEqual(1)
127+
expect(focusEvents[0].actionCount).toEqual(1)
128+
}
129+
expect(blurEvents.length).toEqual(1)
130+
expect(JSON.parse(blurEvents[0].actionMs).length).toEqual(1)
131+
expect(blurEvents[0].actionCount).toEqual(1)
132+
})
133+
134+
it('detect when agent is running inside iframe', async () => {
135+
const testUrl = await browser.testHandle.assetURL('iframe/same-origin.html', { init })
136+
await browser.url(testUrl).then(() => browser.pause(2000))
137+
138+
const [insHarvests] = await Promise.all([
139+
insightsCapture.waitForResult({ timeout: 5000 }),
140+
browser.execute(function () {
141+
const frame = document.querySelector('iframe')
142+
const frameBody = frame.contentWindow.document.querySelector('body')
143+
frame.focus()
144+
frameBody.click()
145+
window.focus()
146+
window.location.reload()
147+
})
148+
149+
])
150+
151+
const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins) // firefox sends a window focus event on load, so we may end up with 2 harvests
152+
expect(userActionsHarvest.length).toBeGreaterThanOrEqual(3) // 3 page events above, plus the occasional window focus event mentioned above
153+
userActionsHarvest.forEach(ua => {
154+
expect(ua.eventType).toEqual('UserAction')
155+
expect(ua.iframe).toEqual(true)
156+
})
157+
})
158+
159+
it('have string classNames for SVG elements', async () => {
160+
const testUrl = await browser.testHandle.assetURL('svg.html', { init })
161+
await browser.url(testUrl).then(() => browser.waitForAgentLoad())
162+
163+
const [insHarvests] = await Promise.all([
164+
insightsCapture.waitForResult({ timeout: 6000 }),
165+
$('#testRect').click().then(() => $('body').click())
166+
])
167+
168+
const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins)
169+
const clickUAs = userActionsHarvest.filter(ua => ua.action === 'click')
170+
expect(clickUAs[0]).toMatchObject({
171+
eventType: 'UserAction',
172+
action: 'click',
173+
actionCount: 1,
174+
target: 'html>body>svg>rect#testRect:nth-of-type(1)',
175+
nearestId: 'testRect',
176+
nearestClass: 'test-class',
177+
nearestTag: 'rect'
178+
})
179+
})
180+
})

0 commit comments

Comments
 (0)