Skip to content

Commit eb2f95b

Browse files
committed
AG-29014 Fix 'trusted-click-element' — element was removed and added again before it was clicked. #391
Squashed commit of the following: commit bc53c30 Merge: 6c2150f 39e9fbf Author: Adam Wróblewski <[email protected]> Date: Thu Nov 28 12:27:35 2024 +0100 Merge branch 'master' into fix/AG-29014 commit 6c2150f Author: Adam Wróblewski <[email protected]> Date: Tue Nov 26 12:20:03 2024 +0100 Do not use destructuring assignment Change selectorText value to null commit c273a04 Author: Adam Wróblewski <[email protected]> Date: Mon Nov 25 14:52:00 2024 +0100 Update JSDoc for interface commit 0cb9c15 Author: Adam Wróblewski <[email protected]> Date: Mon Nov 25 14:15:21 2024 +0100 Add JSDoc for interface Remove properties from findAndClickElement JSDoc commit 1432c5a Author: Adam Wróblewski <[email protected]> Date: Mon Nov 25 10:26:48 2024 +0100 Update changelog and comment commit 7c65269 Author: Adam Wróblewski <[email protected]> Date: Fri Nov 22 18:59:46 2024 +0100 Fix trusted-click-element Check if element is connected to Document
1 parent 39e9fbf commit eb2f95b

File tree

3 files changed

+98
-6
lines changed

3 files changed

+98
-6
lines changed

Diff for: CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
1010
<!-- TODO: change `@added unknown` tag due to the actual version -->
1111
<!-- during new scriptlets or redirects releasing -->
1212

13+
## [Unreleased]
14+
15+
### Fixed
16+
17+
- issue with `trusted-click-element` scriptlet when `delay` was used and the element was removed
18+
and added again before it was clicked [#391]
19+
20+
[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v2.0.1...HEAD
21+
[#391]: https://github.com/AdguardTeam/Scriptlets/issues/391
22+
1323
## [v2.0.1] - 2024-11-13
1424

1525
### Added

Diff for: src/scriptlets/trusted-click-element.ts

+58-6
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,27 @@ import { type Source } from './scriptlets';
127127
* @added v1.7.3.
128128
*/
129129
/* eslint-enable max-len */
130+
131+
/**
132+
* Object that contains information about element that can be clicked
133+
*/
134+
interface ElementObject {
135+
/**
136+
* HTML element to be clicked, or null if not found
137+
*/
138+
element: HTMLElement | null;
139+
140+
/**
141+
* Indicates whether the element has been clicked
142+
*/
143+
clicked: boolean;
144+
145+
/**
146+
* CSS selector text used to find the element
147+
*/
148+
selectorText: string | null;
149+
}
150+
130151
export function trustedClickElement(
131152
source: Source,
132153
selectors: string,
@@ -313,14 +334,38 @@ export function trustedClickElement(
313334
.split(SELECTORS_DELIMITER)
314335
.map((selector) => selector.trim());
315336

316-
const createElementObj = (element: any): Object => {
337+
const createElementObj = (element: any, selector?: string | null): Object => {
317338
return {
318339
element: element || null,
319340
clicked: false,
341+
selectorText: selector || null,
320342
};
321343
};
322344
const elementsSequence = Array(selectorsSequence.length).fill(createElementObj(null));
323345

346+
/**
347+
* Attempts to find and click an element based on the provided selector data.
348+
*
349+
* @param elementObj - Object containing element selector information
350+
*
351+
*/
352+
const findAndClickElement = (elementObj: ElementObject): void => {
353+
try {
354+
if (!elementObj.selectorText) {
355+
return;
356+
}
357+
const element = queryShadowSelector(elementObj.selectorText) as HTMLElement;
358+
if (!element) {
359+
logMessage(source, `Could not find element: '${elementObj.selectorText}'`);
360+
return;
361+
}
362+
element.click();
363+
elementObj.clicked = true;
364+
} catch (error) {
365+
logMessage(source, `Could not click element: '${elementObj.selectorText}'`);
366+
}
367+
};
368+
324369
// Flag indicating if the reload is set
325370
let shouldReloadAfterClick: boolean = false;
326371
// Value used for reload timing
@@ -388,8 +433,15 @@ export function trustedClickElement(
388433
if (textMatchRegexp && !doesElementContainText(elementObj.element, textMatchRegexp)) {
389434
continue;
390435
}
391-
elementObj.element.click();
392-
elementObj.clicked = true;
436+
// Checks if node is connected to a Document object,
437+
// if not, try to find the element again
438+
// https://github.com/AdguardTeam/Scriptlets/issues/391
439+
if (elementObj.element.isConnected) {
440+
elementObj.element.click();
441+
elementObj.clicked = true;
442+
} else {
443+
findAndClickElement(elementObj);
444+
}
393445
}
394446
}
395447

@@ -407,8 +459,8 @@ export function trustedClickElement(
407459
}
408460
};
409461

410-
const handleElement = (element: Element, i: number) => {
411-
const elementObj = createElementObj(element);
462+
const handleElement = (element: Element, i: number, selector: string) => {
463+
const elementObj = createElementObj(element, selector);
412464
elementsSequence[i] = elementObj;
413465

414466
if (canClick) {
@@ -433,7 +485,7 @@ export function trustedClickElement(
433485
return;
434486
}
435487

436-
handleElement(element, i);
488+
handleElement(element, i, selector);
437489
fulfilledSelectors.push(selector);
438490
});
439491

Diff for: tests/scriptlets/trusted-click-element.test.js

+30
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,36 @@ test('Element added to DOM is clicked', (assert) => {
7474
}, 150);
7575
});
7676

77+
test('Element added to DOM, removed and then added again - should be clicked', (assert) => {
78+
const DELAY = 100;
79+
const ELEM_COUNT = 1;
80+
const ASSERTIONS = ELEM_COUNT + 1;
81+
assert.expect(ASSERTIONS);
82+
83+
const done = assert.async();
84+
const selectorsString = `#${PANEL_ID} > #${CLICKABLE_NAME}${ELEM_COUNT}`;
85+
86+
runScriptlet(name, [selectorsString, '', DELAY]);
87+
88+
const panelToRemove = createPanel();
89+
const clickableToRemove = createClickable(1);
90+
panelToRemove.appendChild(clickableToRemove);
91+
92+
let clickable;
93+
setTimeout(() => {
94+
removePanel();
95+
const panel = createPanel();
96+
clickable = createClickable(1);
97+
panel.appendChild(clickable);
98+
}, 10);
99+
100+
setTimeout(() => {
101+
assert.ok(clickable.getAttribute('clicked'), 'Element should be clicked');
102+
assert.strictEqual(window.hit, 'FIRED', 'hit func executed');
103+
done();
104+
}, 150);
105+
});
106+
77107
test('Multiple elements clicked - one element loaded before scriptlet, rest added later', (assert) => {
78108
const CLICK_ORDER = [1, 2, 3];
79109
// Assert elements for being clicked, hit func execution & click order

0 commit comments

Comments
 (0)