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.
Files changed (113) hide show
  1. package/dist/ai/llmcalls/llmAction.js +49 -13
  2. package/dist/ai/llmcalls/parseLlmOputput.js +5 -5
  3. package/dist/ai/llmprompts/promptRegistry.js +4 -0
  4. package/dist/ai/llmprompts/systemPrompts/actionExtractorPrompt.js +39 -11
  5. package/dist/ai/llmprompts/systemPrompts/fireflinkElementIndexExtactors.js +6 -6
  6. package/dist/ai/llmprompts/systemPrompts/fireflinkElementIndexExtractor_Mob.js +32 -0
  7. package/dist/ai/llmprompts/systemPrompts/mobileKeywordExtractor.js +17 -8
  8. package/dist/ai/llmprompts/systemPrompts/otpExtractorPrompt.d.ts +1 -0
  9. package/dist/ai/llmprompts/systemPrompts/otpExtractorPrompt.js +22 -0
  10. package/dist/ai/llmprompts/systemPrompts/verifyActionExtractorPrompt.js +3 -1
  11. package/dist/ai/llmprompts/systemPrompts/visionPrompt.js +14 -6
  12. package/dist/ai/llmprompts/systemPrompts/visionPromptMobile.js +3 -0
  13. package/dist/ai/llmprompts/userPrompts/userPrompt.js +1 -0
  14. package/dist/automation/actions/executor.d.ts +4 -0
  15. package/dist/automation/actions/executor.js +20 -0
  16. package/dist/automation/actions/interaction/clear/clear.js +1 -1
  17. package/dist/automation/actions/interaction/clear/clearAndEnter.js +1 -1
  18. package/dist/automation/actions/interaction/click/click.js +10 -3
  19. package/dist/automation/actions/interaction/click/clickNtimes.js +1 -1
  20. package/dist/automation/actions/interaction/click/doubleClick.js +1 -1
  21. package/dist/automation/actions/interaction/click/rightClick.js +1 -1
  22. package/dist/automation/actions/interaction/elementlessActions/getOtp.d.ts +2 -0
  23. package/dist/automation/actions/interaction/elementlessActions/getOtp.js +101 -0
  24. package/dist/automation/actions/interaction/enterActions/enterInput.js +11 -6
  25. package/dist/automation/actions/interaction/enterActions/enterInputAndPress.js +2 -2
  26. package/dist/automation/actions/interaction/enterActions/enterusingJs.js +2 -2
  27. package/dist/automation/actions/interaction/enterActions/waitAndEnter.js +1 -1
  28. package/dist/automation/actions/interaction/upload/uploadfile.js +1 -1
  29. package/dist/automation/actions/interaction/verify/VerifyNavigateURL.js +38 -11
  30. package/dist/automation/actions/interface/FireFlinkResponseSchema.d.ts +12 -0
  31. package/dist/automation/actions/interface/FireFlinkResponseSchema.js +19 -3
  32. package/dist/automation/actions/interface/getOtpInterface.d.ts +15 -0
  33. package/dist/automation/actions/interface/getOtpInterface.js +2 -0
  34. package/dist/automation/browserSession/initiateBrowserSession.d.ts +1 -1
  35. package/dist/automation/browserSession/initiateBrowserSession.js +15 -7
  36. package/dist/automation/driverState.d.ts +2 -0
  37. package/dist/automation/driverState.js +4 -0
  38. package/dist/automation/mobileSession/initiateMobileSession.d.ts +4 -5
  39. package/dist/automation/mobileSession/initiateMobileSession.js +83 -19
  40. package/dist/core/constants/allAction.js +5 -1
  41. package/dist/core/constants/supportedActions.js +5 -2
  42. package/dist/core/interfaces/actionInterface.d.ts +4 -0
  43. package/dist/core/interfaces/browserConfigurationInterface.d.ts +0 -4
  44. package/dist/core/interfaces/browserConfigurationInterface.js +0 -1
  45. package/dist/core/interfaces/executionDetails.d.ts +6 -3
  46. package/dist/core/interfaces/fireflinkScriptPayloadInterface.d.ts +22 -16
  47. package/dist/core/interfaces/llmResponseInterface.d.ts +1 -0
  48. package/dist/core/interfaces/promptInterface.d.ts +1 -0
  49. package/dist/core/main/SessionManager.d.ts +9 -0
  50. package/dist/core/main/SessionManager.js +23 -0
  51. package/dist/core/main/actionHandlerFactory.js +261 -64
  52. package/dist/core/main/executionContext.d.ts +7 -1
  53. package/dist/core/main/executionContext.js +93 -3
  54. package/dist/core/main/runAutomationScript.d.ts +1 -0
  55. package/dist/core/main/runAutomationScript.js +912 -304
  56. package/dist/core/main/stepProcessor.js +7 -2
  57. package/dist/core/types/browserType.d.ts +3 -3
  58. package/dist/core/types/browserType.js +2 -2
  59. package/dist/core/types/messageType.d.ts +13 -0
  60. package/dist/core/types/messageType.js +15 -0
  61. package/dist/core/types/promptType.d.ts +2 -1
  62. package/dist/core/types/promptType.js +1 -0
  63. package/dist/domAnalysis/searchBest.js +15 -16
  64. package/dist/domAnalysis/simplifyAndFlatten.js +6 -4
  65. package/dist/fireflinkData/fireflinkLocators/getListOfLocators.d.ts +3 -0
  66. package/dist/fireflinkData/fireflinkLocators/getListOfLocators.js +23 -15
  67. package/dist/fireflinkData/fireflinkLocators/parser/getElementsFromHTML.d.ts +3 -3
  68. package/dist/fireflinkData/fireflinkLocators/parser/getElementsFromHTML.js +134 -118
  69. package/dist/fireflinkData/fireflinkLocators/types/locator.d.ts +11 -1
  70. package/dist/fireflinkData/fireflinkLocators/utils/androidSelector.d.ts +1 -1
  71. package/dist/fireflinkData/fireflinkLocators/utils/androidSelector.js +35 -9
  72. package/dist/fireflinkData/fireflinkLocators/utils/cssSelector.d.ts +10 -10
  73. package/dist/fireflinkData/fireflinkLocators/utils/cssSelector.js +56 -59
  74. package/dist/fireflinkData/fireflinkLocators/utils/iosSelector.d.ts +1 -1
  75. package/dist/fireflinkData/fireflinkLocators/utils/iosSelector.js +53 -29
  76. package/dist/fireflinkData/fireflinkLocators/utils/referenceXpath.d.ts +8 -8
  77. package/dist/fireflinkData/fireflinkLocators/utils/referenceXpath.js +113 -190
  78. package/dist/fireflinkData/fireflinkLocators/utils/xpath.d.ts +17 -26
  79. package/dist/fireflinkData/fireflinkLocators/utils/xpath.js +314 -786
  80. package/dist/fireflinkData/fireflinkLocators/utils/xpathHelpers.d.ts +68 -70
  81. package/dist/fireflinkData/fireflinkLocators/utils/xpathHelpers.js +498 -517
  82. package/dist/fireflinkData/fireflinkScript/appendScriptData.d.ts +1 -1
  83. package/dist/fireflinkData/fireflinkScript/appendScriptData.js +5 -8
  84. package/dist/fireflinkData/fireflinkScript/scriptGenrationData.d.ts +2 -2
  85. package/dist/fireflinkData/fireflinkScript/scriptGenrationData.js +17 -27
  86. package/dist/llmConfig/llmConfiguration.js +13 -6
  87. package/dist/service/api/fireflinkApi.service.d.ts +1 -1
  88. package/dist/service/api/fireflinkApi.service.js +1 -2
  89. package/dist/service/fireflink.service.d.ts +3 -3
  90. package/dist/service/fireflink.service.js +23 -21
  91. package/dist/service/kafka/fireflinkKafka.service.d.ts +1 -2
  92. package/dist/service/kafka/fireflinkKafka.service.js +6 -4
  93. package/dist/tests/test12.d.ts +0 -1
  94. package/dist/tests/test12.js +65 -78
  95. package/dist/tests/testkaf.d.ts +0 -1
  96. package/dist/tests/testkaf.js +51 -55
  97. package/dist/utils/InstancesDetails/getInstancesInfo.js +1 -1
  98. package/dist/utils/browserCap/capability.d.ts +5 -1
  99. package/dist/utils/browserCap/capability.js +91 -15
  100. package/dist/utils/helpers/domSanitizer.d.ts +1 -0
  101. package/dist/utils/helpers/domSanitizer.js +9 -0
  102. package/dist/utils/helpers/enterActionHelper.js +23 -4
  103. package/dist/utils/helpers/xpathcreation.js +2 -2
  104. package/dist/utils/javascript/jsFindElement.d.ts +1 -1
  105. package/dist/utils/javascript/jsFindElement.js +2 -2
  106. package/dist/utils/newTab/newTabTracker.js +18 -6
  107. package/dist/utils/workers/LocatorWorkerInterface.d.ts +22 -0
  108. package/dist/utils/workers/LocatorWorkerInterface.js +2 -0
  109. package/dist/utils/workers/LocatorWorkerPool.d.ts +13 -0
  110. package/dist/utils/workers/LocatorWorkerPool.js +89 -0
  111. package/dist/utils/workers/locatorWorker.d.ts +1 -0
  112. package/dist/utils/workers/locatorWorker.js +44 -0
  113. 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
- ...(path ? [`--user-data-dir=${path}`] : []),
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
- ...(path ? ['-profile', path] : []),
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
- ...(path ? [`--user-data-dir=${path}`] : []),
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
- return el?.getAttribute("ff-xpath") || "";
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
- return { ffNumber: numericFF, xpathPath: "" };
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
- // console.log("xpathPath", xpathPath);
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 buildXPath(node) {
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 = buildXPath(element);
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 buildXPath(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 = buildXPath(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";
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 buildXPath(el) {
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 = buildXPath(targetNode);
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
- // Valid URL → accept it
26
- scriptAppender.add(async () => ({
27
- nlpName: "SwitchToWindowBasedOnURL",
28
- stepInputs: [url],
29
- elementsData: []
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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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.29",
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",