ff-automationv2 2.2.29 → 2.2.30
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/ai/llmcalls/llmAction.js +49 -13
- package/dist/ai/llmcalls/parseLlmOputput.js +5 -5
- package/dist/ai/llmprompts/promptRegistry.js +4 -0
- package/dist/ai/llmprompts/systemPrompts/actionExtractorPrompt.js +39 -11
- package/dist/ai/llmprompts/systemPrompts/fireflinkElementIndexExtactors.js +6 -6
- package/dist/ai/llmprompts/systemPrompts/fireflinkElementIndexExtractor_Mob.js +32 -0
- package/dist/ai/llmprompts/systemPrompts/mobileKeywordExtractor.js +17 -8
- package/dist/ai/llmprompts/systemPrompts/otpExtractorPrompt.d.ts +1 -0
- package/dist/ai/llmprompts/systemPrompts/otpExtractorPrompt.js +22 -0
- package/dist/ai/llmprompts/systemPrompts/verifyActionExtractorPrompt.js +3 -1
- package/dist/ai/llmprompts/systemPrompts/visionPrompt.js +14 -6
- package/dist/ai/llmprompts/systemPrompts/visionPromptMobile.js +3 -0
- package/dist/ai/llmprompts/userPrompts/userPrompt.js +1 -0
- package/dist/automation/actions/executor.d.ts +4 -0
- package/dist/automation/actions/executor.js +20 -0
- package/dist/automation/actions/interaction/clear/clear.js +1 -1
- package/dist/automation/actions/interaction/clear/clearAndEnter.js +1 -1
- package/dist/automation/actions/interaction/click/click.js +10 -3
- package/dist/automation/actions/interaction/click/clickNtimes.js +1 -1
- package/dist/automation/actions/interaction/click/doubleClick.js +1 -1
- package/dist/automation/actions/interaction/click/rightClick.js +1 -1
- package/dist/automation/actions/interaction/elementlessActions/getOtp.d.ts +2 -0
- package/dist/automation/actions/interaction/elementlessActions/getOtp.js +101 -0
- package/dist/automation/actions/interaction/enterActions/enterInput.js +11 -6
- package/dist/automation/actions/interaction/enterActions/enterInputAndPress.js +2 -2
- package/dist/automation/actions/interaction/enterActions/enterusingJs.js +2 -2
- package/dist/automation/actions/interaction/enterActions/waitAndEnter.js +1 -1
- package/dist/automation/actions/interaction/upload/uploadfile.js +1 -1
- package/dist/automation/actions/interaction/verify/VerifyNavigateURL.js +38 -11
- package/dist/automation/actions/interface/FireFlinkResponseSchema.d.ts +12 -0
- package/dist/automation/actions/interface/FireFlinkResponseSchema.js +19 -3
- package/dist/automation/actions/interface/getOtpInterface.d.ts +15 -0
- package/dist/automation/actions/interface/getOtpInterface.js +2 -0
- package/dist/automation/browserSession/initiateBrowserSession.d.ts +1 -1
- package/dist/automation/browserSession/initiateBrowserSession.js +15 -7
- package/dist/automation/driverState.d.ts +2 -0
- package/dist/automation/driverState.js +4 -0
- package/dist/automation/mobileSession/initiateMobileSession.d.ts +4 -5
- package/dist/automation/mobileSession/initiateMobileSession.js +83 -19
- package/dist/core/constants/allAction.js +5 -1
- package/dist/core/constants/supportedActions.js +5 -2
- package/dist/core/interfaces/actionInterface.d.ts +4 -0
- package/dist/core/interfaces/browserConfigurationInterface.d.ts +0 -4
- package/dist/core/interfaces/browserConfigurationInterface.js +0 -1
- package/dist/core/interfaces/executionDetails.d.ts +6 -3
- package/dist/core/interfaces/fireflinkScriptPayloadInterface.d.ts +22 -16
- package/dist/core/interfaces/llmResponseInterface.d.ts +1 -0
- package/dist/core/interfaces/promptInterface.d.ts +1 -0
- package/dist/core/main/SessionManager.d.ts +9 -0
- package/dist/core/main/SessionManager.js +23 -0
- package/dist/core/main/actionHandlerFactory.js +261 -64
- package/dist/core/main/executionContext.d.ts +7 -1
- package/dist/core/main/executionContext.js +93 -3
- package/dist/core/main/runAutomationScript.d.ts +1 -0
- package/dist/core/main/runAutomationScript.js +912 -304
- package/dist/core/main/stepProcessor.js +7 -2
- package/dist/core/types/browserType.d.ts +3 -3
- package/dist/core/types/browserType.js +2 -2
- package/dist/core/types/messageType.d.ts +13 -0
- package/dist/core/types/messageType.js +15 -0
- package/dist/core/types/promptType.d.ts +2 -1
- package/dist/core/types/promptType.js +1 -0
- package/dist/domAnalysis/searchBest.js +15 -16
- package/dist/domAnalysis/simplifyAndFlatten.js +6 -4
- package/dist/fireflinkData/fireflinkLocators/getListOfLocators.d.ts +3 -0
- package/dist/fireflinkData/fireflinkLocators/getListOfLocators.js +23 -15
- package/dist/fireflinkData/fireflinkLocators/parser/getElementsFromHTML.d.ts +3 -3
- package/dist/fireflinkData/fireflinkLocators/parser/getElementsFromHTML.js +134 -118
- package/dist/fireflinkData/fireflinkLocators/types/locator.d.ts +11 -1
- package/dist/fireflinkData/fireflinkLocators/utils/androidSelector.d.ts +1 -1
- package/dist/fireflinkData/fireflinkLocators/utils/androidSelector.js +35 -9
- package/dist/fireflinkData/fireflinkLocators/utils/cssSelector.d.ts +10 -10
- package/dist/fireflinkData/fireflinkLocators/utils/cssSelector.js +56 -59
- package/dist/fireflinkData/fireflinkLocators/utils/iosSelector.d.ts +1 -1
- package/dist/fireflinkData/fireflinkLocators/utils/iosSelector.js +53 -29
- package/dist/fireflinkData/fireflinkLocators/utils/referenceXpath.d.ts +8 -8
- package/dist/fireflinkData/fireflinkLocators/utils/referenceXpath.js +113 -190
- package/dist/fireflinkData/fireflinkLocators/utils/xpath.d.ts +17 -26
- package/dist/fireflinkData/fireflinkLocators/utils/xpath.js +314 -786
- package/dist/fireflinkData/fireflinkLocators/utils/xpathHelpers.d.ts +68 -70
- package/dist/fireflinkData/fireflinkLocators/utils/xpathHelpers.js +498 -517
- package/dist/fireflinkData/fireflinkScript/appendScriptData.d.ts +1 -1
- package/dist/fireflinkData/fireflinkScript/appendScriptData.js +5 -8
- package/dist/fireflinkData/fireflinkScript/scriptGenrationData.d.ts +2 -2
- package/dist/fireflinkData/fireflinkScript/scriptGenrationData.js +17 -27
- package/dist/llmConfig/llmConfiguration.js +13 -6
- package/dist/service/api/fireflinkApi.service.d.ts +1 -1
- package/dist/service/api/fireflinkApi.service.js +1 -2
- package/dist/service/fireflink.service.d.ts +3 -3
- package/dist/service/fireflink.service.js +23 -21
- package/dist/service/kafka/fireflinkKafka.service.d.ts +1 -2
- package/dist/service/kafka/fireflinkKafka.service.js +6 -4
- package/dist/tests/test12.d.ts +0 -1
- package/dist/tests/test12.js +65 -78
- package/dist/tests/testkaf.d.ts +0 -1
- package/dist/tests/testkaf.js +51 -55
- package/dist/utils/InstancesDetails/getInstancesInfo.js +1 -1
- package/dist/utils/browserCap/capability.d.ts +5 -1
- package/dist/utils/browserCap/capability.js +91 -15
- package/dist/utils/helpers/domSanitizer.d.ts +1 -0
- package/dist/utils/helpers/domSanitizer.js +9 -0
- package/dist/utils/helpers/enterActionHelper.js +23 -4
- package/dist/utils/helpers/xpathcreation.js +2 -2
- package/dist/utils/javascript/jsFindElement.d.ts +1 -1
- package/dist/utils/javascript/jsFindElement.js +2 -2
- package/dist/utils/newTab/newTabTracker.js +18 -6
- package/dist/utils/workers/LocatorWorkerInterface.d.ts +22 -0
- package/dist/utils/workers/LocatorWorkerInterface.js +2 -0
- package/dist/utils/workers/LocatorWorkerPool.d.ts +13 -0
- package/dist/utils/workers/LocatorWorkerPool.js +89 -0
- package/dist/utils/workers/locatorWorker.d.ts +1 -0
- package/dist/utils/workers/locatorWorker.js +44 -0
- package/package.json +3 -1
|
@@ -1,39 +1,115 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getCapabilities = getCapabilities;
|
|
4
|
-
function getCapabilities(browserName, path, profile) {
|
|
4
|
+
function getCapabilities(browserName, path, profile, customCaps = []) {
|
|
5
5
|
const isLinux = process.platform === 'linux';
|
|
6
|
+
const caps = customCaps.map(c => c.toLowerCase());
|
|
7
|
+
const hasCap = (name) => caps.includes(name.toLowerCase());
|
|
8
|
+
const getChromiumArgs = (isEdge) => {
|
|
9
|
+
const args = [
|
|
10
|
+
...(path ? [`--user-data-dir=${path}`] : []),
|
|
11
|
+
...(profile ? [`--profile-directory=${profile}`] : []),
|
|
12
|
+
...(isLinux ? ['--ozone-platform=x11'] : []),
|
|
13
|
+
];
|
|
14
|
+
if (hasCap("headless"))
|
|
15
|
+
args.push('--headless=new');
|
|
16
|
+
if (hasCap("incognito"))
|
|
17
|
+
args.push(isEdge ? '--inprivate' : '--incognito');
|
|
18
|
+
if (hasCap("ignoreCertificates"))
|
|
19
|
+
args.push('--ignore-certificate-errors');
|
|
20
|
+
if (hasCap("startMaximized"))
|
|
21
|
+
args.push('--start-maximized');
|
|
22
|
+
if (hasCap("disableGpu"))
|
|
23
|
+
args.push('--disable-gpu');
|
|
24
|
+
if (hasCap("disableNotifications"))
|
|
25
|
+
args.push('--disable-notifications');
|
|
26
|
+
if (hasCap("disableExtensions"))
|
|
27
|
+
args.push('--disable-extensions');
|
|
28
|
+
if (hasCap("disablePopups"))
|
|
29
|
+
args.push('--disable-popup-blocking');
|
|
30
|
+
if (hasCap("muteAudio"))
|
|
31
|
+
args.push('--mute-audio');
|
|
32
|
+
if (hasCap("noSandbox"))
|
|
33
|
+
args.push('--no-sandbox', '--disable-dev-shm-usage');
|
|
34
|
+
if (hasCap("disableWebSecurity"))
|
|
35
|
+
args.push('--disable-web-security');
|
|
36
|
+
if (hasCap("disableLocalNetwork"))
|
|
37
|
+
args.push('--disable-local-network-saving-common-name');
|
|
38
|
+
return args;
|
|
39
|
+
};
|
|
40
|
+
const getChromiumPrefs = () => {
|
|
41
|
+
const prefs = {};
|
|
42
|
+
if (hasCap("geolocation"))
|
|
43
|
+
prefs["profile.default_content_setting_values.geolocation"] = 2;
|
|
44
|
+
if (hasCap("camera"))
|
|
45
|
+
prefs["profile.default_content_setting_values.media_stream_camera"] = 2;
|
|
46
|
+
if (hasCap("microphone"))
|
|
47
|
+
prefs["profile.default_content_setting_values.media_stream_mic"] = 2;
|
|
48
|
+
if (hasCap("popups") || hasCap("disablePopups"))
|
|
49
|
+
prefs["profile.default_content_settings.popups"] = 0;
|
|
50
|
+
if (hasCap("passwordManager")) {
|
|
51
|
+
prefs["credentials_enable_service"] = false;
|
|
52
|
+
prefs["profile.password_manager_enabled"] = false;
|
|
53
|
+
}
|
|
54
|
+
return prefs;
|
|
55
|
+
};
|
|
56
|
+
const getFirefoxOptions = () => {
|
|
57
|
+
const args = [
|
|
58
|
+
...(path ? ['-profile', path] : []),
|
|
59
|
+
...(isLinux ? ['--ozone-platform=x11'] : []),
|
|
60
|
+
];
|
|
61
|
+
if (hasCap("headless"))
|
|
62
|
+
args.push('-headless');
|
|
63
|
+
if (hasCap("incognito"))
|
|
64
|
+
args.push('-private');
|
|
65
|
+
if (hasCap("disableGpu"))
|
|
66
|
+
args.push('--disable-gpu');
|
|
67
|
+
if (hasCap("disableExtensions"))
|
|
68
|
+
args.push('-safe-mode');
|
|
69
|
+
const prefs = {};
|
|
70
|
+
if (hasCap("disableNotifications") || hasCap("notifications"))
|
|
71
|
+
prefs["dom.webnotifications.enabled"] = false;
|
|
72
|
+
if (hasCap("disablePopups") || hasCap("popups"))
|
|
73
|
+
prefs["dom.disable_open_during_load"] = false;
|
|
74
|
+
if (hasCap("muteAudio"))
|
|
75
|
+
prefs["media.volume_scale"] = "0.0";
|
|
76
|
+
if (hasCap("disableWebSecurity"))
|
|
77
|
+
prefs["security.fileuri.strict_origin_policy"] = false;
|
|
78
|
+
if (hasCap("geolocation"))
|
|
79
|
+
prefs["geo.enabled"] = false;
|
|
80
|
+
if (hasCap("camera"))
|
|
81
|
+
prefs["media.navigator.permission.disabled"] = true;
|
|
82
|
+
if (hasCap("microphone"))
|
|
83
|
+
prefs["media.peerconnection.enabled"] = false;
|
|
84
|
+
if (hasCap("passwordManager"))
|
|
85
|
+
prefs["signon.rememberSignons"] = false;
|
|
86
|
+
return { args, prefs };
|
|
87
|
+
};
|
|
88
|
+
const firefoxOptions = getFirefoxOptions();
|
|
6
89
|
const browserCapabilities = {
|
|
7
90
|
chrome: {
|
|
8
91
|
browserName: 'chrome',
|
|
9
92
|
"wdio:enforceWebDriverClassic": true,
|
|
10
93
|
"goog:chromeOptions": {
|
|
11
|
-
args:
|
|
12
|
-
|
|
13
|
-
...(profile ? [`--profile-directory=${profile}`] : []),
|
|
14
|
-
...(isLinux ? ['--ozone-platform=x11'] : []),
|
|
15
|
-
],
|
|
94
|
+
args: getChromiumArgs(false),
|
|
95
|
+
prefs: getChromiumPrefs()
|
|
16
96
|
},
|
|
17
97
|
},
|
|
18
98
|
firefox: {
|
|
19
99
|
browserName: 'firefox',
|
|
20
100
|
"wdio:enforceWebDriverClassic": true,
|
|
101
|
+
acceptInsecureCerts: hasCap("ignoreCertificates"),
|
|
21
102
|
"moz:firefoxOptions": {
|
|
22
|
-
args:
|
|
23
|
-
|
|
24
|
-
...(isLinux ? ['--ozone-platform=x11'] : []),
|
|
25
|
-
],
|
|
103
|
+
args: firefoxOptions.args,
|
|
104
|
+
prefs: firefoxOptions.prefs
|
|
26
105
|
},
|
|
27
106
|
},
|
|
28
107
|
edge: {
|
|
29
108
|
browserName: 'MicrosoftEdge',
|
|
30
109
|
"wdio:enforceWebDriverClassic": true,
|
|
31
110
|
"ms:edgeOptions": {
|
|
32
|
-
args:
|
|
33
|
-
|
|
34
|
-
...(profile ? [`--profile-directory=${profile}`] : []),
|
|
35
|
-
...(isLinux ? ['--ozone-platform=x11'] : []),
|
|
36
|
-
],
|
|
111
|
+
args: getChromiumArgs(true),
|
|
112
|
+
prefs: getChromiumPrefs()
|
|
37
113
|
},
|
|
38
114
|
},
|
|
39
115
|
safari: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sanitizeWebDom: (domString: string) => string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanitizeWebDom = void 0;
|
|
4
|
+
const sanitizeWebDom = (domString) => {
|
|
5
|
+
return (domString || "")
|
|
6
|
+
.replace(/<noscript[\s\S]*?<\/noscript>/gi, "")
|
|
7
|
+
.replace(/<noscript[^>]*>/gi, "");
|
|
8
|
+
};
|
|
9
|
+
exports.sanitizeWebDom = sanitizeWebDom;
|
|
@@ -8,13 +8,14 @@ exports.enterActionHelperMob = enterActionHelperMob;
|
|
|
8
8
|
const jsdom_1 = require("jsdom");
|
|
9
9
|
const xmldom_1 = require("@xmldom/xmldom");
|
|
10
10
|
const xpathcreation_js_1 = require("./xpathcreation.js");
|
|
11
|
+
const domSanitizer_js_1 = require("./domSanitizer.js");
|
|
11
12
|
const logData_js_1 = require("../logger/logData.js");
|
|
12
13
|
const xpath_1 = __importDefault(require("xpath"));
|
|
13
14
|
const enterActionHelper = (pageDOM, ffNumber) => {
|
|
14
15
|
// Extract numeric part
|
|
15
16
|
const match = ffNumber.match(/\d+$/);
|
|
16
17
|
let numericFF = match ? parseInt(match[0], 10) : 0;
|
|
17
|
-
const parser = new jsdom_1.JSDOM(pageDOM);
|
|
18
|
+
const parser = new jsdom_1.JSDOM((0, domSanitizer_js_1.sanitizeWebDom)(pageDOM));
|
|
18
19
|
const doc = parser.window.document;
|
|
19
20
|
const isEditable = (el) => {
|
|
20
21
|
if (!el)
|
|
@@ -24,11 +25,21 @@ const enterActionHelper = (pageDOM, ffNumber) => {
|
|
|
24
25
|
el.getAttribute("contenteditable") === "true");
|
|
25
26
|
};
|
|
26
27
|
const getXPath = (el) => {
|
|
27
|
-
|
|
28
|
+
if (!el)
|
|
29
|
+
return "";
|
|
30
|
+
const directXpath = el.getAttribute("ff-xpath");
|
|
31
|
+
if (directXpath)
|
|
32
|
+
return directXpath;
|
|
33
|
+
const ffAttr = el.getAttribute("ff-inspect");
|
|
34
|
+
if (ffAttr) {
|
|
35
|
+
return (0, xpathcreation_js_1.getWebXPathByFFIndex)(pageDOM, ffAttr).xpathPath || "";
|
|
36
|
+
}
|
|
37
|
+
return "";
|
|
28
38
|
};
|
|
29
39
|
const element = doc.querySelector(`[ff-inspect="Fire-Flink-${numericFF}"]`);
|
|
30
40
|
if (!element) {
|
|
31
|
-
|
|
41
|
+
const fallbackResult = (0, xpathcreation_js_1.getWebXPathByFFIndex)(pageDOM, `Fire-Flink-${numericFF}`);
|
|
42
|
+
return { ffNumber: numericFF, xpathPath: fallbackResult.xpathPath || "" };
|
|
32
43
|
}
|
|
33
44
|
let xpathPath = "";
|
|
34
45
|
// Current element editable
|
|
@@ -46,6 +57,11 @@ const enterActionHelper = (pageDOM, ffNumber) => {
|
|
|
46
57
|
}
|
|
47
58
|
if (editableChild) {
|
|
48
59
|
xpathPath = getXPath(editableChild);
|
|
60
|
+
const childFF = editableChild.getAttribute("ff-inspect");
|
|
61
|
+
const match = childFF?.match(/\d+$/);
|
|
62
|
+
if (match) {
|
|
63
|
+
numericFF = parseInt(match[0], 10);
|
|
64
|
+
}
|
|
49
65
|
}
|
|
50
66
|
else {
|
|
51
67
|
// Try next 10 FF elements
|
|
@@ -64,7 +80,10 @@ const enterActionHelper = (pageDOM, ffNumber) => {
|
|
|
64
80
|
}
|
|
65
81
|
}
|
|
66
82
|
}
|
|
67
|
-
|
|
83
|
+
if (!xpathPath) {
|
|
84
|
+
const fallbackResult = (0, xpathcreation_js_1.getWebXPathByFFIndex)(pageDOM, `Fire-Flink-${numericFF}`);
|
|
85
|
+
xpathPath = fallbackResult.xpathPath || "";
|
|
86
|
+
}
|
|
68
87
|
return {
|
|
69
88
|
ffNumber: numericFF,
|
|
70
89
|
xpathPath
|
|
@@ -64,7 +64,7 @@ function getFFIndexAndXPath(annotatedDom, fireflinkIndex, platform) {
|
|
|
64
64
|
return { xpathPath: "" };
|
|
65
65
|
}
|
|
66
66
|
const ffInspector = element.getAttribute("FF-inspecter") || "";
|
|
67
|
-
function
|
|
67
|
+
function buildAiXPath(node) {
|
|
68
68
|
if (node.nodeType !== 1)
|
|
69
69
|
return "";
|
|
70
70
|
const parts = [];
|
|
@@ -83,7 +83,7 @@ function getFFIndexAndXPath(annotatedDom, fireflinkIndex, platform) {
|
|
|
83
83
|
}
|
|
84
84
|
return "/" + parts.join("/");
|
|
85
85
|
}
|
|
86
|
-
let xpathPath =
|
|
86
|
+
let xpathPath = buildAiXPath(element);
|
|
87
87
|
xpathPath = xpathPath.replace("/AppiumAUT[1]", "");
|
|
88
88
|
if (isBounds) {
|
|
89
89
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const Find_element_by_ff_js_script = "\n const target = arguments[0];\n const callback = arguments[arguments.length - 1];\n\n const MAX_WAIT = 2000;\n const INTERVAL = 50;\n const start = Date.now();\n\n // ---------------- FINDER ----------------\n function findByFfInspect(root) {\n const visited = new Set();\n const stack = [{ node: root, frames: [], currentDoc: root }];\n\n while (stack.length > 0) {\n const { node, frames, currentDoc } = stack.pop();\n if (!node) continue;\n\n if (\n node.nodeType === Node.DOCUMENT_NODE ||\n node.nodeType === Node.DOCUMENT_FRAGMENT_NODE\n ) {\n if (visited.has(node)) continue;\n visited.add(node);\n }\n\n if (node.nodeType === Node.ELEMENT_NODE) {\n\n if (node.getAttribute && node.getAttribute(\"ff-inspect\") === target) {\n return { node, frames, iframe_dom: currentDoc };\n }\n\n // Shadow DOM\n if (node.shadowRoot) {\n stack.push({\n node: node.shadowRoot,\n frames,\n currentDoc\n });\n }\n\n // Iframe traversal\n if (node.tagName === \"IFRAME\") {\n try {\n const doc = node.contentDocument || node.contentWindow?.document;\n if (doc) {\n const meta = {\n id: node.id || null,\n name: node.getAttribute(\"name\") || null,\n title: node.getAttribute(\"title\") || null,\n aria: node.getAttribute(\"aria-label\") || null,\n src: node.getAttribute(\"src\") || null,\n };\n\n stack.push({\n node: doc,\n frames: [...frames, meta],\n currentDoc: doc\n });\n }\n } catch (e) {}\n }\n\n const kids = node.children || node.childNodes;\n for (let i = kids.length - 1; i >= 0; i--) {\n stack.push({ node: kids[i], frames, currentDoc });\n }\n }\n\n else if (\n node.nodeType === Node.DOCUMENT_NODE ||\n node.nodeType === Node.DOCUMENT_FRAGMENT_NODE\n ) {\n const kids = node.childNodes;\n for (let i = kids.length - 1; i >= 0; i--) {\n stack.push({ node: kids[i], frames, currentDoc });\n }\n }\n }\n\n return null;\n }\n\n // ---------------- CLICKABILITY ----------------\n function isClickable(el) {\n if (!el || el.nodeType !== Node.ELEMENT_NODE) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === \"button\" || tag === \"a\") return true;\n if (el.getAttribute(\"role\") === \"button\") return true;\n if (tag === \"input\") {\n const t = (el.getAttribute(\"type\") || \"\").toLowerCase();\n return t === \"button\" || t === \"submit\";\n }\n return false;\n }\n\n function findClickableInside(root) {\n const queue = [root];\n while (queue.length) {\n const node = queue.shift();\n if (isClickable(node)) return node;\n const kids = node.children || node.childNodes;\n for (let k of kids) queue.push(k);\n }\n return null;\n }\n\n // ---------------- XPATH BUILDER ----------------\n function
|
|
1
|
+
export declare const Find_element_by_ff_js_script = "\n const target = arguments[0];\n const callback = arguments[arguments.length - 1];\n\n const MAX_WAIT = 2000;\n const INTERVAL = 50;\n const start = Date.now();\n\n // ---------------- FINDER ----------------\n function findByFfInspect(root) {\n const visited = new Set();\n const stack = [{ node: root, frames: [], currentDoc: root }];\n\n while (stack.length > 0) {\n const { node, frames, currentDoc } = stack.pop();\n if (!node) continue;\n\n if (\n node.nodeType === Node.DOCUMENT_NODE ||\n node.nodeType === Node.DOCUMENT_FRAGMENT_NODE\n ) {\n if (visited.has(node)) continue;\n visited.add(node);\n }\n\n if (node.nodeType === Node.ELEMENT_NODE) {\n\n if (node.getAttribute && node.getAttribute(\"ff-inspect\") === target) {\n return { node, frames, iframe_dom: currentDoc };\n }\n\n // Shadow DOM\n if (node.shadowRoot) {\n stack.push({\n node: node.shadowRoot,\n frames,\n currentDoc\n });\n }\n\n // Iframe traversal\n if (node.tagName === \"IFRAME\") {\n try {\n const doc = node.contentDocument || node.contentWindow?.document;\n if (doc) {\n const meta = {\n id: node.id || null,\n name: node.getAttribute(\"name\") || null,\n title: node.getAttribute(\"title\") || null,\n aria: node.getAttribute(\"aria-label\") || null,\n src: node.getAttribute(\"src\") || null,\n };\n\n stack.push({\n node: doc,\n frames: [...frames, meta],\n currentDoc: doc\n });\n }\n } catch (e) {}\n }\n\n const kids = node.children || node.childNodes;\n for (let i = kids.length - 1; i >= 0; i--) {\n stack.push({ node: kids[i], frames, currentDoc });\n }\n }\n\n else if (\n node.nodeType === Node.DOCUMENT_NODE ||\n node.nodeType === Node.DOCUMENT_FRAGMENT_NODE\n ) {\n const kids = node.childNodes;\n for (let i = kids.length - 1; i >= 0; i--) {\n stack.push({ node: kids[i], frames, currentDoc });\n }\n }\n }\n\n return null;\n }\n\n // ---------------- CLICKABILITY ----------------\n function isClickable(el) {\n if (!el || el.nodeType !== Node.ELEMENT_NODE) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === \"button\" || tag === \"a\") return true;\n if (el.getAttribute(\"role\") === \"button\") return true;\n if (tag === \"input\") {\n const t = (el.getAttribute(\"type\") || \"\").toLowerCase();\n return t === \"button\" || t === \"submit\";\n }\n return false;\n }\n\n function findClickableInside(root) {\n const queue = [root];\n while (queue.length) {\n const node = queue.shift();\n if (isClickable(node)) return node;\n const kids = node.children || node.childNodes;\n for (let k of kids) queue.push(k);\n }\n return null;\n }\n\n // ---------------- XPATH BUILDER ----------------\n function buildAiXPath(el) {\n if (!el) return null;\n\n if (el.id) return \"//*[@id=\\\"\" + el.id + \"\\\"]\";\n\n const name = el.getAttribute(\"name\");\n if (name) return \"//*[@name=\\\"\" + name + \"\\\"]\";\n\n const text = el.textContent && el.textContent.trim();\n if (text && text.length < 50) {\n const escaped = text.replace(/\"/g, '\\\"');\n const xp = \"//*[text()=\\\"\" + escaped + \"\\\"]\";\n try {\n const n = document.evaluate(xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);\n if (n.singleNodeValue === el) return xp;\n } catch {}\n }\n\n const parts = [];\n let node = el;\n\n while (node && node.nodeType === Node.ELEMENT_NODE) {\n let idx = 1;\n let sib = node.previousSibling;\n while (sib) {\n if (sib.nodeType === Node.ELEMENT_NODE && sib.nodeName === node.nodeName) idx++;\n sib = sib.previousSibling;\n }\n parts.unshift(node.nodeName.toLowerCase() + \"[\" + idx + \"]\");\n node = node.parentNode instanceof ShadowRoot ? node.parentNode.host : node.parentNode;\n }\n\n return \"/\" + parts.join(\"/\");\n }\n\n // ---------------- WAIT LOOP ----------------\n (function attempt() {\n const found = findByFfInspect(document);\n\n if (!found) {\n if (Date.now() - start > MAX_WAIT) {\n return callback({ success: false, reason: \"timeout\" });\n }\n return setTimeout(attempt, INTERVAL);\n }\n\n let targetNode = found.node;\n if (!isClickable(targetNode)) {\n const inside = findClickableInside(targetNode);\n if (inside) targetNode = inside;\n }\n\n const xpath_if = buildAiXPath(targetNode);\n\n return callback({\n success: true,\n xpath_if,\n frames: found.frames,\n iframe_dom_html: found.iframe_dom?.documentElement?.outerHTML || null\n });\n })();\n";
|
|
@@ -108,7 +108,7 @@ exports.Find_element_by_ff_js_script = `
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// ---------------- XPATH BUILDER ----------------
|
|
111
|
-
function
|
|
111
|
+
function buildAiXPath(el) {
|
|
112
112
|
if (!el) return null;
|
|
113
113
|
|
|
114
114
|
if (el.id) return "//*[@id=\\"" + el.id + "\\"]";
|
|
@@ -160,7 +160,7 @@ exports.Find_element_by_ff_js_script = `
|
|
|
160
160
|
if (inside) targetNode = inside;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
const xpath_if =
|
|
163
|
+
const xpath_if = buildAiXPath(targetNode);
|
|
164
164
|
|
|
165
165
|
return callback({
|
|
166
166
|
success: true,
|
|
@@ -4,6 +4,9 @@ exports.newTabTracker = newTabTracker;
|
|
|
4
4
|
async function newTabTracker(driver, stepAction, previousHandles, scriptAppender) {
|
|
5
5
|
if (stepAction === "switch")
|
|
6
6
|
return;
|
|
7
|
+
if (!previousHandles || previousHandles.length === 0) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
7
10
|
const currentHandle = await driver.getWindowHandle();
|
|
8
11
|
const currentHandles = await driver.getWindowHandles();
|
|
9
12
|
if (currentHandles.length <= previousHandles.length) {
|
|
@@ -22,12 +25,21 @@ async function newTabTracker(driver, stepAction, previousHandles, scriptAppender
|
|
|
22
25
|
await driver.switchToWindow(currentHandle);
|
|
23
26
|
continue;
|
|
24
27
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const title = await driver.getTitle();
|
|
29
|
+
if (title && title.trim()) {
|
|
30
|
+
scriptAppender.add(async () => ({
|
|
31
|
+
nlpName: "SwitchToWindowIfTitleContainsString",
|
|
32
|
+
stepInputs: [title],
|
|
33
|
+
elementsData: []
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
scriptAppender.add(async () => ({
|
|
38
|
+
nlpName: "SwitchToWindowBasedOnURL",
|
|
39
|
+
stepInputs: [url],
|
|
40
|
+
elementsData: []
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
31
43
|
return;
|
|
32
44
|
}
|
|
33
45
|
catch (error) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Worker } from "worker_threads";
|
|
2
|
+
import { platform } from "../../core/types/scriptTypesType.js";
|
|
3
|
+
export interface ILocatorWorker {
|
|
4
|
+
id: number;
|
|
5
|
+
html: string;
|
|
6
|
+
attribute: string;
|
|
7
|
+
platform: platform;
|
|
8
|
+
sessionPath: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PendingRequest {
|
|
11
|
+
resolve: (value: any) => void;
|
|
12
|
+
reject: (reason?: any) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkerInfo {
|
|
15
|
+
worker: Worker;
|
|
16
|
+
busy: boolean;
|
|
17
|
+
pending: Map<number, PendingRequest>;
|
|
18
|
+
}
|
|
19
|
+
export interface WorkerTask extends ILocatorWorker {
|
|
20
|
+
resolve: (value: any) => void;
|
|
21
|
+
reject: (reason?: any) => void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { platform } from "../../core/types/scriptTypesType.js";
|
|
2
|
+
export declare class LocatorWorkerPool {
|
|
3
|
+
private readonly sessionPath;
|
|
4
|
+
private workers;
|
|
5
|
+
private queue;
|
|
6
|
+
private requestId;
|
|
7
|
+
private readonly workerCount;
|
|
8
|
+
constructor(sessionPath: string);
|
|
9
|
+
getFireFlinkElement(html: string, attribute: string, platform: platform): Promise<any>;
|
|
10
|
+
private onWorkerMessage;
|
|
11
|
+
private dispatchTask;
|
|
12
|
+
dispose(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LocatorWorkerPool = void 0;
|
|
7
|
+
const worker_threads_1 = require("worker_threads");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
class LocatorWorkerPool {
|
|
10
|
+
constructor(sessionPath) {
|
|
11
|
+
this.sessionPath = sessionPath;
|
|
12
|
+
this.workers = [];
|
|
13
|
+
this.queue = [];
|
|
14
|
+
this.requestId = 0;
|
|
15
|
+
this.workerCount = 5;
|
|
16
|
+
const workerPath = path_1.default.join(__dirname, "locatorWorker.js");
|
|
17
|
+
for (let i = 0; i < this.workerCount; i++) {
|
|
18
|
+
const worker = new worker_threads_1.Worker(workerPath);
|
|
19
|
+
const workerInfo = {
|
|
20
|
+
worker,
|
|
21
|
+
busy: false,
|
|
22
|
+
pending: new Map()
|
|
23
|
+
};
|
|
24
|
+
worker.on("message", (response) => {
|
|
25
|
+
this.onWorkerMessage(workerInfo, response);
|
|
26
|
+
});
|
|
27
|
+
worker.on("error", (error) => {
|
|
28
|
+
console.error("Worker Error:", error);
|
|
29
|
+
});
|
|
30
|
+
this.workers.push(workerInfo);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async getFireFlinkElement(html, attribute, platform) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const task = {
|
|
36
|
+
id: this.requestId++,
|
|
37
|
+
html,
|
|
38
|
+
attribute,
|
|
39
|
+
platform,
|
|
40
|
+
sessionPath: this.sessionPath,
|
|
41
|
+
resolve,
|
|
42
|
+
reject
|
|
43
|
+
};
|
|
44
|
+
const freeWorker = this.workers.find(w => !w.busy);
|
|
45
|
+
if (freeWorker) {
|
|
46
|
+
this.dispatchTask(freeWorker, task);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
this.queue.push(task);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
onWorkerMessage(workerInfo, response) {
|
|
54
|
+
const pending = workerInfo.pending.get(response.id);
|
|
55
|
+
if (!pending) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
workerInfo.pending.delete(response.id);
|
|
59
|
+
if (response.success) {
|
|
60
|
+
pending.resolve(response.result);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
pending.reject(new Error(response.error));
|
|
64
|
+
}
|
|
65
|
+
workerInfo.busy = false;
|
|
66
|
+
const nextTask = this.queue.shift();
|
|
67
|
+
if (nextTask) {
|
|
68
|
+
this.dispatchTask(workerInfo, nextTask);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
dispatchTask(workerInfo, task) {
|
|
72
|
+
workerInfo.busy = true;
|
|
73
|
+
workerInfo.pending.set(task.id, {
|
|
74
|
+
resolve: task.resolve,
|
|
75
|
+
reject: task.reject
|
|
76
|
+
});
|
|
77
|
+
workerInfo.worker.postMessage({
|
|
78
|
+
id: task.id,
|
|
79
|
+
html: task.html,
|
|
80
|
+
attribute: task.attribute,
|
|
81
|
+
platform: task.platform,
|
|
82
|
+
sessionPath: this.sessionPath,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async dispose() {
|
|
86
|
+
await Promise.all(this.workers.map(workerInfo => workerInfo.worker.terminate()));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.LocatorWorkerPool = LocatorWorkerPool;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const worker_threads_1 = require("worker_threads");
|
|
4
|
+
const getElementsFromHTML_js_1 = require("../../fireflinkData/fireflinkLocators/parser/getElementsFromHTML.js");
|
|
5
|
+
const logData_js_1 = require("../logger/logData.js");
|
|
6
|
+
if (!worker_threads_1.parentPort) {
|
|
7
|
+
throw new Error("locatorWorker must be started as a Worker Thread.");
|
|
8
|
+
}
|
|
9
|
+
const port = worker_threads_1.parentPort;
|
|
10
|
+
let loggerInitialized = false;
|
|
11
|
+
port.on("message", (data) => {
|
|
12
|
+
const { id, html, attribute, platform, sessionPath } = data;
|
|
13
|
+
try {
|
|
14
|
+
if (!loggerInitialized) {
|
|
15
|
+
(0, logData_js_1.initLogger)(sessionPath);
|
|
16
|
+
loggerInitialized = true;
|
|
17
|
+
}
|
|
18
|
+
const formattedAttribute = attribute.replace("[", "//*[@");
|
|
19
|
+
const result = (0, getElementsFromHTML_js_1.getAiElementsFromHTML)({
|
|
20
|
+
locators: [
|
|
21
|
+
{
|
|
22
|
+
name: "xpath",
|
|
23
|
+
type: "Static",
|
|
24
|
+
value: formattedAttribute,
|
|
25
|
+
reference: "local",
|
|
26
|
+
status: "NOT_USED",
|
|
27
|
+
isRecorded: "N",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}, html, platform);
|
|
31
|
+
port.postMessage({
|
|
32
|
+
id,
|
|
33
|
+
success: true,
|
|
34
|
+
result,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
port.postMessage({
|
|
39
|
+
id,
|
|
40
|
+
success: false,
|
|
41
|
+
error: error.message,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ff-automationv2",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.30",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"description": "This lib is used to automate the manual testcase",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/js": "10.0.1",
|
|
38
38
|
"@types/adm-zip": "^0.5.8",
|
|
39
|
+
"@types/express": "^5.0.6",
|
|
39
40
|
"@types/fs-extra": "^11.0.4",
|
|
40
41
|
"@types/js-beautify": "^1.14.3",
|
|
41
42
|
"@types/jsdom": "27.0.0",
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
"ai": "^6.0.209",
|
|
72
73
|
"axios": "^1.13.5",
|
|
73
74
|
"cheerio": "1.2.0",
|
|
75
|
+
"express": "^5.2.1",
|
|
74
76
|
"fast-fuzzy": "^1.12.0",
|
|
75
77
|
"fastest-levenshtein": "^1.0.16",
|
|
76
78
|
"google-auth": "^0.0.1",
|