diff --git a/injected/integration-test/broker-protection-tests/broker-protection-captcha.spec.js b/injected/integration-test/broker-protection-tests/broker-protection-captcha.spec.js index 3a91d6dfca..adb054420f 100644 --- a/injected/integration-test/broker-protection-tests/broker-protection-captcha.spec.js +++ b/injected/integration-test/broker-protection-tests/broker-protection-captcha.spec.js @@ -138,6 +138,44 @@ test.describe('Broker Protection Captcha', () => { await dbp.isCaptchaTokenFilled(imageCaptchaResponseSelector); }); + + test('retry fails with permanently invalid element type', async ({ page, createConfiguredDbp }) => { + const dbp = await createConfiguredDbp(BROKER_PROTECTION_CONFIGS.default); + await dbp.navigatesTo('image-captcha.html'); + + // Replace input with a div element which cannot accept token injection + await page.evaluate(() => { + const element = document.getElementById('svgCaptchaInputId'); + if (element) { + const div = document.createElement('div'); + div.id = 'svgCaptchaInputId'; + div.textContent = 'This is a div, not an input'; + element.parentNode?.replaceChild(div, element); + } + }); + + await dbp.simulateSubscriptionMessage('onActionReceived', { + state: { + action: { + actionType: 'solveCaptcha', + id: 'retry-invalid-element', + selector: '#svgCaptchaInputId', + retry: { + environment: 'web', + maxAttempts: 3, + interval: { ms: 200 }, + }, + }, + data: { + token: 'RETRY_FAIL_TOKEN', + }, + }, + }); + + // Should fail after all retry attempts because div is invalid for token injection + const response = await dbp.collector.waitForMessage('actionCompleted'); + dbp.isErrorMessage(response); + }); }); }); diff --git a/injected/integration-test/broker-protection-tests/broker-protection.spec.js b/injected/integration-test/broker-protection-tests/broker-protection.spec.js index 6e1415c34a..128bd507da 100644 --- a/injected/integration-test/broker-protection-tests/broker-protection.spec.js +++ b/injected/integration-test/broker-protection-tests/broker-protection.spec.js @@ -640,6 +640,7 @@ test.describe('Broker Protection communications', () => { const response = await dbp.collector.waitForMessage('actionCompleted'); dbp.isSuccessMessage(response); }); + test('ensuring retry doesnt apply everywhere', async ({ page }, workerInfo) => { const dbp = BrokerProtectionPage.create(page, workerInfo.project.use); await dbp.enabled(); diff --git a/injected/src/features/broker-protection.js b/injected/src/features/broker-protection.js index 58fe55aa74..ba5ad03726 100644 --- a/injected/src/features/broker-protection.js +++ b/injected/src/features/broker-protection.js @@ -110,6 +110,7 @@ export default class BrokerProtection extends ContentFeature { }; } } + return retryConfig; } } diff --git a/injected/src/features/broker-protection/types.js b/injected/src/features/broker-protection/types.js index 45a2793477..6b81a9c93a 100644 --- a/injected/src/features/broker-protection/types.js +++ b/injected/src/features/broker-protection/types.js @@ -12,6 +12,7 @@ * @property {string} [captchaType] * @property {string} [injectCaptchaHandler] * @property {string} [dataSource] + * @property {{environment: string, maxAttempts: number, interval: {ms: number}}} [retry] */ /** diff --git a/injected/src/features/broker-protection/utils/utils.js b/injected/src/features/broker-protection/utils/utils.js index 8429232ead..b30d02266d 100644 --- a/injected/src/features/broker-protection/utils/utils.js +++ b/injected/src/features/broker-protection/utils/utils.js @@ -1,3 +1,13 @@ +/** + * Gets the owner document of an element or uses document as fallback + * + * @param {Node|Element} element + * @return {Document} The owner document + */ +function getOwnerDocument(element) { + return element.ownerDocument || document; +} + /** * Get a single element. * @@ -75,7 +85,8 @@ export function getElementMatches(element, selector) { * @return {boolean} */ function matchesXPath(element, selector) { - const xpathResult = document.evaluate(selector, element, null, XPathResult.BOOLEAN_TYPE, null); + const ownerDoc = getOwnerDocument(element); + const xpathResult = ownerDoc.evaluate(selector, element, null, XPathResult.BOOLEAN_TYPE, null); return xpathResult.booleanValue; } @@ -131,7 +142,8 @@ function safeQuerySelector(element, selector) { */ function safeQuerySelectorXPath(element, selector) { try { - const match = document.evaluate(selector, element, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); + const ownerDoc = getOwnerDocument(element); + const match = ownerDoc.evaluate(selector, element, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); const single = match?.singleNodeValue; if (single) { return /** @type {HTMLElement} */ (single); @@ -150,8 +162,9 @@ function safeQuerySelectorXPath(element, selector) { */ function safeQuerySelectorAllXpath(element, selector) { try { + const ownerDoc = getOwnerDocument(element); // gets all elements matching the xpath query - const xpathResult = document.evaluate(selector, element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + const xpathResult = ownerDoc.evaluate(selector, element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); if (xpathResult) { /** @type {HTMLElement[]} */ const matchedNodes = [];