ff-automationv2 2.2.34-beta.2 → 2.2.34-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/automation/actions/executor.d.ts +1 -1
- package/dist/automation/actions/executor.js +3 -2
- package/dist/core/helpers/elementSelectorResolver.d.ts +5 -3
- package/dist/core/helpers/elementSelectorResolver.js +56 -23
- package/dist/core/interfaces/automationRunnerInterface.d.ts +1 -0
- package/dist/core/main/actionHandlerFactory.js +1 -1
- package/dist/core/main/runAutomationScript.js +17 -2
- package/dist/utils/DomExtraction/jsForAttributeInjection.d.ts +8 -2
- package/dist/utils/DomExtraction/jsForAttributeInjection.js +89 -24
- package/dist/utils/crossOrigin/crossOriginHelper.d.ts +5 -0
- package/dist/utils/crossOrigin/crossOriginHelper.js +195 -0
- package/dist/utils/crossOrigin/crossOriginInterface.d.ts +43 -0
- package/dist/utils/crossOrigin/crossOriginInterface.js +2 -0
- package/dist/utils/helpers/domSanitizer.js +3 -1
- package/dist/utils/iframesExtraction/findElementUsingFF.js +4 -13
- package/dist/utils/iframesExtraction/iframeInterface.d.ts +1 -0
- package/package.json +1 -1
|
@@ -19,7 +19,7 @@ export declare class ActionExecutor implements IActionExecutor {
|
|
|
19
19
|
navigate(url: string): Promise<void>;
|
|
20
20
|
goBack(): Promise<void>;
|
|
21
21
|
refresh(): Promise<void>;
|
|
22
|
-
switchToFrame(selector: string, frames: FrameMeta[], elementName: string, elementType: string, pageDOM: string, fireflinkIndex: string): Promise<void>;
|
|
22
|
+
switchToFrame(selector: string, frames: FrameMeta[], elementName: string, elementType: string, pageDOM: string, fireflinkIndex: string, usedElementNames?: string[]): Promise<void>;
|
|
23
23
|
wait(time: string): Promise<void>;
|
|
24
24
|
click(pageDOM: string, selector: string, fireflinkIndex: string, elementName: string, elementType: string): Promise<void>;
|
|
25
25
|
clickUsingJS(pageDOM: string, selector: string, fireflinkIndex: string, elementName: string, elementType: string): Promise<void>;
|
|
@@ -424,7 +424,7 @@ class ActionExecutor {
|
|
|
424
424
|
throw error;
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
|
-
async switchToFrame(selector, frames, elementName, elementType, pageDOM, fireflinkIndex) {
|
|
427
|
+
async switchToFrame(selector, frames, elementName, elementType, pageDOM, fireflinkIndex, usedElementNames) {
|
|
428
428
|
try {
|
|
429
429
|
await (0, findElementUsingFF_js_1.findElementUsingFF)({
|
|
430
430
|
driver: this.driver,
|
|
@@ -436,7 +436,8 @@ class ActionExecutor {
|
|
|
436
436
|
pageDOM,
|
|
437
437
|
fireflinkIndex,
|
|
438
438
|
elementGetter: this.elementGetter,
|
|
439
|
-
platform: this.platform
|
|
439
|
+
platform: this.platform,
|
|
440
|
+
usedElementNames
|
|
440
441
|
});
|
|
441
442
|
}
|
|
442
443
|
catch (error) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { platform as PlatformType } from "../types/scriptTypesType.js";
|
|
2
2
|
import { ShadowIframeResult } from "../interfaces/automationRunnerInterface.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { FrameMeta } from "../../utils/iframesExtraction/iframeInterface.js";
|
|
4
|
+
import { findCrossOriginIframeForElement } from "../../utils/crossOrigin/crossOriginHelper.js";
|
|
5
|
+
export { ShadowIframeResult, FrameMeta, findCrossOriginIframeForElement };
|
|
6
|
+
export declare function generateUniqueName(currentName: string | undefined, usedNames: string[], suffix?: string): string | undefined;
|
|
5
7
|
export declare function normalizeSvgSelector(selector: string): string;
|
|
6
8
|
export declare function resolveElementSelector(doc: any, domInfo: any, fireflinkIndex: string, platform: PlatformType, driver: any, fallbackAttempt: number, driverContext?: any): Promise<{
|
|
7
9
|
selector: string;
|
|
8
10
|
doc: any;
|
|
9
11
|
domInfo: any;
|
|
10
12
|
}>;
|
|
11
|
-
export declare function handleShadowAndIframe(driver: any, stepAction: string, selector: string, fireflinkIndex: string, domInfo: any, actionHandlers: Record<string, any>, elementName: string, elementType: string): Promise<ShadowIframeResult>;
|
|
13
|
+
export declare function handleShadowAndIframe(driver: any, stepAction: string, selector: string, fireflinkIndex: string, domInfo: any, actionHandlers: Record<string, any>, elementName: string, elementType: string, usedElementNames?: string[]): Promise<ShadowIframeResult>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findCrossOriginIframeForElement = void 0;
|
|
3
4
|
exports.generateUniqueName = generateUniqueName;
|
|
4
5
|
exports.normalizeSvgSelector = normalizeSvgSelector;
|
|
5
6
|
exports.resolveElementSelector = resolveElementSelector;
|
|
@@ -9,11 +10,13 @@ const jsForAttributeInjection_js_1 = require("../../utils/DomExtraction/jsForAtt
|
|
|
9
10
|
const jsFindElement_js_1 = require("../../utils/javascript/jsFindElement.js");
|
|
10
11
|
const runnerDomPipeline_js_1 = require("./runnerDomPipeline.js");
|
|
11
12
|
const logData_js_1 = require("../../utils/logger/logData.js");
|
|
12
|
-
|
|
13
|
+
const crossOriginHelper_js_1 = require("../../utils/crossOrigin/crossOriginHelper.js");
|
|
14
|
+
Object.defineProperty(exports, "findCrossOriginIframeForElement", { enumerable: true, get: function () { return crossOriginHelper_js_1.findCrossOriginIframeForElement; } });
|
|
15
|
+
function generateUniqueName(currentName, usedNames, suffix) {
|
|
13
16
|
if (!currentName) {
|
|
14
17
|
return currentName;
|
|
15
18
|
}
|
|
16
|
-
const baseName = currentName;
|
|
19
|
+
const baseName = suffix ? `${currentName}${suffix}` : currentName;
|
|
17
20
|
let newName = baseName;
|
|
18
21
|
let count = 0;
|
|
19
22
|
while (usedNames.includes(newName)) {
|
|
@@ -60,7 +63,7 @@ async function resolveElementSelector(doc, domInfo, fireflinkIndex, platform, dr
|
|
|
60
63
|
selector = selector.replace(/\/div\[0\]\/div\[0\]/g, "/div[1]").replace(/\/div\[0\]/g, "/div[1]");
|
|
61
64
|
return { selector, doc: updatedDoc, domInfo: updatedDomInfo };
|
|
62
65
|
}
|
|
63
|
-
async function handleShadowAndIframe(driver, stepAction, selector, fireflinkIndex, domInfo, actionHandlers, elementName, elementType) {
|
|
66
|
+
async function handleShadowAndIframe(driver, stepAction, selector, fireflinkIndex, domInfo, actionHandlers, elementName, elementType, usedElementNames) {
|
|
64
67
|
let updatedStepAction = stepAction;
|
|
65
68
|
let updatedSelector = selector;
|
|
66
69
|
let iframeDomHtml = null;
|
|
@@ -68,36 +71,66 @@ async function handleShadowAndIframe(driver, stepAction, selector, fireflinkInde
|
|
|
68
71
|
updatedStepAction = updatedStepAction + "_shadow";
|
|
69
72
|
logData_js_1.logger.info(updatedStepAction);
|
|
70
73
|
}
|
|
71
|
-
else
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
else {
|
|
75
|
+
const crossOriginInfo = (0, crossOriginHelper_js_1.findCrossOriginIframeForElement)(domInfo?.dom, fireflinkIndex);
|
|
76
|
+
if (crossOriginInfo && crossOriginInfo.frameMeta) {
|
|
77
|
+
const frameHandler = actionHandlers["switchToFrame"];
|
|
78
|
+
await frameHandler({
|
|
79
|
+
selector: updatedSelector,
|
|
80
|
+
frames: [crossOriginInfo.frameMeta],
|
|
81
|
+
elementName,
|
|
82
|
+
elementType,
|
|
83
|
+
pageDOM: domInfo.dom,
|
|
84
|
+
fireflinkIndex,
|
|
85
|
+
usedElementNames,
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
stepAction: updatedStepAction,
|
|
89
|
+
selector: updatedSelector,
|
|
90
|
+
iframeDomHtml: crossOriginInfo.iframeDom || null,
|
|
91
|
+
handled: true,
|
|
92
|
+
isIframe: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
else if (updatedSelector.includes("iframe")) {
|
|
96
|
+
const result_if = await driver.executeAsyncScript(jsFindElement_js_1.Find_element_by_ff_js_script, [fireflinkIndex]);
|
|
97
|
+
if (!result_if?.success) {
|
|
98
|
+
logData_js_1.logger.info("Not found");
|
|
99
|
+
return {
|
|
100
|
+
stepAction: updatedStepAction,
|
|
101
|
+
selector: updatedSelector,
|
|
102
|
+
iframeDomHtml: null,
|
|
103
|
+
handled: false,
|
|
104
|
+
isIframe: false,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const { xpath_if, frames, iframe_dom_html } = result_if;
|
|
108
|
+
updatedSelector = xpath_if;
|
|
109
|
+
iframeDomHtml = iframe_dom_html;
|
|
110
|
+
const frameHandler = actionHandlers["switchToFrame"];
|
|
111
|
+
await frameHandler({
|
|
112
|
+
selector: updatedSelector,
|
|
113
|
+
frames: frames,
|
|
114
|
+
elementName,
|
|
115
|
+
elementType,
|
|
116
|
+
pageDOM: domInfo.dom,
|
|
117
|
+
fireflinkIndex,
|
|
118
|
+
usedElementNames,
|
|
119
|
+
});
|
|
77
120
|
return {
|
|
78
121
|
stepAction: updatedStepAction,
|
|
79
122
|
selector: updatedSelector,
|
|
80
|
-
iframeDomHtml
|
|
81
|
-
handled:
|
|
123
|
+
iframeDomHtml,
|
|
124
|
+
handled: true,
|
|
125
|
+
isIframe: true,
|
|
82
126
|
};
|
|
83
127
|
}
|
|
84
|
-
const { xpath_if, frames, iframe_dom_html } = result_if;
|
|
85
|
-
updatedSelector = xpath_if;
|
|
86
|
-
iframeDomHtml = iframe_dom_html;
|
|
87
|
-
const frameHandler = actionHandlers["switchToFrame"];
|
|
88
|
-
await frameHandler({
|
|
89
|
-
selector: updatedSelector,
|
|
90
|
-
frames: frames,
|
|
91
|
-
elementName,
|
|
92
|
-
elementType,
|
|
93
|
-
pageDOM: domInfo.dom,
|
|
94
|
-
fireflinkIndex,
|
|
95
|
-
});
|
|
96
128
|
}
|
|
97
129
|
return {
|
|
98
130
|
stepAction: updatedStepAction,
|
|
99
131
|
selector: updatedSelector,
|
|
100
132
|
iframeDomHtml,
|
|
101
133
|
handled: true,
|
|
134
|
+
isIframe: false,
|
|
102
135
|
};
|
|
103
136
|
}
|
|
@@ -389,7 +389,7 @@ function createActionHandlers(context) {
|
|
|
389
389
|
await executor.wait(result.value);
|
|
390
390
|
}),
|
|
391
391
|
switchToFrame: withExecutor(async (executor, result) => {
|
|
392
|
-
await executor.switchToFrame(result.selector, result.frames, result.elementName, result.elementType, result.pageDOM, result.fireflinkIndex);
|
|
392
|
+
await executor.switchToFrame(result.selector, result.frames, result.elementName, result.elementType, result.pageDOM, result.fireflinkIndex, result.usedElementNames);
|
|
393
393
|
}),
|
|
394
394
|
Click: withExecutor(async (executor, result) => {
|
|
395
395
|
await executor.click(result.pageDOM, result.selector, result.fireflinkIndex, result.elementName, result.elementType);
|
|
@@ -561,6 +561,7 @@ class AutomationRunner {
|
|
|
561
561
|
}
|
|
562
562
|
baseStepActionBeforeFallback = stepAction;
|
|
563
563
|
for (let fallbackAttempt = 1; fallbackAttempt <= 2; fallbackAttempt++) {
|
|
564
|
+
let shadowIframeResult = null;
|
|
564
565
|
try {
|
|
565
566
|
let iframeDomHtml = null;
|
|
566
567
|
if (fallbackAttempt > 1) {
|
|
@@ -606,7 +607,7 @@ class AutomationRunner {
|
|
|
606
607
|
elementType1: stepResult.response.drag_element_type,
|
|
607
608
|
elementType2: stepResult.response.drop_element_type,
|
|
608
609
|
});
|
|
609
|
-
|
|
610
|
+
break;
|
|
610
611
|
}
|
|
611
612
|
fireflinkIndex = stepResult.response.attributeValue;
|
|
612
613
|
if (fireflinkIndex === "Fire-Flink-0") {
|
|
@@ -621,7 +622,7 @@ class AutomationRunner {
|
|
|
621
622
|
logData_js_1.logger.info(`Unable to resolve xpath for ${fireflinkIndex}`);
|
|
622
623
|
throw new Error(`Unable to resolve xpath for ${fireflinkIndex}`);
|
|
623
624
|
}
|
|
624
|
-
|
|
625
|
+
shadowIframeResult = await (0, elementSelectorResolver_js_1.handleShadowAndIframe)(driver, stepAction, selector, fireflinkIndex, domInfo, actionHandlers, currentElementName || result.response.elementName, stepResult.response.elementType, usedElementNames);
|
|
625
626
|
if (!shadowIframeResult.handled) {
|
|
626
627
|
return;
|
|
627
628
|
}
|
|
@@ -649,6 +650,12 @@ class AutomationRunner {
|
|
|
649
650
|
returnTypeVariableName: currentReturnTypeVariableName,
|
|
650
651
|
...(currentVariableName ? { variableName: currentVariableName } : {}),
|
|
651
652
|
});
|
|
653
|
+
if (shadowIframeResult?.isIframe) {
|
|
654
|
+
const defaultHandler = actionHandlers["switchToDefaultContent"];
|
|
655
|
+
if (defaultHandler) {
|
|
656
|
+
await defaultHandler({ driver });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
652
659
|
preCapturedDomInfo = await (0, runnerPostActionHelper_js_1.handlePostActionWeb)(currentPlatform, driver, action, beforeHandles, context.scriptAppender, stepCount);
|
|
653
660
|
if (fallbackAttempt === 2) {
|
|
654
661
|
if (driverContext) {
|
|
@@ -659,6 +666,14 @@ class AutomationRunner {
|
|
|
659
666
|
break;
|
|
660
667
|
}
|
|
661
668
|
catch (error) {
|
|
669
|
+
if (shadowIframeResult?.isIframe && driver) {
|
|
670
|
+
try {
|
|
671
|
+
await driver.switchFrame(null);
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
/* ignore */
|
|
675
|
+
}
|
|
676
|
+
}
|
|
662
677
|
const visionResult = await (0, visionFallbackHandler_js_1.executeVisionFallback)({
|
|
663
678
|
driver,
|
|
664
679
|
platform: currentPlatform,
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { CrossOriginIframeInfo, RawDomInjectionResult, ICombineDomResult, InjectIframeFunction } from "../crossOrigin/crossOriginInterface.js";
|
|
2
|
+
import { markCrossOriginElements, combineDom, processCrossOriginIframes } from "../crossOrigin/crossOriginHelper.js";
|
|
3
|
+
export { CrossOriginIframeInfo, RawDomInjectionResult, ICombineDomResult, InjectIframeFunction, markCrossOriginElements, combineDom, processCrossOriginIframes };
|
|
1
4
|
export interface AnnotatedDOMResult {
|
|
2
5
|
dom: string;
|
|
3
6
|
}
|
|
4
7
|
export declare function getAnnotatedDOM(browser: WebdriverIO.Browser): Promise<AnnotatedDOMResult>;
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
8
|
+
export declare function injectReact(browser: WebdriverIO.Browser, startIndex?: number): Promise<RawDomInjectionResult>;
|
|
9
|
+
export declare function getReactAnnotatedDOM(browser: WebdriverIO.Browser, startIndex?: number): Promise<AnnotatedDOMResult>;
|
|
10
|
+
export declare const INJECT_AND_SERIALIZE_DOM_JS = "\nreturn (function (startIndex) {\n let nodeIndex = typeof startIndex === \"number\" ? startIndex : 0;\nfunction getCoverageStatus(el) {\n if (!el || !el.getBoundingClientRect) return \"unknown\";\n const s = getComputedStyle(el);\n if (s.pointerEvents === \"none\") return \"false\";\n const rect = el.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return \"false\";\n // Off-screen element \u2014 check if something is blocking the viewport\n const isOffScreen =\n rect.bottom < 0 ||\n rect.right < 0 ||\n rect.top > window.innerHeight ||\n rect.left > window.innerWidth;\n if (isOffScreen) {\n const centerEl = document.elementFromPoint(\n window.innerWidth / 2,\n window.innerHeight / 2\n );\n if (centerEl && centerEl !== document.body && centerEl !== document.documentElement) {\n // Something is at center of viewport \u2014 check if el and centerEl\n // share the same scrollable container\n const elScrollParent = getScrollParent(el);\n const centerScrollParent = getScrollParent(centerEl);\n if (elScrollParent !== centerScrollParent) return \"true\";\n }\n return \"false\";\n }\n // On-screen element \u2014 use elementFromPoint\n const testPoints = [\n [rect.left + 5, rect.top + 5],\n [rect.right - 5, rect.top + 5],\n [rect.left + 5, rect.bottom - 5],\n [rect.right - 5, rect.bottom - 5],\n [rect.left + rect.width / 2, rect.top + rect.height / 2]\n ];\n for (const [x, y] of testPoints) {\n if (x < 0 || y < 0 || x > window.innerWidth || y > window.innerHeight) continue;\n const topEl = document.elementFromPoint(x, y);\n if (!topEl) continue;\n if (topEl === el || el.contains(topEl)) continue;\n if (topEl.contains(el)) continue; // topEl is ancestor, not a cover\n return \"true\";\n }\n return \"false\";\n}\nfunction getScrollParent(node) {\n if (!node || node === document.body) return document.body;\n const s = getComputedStyle(node);\n if (\n s.overflow === \"auto\" || s.overflow === \"scroll\" ||\n s.overflowY === \"auto\" || s.overflowY === \"scroll\"\n ) return node;\n return getScrollParent(node.parentElement);\n}\nfunction isUsable(node) {\n const s = getComputedStyle(node);\n if (s.display === \"none\" || s.visibility === \"hidden\" || s.opacity === \"0\") return false;\n const rect = node.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return false;\n return true;\n}\n\nfunction isSelectRelated(node) {\n return (\n node.matches?.(\"select, option\") ||\n node.closest?.(\"select, [role='listbox'], [role='combobox']\")\n );\n}\n function annotate(node) {\n const s = getComputedStyle(node);\n node.setAttribute(\"element-visibility\", s.visibility || \"\");\n node.setAttribute(\"element-pointer-events\", s.pointerEvents || \"\");\n node.setAttribute(\"element-zindex\", s.zIndex || \"\");\n if (isSelectRelated(node)) {\n node.setAttribute(\"element-covered\", \"false\");\n return;\n }\n\n if (!isUsable(node)) {\n node.setAttribute(\"element-covered\", \"true\");\n return;\n }\n try {\n node.setAttribute(\"element-covered\", getCoverageStatus(node));\n \n } catch {\n node.setAttribute(\"element-covered\", \"unknown\");\n }\n }\n function getXPath(node) {\n if (!node || node.nodeType !== 1) return \"\";\n let segments = [];\n let current = node;\n while (current && current.nodeType === 1) {\n const parent = current.parentNode;\n if (parent instanceof ShadowRoot) {\n const host = parent.host;\n const siblings = Array.from(parent.children).filter(n => n.tagName === current.tagName);\n const index = siblings.indexOf(current) + 1;\n segments.unshift(\"shadow/\" + current.tagName.toLowerCase() + \"[\" + index + \"]\");\n current = host;\n continue;\n }\n let index = 1;\n if (parent && parent.children) {\n const siblings = Array.from(parent.children).filter(n => n.tagName === current.tagName);\n index = siblings.indexOf(current) + 1;\n }\n segments.unshift(current.tagName.toLowerCase() + \"[\" + index + \"]\");\n if (!parent || parent.nodeType !== 1) break;\n current = parent;\n }\n let path = \"/\" + segments.join(\"/\");\n if (node.ownerDocument !== document) {\n const frameEl = node.ownerDocument.defaultView && node.ownerDocument.defaultView.frameElement;\n if (frameEl) {\n let framePath = frameEl.getAttribute(\"ff-xpath\");\n if (!framePath) framePath = getXPath(frameEl);\n path = framePath + path;\n }\n }\n path = path.replace(//div[0]/div[0]/g, \"/div[1]\").replace(//div[0]/g, \"/div[1]\");\n return path;\n }\n function walk(root) {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);\n let node = walker.currentNode;\n while (node) {\n if (node.nodeType === 1 \n && typeof node.setAttribute === 'function'\n && ![\"META\", \"STYLE\", \"SCRIPT\"].includes(node.tagName)) {\n node.setAttribute(\"ff-inspect\", \"Fire-Flink-\" + nodeIndex++);\n node.setAttribute(\"ff-xpath\", getXPath(node));\n annotate(node);\n }\n if (node.shadowRoot) walk(node.shadowRoot);\n if (node.tagName === \"IFRAME\") {\n try {\n const doc = node.contentDocument;\n if (doc && doc.documentElement) {\n walk(doc.documentElement);\n } else {\n node.setAttribute(\"ff-cross-origin\", \"true\");\n }\n } catch {\n node.setAttribute(\"ff-cross-origin\", \"true\");\n }\n }\n node = walker.nextNode();\n }\n }\nfunction cleanup(root) {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);\n let node = walker.currentNode;\n while (node) {\n if (node.nodeType === 1 && typeof node.removeAttribute === 'function') {\n node.removeAttribute(\"ff-inspect\");\n node.removeAttribute(\"ff-xpath\");\n node.removeAttribute(\"element-visibility\");\n node.removeAttribute(\"element-pointer-events\");\n node.removeAttribute(\"element-zindex\");\n node.removeAttribute(\"element-covered\");\n node.removeAttribute(\"ff-cross-origin\");\n}\n if (node.shadowRoot) cleanup(node.shadowRoot);\n node = walker.nextNode();\n }\n}\ncleanup(document.documentElement);\n// Force browser to settle DOM after cleanup\nvoid document.documentElement.offsetHeight;\nwalk(document.documentElement);\n function serializeNode(node) {\n if (node.nodeType === Node.TEXT_NODE) return node.textContent;\n if (node.nodeType !== Node.ELEMENT_NODE) return \"\";\n const tag = node.tagName.toLowerCase();\n let html = \"<\" + tag;\n for (const attr of node.attributes) {\n if (attr.name === \"srcdoc\") continue;\n html += \" \" + attr.name + \"=\"\" + attr.value.replace(/\"/g, \""\") + \"\"\";\n }\n html += \">\";\n if (node.shadowRoot) {\n html += \"<!--#shadow-root-->\";\n node.shadowRoot.childNodes.forEach(child => { html += serializeNode(child); });\n html += \"<!--#/shadow-root-->\";\n }\n if (tag === \"iframe\" && node.getAttribute(\"ff-cross-origin\") !== \"true\") {\n try {\n const doc = node.contentDocument;\n if (doc) html += serializeNode(doc.documentElement);\n } catch {}\n }\n node.childNodes.forEach(child => { html += serializeNode(child); });\n html += \"</\" + tag + \">\";\n return html;\n }\n\n const crossOriginIframes =\n Array.from(\n document.querySelectorAll(\"iframe\")\n )\n .filter(\n iframe =>\n iframe.getAttribute(\n \"ff-cross-origin\"\n ) === \"true\"\n )\n .map(iframe => ({\n xpath:\n iframe.getAttribute(\n \"ff-xpath\"\n ),\n\n src:\n iframe.getAttribute(\"src\"),\n\n title:\n iframe.getAttribute(\"title\"),\n\n name:\n iframe.getAttribute(\"name\")\n }));\n\n return {\n dom: \"<!DOCTYPE html>\n\" + serializeNode(document.documentElement),\n crossOriginIframes: crossOriginIframes,\n nextNodeIndex: nodeIndex\n };\n})(arguments[0]);\n";
|
|
11
|
+
export declare function injectAngular(browser: WebdriverIO.Browser, startIndex?: number): Promise<RawDomInjectionResult>;
|
|
12
|
+
export declare function getAngularAnnotatedDOM(browser: WebdriverIO.Browser, startIndex?: number): Promise<AnnotatedDOMResult>;
|
|
7
13
|
export declare function getAnnotatedDOMForMobile(driver: WebdriverIO.Browser, existingDom?: string): Promise<AnnotatedDOMResult>;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.INJECT_AND_SERIALIZE_DOM_JS = exports.processCrossOriginIframes = exports.combineDom = exports.markCrossOriginElements = void 0;
|
|
3
4
|
exports.getAnnotatedDOM = getAnnotatedDOM;
|
|
5
|
+
exports.injectReact = injectReact;
|
|
4
6
|
exports.getReactAnnotatedDOM = getReactAnnotatedDOM;
|
|
7
|
+
exports.injectAngular = injectAngular;
|
|
5
8
|
exports.getAngularAnnotatedDOM = getAngularAnnotatedDOM;
|
|
6
9
|
exports.getAnnotatedDOMForMobile = getAnnotatedDOMForMobile;
|
|
7
10
|
const xmldom_1 = require("@xmldom/xmldom");
|
|
8
|
-
const logData_js_1 = require("../../utils/logger/logData.js");
|
|
9
11
|
const frameWorkDetector_js_1 = require("../frameWorkDetector/frameWorkDetector.js");
|
|
12
|
+
const crossOriginHelper_js_1 = require("../crossOrigin/crossOriginHelper.js");
|
|
13
|
+
Object.defineProperty(exports, "markCrossOriginElements", { enumerable: true, get: function () { return crossOriginHelper_js_1.markCrossOriginElements; } });
|
|
14
|
+
Object.defineProperty(exports, "combineDom", { enumerable: true, get: function () { return crossOriginHelper_js_1.combineDom; } });
|
|
15
|
+
Object.defineProperty(exports, "processCrossOriginIframes", { enumerable: true, get: function () { return crossOriginHelper_js_1.processCrossOriginIframes; } });
|
|
10
16
|
async function getAnnotatedDOM(browser) {
|
|
11
17
|
const isAngular = await frameWorkDetector_js_1.frameWorkDetector.isAngular(browser);
|
|
12
18
|
if (isAngular) {
|
|
@@ -14,10 +20,12 @@ async function getAnnotatedDOM(browser) {
|
|
|
14
20
|
}
|
|
15
21
|
return getReactAnnotatedDOM(browser);
|
|
16
22
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
// ==========================================================
|
|
24
|
+
// REACT INJECTION
|
|
25
|
+
// ==========================================================
|
|
26
|
+
async function injectReact(browser, startIndex = 0) {
|
|
27
|
+
return await browser.execute((startIndexArg) => {
|
|
28
|
+
let nodeIndex = typeof startIndexArg === "number" ? startIndexArg : 0;
|
|
21
29
|
// ==========================================================
|
|
22
30
|
// COVERAGE CHECK
|
|
23
31
|
// ==========================================================
|
|
@@ -153,7 +161,6 @@ async function getReactAnnotatedDOM(browser) {
|
|
|
153
161
|
const xpath = getXPath(node);
|
|
154
162
|
node.setAttribute("ff-inspect", fireId);
|
|
155
163
|
node.setAttribute("ff-xpath", xpath);
|
|
156
|
-
// xpaths[fireId] = xpath;
|
|
157
164
|
annotate(node);
|
|
158
165
|
}
|
|
159
166
|
if (node.shadowRoot) {
|
|
@@ -163,9 +170,12 @@ async function getReactAnnotatedDOM(browser) {
|
|
|
163
170
|
try {
|
|
164
171
|
const doc = node
|
|
165
172
|
.contentDocument;
|
|
166
|
-
if (doc
|
|
173
|
+
if (doc && doc.documentElement) {
|
|
167
174
|
walk(doc.documentElement);
|
|
168
175
|
}
|
|
176
|
+
else {
|
|
177
|
+
node.setAttribute("ff-cross-origin", "true");
|
|
178
|
+
}
|
|
169
179
|
}
|
|
170
180
|
catch {
|
|
171
181
|
node.setAttribute("ff-cross-origin", "true");
|
|
@@ -216,8 +226,8 @@ async function getReactAnnotatedDOM(browser) {
|
|
|
216
226
|
html += serializeNode(doc.documentElement);
|
|
217
227
|
}
|
|
218
228
|
}
|
|
219
|
-
catch
|
|
220
|
-
|
|
229
|
+
catch {
|
|
230
|
+
// inaccessible iframe
|
|
221
231
|
}
|
|
222
232
|
}
|
|
223
233
|
const voidElements = new Set([
|
|
@@ -247,16 +257,33 @@ async function getReactAnnotatedDOM(browser) {
|
|
|
247
257
|
html += "</" + tag + ">";
|
|
248
258
|
return html;
|
|
249
259
|
}
|
|
260
|
+
const crossOriginIframes = Array.from(document.querySelectorAll("iframe"))
|
|
261
|
+
.filter(iframe => iframe.getAttribute("ff-cross-origin") === "true")
|
|
262
|
+
.map(iframe => ({
|
|
263
|
+
xpath: iframe.getAttribute("ff-xpath"),
|
|
264
|
+
src: iframe.getAttribute("src"),
|
|
265
|
+
title: iframe.getAttribute("title"),
|
|
266
|
+
name: iframe.getAttribute("name")
|
|
267
|
+
}));
|
|
250
268
|
return {
|
|
251
269
|
dom: "<!DOCTYPE html>\n" +
|
|
252
|
-
serializeNode(document.documentElement)
|
|
253
|
-
|
|
270
|
+
serializeNode(document.documentElement),
|
|
271
|
+
crossOriginIframes: crossOriginIframes,
|
|
272
|
+
nextNodeIndex: nodeIndex
|
|
254
273
|
};
|
|
255
|
-
});
|
|
274
|
+
}, startIndex);
|
|
275
|
+
}
|
|
276
|
+
async function getReactAnnotatedDOM(browser, startIndex = 0) {
|
|
277
|
+
const parentResult = await injectReact(browser, startIndex);
|
|
278
|
+
const dom = await (0, crossOriginHelper_js_1.processCrossOriginIframes)(browser, parentResult, injectReact);
|
|
279
|
+
return { dom };
|
|
256
280
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
281
|
+
// ==========================================================
|
|
282
|
+
// ANGULAR INJECTION
|
|
283
|
+
// ==========================================================
|
|
284
|
+
exports.INJECT_AND_SERIALIZE_DOM_JS = `
|
|
285
|
+
return (function (startIndex) {
|
|
286
|
+
let nodeIndex = typeof startIndex === "number" ? startIndex : 0;
|
|
260
287
|
function getCoverageStatus(el) {
|
|
261
288
|
if (!el || !el.getBoundingClientRect) return "unknown";
|
|
262
289
|
const s = getComputedStyle(el);
|
|
@@ -395,7 +422,11 @@ function isSelectRelated(node) {
|
|
|
395
422
|
if (node.tagName === "IFRAME") {
|
|
396
423
|
try {
|
|
397
424
|
const doc = node.contentDocument;
|
|
398
|
-
if (doc && doc.documentElement)
|
|
425
|
+
if (doc && doc.documentElement) {
|
|
426
|
+
walk(doc.documentElement);
|
|
427
|
+
} else {
|
|
428
|
+
node.setAttribute("ff-cross-origin", "true");
|
|
429
|
+
}
|
|
399
430
|
} catch {
|
|
400
431
|
node.setAttribute("ff-cross-origin", "true");
|
|
401
432
|
}
|
|
@@ -404,7 +435,6 @@ function isSelectRelated(node) {
|
|
|
404
435
|
}
|
|
405
436
|
}
|
|
406
437
|
function cleanup(root) {
|
|
407
|
-
const startNode = (root.nodeType === 1) ? root : null;
|
|
408
438
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);
|
|
409
439
|
let node = walker.currentNode;
|
|
410
440
|
while (node) {
|
|
@@ -432,7 +462,7 @@ walk(document.documentElement);
|
|
|
432
462
|
let html = "<" + tag;
|
|
433
463
|
for (const attr of node.attributes) {
|
|
434
464
|
if (attr.name === "srcdoc") continue;
|
|
435
|
-
html += " " + attr.name + "
|
|
465
|
+
html += " " + attr.name + "=\"" + attr.value.replace(/"/g, """) + "\"";
|
|
436
466
|
}
|
|
437
467
|
html += ">";
|
|
438
468
|
if (node.shadowRoot) {
|
|
@@ -450,14 +480,49 @@ walk(document.documentElement);
|
|
|
450
480
|
html += "</" + tag + ">";
|
|
451
481
|
return html;
|
|
452
482
|
}
|
|
453
|
-
|
|
454
|
-
|
|
483
|
+
|
|
484
|
+
const crossOriginIframes =
|
|
485
|
+
Array.from(
|
|
486
|
+
document.querySelectorAll("iframe")
|
|
487
|
+
)
|
|
488
|
+
.filter(
|
|
489
|
+
iframe =>
|
|
490
|
+
iframe.getAttribute(
|
|
491
|
+
"ff-cross-origin"
|
|
492
|
+
) === "true"
|
|
493
|
+
)
|
|
494
|
+
.map(iframe => ({
|
|
495
|
+
xpath:
|
|
496
|
+
iframe.getAttribute(
|
|
497
|
+
"ff-xpath"
|
|
498
|
+
),
|
|
499
|
+
|
|
500
|
+
src:
|
|
501
|
+
iframe.getAttribute("src"),
|
|
502
|
+
|
|
503
|
+
title:
|
|
504
|
+
iframe.getAttribute("title"),
|
|
505
|
+
|
|
506
|
+
name:
|
|
507
|
+
iframe.getAttribute("name")
|
|
508
|
+
}));
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
dom: "<!DOCTYPE html>\n" + serializeNode(document.documentElement),
|
|
512
|
+
crossOriginIframes: crossOriginIframes,
|
|
513
|
+
nextNodeIndex: nodeIndex
|
|
514
|
+
};
|
|
515
|
+
})(arguments[0]);
|
|
455
516
|
`;
|
|
456
|
-
async function
|
|
457
|
-
const fn = new Function(INJECT_AND_SERIALIZE_DOM_JS);
|
|
458
|
-
|
|
517
|
+
async function injectAngular(browser, startIndex = 0) {
|
|
518
|
+
const fn = new Function("startIndex", exports.INJECT_AND_SERIALIZE_DOM_JS);
|
|
519
|
+
return await browser.execute(fn, startIndex);
|
|
520
|
+
}
|
|
521
|
+
async function getAngularAnnotatedDOM(browser, startIndex = 0) {
|
|
522
|
+
const parentResult = await injectAngular(browser, startIndex);
|
|
523
|
+
const dom = await (0, crossOriginHelper_js_1.processCrossOriginIframes)(browser, parentResult, injectAngular);
|
|
459
524
|
return {
|
|
460
|
-
dom
|
|
525
|
+
dom
|
|
461
526
|
};
|
|
462
527
|
}
|
|
463
528
|
async function getAnnotatedDOMForMobile(driver, existingDom) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { RawDomInjectionResult, ICombineDomResult, InjectIframeFunction, ICrossOriginElementInfo } from "./crossOriginInterface.js";
|
|
2
|
+
export declare function markCrossOriginElements(iframeDom: string): string;
|
|
3
|
+
export declare function combineDom(result: ICombineDomResult): string;
|
|
4
|
+
export declare function processCrossOriginIframes(browser: WebdriverIO.Browser, parentResult: RawDomInjectionResult, injectIframeFn: InjectIframeFunction): Promise<string>;
|
|
5
|
+
export declare function findCrossOriginIframeForElement(pageDom: string, fireflinkIndex: string): ICrossOriginElementInfo | null;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.markCrossOriginElements = markCrossOriginElements;
|
|
4
|
+
exports.combineDom = combineDom;
|
|
5
|
+
exports.processCrossOriginIframes = processCrossOriginIframes;
|
|
6
|
+
exports.findCrossOriginIframeForElement = findCrossOriginIframeForElement;
|
|
7
|
+
const logData_js_1 = require("../logger/logData.js");
|
|
8
|
+
function markCrossOriginElements(iframeDom) {
|
|
9
|
+
return iframeDom.replace(/<([a-zA-Z][^<>]*)>/g, (match, p1) => {
|
|
10
|
+
if (p1.startsWith("!") || p1.startsWith("?")) {
|
|
11
|
+
return match;
|
|
12
|
+
}
|
|
13
|
+
if (!match.includes("ff-cross-origin=")) {
|
|
14
|
+
return match.slice(0, -1) + ' ff-cross-origin="true">';
|
|
15
|
+
}
|
|
16
|
+
return match;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function combineDom(result) {
|
|
20
|
+
const parentDom = result.parent.dom;
|
|
21
|
+
let combinedDom = parentDom;
|
|
22
|
+
for (const iframeInfo of result.crossOriginIframes) {
|
|
23
|
+
let iframeDom = iframeInfo.result?.dom;
|
|
24
|
+
if (!iframeDom) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
iframeDom = markCrossOriginElements(iframeDom);
|
|
28
|
+
const xpath = iframeInfo.xpath;
|
|
29
|
+
if (!xpath) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const marker = `ff-xpath="${xpath}"`;
|
|
33
|
+
const index = combinedDom.indexOf(marker);
|
|
34
|
+
if (index === -1) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const iframeStart = combinedDom.lastIndexOf("<iframe", index);
|
|
38
|
+
if (iframeStart === -1) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const iframeEnd = combinedDom.indexOf(">", index);
|
|
42
|
+
if (iframeEnd === -1) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const insertPosition = iframeEnd + 1;
|
|
46
|
+
combinedDom =
|
|
47
|
+
combinedDom.slice(0, insertPosition) +
|
|
48
|
+
"\n<!-- CROSS-ORIGIN IFRAME DOM START -->\n" +
|
|
49
|
+
iframeDom +
|
|
50
|
+
"\n<!-- CROSS-ORIGIN IFRAME DOM END -->\n" +
|
|
51
|
+
combinedDom.slice(insertPosition);
|
|
52
|
+
}
|
|
53
|
+
return combinedDom;
|
|
54
|
+
}
|
|
55
|
+
async function processCrossOriginIframes(browser, parentResult, injectIframeFn) {
|
|
56
|
+
if (!parentResult || !parentResult.dom) {
|
|
57
|
+
return parentResult?.dom || "";
|
|
58
|
+
}
|
|
59
|
+
const crossOriginIframes = parentResult.crossOriginIframes || [];
|
|
60
|
+
if (!crossOriginIframes.length) {
|
|
61
|
+
return parentResult.dom;
|
|
62
|
+
}
|
|
63
|
+
let nextIndex = parentResult.nextNodeIndex || 0;
|
|
64
|
+
const iframeResults = [];
|
|
65
|
+
for (const iframeInfo of crossOriginIframes) {
|
|
66
|
+
const title = iframeInfo.title;
|
|
67
|
+
const src = iframeInfo.src;
|
|
68
|
+
const xpath = iframeInfo.xpath;
|
|
69
|
+
const name = iframeInfo.name;
|
|
70
|
+
try {
|
|
71
|
+
await browser.switchFrame(null);
|
|
72
|
+
let iframeElement = null;
|
|
73
|
+
if (title) {
|
|
74
|
+
try {
|
|
75
|
+
const el = await browser.$(`//iframe[@title='${title}']`);
|
|
76
|
+
if (await el.isExisting())
|
|
77
|
+
iframeElement = el;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* ignore */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!iframeElement && src) {
|
|
84
|
+
try {
|
|
85
|
+
const el = await browser.$(`//iframe[contains(@src,'${src}')]`);
|
|
86
|
+
if (await el.isExisting())
|
|
87
|
+
iframeElement = el;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* ignore */
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!iframeElement && name) {
|
|
94
|
+
try {
|
|
95
|
+
const el = await browser.$(`//iframe[@name='${name}']`);
|
|
96
|
+
if (await el.isExisting())
|
|
97
|
+
iframeElement = el;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* ignore */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!iframeElement && xpath) {
|
|
104
|
+
try {
|
|
105
|
+
const el = await browser.$(xpath);
|
|
106
|
+
if (await el.isExisting())
|
|
107
|
+
iframeElement = el;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* ignore */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!iframeElement) {
|
|
114
|
+
throw new Error(`Unable to locate cross-origin iframe with title=${title}, src=${src}`);
|
|
115
|
+
}
|
|
116
|
+
await browser.switchFrame(iframeElement);
|
|
117
|
+
const iframeResult = await injectIframeFn(browser, nextIndex);
|
|
118
|
+
if (iframeResult && typeof iframeResult.nextNodeIndex === "number") {
|
|
119
|
+
nextIndex = iframeResult.nextNodeIndex;
|
|
120
|
+
}
|
|
121
|
+
iframeResults.push({
|
|
122
|
+
title,
|
|
123
|
+
src,
|
|
124
|
+
name,
|
|
125
|
+
xpath,
|
|
126
|
+
result: iframeResult
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
iframeResults.push({
|
|
131
|
+
title,
|
|
132
|
+
src,
|
|
133
|
+
name,
|
|
134
|
+
xpath,
|
|
135
|
+
error: e.message || String(e)
|
|
136
|
+
});
|
|
137
|
+
logData_js_1.logger.error(`Failed iframe: ${title || src || xpath} | ${e.message || String(e)}`);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
try {
|
|
141
|
+
await browser.switchFrame(null);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
/* ignore */
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return combineDom({
|
|
149
|
+
parent: parentResult,
|
|
150
|
+
crossOriginIframes: iframeResults
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function findCrossOriginIframeForElement(pageDom, fireflinkIndex) {
|
|
154
|
+
if (!pageDom || !fireflinkIndex)
|
|
155
|
+
return null;
|
|
156
|
+
const escaped = fireflinkIndex.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
157
|
+
const marker = new RegExp(`<[^>]+\\bff-inspect\\s*=\\s*["']${escaped}["'][^>]*>`, "i");
|
|
158
|
+
const match = marker.exec(pageDom);
|
|
159
|
+
if (!match)
|
|
160
|
+
return null;
|
|
161
|
+
const tag = match[0];
|
|
162
|
+
const isCrossOrigin = /ff-cross-origin\s*=\s*["']true["']/i.test(tag);
|
|
163
|
+
if (!isCrossOrigin)
|
|
164
|
+
return null;
|
|
165
|
+
const elemIndex = match.index;
|
|
166
|
+
const iframeStart = pageDom.lastIndexOf("<iframe", elemIndex);
|
|
167
|
+
if (iframeStart === -1)
|
|
168
|
+
return null;
|
|
169
|
+
const iframeTagEnd = pageDom.indexOf(">", iframeStart);
|
|
170
|
+
if (iframeTagEnd === -1 || iframeTagEnd > elemIndex)
|
|
171
|
+
return null;
|
|
172
|
+
const iframeTag = pageDom.substring(iframeStart, iframeTagEnd + 1);
|
|
173
|
+
const getAttr = (name) => {
|
|
174
|
+
const attrMatch = new RegExp(`\\b${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(iframeTag);
|
|
175
|
+
return attrMatch ? attrMatch[1] : undefined;
|
|
176
|
+
};
|
|
177
|
+
const frameMeta = {
|
|
178
|
+
id: getAttr("id"),
|
|
179
|
+
name: getAttr("name"),
|
|
180
|
+
title: getAttr("title"),
|
|
181
|
+
aria: getAttr("aria-label"),
|
|
182
|
+
src: getAttr("src"),
|
|
183
|
+
};
|
|
184
|
+
const blockStart = pageDom.indexOf("<!-- CROSS-ORIGIN IFRAME DOM START -->", iframeTagEnd);
|
|
185
|
+
const blockEnd = pageDom.indexOf("<!-- CROSS-ORIGIN IFRAME DOM END -->", blockStart);
|
|
186
|
+
let iframeDom;
|
|
187
|
+
if (blockStart !== -1 && blockEnd !== -1 && elemIndex > blockStart && elemIndex < blockEnd) {
|
|
188
|
+
iframeDom = pageDom.substring(blockStart + "<!-- CROSS-ORIGIN IFRAME DOM START -->".length, blockEnd).trim();
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
isCrossOrigin: true,
|
|
192
|
+
frameMeta,
|
|
193
|
+
iframeDom,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { FrameMeta } from "../iframesExtraction/iframeInterface.js";
|
|
2
|
+
export interface CrossOriginIframeInfo {
|
|
3
|
+
xpath?: string | null;
|
|
4
|
+
src?: string | null;
|
|
5
|
+
title?: string | null;
|
|
6
|
+
name?: string | null;
|
|
7
|
+
result?: RawDomInjectionResult;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RawDomInjectionResult {
|
|
11
|
+
dom: string;
|
|
12
|
+
crossOriginIframes: CrossOriginIframeInfo[];
|
|
13
|
+
nextNodeIndex: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ICombineDomResult {
|
|
16
|
+
parent: {
|
|
17
|
+
dom: string;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
};
|
|
20
|
+
crossOriginIframes: Array<{
|
|
21
|
+
xpath?: string | null;
|
|
22
|
+
result?: {
|
|
23
|
+
dom?: string;
|
|
24
|
+
};
|
|
25
|
+
[key: string]: any;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
export type InjectIframeFunction = (browser: WebdriverIO.Browser, startIndex: number) => Promise<RawDomInjectionResult>;
|
|
29
|
+
export interface ICrossOriginElementInfo {
|
|
30
|
+
isCrossOrigin: boolean;
|
|
31
|
+
frameMeta?: FrameMeta;
|
|
32
|
+
iframeDom?: string;
|
|
33
|
+
}
|
|
34
|
+
export type MarkCrossOriginElementsFn = (iframeDom: string) => string;
|
|
35
|
+
export type CombineDomFn = (result: ICombineDomResult) => string;
|
|
36
|
+
export type ProcessCrossOriginIframesFn = (browser: WebdriverIO.Browser, parentResult: RawDomInjectionResult, injectIframeFn: InjectIframeFunction) => Promise<string>;
|
|
37
|
+
export type FindCrossOriginIframeForElementFn = (pageDom: string, fireflinkIndex: string) => ICrossOriginElementInfo | null;
|
|
38
|
+
export interface ICrossOriginHelper {
|
|
39
|
+
markCrossOriginElements: MarkCrossOriginElementsFn;
|
|
40
|
+
combineDom: CombineDomFn;
|
|
41
|
+
processCrossOriginIframes: ProcessCrossOriginIframesFn;
|
|
42
|
+
findCrossOriginIframeForElement: FindCrossOriginIframeForElementFn;
|
|
43
|
+
}
|
|
@@ -4,6 +4,8 @@ exports.sanitizeWebDom = void 0;
|
|
|
4
4
|
const sanitizeWebDom = (domString) => {
|
|
5
5
|
return (domString || "")
|
|
6
6
|
.replace(/<noscript[\s\S]*?<\/noscript>/gi, "")
|
|
7
|
-
.replace(/<noscript[^>]*>/gi, "")
|
|
7
|
+
.replace(/<noscript[^>]*>/gi, "")
|
|
8
|
+
.replace(/<iframe\b/gi, "<ff-iframe")
|
|
9
|
+
.replace(/<\/iframe>/gi, "</ff-iframe>");
|
|
8
10
|
};
|
|
9
11
|
exports.sanitizeWebDom = sanitizeWebDom;
|
|
@@ -3,24 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.findElementUsingFF = void 0;
|
|
4
4
|
const appendScriptData_js_1 = require("../../fireflinkData/fireflinkScript/appendScriptData.js");
|
|
5
5
|
const switchToIframeByMeta_js_1 = require("./switchToIframeByMeta.js");
|
|
6
|
+
const elementSelectorResolver_js_1 = require("../../core/helpers/elementSelectorResolver.js");
|
|
6
7
|
const findElementUsingFF = async (args) => {
|
|
7
8
|
await args.driver.switchFrame(null);
|
|
8
|
-
const usedElementNames = [];
|
|
9
|
+
const usedElementNames = args.usedElementNames || [];
|
|
9
10
|
for (const frameMeta of args.frames) {
|
|
10
11
|
const xpathUsed = await (0, switchToIframeByMeta_js_1.switchToIframeByMeta)(args.driver, frameMeta);
|
|
11
12
|
if (xpathUsed) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const baseName = finalElementName;
|
|
15
|
-
let newName = baseName;
|
|
16
|
-
let count = 0;
|
|
17
|
-
while (usedElementNames.includes(newName)) {
|
|
18
|
-
count++;
|
|
19
|
-
newName = `${baseName}_${count}`;
|
|
20
|
-
}
|
|
21
|
-
finalElementName = newName;
|
|
22
|
-
usedElementNames.push(finalElementName);
|
|
23
|
-
}
|
|
13
|
+
const baseName = args.elementName ? args.elementName.trim().replace(/\s+/g, "-") : args.elementName;
|
|
14
|
+
const finalElementName = ((0, elementSelectorResolver_js_1.generateUniqueName)(baseName, usedElementNames, "-iframe") || baseName)?.replace(/\s+/g, "-");
|
|
24
15
|
(0, appendScriptData_js_1.appendScriptData)({
|
|
25
16
|
scriptDataAppender: args.scriptDataAppender,
|
|
26
17
|
nlpName: "SwitchToFrameByFrameElement",
|