ff-automationv2 2.2.33 → 2.2.34-beta.1

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 (55) hide show
  1. package/dist/ai/llmprompts/llmPromptTypes/systemPromptTypes.d.ts +2 -0
  2. package/dist/ai/llmprompts/promptRegistry.js +4 -0
  3. package/dist/ai/llmprompts/systemPrompts/multichannelKeywordExtractor.d.ts +6 -0
  4. package/dist/ai/llmprompts/systemPrompts/multichannelKeywordExtractor.js +86 -0
  5. package/dist/ai/llmprompts/userPrompts/userPrompt.js +1 -0
  6. package/dist/automation/actions/interaction/clear/clear.js +4 -1
  7. package/dist/automation/actions/interaction/clear/clearAndEnter.js +4 -1
  8. package/dist/automation/actions/interaction/elementlessActions/getOtp.js +2 -2
  9. package/dist/automation/actions/interaction/enterActions/EnterInputIntoElementFromClipBoardInterface_mob.js +4 -1
  10. package/dist/automation/actions/interaction/enterActions/enterInput.js +5 -2
  11. package/dist/automation/actions/interaction/enterActions/enterInputAndPress.js +4 -1
  12. package/dist/automation/actions/interaction/enterActions/waitAndEnter.js +4 -1
  13. package/dist/automation/actions/interaction/get/getText.js +9 -2
  14. package/dist/automation/actions/interaction/iframe/switchToDefaultContent.js +1 -1
  15. package/dist/automation/actions/interaction/shadowDom/shadowDomUpload.js +88 -10
  16. package/dist/automation/actions/interaction/verify/VerifyElementNotContainsText.js +4 -1
  17. package/dist/automation/actions/interaction/verify/verifypartialtext.js +9 -2
  18. package/dist/automation/actions/interaction/verify/verifytext.js +9 -2
  19. package/dist/automation/actions/interface/FireFlinkResponseSchema.d.ts +30 -0
  20. package/dist/automation/actions/interface/FireFlinkResponseSchema.js +8 -1
  21. package/dist/core/helpers/elementSelectorResolver.d.ts +1 -1
  22. package/dist/core/helpers/elementSelectorResolver.js +3 -2
  23. package/dist/core/helpers/runnerDomPipeline.d.ts +2 -2
  24. package/dist/core/helpers/runnerDomPipeline.js +20 -9
  25. package/dist/core/helpers/visionFallbackHandler.js +4 -4
  26. package/dist/core/interfaces/automationRunnerInterface.d.ts +1 -0
  27. package/dist/core/interfaces/executionDetails.d.ts +2 -1
  28. package/dist/core/interfaces/fireflinkScriptPayloadInterface.d.ts +1 -3
  29. package/dist/core/interfaces/promptInterface.d.ts +5 -0
  30. package/dist/core/main/actionHandlerFactory.js +3 -3
  31. package/dist/core/main/executionContext.d.ts +2 -0
  32. package/dist/core/main/executionContext.js +18 -3
  33. package/dist/core/main/runAutomationScript.d.ts +2 -0
  34. package/dist/core/main/runAutomationScript.js +78 -20
  35. package/dist/core/types/promptType.d.ts +1 -0
  36. package/dist/core/types/promptType.js +1 -0
  37. package/dist/fireflinkData/fireflinkLocators/getListOfLocators.d.ts +2 -1
  38. package/dist/fireflinkData/fireflinkLocators/getListOfLocators.js +2 -2
  39. package/dist/fireflinkData/fireflinkScript/appendScriptData.js +61 -14
  40. package/dist/fireflinkData/fireflinkScript/scriptGenrationData.d.ts +1 -0
  41. package/dist/fireflinkData/fireflinkScript/scriptGenrationData.js +1 -0
  42. package/dist/imageAnalysisWeb/annotateScreenshot.d.ts +1 -1
  43. package/dist/imageAnalysisWeb/annotateScreenshot.js +24 -10
  44. package/dist/imageAnalysisWeb/generateAnnotatedScreenshots.js +5 -2
  45. package/dist/tests/senario.d.ts +0 -0
  46. package/dist/tests/senario.js +1120 -0
  47. package/dist/tests/test12.js +30 -62
  48. package/dist/utils/DomExtraction/jsForAttributeInjection.js +23 -0
  49. package/dist/utils/JsPathGenerator/generateJsPath.d.ts +2 -0
  50. package/dist/utils/JsPathGenerator/generateJsPath.js +169 -0
  51. package/dist/utils/helpers/xpathcreation.js +3 -0
  52. package/dist/utils/iframesExtraction/findElementUsingFF.js +1 -2
  53. package/dist/utils/iframesExtraction/switchToIframeByMeta.js +1 -1
  54. package/dist/utils/workers/locatorWorker.js +3 -1
  55. package/package.json +1 -1
@@ -1,5 +1,7 @@
1
1
  export type keywordExtractor = {
2
2
  priorAndNextSteps: string[];
3
+ allSteps?: string[];
4
+ previousPlatform?: string | null;
3
5
  };
4
6
  export type FFInspectorArgs = {
5
7
  stepAction: string;
@@ -13,6 +13,7 @@ const getActionExtractorPrompt_js_1 = require("./systemPrompts/getActionExtracto
13
13
  const verifyActionExtractorPromptMob_js_1 = require("./systemPrompts/verifyActionExtractorPromptMob.js");
14
14
  const getActionExtractorPromptMob_js_1 = require("./systemPrompts/getActionExtractorPromptMob.js");
15
15
  const mobileKeywordExtractor_js_1 = require("./systemPrompts/mobileKeywordExtractor.js");
16
+ const multichannelKeywordExtractor_js_1 = require("./systemPrompts/multichannelKeywordExtractor.js");
16
17
  const combinedActionExtractorPromptMob_js_1 = require("./systemPrompts/combinedActionExtractorPromptMob.js");
17
18
  const waitActionExtractorPromptMob_js_1 = require("./systemPrompts/waitActionExtractorPromptMob.js");
18
19
  const waitActionExtractorPrompt_js_1 = require("./systemPrompts/waitActionExtractorPrompt.js");
@@ -25,6 +26,7 @@ exports.prompts = {
25
26
  web: {
26
27
  userStoryToList: userStoryToListPrompt_js_1.buildStepExtractionPrompt,
27
28
  keywordExtractorPrompt: actionExtractorPrompt_js_1.keywordExtractor,
29
+ multichannelKeywordExtractorPrompt: multichannelKeywordExtractor_js_1.keywordExtractorMultichannel,
28
30
  errorDescriptionPrompt: errorDescriptionPrompt_js_1.buildErrorDescriptionPrompt,
29
31
  ffInspectorNumExtractor: fireflinkElementIndexExtactors_js_1.ffInspectorNumExtractor,
30
32
  verifyActionExtractorPrompt: verifyActionExtractorPrompt_js_1.verifyActionExtractorPrompt,
@@ -40,6 +42,7 @@ exports.prompts = {
40
42
  android: {
41
43
  userStoryToList: userStoryToListPrompt_js_1.androidbuildStepExtractionPrompt,
42
44
  keywordExtractorPrompt: mobileKeywordExtractor_js_1.keywordExtractorMobile,
45
+ multichannelKeywordExtractorPrompt: multichannelKeywordExtractor_js_1.keywordExtractorMultichannel,
43
46
  errorDescriptionPrompt: errorDescriptionPrompt_js_1.buildErrorDescriptionPrompt,
44
47
  ffInspectorNumExtractor: fireflinkElementIndexExtractor_Mob_js_1.ffInspectorNumExtractorMob,
45
48
  verifyActionExtractorPrompt: verifyActionExtractorPromptMob_js_1.verifyActionExtractorPromptMob,
@@ -55,6 +58,7 @@ exports.prompts = {
55
58
  ios: {
56
59
  userStoryToList: userStoryToListPrompt_js_1.androidbuildStepExtractionPrompt,
57
60
  keywordExtractorPrompt: mobileKeywordExtractor_js_1.keywordExtractorMobile,
61
+ multichannelKeywordExtractorPrompt: multichannelKeywordExtractor_js_1.keywordExtractorMultichannel,
58
62
  errorDescriptionPrompt: errorDescriptionPrompt_js_1.buildErrorDescriptionPrompt,
59
63
  ffInspectorNumExtractor: fireflinkElementIndexExtractor_Mob_js_1.ffInspectorNumExtractorMob,
60
64
  verifyActionExtractorPrompt: verifyActionExtractorPromptMob_js_1.verifyActionExtractorPromptMob,
@@ -0,0 +1,6 @@
1
+ import { keywordExtractor } from "../llmPromptTypes/systemPromptTypes";
2
+ export type multichannelKeywordExtractor = keywordExtractor & {
3
+ allSteps: string[];
4
+ previousPlatform?: string | null;
5
+ };
6
+ export declare function keywordExtractorMultichannel({ priorAndNextSteps, allSteps, previousPlatform, }: multichannelKeywordExtractor): Promise<string>;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.keywordExtractorMultichannel = keywordExtractorMultichannel;
4
+ const actionExtractorPrompt_js_1 = require("./actionExtractorPrompt.js");
5
+ const mobileKeywordExtractor_js_1 = require("./mobileKeywordExtractor.js");
6
+ async function keywordExtractorMultichannel({ priorAndNextSteps, allSteps, previousPlatform, }) {
7
+ const webPrompt = await (0, actionExtractorPrompt_js_1.keywordExtractor)({ priorAndNextSteps });
8
+ const mobilePrompt = await (0, mobileKeywordExtractor_js_1.keywordExtractorMobile)({ priorAndNextSteps });
9
+ const allowedActions = [
10
+ "enter",
11
+ "refresh",
12
+ "wait",
13
+ "verify",
14
+ "scroll",
15
+ "navigate",
16
+ "navigateBack",
17
+ "click",
18
+ "maximize",
19
+ "minimize",
20
+ "get",
21
+ "upload",
22
+ "close",
23
+ "open",
24
+ "openApp",
25
+ "drag_and_drop",
26
+ "closeApp",
27
+ "switch",
28
+ "cleartext",
29
+ "tap",
30
+ "combined",
31
+ "mouseAction",
32
+ "switchToAndroid",
33
+ "switchToWeb",
34
+ "getOtp",
35
+ "enterOtp",
36
+ "arithmetic",
37
+ ];
38
+ return `
39
+ You are extracting keywords for a MULTICHANNEL test. Platform is not provided by the user.
40
+ You MUST decide the platform for the CURRENT step and return it so downstream execution stays unchanged.
41
+
42
+ previousPlatform: ${previousPlatform ?? "none"}
43
+ allSteps (full script, use as context only): ${JSON.stringify(allSteps)}
44
+ nearbySteps: ${JSON.stringify(priorAndNextSteps)}
45
+
46
+ PLATFORM DECISION RULES (mandatory):
47
+ - platform MUST be exactly one of: "web" | "android" | "ios"
48
+ - Decide using the CURRENT step first, then allSteps for context, then previousPlatform.
49
+ - If the current step is in the middle of a flow and does not clearly name a platform, browser, URL, app, package, bundle, or device, you MUST reuse previousPlatform.
50
+ - If previousPlatform is "none" (first step), infer from the current step and allSteps:
51
+ - web: browser, URL, http/https, navigate, click, open browser, web page, tab, window
52
+ - android: Android app, APK, appPackage, appActivity, UiAutomator, Android device
53
+ - ios: iOS app, iPhone, IPA, bundle id, XCUITest, Safari on iOS
54
+ - Do not switch platform on generic UI verbs (tap/click/enter/verify/scroll) unless the step or surrounding allSteps clearly change channel.
55
+ - After you choose platform, follow THAT platform's keyword/action rules below. Do not mix web actions onto mobile or mobile actions onto web.
56
+ - web actions include: click, open, close, navigate, navigateBack, maximize, minimize, refresh, mouseAction, drag_and_drop, switch, cleartext, upload
57
+ - android/ios actions include: tap, openApp, closeApp, combined, enterOtp
58
+ - shared: enter, wait, verify, scroll, get, getOtp, arithmetic, switchToAndroid, switchToWeb
59
+ - action must be one of: ${JSON.stringify(allowedActions)}
60
+
61
+ ===== WEB KEYWORD RULES (use when platform is web) =====
62
+ ${webPrompt}
63
+
64
+ ===== ANDROID / iOS KEYWORD RULES (use when platform is android or ios) =====
65
+ ${mobilePrompt}
66
+
67
+ Respond ONLY with valid JSON in this EXACT format (this overrides the JSON formats in the sections above):
68
+ {
69
+ "steps": [
70
+ {
71
+ "platform": "web" | "android" | "ios",
72
+ "keywords": ["key1", "key2"],
73
+ "elementName": "element name",
74
+ "action": ${allowedActions.join("|")},
75
+ "isStepValueDependable": boolean,
76
+ "liveLog": "Short human-readable description of what the AI is about to do.",
77
+ "fileType": "pdf" | other lowercase file extension | null,
78
+ "step": "Single atomic instruction for this step."
79
+ }
80
+ ]
81
+ }
82
+
83
+ Every object in "steps" MUST include platform. If you split one user step into multiple objects, keep the same platform unless the wording clearly switches channel.
84
+ No other text.
85
+ `;
86
+ }
@@ -6,6 +6,7 @@ const imageUtils_js_1 = require("../../../utils/helpers/imageUtils.js");
6
6
  exports.userInputFormatters = {
7
7
  [promptType_js_1.PromptType.USER_STORY_TO_LIST]: (userInput) => `Convert the following user story into steps:\n\n${JSON.stringify(userInput.userStory, null, 2)}`,
8
8
  [promptType_js_1.PromptType.KEYWORD_EXTRACTOR]: (userInput) => `Extract keywords from the following input:\n\n${JSON.stringify(userInput.currentStep, null, 2)}`,
9
+ [promptType_js_1.PromptType.MULTICHANNEL_KEYWORD_EXTRACTOR]: (userInput) => `Extract keywords and decide platform for the following current step:\n\n${JSON.stringify(userInput.currentStep, null, 2)}`,
9
10
  [promptType_js_1.PromptType.ERROR_DESCRIPTION]: (error) => `Analyze the following error context:\n\n${JSON.stringify(error.error, null, 2)}`,
10
11
  [promptType_js_1.PromptType.FF_INSPECTOR]: (userInput) => `Inspect the DOM for the following action context:\n\n${JSON.stringify(userInput.currentStep, null, 2)}`,
11
12
  [promptType_js_1.PromptType.VERIFY_PROMPT]: (userInput) => `Inspect the DOM for the following verify context:\n\n${JSON.stringify(userInput.currentStep, null, 2)}`,
@@ -9,7 +9,10 @@ async function clear(args) {
9
9
  case "android":
10
10
  case "ios":
11
11
  try {
12
- const { ffNumber, xpathPath } = (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
12
+ const isWebView = args.pageDOM?.includes("ff-inspect");
13
+ const { ffNumber, xpathPath } = isWebView
14
+ ? (0, enterActionHelper_js_1.enterActionHelper)(args.pageDOM, args.fireflinkIndex)
15
+ : (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
13
16
  await args.driver.$(xpathPath).clearValue();
14
17
  const ffElementNumber = `Fire-Flink-${ffNumber}`;
15
18
  (0, appendScriptData_js_1.appendScriptData)({
@@ -10,7 +10,10 @@ async function clearAndEnter(args) {
10
10
  case "android":
11
11
  case "ios":
12
12
  try {
13
- const { ffNumber, xpathPath } = (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
13
+ const isWebView = args.pageDOM?.includes("ff-inspect");
14
+ const { ffNumber, xpathPath } = isWebView
15
+ ? (0, enterActionHelper_js_1.enterActionHelper)(args.pageDOM, args.fireflinkIndex)
16
+ : (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
14
17
  const element = args.driver.$(xpathPath);
15
18
  await element.clearValue();
16
19
  await element.setValue(args.value);
@@ -46,7 +46,7 @@ async function GetOtpAction(args) {
46
46
  (0, appendScriptData_js_1.appendScriptData)({
47
47
  scriptDataAppender: args.scriptDataAppender,
48
48
  nlpName: "MOB_FetchOtpFromMessages",
49
- stepInputs: [existingSms.address, timeFrom + "s", waitTime],
49
+ stepInputs: [existingSms.address, timeFrom + "s", waitTime, _regex],
50
50
  platform: "android",
51
51
  returnTypeVariableName: args.returnTypeVariableName
52
52
  });
@@ -84,7 +84,7 @@ async function GetOtpAction(args) {
84
84
  (0, appendScriptData_js_1.appendScriptData)({
85
85
  scriptDataAppender: args.scriptDataAppender,
86
86
  nlpName: "MOB_FetchOtpFromMessages",
87
- stepInputs: [latestSms.address, timeFrom + "s", waitTime],
87
+ stepInputs: [latestSms.address, timeFrom + "s", waitTime, regex],
88
88
  platform: "android",
89
89
  returnTypeVariableName: args.returnTypeVariableName
90
90
  });
@@ -7,7 +7,10 @@ const logData_js_1 = require("../../../../utils/logger/logData.js");
7
7
  const saveStepInputData_js_1 = require("../../../../utils/helpers/saveStepInputData.js");
8
8
  async function MOB_EnterInputIntoElementFromClipBoard(args) {
9
9
  try {
10
- const { ffNumber, xpathPath } = (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
10
+ const isWebView = args.pageDOM?.includes("ff-inspect");
11
+ const { ffNumber, xpathPath } = isWebView
12
+ ? (0, enterActionHelper_js_1.enterActionHelper)(args.pageDOM, args.fireflinkIndex)
13
+ : (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
11
14
  const element = await args.driver.$(xpathPath);
12
15
  const base64Value = await args.driver.getClipboard('plaintext');
13
16
  const value = Buffer.from(base64Value, 'base64').toString('utf-8');
@@ -44,7 +44,10 @@ async function enterInput(args) {
44
44
  case "android":
45
45
  case "ios":
46
46
  try {
47
- const { ffNumber, xpathPath } = (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
47
+ const isWebView = args.pageDOM?.includes("ff-inspect");
48
+ const { ffNumber, xpathPath } = isWebView
49
+ ? (0, enterActionHelper_js_1.enterActionHelper)(args.pageDOM, args.fireflinkIndex)
50
+ : (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
48
51
  const element = await args.driver.$(xpathPath);
49
52
  if (args.stepIndex + 1 < args.filteredSteps.length &&
50
53
  args.filteredSteps[args.stepIndex].toLowerCase().includes("enter")) {
@@ -54,7 +57,7 @@ async function enterInput(args) {
54
57
  else {
55
58
  await element.setValue(args.value);
56
59
  }
57
- if (args.platform === "android") {
60
+ if (args.platform === "android" && !isWebView) {
58
61
  try {
59
62
  const text = await element.getAttribute("text");
60
63
  if (text !== args.value) {
@@ -8,7 +8,10 @@ async function enterInputAndPress(args) {
8
8
  switch (args.platform) {
9
9
  case "android":
10
10
  try {
11
- const { ffNumber, xpathPath } = (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
11
+ const isWebView = args.pageDOM?.includes("ff-inspect");
12
+ const { ffNumber, xpathPath } = isWebView
13
+ ? (0, enterActionHelper_js_1.enterActionHelper)(args.pageDOM, args.fireflinkIndex)
14
+ : (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
12
15
  const element = await args.driver.$(xpathPath);
13
16
  const parts = String(args.value).split(",");
14
17
  const inputValue = parts[0]?.trim() || "";
@@ -19,7 +19,10 @@ async function waitAndEnter(args) {
19
19
  // ---------------- ANDROID ----------------
20
20
  case "android":
21
21
  try {
22
- const { ffNumber, xpathPath } = (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
22
+ const isWebView = args.pageDOM?.includes("ff-inspect");
23
+ const { ffNumber, xpathPath } = isWebView
24
+ ? (0, enterActionHelper_js_1.enterActionHelper)(args.pageDOM, args.fireflinkIndex)
25
+ : (0, enterActionHelper_js_1.enterActionHelperMob)(args.pageDOM, args.fireflinkIndex);
23
26
  const { inputValue, waitTimeSeconds } = parseValue(args.value);
24
27
  const element = await args.driver.$(xpathPath);
25
28
  // Wait before typing
@@ -11,8 +11,15 @@ const getText = async (args) => {
11
11
  case "ios":
12
12
  try {
13
13
  const element = await args.driver.$(args.selector);
14
- const attrName = args.platform === "ios" ? "value" : "text";
15
- const _text = await element.getAttribute(attrName);
14
+ const isWebView = args.pageDOM?.includes("ff-inspect");
15
+ let _text = null;
16
+ if (isWebView) {
17
+ _text = await element.getText();
18
+ }
19
+ else {
20
+ const attrName = args.platform === "ios" ? "value" : "text";
21
+ _text = await element.getAttribute(attrName);
22
+ }
16
23
  logData_js_1.logger.info("Text", _text);
17
24
  (0, saveStepInputData_js_1.saveStepInputData)(args, _text);
18
25
  (0, appendScriptData_js_1.appendScriptData)({
@@ -4,7 +4,7 @@ exports.switchToDefaultContent = switchToDefaultContent;
4
4
  const appendScriptData_js_1 = require("../../../../fireflinkData/fireflinkScript/appendScriptData.js");
5
5
  async function switchToDefaultContent(args) {
6
6
  try {
7
- await args.driver.switchToFrame(null);
7
+ await args.driver.switchFrame(null);
8
8
  (0, appendScriptData_js_1.appendScriptData)({
9
9
  scriptDataAppender: args.scriptDataAppender,
10
10
  nlpName: "SwitchToDefaultContent"
@@ -1,26 +1,104 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.uploadFileShadowElement = uploadFileShadowElement;
4
- const jsForShadowRoot_js_1 = require("../../../../utils/javascript/jsForShadowRoot.js");
5
4
  const appendScriptData_js_1 = require("../../../../fireflinkData/fireflinkScript/appendScriptData.js");
5
+ const generateJsPath_js_1 = require("../../../../utils/JsPathGenerator/generateJsPath.js");
6
+ const logData_js_1 = require("../../../../utils/logger/logData.js");
6
7
  async function uploadFileShadowElement(args) {
7
8
  try {
8
- const executableJS = jsForShadowRoot_js_1.SHADOW_DOM_UPLOAD_JS.replace("arguments[0]", `"${args.selector}"`);
9
- const exists = await args.driver.execute(jsForShadowRoot_js_1.SHADOW_DOM_UPLOAD_JS, args.selector);
10
- if (!exists) {
11
- throw new Error("uploda for shadow dom is failed");
9
+ const parts = args.selector.split("/shadow/");
10
+ let fileInput = null;
11
+ // Build shadow host chain ONCE — shared across all tiers (avoids duplicate traversal)
12
+ const shadowHosts = [];
13
+ try {
14
+ let el = await args.driver.$(parts[0]);
15
+ shadowHosts.push(el);
16
+ for (let i = 1; i < parts.length - 1; i++) {
17
+ el = await el.shadow$((0, generateJsPath_js_1.convertToCssSelector)(parts[i]));
18
+ shadowHosts.push(el);
19
+ }
20
+ }
21
+ catch {
22
+ // partial shadowHosts[] is still usable for whatever was resolved
23
+ }
24
+ const lastKnownHost = shadowHosts.length > 0 ? shadowHosts[shadowHosts.length - 1] : null;
25
+ // Tier 1 — shadow$() on deepest known host (1 level below last host)
26
+ if (lastKnownHost && await lastKnownHost.isExisting()) {
27
+ try {
28
+ const candidate = await lastKnownHost.shadow$('input[type="file"]');
29
+ if (candidate && await candidate.isExisting()) {
30
+ fileInput = candidate;
31
+ }
32
+ }
33
+ catch {
34
+ // fall through to Tier 2
35
+ }
36
+ }
37
+ // Tier 2 — Option A: element-scoped >>> deep-pierce from last known host
38
+ if (!fileInput && lastKnownHost && await lastKnownHost.isExisting()) {
39
+ try {
40
+ const candidate = await lastKnownHost.$('>>>input[type="file"]');
41
+ if (candidate && await candidate.isExisting()) {
42
+ fileInput = candidate;
43
+ }
44
+ }
45
+ catch {
46
+ // fall through to Tier 3
47
+ }
48
+ }
49
+ // Tier 3 — backtrack deepest → outermost across all recorded shadow hosts
50
+ if (!fileInput) {
51
+ for (let i = shadowHosts.length - 1; i >= 0; i--) {
52
+ try {
53
+ const candidate = await shadowHosts[i].shadow$('input[type="file"]');
54
+ if (candidate && await candidate.isExisting()) {
55
+ fileInput = candidate;
56
+ break;
57
+ }
58
+ }
59
+ catch {
60
+ // continue up the chain
61
+ }
62
+ }
63
+ }
64
+ if (!fileInput) {
65
+ throw new Error(`Shadow DOM file input not found. Selector: "${args.selector}"`);
12
66
  }
13
67
  const remoteFilePath = await args.driver.uploadFile(args.value);
14
- const fileInput = await args.driver.$(exists);
15
- await fileInput.setValue(remoteFilePath);
68
+ try {
69
+ await fileInput.setValue(remoteFilePath);
70
+ }
71
+ catch (err) {
72
+ logData_js_1.logger.info(`setValue failed on shadow file input, trying addValue: ${err.message}`);
73
+ await fileInput.addValue(remoteFilePath);
74
+ }
75
+ //Dispatch change and input events so custom UI components update their state
76
+ await args.driver.execute((el) => {
77
+ if (el) {
78
+ el.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
79
+ el.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
80
+ }
81
+ }, fileInput);
82
+ logData_js_1.logger.info(`Successfully uploaded file into shadow DOM element`);
83
+ // Generate JSPath
84
+ const jsPath = (0, generateJsPath_js_1.generateJsPath)(args.selector, 'input[type="file"]');
85
+ console.warn("RAW JSPath:\n" + jsPath);
86
+ const ffElementIndex = `Fire-Flink-${args.fireflinkIndex}`;
16
87
  (0, appendScriptData_js_1.appendScriptData)({
17
88
  scriptDataAppender: args.scriptDataAppender,
18
- nlpName: "ExecuteJavaScript",
19
- stepInputs: [executableJS]
89
+ nlpName: "SendKeys",
90
+ stepInputs: [args.value],
91
+ pageDOM: args.pageDOM,
92
+ elementGetter: args.elementGetter,
93
+ ffNumber: ffElementIndex,
94
+ elementName: args.elementName,
95
+ elementType: "button",
96
+ selector: jsPath,
97
+ platform: args.platform
20
98
  });
21
99
  }
22
100
  catch (error) {
23
- args.scriptDataAppender.setErrorNLP("ExecuteJavaScript");
101
+ args.scriptDataAppender.setErrorNLP("SendKeys");
24
102
  throw new Error("Shadow Upload action failed", {
25
103
  cause: error
26
104
  });
@@ -7,7 +7,10 @@ async function verifyElementNotContainsText(args) {
7
7
  case "android":
8
8
  try {
9
9
  const element = await args.driver.$(args.selector);
10
- const expected = await element.getAttribute("text");
10
+ const isWebView = args.pageDOM?.includes("ff-inspect");
11
+ const expected = isWebView
12
+ ? (await element.getText()) || ""
13
+ : (await element.getAttribute("text")) || "";
11
14
  const caseSensitive = false;
12
15
  if (expected == args.value) {
13
16
  throw new Error(`Element do contain the text. Expected: not containing "${args.value}", Actual: contains "${expected}"`);
@@ -10,8 +10,15 @@ async function verifyPartialText(args) {
10
10
  case "ios":
11
11
  try {
12
12
  const element = await args.driver.$(args.selector);
13
- const attributeName = args.platform === "ios" ? "value" : "text";
14
- const actual = (await element.getAttribute(attributeName)) || "";
13
+ const isWebView = args.pageDOM?.includes("ff-inspect");
14
+ let actual;
15
+ if (isWebView) {
16
+ actual = (await element.getText()) || "";
17
+ }
18
+ else {
19
+ const attributeName = args.platform === "ios" ? "value" : "text";
20
+ actual = (await element.getAttribute(attributeName)) || "";
21
+ }
15
22
  const expected = args.value;
16
23
  if (!actual?.includes(expected)) {
17
24
  throw new Error(`Element does not contain the expected text. Expected to contain: "${args.value}", Actual: "${actual}"`);
@@ -10,8 +10,15 @@ async function verifyText(args) {
10
10
  case "ios":
11
11
  try {
12
12
  const element = await args.driver.$(args.selector);
13
- const attributeName = args.platform === "ios" ? "value" : "text";
14
- const text = (await element.getAttribute(attributeName)) || "";
13
+ const isWebView = args.pageDOM?.includes("ff-inspect");
14
+ let text;
15
+ if (isWebView) {
16
+ text = (await element.getText()) || "";
17
+ }
18
+ else {
19
+ const attributeName = args.platform === "ios" ? "value" : "text";
20
+ text = (await element.getAttribute(attributeName)) || "";
21
+ }
15
22
  const expected = (args.value || "").toLowerCase();
16
23
  const isMatched = text.toLowerCase().trim() === expected.trim();
17
24
  logData_js_1.logger.info("text", { text });
@@ -19,6 +19,36 @@ export declare const KeywordSchema: z.ZodObject<{
19
19
  step: z.ZodString;
20
20
  }, z.core.$strip>>;
21
21
  }, z.core.$strip>;
22
+ export declare const MultichannelStepItemSchema: z.ZodObject<{
23
+ keywords: z.ZodArray<z.ZodString>;
24
+ elementName: z.ZodString;
25
+ action: z.ZodString;
26
+ isStepValueDependable: z.ZodBoolean;
27
+ liveLog: z.ZodString;
28
+ fileType: z.ZodNullable<z.ZodString>;
29
+ step: z.ZodString;
30
+ platform: z.ZodEnum<{
31
+ web: "web";
32
+ android: "android";
33
+ ios: "ios";
34
+ }>;
35
+ }, z.core.$strip>;
36
+ export declare const MultichannelKeywordSchema: z.ZodObject<{
37
+ steps: z.ZodArray<z.ZodObject<{
38
+ keywords: z.ZodArray<z.ZodString>;
39
+ elementName: z.ZodString;
40
+ action: z.ZodString;
41
+ isStepValueDependable: z.ZodBoolean;
42
+ liveLog: z.ZodString;
43
+ fileType: z.ZodNullable<z.ZodString>;
44
+ step: z.ZodString;
45
+ platform: z.ZodEnum<{
46
+ web: "web";
47
+ android: "android";
48
+ ios: "ios";
49
+ }>;
50
+ }, z.core.$strip>>;
51
+ }, z.core.$strip>;
22
52
  export declare const AlertShema: z.ZodObject<{
23
53
  action: z.ZodString;
24
54
  inputText: z.ZodString;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ArithmeticSchema = exports.InputContextSchema = exports.OtpExtractorSchema = exports.ErrorDescriptionSchema = exports.UserStorySchema = exports.VisionSchema = exports.DefaultInteractionSchema = exports.MouseActionSchema = exports.UploadActionSchema = exports.GetOtpActionSchema = exports.ClearTextActionSchema = exports.EnterActionSchema = exports.TapActionSchema = exports.ClickActionSchema = exports.SwitchActionSchema = exports.MobileScrollSchema = exports.WebScrollSchema = exports.DragDropActionSchema = exports.MobileCombinedSchema = exports.CombinedActionSchema = exports.WaitActionSchema = exports.MobileGetSchema = exports.MobileFileVerifySchema = exports.MobileVerifySchema = exports.WebGetSchema = exports.WebFileVerifySchema = exports.WebVerifySchema = exports.AlertShema = exports.KeywordSchema = exports.StepItemSchema = void 0;
3
+ exports.ArithmeticSchema = exports.InputContextSchema = exports.OtpExtractorSchema = exports.ErrorDescriptionSchema = exports.UserStorySchema = exports.VisionSchema = exports.DefaultInteractionSchema = exports.MouseActionSchema = exports.UploadActionSchema = exports.GetOtpActionSchema = exports.ClearTextActionSchema = exports.EnterActionSchema = exports.TapActionSchema = exports.ClickActionSchema = exports.SwitchActionSchema = exports.MobileScrollSchema = exports.WebScrollSchema = exports.DragDropActionSchema = exports.MobileCombinedSchema = exports.CombinedActionSchema = exports.WaitActionSchema = exports.MobileGetSchema = exports.MobileFileVerifySchema = exports.MobileVerifySchema = exports.WebGetSchema = exports.WebFileVerifySchema = exports.WebVerifySchema = exports.AlertShema = exports.MultichannelKeywordSchema = exports.MultichannelStepItemSchema = exports.KeywordSchema = exports.StepItemSchema = void 0;
4
4
  exports.getSchemaForPrompt = getSchemaForPrompt;
5
5
  const zod_1 = require("zod");
6
6
  const promptType_js_1 = require("../../../core/types/promptType.js");
@@ -16,6 +16,12 @@ exports.StepItemSchema = zod_1.z.object({
16
16
  exports.KeywordSchema = zod_1.z.object({
17
17
  steps: zod_1.z.array(exports.StepItemSchema)
18
18
  });
19
+ exports.MultichannelStepItemSchema = exports.StepItemSchema.extend({
20
+ platform: zod_1.z.enum(["web", "android", "ios"]),
21
+ });
22
+ exports.MultichannelKeywordSchema = zod_1.z.object({
23
+ steps: zod_1.z.array(exports.MultichannelStepItemSchema)
24
+ });
19
25
  exports.AlertShema = zod_1.z.object({
20
26
  action: zod_1.z.string(),
21
27
  inputText: zod_1.z.string()
@@ -202,6 +208,7 @@ exports.ArithmeticSchema = zod_1.z.object({
202
208
  });
203
209
  const schemaMap = {
204
210
  [promptType_js_1.PromptType.KEYWORD_EXTRACTOR]: exports.KeywordSchema,
211
+ [promptType_js_1.PromptType.MULTICHANNEL_KEYWORD_EXTRACTOR]: exports.MultichannelKeywordSchema,
205
212
  [promptType_js_1.PromptType.VISION_PROMPT]: exports.VisionSchema,
206
213
  [promptType_js_1.PromptType.USER_STORY_TO_LIST]: exports.UserStorySchema,
207
214
  [promptType_js_1.PromptType.ERROR_DESCRIPTION]: exports.ErrorDescriptionSchema,
@@ -3,7 +3,7 @@ import { ShadowIframeResult } from "../interfaces/automationRunnerInterface.js";
3
3
  export { ShadowIframeResult };
4
4
  export declare function generateUniqueName(currentName: string | undefined, usedNames: string[]): string | undefined;
5
5
  export declare function normalizeSvgSelector(selector: string): string;
6
- export declare function resolveElementSelector(doc: any, domInfo: any, fireflinkIndex: string, platform: PlatformType, driver: any): Promise<{
6
+ export declare function resolveElementSelector(doc: any, domInfo: any, fireflinkIndex: string, platform: PlatformType, driver: any, fallbackAttempt: number, driverContext?: any): Promise<{
7
7
  selector: string;
8
8
  doc: any;
9
9
  domInfo: any;
@@ -31,11 +31,11 @@ function normalizeSvgSelector(selector) {
31
31
  }
32
32
  return selector;
33
33
  }
34
- async function resolveElementSelector(doc, domInfo, fireflinkIndex, platform, driver) {
34
+ async function resolveElementSelector(doc, domInfo, fireflinkIndex, platform, driver, fallbackAttempt, driverContext) {
35
35
  let selector;
36
36
  let updatedDoc = doc;
37
37
  let updatedDomInfo = domInfo;
38
- if (platform === "web") {
38
+ if (platform === "web" || (driverContext && fallbackAttempt > 1)) {
39
39
  if (!updatedDoc) {
40
40
  logData_js_1.logger.info("doc is undefined. Rebuilding document from domInfo.");
41
41
  if (!updatedDomInfo || !updatedDomInfo.dom) {
@@ -57,6 +57,7 @@ async function resolveElementSelector(doc, domInfo, fireflinkIndex, platform, dr
57
57
  selector = result.xpathPath;
58
58
  }
59
59
  selector = normalizeSvgSelector(selector);
60
+ selector = selector.replace(/\/div\[0\]\/div\[0\]/g, "/div[1]").replace(/\/div\[0\]/g, "/div[1]");
60
61
  return { selector, doc: updatedDoc, domInfo: updatedDomInfo };
61
62
  }
62
63
  async function handleShadowAndIframe(driver, stepAction, selector, fireflinkIndex, domInfo, actionHandlers, elementName, elementType) {
@@ -4,6 +4,6 @@ import { DomCaptureResult, DomProcessResult } from "../interfaces/automationRunn
4
4
  export { DomCaptureResult, DomProcessResult };
5
5
  export declare class RunnerDomPipeline {
6
6
  static parseDomDocument(domHtml: string, platform: PlatformType): any;
7
- static captureAndParseDom(driver: any, platform: PlatformType, stepCount: number, action: string, isRetry?: boolean): Promise<DomCaptureResult>;
8
- static processRelevantDom(domProcessor: DomProcessingEngine, domInfo: any, keywords: any, stepCount: number, action: string, currentStep: string, platform: PlatformType, driver: any, isRetry?: boolean): Promise<DomProcessResult>;
7
+ static captureAndParseDom(driver: any, platform: PlatformType, stepCount: number, action: string, isRetry?: boolean, driverContext?: any): Promise<DomCaptureResult>;
8
+ static processRelevantDom(domProcessor: DomProcessingEngine, domInfo: any, keywords: any, stepCount: number, action: string, currentStep: string, platform: PlatformType, driver: any, isRetry?: boolean, driverContext?: any): Promise<DomProcessResult>;
9
9
  }
@@ -19,7 +19,7 @@ class RunnerDomPipeline {
19
19
  return parser.window.document;
20
20
  }
21
21
  }
22
- static async captureAndParseDom(driver, platform, stepCount, action, isRetry = false) {
22
+ static async captureAndParseDom(driver, platform, stepCount, action, isRetry = false, driverContext) {
23
23
  try {
24
24
  let domInfo;
25
25
  let doc;
@@ -32,12 +32,23 @@ class RunnerDomPipeline {
32
32
  doc = RunnerDomPipeline.parseDomDocument(domInfo.dom, "web");
33
33
  }
34
34
  else {
35
- domInfo = await (0, jsForAttributeInjection_js_1.getAnnotatedDOMForMobile)(driver);
36
- const fileName = isRetry
37
- ? `annotated-dom-Mob-retry-${stepCount}`
38
- : `annotated-dom-Mob-${stepCount}`;
39
- logData_js_1.logger.saveDOM(domInfo.dom, fileName);
40
- doc = RunnerDomPipeline.parseDomDocument(domInfo.dom, platform);
35
+ if (driverContext) {
36
+ await driver.switchContext(driverContext);
37
+ domInfo = await (0, jsForAttributeInjection_js_1.getAnnotatedDOM)(driver);
38
+ const fileName = isRetry
39
+ ? `annotated-dom-retry-webview-${stepCount}`
40
+ : `annotated-dom-${stepCount}`;
41
+ logData_js_1.logger.saveDOM(domInfo.dom, fileName);
42
+ doc = RunnerDomPipeline.parseDomDocument(domInfo.dom, "web");
43
+ }
44
+ else {
45
+ domInfo = await (0, jsForAttributeInjection_js_1.getAnnotatedDOMForMobile)(driver);
46
+ const fileName = isRetry
47
+ ? `annotated-dom-Mob-retry-${stepCount}`
48
+ : `annotated-dom-Mob-${stepCount}`;
49
+ logData_js_1.logger.saveDOM(domInfo.dom, fileName);
50
+ doc = RunnerDomPipeline.parseDomDocument(domInfo.dom, platform);
51
+ }
41
52
  }
42
53
  return { domInfo, doc };
43
54
  }
@@ -48,11 +59,11 @@ class RunnerDomPipeline {
48
59
  throw error;
49
60
  }
50
61
  }
51
- static async processRelevantDom(domProcessor, domInfo, keywords, stepCount, action, currentStep, platform, driver, isRetry = false) {
62
+ static async processRelevantDom(domProcessor, domInfo, keywords, stepCount, action, currentStep, platform, driver, isRetry = false, driverContext) {
52
63
  try {
53
64
  let extractedRelevantDom;
54
65
  let toonDom;
55
- if (platform === "web") {
66
+ if (platform === "web" || driverContext) {
56
67
  extractedRelevantDom = await domProcessor.webProcess({
57
68
  keywords: keywords,
58
69
  rawDom: domInfo?.dom,