ff-automationv2 2.2.34-beta.0 → 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.
@@ -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)}`,
@@ -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
  });
@@ -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,
@@ -23,7 +23,7 @@ export interface AutomationRequest {
23
23
  userName: string;
24
24
  serverHost: string;
25
25
  browserName: BrowserName;
26
- platform: platform;
26
+ platform?: platform;
27
27
  token: string;
28
28
  appiumPort: number;
29
29
  realDeviceId: string;
@@ -38,4 +38,5 @@ export interface AutomationRequest {
38
38
  adbPath?: string;
39
39
  topic: string;
40
40
  aiExecutionAction: string;
41
+ hasMoreSteps: boolean;
41
42
  }
@@ -9,8 +9,7 @@ export interface IStep {
9
9
  }
10
10
  export interface IPayload {
11
11
  scriptName: string;
12
- scriptType: string;
13
- type?: string;
12
+ type: string;
14
13
  projectId?: string;
15
14
  testCaseId?: string;
16
15
  pageDetails?: any;
@@ -20,7 +19,6 @@ export interface IPayload {
20
19
  projectType?: string;
21
20
  tokensConsumed?: number;
22
21
  error?: IErrorInfo;
23
- platform?: string;
24
22
  serviceProvider?: string;
25
23
  model?: string;
26
24
  }
@@ -4,6 +4,11 @@ export interface PromptArgsMap {
4
4
  [PromptType.KEYWORD_EXTRACTOR]: {
5
5
  priorAndNextSteps: string[];
6
6
  };
7
+ [PromptType.MULTICHANNEL_KEYWORD_EXTRACTOR]: {
8
+ priorAndNextSteps: string[];
9
+ allSteps: string[];
10
+ previousPlatform?: string | null;
11
+ };
7
12
  [PromptType.ERROR_DESCRIPTION]: Record<string, any>;
8
13
  [PromptType.FF_INSPECTOR]: {
9
14
  stepAction: string;
@@ -265,7 +265,7 @@ function createActionHandlers(context) {
265
265
  },
266
266
  openApp: async (result) => {
267
267
  try {
268
- const isIOS = context.request.platform?.toLowerCase() === "ios";
268
+ const isIOS = context.activePlatform === "ios";
269
269
  const appPackage = result.keywords
270
270
  .find((item) => item.startsWith("appPackage:"))
271
271
  ?.substring("appPackage:".length);
@@ -340,7 +340,7 @@ function createActionHandlers(context) {
340
340
  closeApp: async () => {
341
341
  try {
342
342
  await context.mobileSession.close();
343
- const nlpName = context.request.platform.toLowerCase() === "ios" ? "IOS_CloseApp" : "MOB_CloseApp";
343
+ const nlpName = context.activePlatform === "ios" ? "IOS_CloseApp" : "MOB_CloseApp";
344
344
  context.scriptAppender.add(async () => ({
345
345
  nlpName: nlpName,
346
346
  elementsData: [],
@@ -357,7 +357,7 @@ function createActionHandlers(context) {
357
357
  if (!context.browserOpened) {
358
358
  logData_js_1.logger.info("Browser not initialized, auto-opening before navigate...");
359
359
  await context.session.open();
360
- context.executor = new executor_js_1.ActionExecutor(await context.session.getCurrentDriver(), context.scriptAppender, context.locator, context.request.platform, context.adbPath);
360
+ context.executor = new executor_js_1.ActionExecutor(await context.session.getCurrentDriver(), context.scriptAppender, context.locator, context.activePlatform, context.adbPath);
361
361
  context.browserOpened = true;
362
362
  context.scriptAppender.add(async () => ({
363
363
  nlpName: "Open Browser",
@@ -18,6 +18,8 @@ export declare class ExecutionContext {
18
18
  activePlatform: platform;
19
19
  browserOpened: boolean;
20
20
  stepsInputData: any[];
21
+ allManualSteps: string[];
21
22
  constructor(request: AutomationRequest, workerPool: LocatorWorkerPool);
22
23
  initializeMobileDriver(caps: any): Promise<void>;
24
+ syncExecutorToActivePlatform(): Promise<void>;
23
25
  }
@@ -15,15 +15,16 @@ class ExecutionContext {
15
15
  this.executor = null;
16
16
  this.browserOpened = false;
17
17
  this.stepsInputData = [];
18
+ this.allManualSteps = [];
18
19
  this.session = new initiateBrowserSession_js_1.BrowserSession(browserType_js_1.browserName[request.browserName], request.isCloud, request.capabilities, defaultValues_js_1.DEFAULT_PAGE_LOAD, request.cloudConfig);
19
20
  this.locator = new getListOfLocators_js_1.ElementGetter(workerPool);
20
21
  this.scriptAppender = new scriptGenrationData_js_1.ScriptDataAppender();
21
22
  this.mobileSession = new initiateMobileSession_js_1.MobileSession(request.appiumPort, request.isCloud, request.capabilities, request.cloudConfig, request.adbPath);
22
23
  this.adbPath = request.adbPath || "";
23
- this.activePlatform = request.platform.toLowerCase();
24
+ this.activePlatform = (request.platform?.toLowerCase() || "web");
24
25
  }
25
26
  async initializeMobileDriver(caps) {
26
- const isIOS = this.request.platform.toLowerCase() === "ios";
27
+ const isIOS = this.activePlatform === "ios";
27
28
  const appiumCaps = {};
28
29
  const allowedCapabilities = [
29
30
  "app",
@@ -107,7 +108,21 @@ class ExecutionContext {
107
108
  ...appiumCaps
108
109
  };
109
110
  await this.mobileSession.createDriver(capabilities);
110
- this.executor = new executor_js_1.ActionExecutor(await this.mobileSession.getCurrentDriver(), this.scriptAppender, this.locator, this.request.platform, this.adbPath, this.stepsInputData);
111
+ this.executor = new executor_js_1.ActionExecutor(await this.mobileSession.getCurrentDriver(), this.scriptAppender, this.locator, this.activePlatform, this.adbPath, this.stepsInputData);
112
+ }
113
+ async syncExecutorToActivePlatform() {
114
+ try {
115
+ if (this.activePlatform === "web") {
116
+ const webDriver = await this.session.getCurrentDriver();
117
+ this.executor = new executor_js_1.ActionExecutor(webDriver, this.scriptAppender, this.locator, "web", this.adbPath, this.stepsInputData);
118
+ return;
119
+ }
120
+ const mobileDriver = await this.mobileSession.getCurrentDriver();
121
+ this.executor = new executor_js_1.ActionExecutor(mobileDriver, this.scriptAppender, this.locator, this.activePlatform, this.adbPath, this.stepsInputData);
122
+ }
123
+ catch {
124
+ // Driver for the detected platform may not exist yet (open/openApp comes later).
125
+ }
111
126
  }
112
127
  }
113
128
  exports.ExecutionContext = ExecutionContext;
@@ -1,6 +1,7 @@
1
1
  import "../../utils/sslConfig/apiCallPatch.js";
2
2
  import { IAutomationRunner } from "../interfaces/automationRunnerInterface.js";
3
3
  import { AutomationRequest } from "../../core/interfaces/executionDetails.js";
4
+ import { platform } from "../types/scriptTypesType.js";
4
5
  export declare class AutomationRunner implements IAutomationRunner {
5
6
  static sessionTerminationDetails: Record<string, boolean>;
6
7
  static sessionNames: Record<string, string>;
@@ -9,6 +10,7 @@ export declare class AutomationRunner implements IAutomationRunner {
9
10
  static updateSessionTerminationInfo(testCaseId: string): void;
10
11
  static sessionName(testCaseId: string, sessionName: string): void;
11
12
  static getSessionName(testCaseId: string): string;
13
+ static resolveDetectedPlatform(detectedPlatform?: string, previousPlatform?: platform): platform;
12
14
  static normalizeScriptType(scriptType?: string): string;
13
15
  private cleanup;
14
16
  private saveSessionStepData;
@@ -24,7 +24,6 @@ const isAlertPresent_js_1 = require("../../utils/alertPopup/isAlertPresent.js");
24
24
  const logData_js_1 = require("../../utils/logger/logData.js");
25
25
  const sameActionsHelper_js_1 = require("../../utils/helpers/sameActionsHelper.js");
26
26
  const messageType_js_1 = require("../types/messageType.js");
27
- const platformSyntax_js_1 = require("../../utils/helpers/platformSyntax.js");
28
27
  const driverState_js_1 = require("../../automation/driverState.js");
29
28
  const SessionManager_js_1 = require("./SessionManager.js");
30
29
  const runnerKafkaReporter_js_1 = require("../helpers/runnerKafkaReporter.js");
@@ -49,6 +48,18 @@ class AutomationRunner {
49
48
  static getSessionName(testCaseId) {
50
49
  return AutomationRunner.sessionNames[testCaseId];
51
50
  }
51
+ static resolveDetectedPlatform(detectedPlatform, previousPlatform) {
52
+ const value = (detectedPlatform ?? "").trim().toLowerCase();
53
+ if (value === "ios")
54
+ return "ios";
55
+ if (value === "android")
56
+ return "android";
57
+ if (value === "web")
58
+ return "web";
59
+ if (previousPlatform)
60
+ return previousPlatform;
61
+ return "web";
62
+ }
52
63
  static normalizeScriptType(scriptType) {
53
64
  const rawValue = (scriptType ?? "Web").trim();
54
65
  if (!rawValue) {
@@ -150,15 +161,16 @@ class AutomationRunner {
150
161
  const usedElementNames = [];
151
162
  const usedVariableNames = [];
152
163
  (0, logData_js_1.initLogger)(request.sessionPath);
153
- request.type = request.platform;
154
- request.platform = AutomationRunner.normalizeScriptType(request.platform);
155
- const isWebMobile = (request.type?.toLowerCase().includes("web") &&
156
- request.type?.toLowerCase().includes("android")) ||
164
+ const isMultichannel = request.type?.toLowerCase().includes("Multi-Channel");
165
+ if (!isMultichannel) {
166
+ request.platform = AutomationRunner.normalizeScriptType(request.platform);
167
+ }
168
+ const isWebMobile = isMultichannel ||
169
+ (request.type?.toLowerCase().includes("web") &&
170
+ request.type?.toLowerCase().includes("android")) ||
157
171
  (request.type?.toLowerCase() === "android");
158
172
  logData_js_1.logger.info("User Given Step", request.userStory);
159
- const data = request.scriptName ? JSON.parse(request.scriptName) : {};
160
- const hasMoreSteps = data?.hasMoreSteps ?? false;
161
- request.scriptName = data?.automationScriptName ?? "automationScriptName";
173
+ const hasMoreSteps = request.hasMoreSteps;
162
174
  if (!request.isTerminate)
163
175
  AutomationRunner.sessionName(request.testCaseId, request.scriptName);
164
176
  if (request.isTerminate) {
@@ -170,6 +182,7 @@ class AutomationRunner {
170
182
  const instanceDetails = await fireflinkServiceInstance.runGetInstancesDetails(request.projectId, request.token, request.serverHost, request.licenseId);
171
183
  const aiInfo = (0, getInstancesInfo_js_1.getAiInstanceInfo)(instanceDetails.responseObject);
172
184
  let context = SessionManager_js_1.SessionManager.get(request);
185
+ const hadExistingSession = Boolean(context);
173
186
  if (!context) {
174
187
  context = SessionManager_js_1.SessionManager.create(request);
175
188
  }
@@ -183,11 +196,13 @@ class AutomationRunner {
183
196
  const stepProcessor = new stepProcessor_js_1.StepProcessor(llm);
184
197
  let currentPlatform = context.activePlatform;
185
198
  (0, platformContext_js_1.setPlatform)(currentPlatform);
199
+ let previousDetectedPlatform = isMultichannel && hadExistingSession ? context.activePlatform : undefined;
186
200
  const actionHandlers = (0, actionHandlerFactory_js_1.createActionHandlers)(context);
187
201
  const domProcessor = new getRelaventElements_js_1.DomProcessingEngine();
188
202
  let stepCount = 1;
189
203
  const stepsInputData = context.stepsInputData || (context.stepsInputData = []);
190
204
  const listOfSteps = request.userStory.map((step) => String(step));
205
+ context.allManualSteps.push(...listOfSteps);
191
206
  let driver;
192
207
  if (listOfSteps.length === 0 && request?.aiExecutionAction !== "stopExecution") {
193
208
  logData_js_1.logger.info(listOfSteps);
@@ -227,14 +242,40 @@ class AutomationRunner {
227
242
  const start = Math.max(0, index - 3);
228
243
  const end = Math.min(listOfSteps.length, index + 4);
229
244
  const priorAndNextSteps = listOfSteps.slice(start, end);
230
- const result = await stepProcessor.getLLMResponse({
231
- platform: currentPlatform,
232
- type: promptType_js_1.PromptType.KEYWORD_EXTRACTOR,
233
- args: { priorAndNextSteps },
234
- input: { currentStep: step },
235
- });
245
+ const result = isMultichannel
246
+ ? await stepProcessor.getLLMResponse({
247
+ platform: currentPlatform || "web",
248
+ type: promptType_js_1.PromptType.MULTICHANNEL_KEYWORD_EXTRACTOR,
249
+ args: {
250
+ priorAndNextSteps,
251
+ allSteps: context.allManualSteps,
252
+ previousPlatform: previousDetectedPlatform ?? null,
253
+ },
254
+ input: { currentStep: step },
255
+ })
256
+ : await stepProcessor.getLLMResponse({
257
+ platform: currentPlatform,
258
+ type: promptType_js_1.PromptType.KEYWORD_EXTRACTOR,
259
+ args: { priorAndNextSteps },
260
+ input: { currentStep: step },
261
+ });
262
+ if (isMultichannel) {
263
+ const firstStepPlatform = AutomationRunner.resolveDetectedPlatform(result.response?.steps?.[0]?.platform ?? result.response?.platform, previousDetectedPlatform);
264
+ currentPlatform = firstStepPlatform;
265
+ previousDetectedPlatform = firstStepPlatform;
266
+ context.activePlatform = firstStepPlatform;
267
+ context.request.platform = firstStepPlatform;
268
+ (0, platformContext_js_1.setPlatform)(firstStepPlatform);
269
+ }
236
270
  if (currentPlatform === "android" || currentPlatform === "ios") {
237
- if (!driver) {
271
+ let mobileDriver = null;
272
+ try {
273
+ mobileDriver = await context.mobileSession.getCurrentDriver();
274
+ }
275
+ catch {
276
+ mobileDriver = null;
277
+ }
278
+ if (!mobileDriver) {
238
279
  const initialKeywords = result.response?.keywords ||
239
280
  (Array.isArray(result.response?.steps) ? result.response.steps[0]?.keywords : undefined);
240
281
  await context.initializeMobileDriver(initialKeywords);
@@ -253,6 +294,15 @@ class AutomationRunner {
253
294
  const stepObj = stepsArray[i];
254
295
  const action = stepObj.action;
255
296
  const keywords = stepObj.keywords;
297
+ if (isMultichannel) {
298
+ const stepPlatform = AutomationRunner.resolveDetectedPlatform(stepObj.platform, previousDetectedPlatform);
299
+ currentPlatform = stepPlatform;
300
+ previousDetectedPlatform = stepPlatform;
301
+ context.activePlatform = stepPlatform;
302
+ context.request.platform = stepPlatform;
303
+ (0, platformContext_js_1.setPlatform)(stepPlatform);
304
+ await context.syncExecutorToActivePlatform();
305
+ }
256
306
  const currentStep = action?.toLowerCase() === "getotp" ? step : stepObj.step || step;
257
307
  let currentElementName = stepObj.elementName;
258
308
  let currentVariableName;
@@ -686,7 +736,6 @@ class AutomationRunner {
686
736
  const payload = {
687
737
  scriptName: AutomationRunner.getSessionName(request.testCaseId) ?? "SCRIPT_NAME",
688
738
  type: request.type,
689
- scriptType: request.type,
690
739
  projectId: request.projectId,
691
740
  testCaseId: request.testCaseId,
692
741
  pageDetails: request.pageDetails,
@@ -694,7 +743,6 @@ class AutomationRunner {
694
743
  licenseId: request.licenseId,
695
744
  userId: request.userId,
696
745
  projectType: request.projectType,
697
- platform: (0, platformSyntax_js_1.getPlatformSyntax)(currentPlatform),
698
746
  tokensConsumed: (await stepProcessor.getResultTokenUsage()).totalTokens,
699
747
  };
700
748
  this.KafkaPayload.topic = request.topic;
@@ -1,6 +1,7 @@
1
1
  export declare enum PromptType {
2
2
  USER_STORY_TO_LIST = "userStoryToList",
3
3
  KEYWORD_EXTRACTOR = "keywordExtractorPrompt",
4
+ MULTICHANNEL_KEYWORD_EXTRACTOR = "multichannelKeywordExtractorPrompt",
4
5
  ERROR_DESCRIPTION = "errorDescriptionPrompt",
5
6
  FF_INSPECTOR = "ffInspectorNumExtractor",
6
7
  VISION_PROMPT = "visionPrompt",
@@ -5,6 +5,7 @@ var PromptType;
5
5
  (function (PromptType) {
6
6
  PromptType["USER_STORY_TO_LIST"] = "userStoryToList";
7
7
  PromptType["KEYWORD_EXTRACTOR"] = "keywordExtractorPrompt";
8
+ PromptType["MULTICHANNEL_KEYWORD_EXTRACTOR"] = "multichannelKeywordExtractorPrompt";
8
9
  PromptType["ERROR_DESCRIPTION"] = "errorDescriptionPrompt";
9
10
  PromptType["FF_INSPECTOR"] = "ffInspectorNumExtractor";
10
11
  PromptType["VISION_PROMPT"] = "visionPrompt";
@@ -45,29 +45,40 @@ async function appendScriptData(args) {
45
45
  }
46
46
  if (args.elementName) {
47
47
  let locators;
48
- try {
49
- if (!searchAttribute) {
50
- throw new Error("searchAttribute is not found");
51
- }
52
- const ffElement = await args.elementGetter.getFireFlinkElement(args.pageDOM, searchAttribute, effectivePlatform);
53
- // locators = [{ name: "xpath", value: args.selector }];
54
- if (!ffElement?.locators || ffElement.locators.length === 0) {
55
- throw new Error("locators is not found");
56
- }
57
- else {
58
- locators = ffElement.locators;
59
- }
60
- }
61
- catch (error) {
62
- logData_js_1.logger.info(`Element is not found ${error.message}`);
48
+ const isJsPath = args.selector?.includes(".shadowRoot") || args.selector?.startsWith("document.");
49
+ if (isJsPath) {
63
50
  locators = [{
64
- name: "xpath",
65
- type: "static",
51
+ name: "JsPath",
52
+ type: "Static",
66
53
  value: args.selector,
67
54
  isRecorded: "N",
68
55
  isAiRecorded: "Y"
69
56
  }];
70
57
  }
58
+ else {
59
+ try {
60
+ if (!searchAttribute) {
61
+ throw new Error("searchAttribute is not found");
62
+ }
63
+ const ffElement = await args.elementGetter.getFireFlinkElement(args.pageDOM, searchAttribute, effectivePlatform);
64
+ if (!ffElement?.locators || ffElement.locators.length === 0) {
65
+ throw new Error("locators is not found");
66
+ }
67
+ else {
68
+ locators = ffElement.locators;
69
+ }
70
+ }
71
+ catch (error) {
72
+ logData_js_1.logger.info(`Element is not found ${error.message}`);
73
+ locators = [{
74
+ name: "xpath",
75
+ type: "static",
76
+ value: args.selector,
77
+ isRecorded: "N",
78
+ isAiRecorded: "Y"
79
+ }];
80
+ }
81
+ }
71
82
  data.elementsData = [{
72
83
  name: args.elementName,
73
84
  type: args.elementType,
@@ -1 +0,0 @@
1
- export {};
@@ -1,35 +1,34 @@
1
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
- // import { verify } from "node:crypto";
7
- const index_js_1 = require("../index.js");
8
- const path_1 = __importDefault(require("path"));
9
- const fs_1 = __importDefault(require("fs"));
10
- const certPath = path_1.default.join("D:", "Bitbucket", "ai-chat-backend", "Certificates", "V3keystore.jks");
11
- const ssl = {
12
- ca: [fs_1.default.readFileSync(certPath, "utf-8")],
13
- rejectUnauthorized: false,
14
- };
15
- const runner = new index_js_1.AutomationRunner();
16
- runner.run({
17
- // hasMoreSteps: false,
18
- userStory: [
19
- ["Launch the browser"],
20
- ["navigate to url https://www.indiamart.com"],
21
- ["wait for 20 seconds"],
22
- ["get otp from INDMRT message within 60 seconds and wait for 60 seconds"],
23
- ["Enter OTP into enter otp boxes"],
24
- ["wait for 10 seconds"],
25
- ["Close the browser"]
26
- ],
27
- scenario: false,
28
- sslConfig: ssl,
29
- scriptType: "android",
30
- broker: ["test3.fireflink.com:9092"], "scriptName": "script", "type": "Web & Mobile", "projectId": "PJT1006", "testCaseId": "SCR1806", kafkaMessageKey: 'lohit',
31
- "promptId": "a8fba64a-4d91-422c-bdfd-a7609576a63e", "generatedBy": "", "webSocketId": "", "licenseType": "C-Professional", "licenseId": "LIC2026980", "userId": "USR26860", "projectType": "Web", "serverData": "https://performance.fireflink.com", "pageDetails": { "Web": { "pageId": "PAG3e09350f-4917-4c80-8ce5-a08e97fe314e", "pageName": "ai-Page" } }, "userName": "jai", isTerminate: false, "appiumPort": 4723, "realDeviceId": "", "browserName": "Google Chrome", "token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJIUlpZV2NoTURZWXVQaDF3TDkxb3drS3Q1OUtuNC1sOFRJcXF3a2RBV2pjIn0.eyJleHAiOjE3ODA1NzkwNzEsImlhdCI6MTc4MDQ5MjY3MSwianRpIjoib25ydHJvOmYxYzJiNjg2LTJiZGMtNGIxOS04ZGVhLTMwMjhhNDkxZDY5NCIsImlzcyI6Imh0dHA6Ly8xNDguNzIuMTY4LjE2OjMwMTA3L3JlYWxtcy9GaXJlRmxpbmstdGVzdDEiLCJhdWQiOiJ0YXJnZXQtY2xpZW50Iiwic3ViIjoiZjo4ODkyOWIwMC03M2I4LTQ3NGYtYmJiYy1kYjJlMWEzZTA3OTg6amFpYWthc2hAeW9wbWFpbC5jb20iLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmbGluay1zZXJ2aWNlIiwic2lkIjoiZWRiMDgwZDctNzhiYi00NzU5LTgyM2EtMTM1ZDUzYTM5NmQzIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImN1cnJlbnRMaWNlbnNlSWQiOiJMSUMzOTIyIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJjdXJyZW50UHJpdmlsZWdlIjoiQWRtaW4iLCJmdWxsTmFtZSI6ImphaSIsImFjdGl2YXRpb25TdGF0dXMiOiJBQ1RJVkUiLCJwcml2aWxlZ2UiOiJBZG1pbiIsImxpY2Vuc2VOYW1lIjoiRmlyZUZsaW5rIC0gTElDMzkyMnciLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqYWkiLCJ1c2VyTmFtZSI6ImphaWFrYXNoQHlvcG1haWwuY29tIiwiYmlsbGluZ0N5Y2xlIjoiWWVhcmx5IiwiaWQiOiJVU1IyNjc1MiIsImxpY2Vuc2VJZCI6IkxJQzM5MjIiLCJlbWFpbCI6ImphaWFrYXNoQHlvcG1haWwuY29tIn0.LPBCnEKBi8Yi4Qktpd_oW46-P7s0iQ0AejZeXRMIa8aUwfwY8gNl3McIrKLBVqPL4NKYe_05aMaiepvjkolu7RWh120mX90ZdNJuMUjg0WuRaM90xNjmy1YK94mBt0mphKK04u1K4trvhvgW2hTt4rvGRq_oEvH2E_2YqYw310TuP-guqxuZqTv_IJ6HFtvMJkV9L3A579juICZfGdS53-KuZwqn7jA-73nJZBzPJyMucaPz42s36RWpDmfs7MX0-Fcv8LH7FvCjCs63GvCC-tdGoh5m0MRCiElJocWlw9jabsPHymEt5-nemnXIZIon2675wYbxVx99mHA1VBPD2w", "sessionPath": "session", "platform": "android", "platformVersion": "", "env": "test1",
32
- "serverHost": "https://sg-app.fireflink.com", "adbPath": "C:\\FireFlinkClient\\resources\\bin\\win\\android-sdk\\platform-tools\\adb.exe",
33
- topic: "test-topic",
34
- aiExecutionAction: ""
35
- });
2
+ // // import { verify } from "node:crypto";
3
+ // import { AutomationRunner } from "../index.js"
4
+ // import { Kafka, Producer } from "kafkajs";
5
+ // import path from "path";
6
+ // import fs from "fs";
7
+ // const certPath = path.join(
8
+ // "D:", "Bitbucket", "ai-chat-backend", "Certificates", "V3keystore.jks"
9
+ // );
10
+ // const ssl = {
11
+ // ca: [fs.readFileSync(certPath, "utf-8")],
12
+ // rejectUnauthorized: false,
13
+ // }
14
+ // const runner = new AutomationRunner();
15
+ // runner.run({
16
+ // // hasMoreSteps: false,
17
+ // userStory: [
18
+ // ["Launch the browser"],
19
+ // ["navigate to url https://www.indiamart.com"],
20
+ // ["wait for 20 seconds"],
21
+ // ["get otp from INDMRT message within 60 seconds and wait for 60 seconds"],
22
+ // ["Enter OTP into enter otp boxes"],
23
+ // ["wait for 10 seconds"],
24
+ // ["Close the browser"]
25
+ // ],
26
+ // scenario: false,
27
+ // sslConfig: ssl,
28
+ // scriptType: "android",
29
+ // broker: ["test3.fireflink.com:9092"], "scriptName": "script", "type": "Web & Mobile", "projectId": "PJT1006", "testCaseId": "SCR1806", kafkaMessageKey: 'lohit',
30
+ // "promptId": "a8fba64a-4d91-422c-bdfd-a7609576a63e", "generatedBy": "", "webSocketId": "", "licenseType": "C-Professional", "licenseId": "LIC2026980", "userId": "USR26860", "projectType": "Web", "serverData": "https://performance.fireflink.com", "pageDetails": { "Web": { "pageId": "PAG3e09350f-4917-4c80-8ce5-a08e97fe314e", "pageName": "ai-Page" } }, "userName": "jai", isTerminate: false, "appiumPort": 4723, "realDeviceId": "", "browserName": "Google Chrome", "token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJIUlpZV2NoTURZWXVQaDF3TDkxb3drS3Q1OUtuNC1sOFRJcXF3a2RBV2pjIn0.eyJleHAiOjE3ODA1NzkwNzEsImlhdCI6MTc4MDQ5MjY3MSwianRpIjoib25ydHJvOmYxYzJiNjg2LTJiZGMtNGIxOS04ZGVhLTMwMjhhNDkxZDY5NCIsImlzcyI6Imh0dHA6Ly8xNDguNzIuMTY4LjE2OjMwMTA3L3JlYWxtcy9GaXJlRmxpbmstdGVzdDEiLCJhdWQiOiJ0YXJnZXQtY2xpZW50Iiwic3ViIjoiZjo4ODkyOWIwMC03M2I4LTQ3NGYtYmJiYy1kYjJlMWEzZTA3OTg6amFpYWthc2hAeW9wbWFpbC5jb20iLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJmbGluay1zZXJ2aWNlIiwic2lkIjoiZWRiMDgwZDctNzhiYi00NzU5LTgyM2EtMTM1ZDUzYTM5NmQzIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImN1cnJlbnRMaWNlbnNlSWQiOiJMSUMzOTIyIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJjdXJyZW50UHJpdmlsZWdlIjoiQWRtaW4iLCJmdWxsTmFtZSI6ImphaSIsImFjdGl2YXRpb25TdGF0dXMiOiJBQ1RJVkUiLCJwcml2aWxlZ2UiOiJBZG1pbiIsImxpY2Vuc2VOYW1lIjoiRmlyZUZsaW5rIC0gTElDMzkyMnciLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqYWkiLCJ1c2VyTmFtZSI6ImphaWFrYXNoQHlvcG1haWwuY29tIiwiYmlsbGluZ0N5Y2xlIjoiWWVhcmx5IiwiaWQiOiJVU1IyNjc1MiIsImxpY2Vuc2VJZCI6IkxJQzM5MjIiLCJlbWFpbCI6ImphaWFrYXNoQHlvcG1haWwuY29tIn0.LPBCnEKBi8Yi4Qktpd_oW46-P7s0iQ0AejZeXRMIa8aUwfwY8gNl3McIrKLBVqPL4NKYe_05aMaiepvjkolu7RWh120mX90ZdNJuMUjg0WuRaM90xNjmy1YK94mBt0mphKK04u1K4trvhvgW2hTt4rvGRq_oEvH2E_2YqYw310TuP-guqxuZqTv_IJ6HFtvMJkV9L3A579juICZfGdS53-KuZwqn7jA-73nJZBzPJyMucaPz42s36RWpDmfs7MX0-Fcv8LH7FvCjCs63GvCC-tdGoh5m0MRCiElJocWlw9jabsPHymEt5-nemnXIZIon2675wYbxVx99mHA1VBPD2w", "sessionPath": "session", "platform": "android", "platformVersion": "", "env": "test1",
31
+ // "serverHost": "https://sg-app.fireflink.com", "adbPath": "C:\\FireFlinkClient\\resources\\bin\\win\\android-sdk\\platform-tools\\adb.exe",
32
+ // topic: "test-topic",
33
+ // aiExecutionAction: ""
34
+ // });
@@ -0,0 +1,2 @@
1
+ export declare function convertToCssSelector(pathFragment: string): string;
2
+ export declare function generateJsPath(selector: string, finalSelectorOverride?: string): string;
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertToCssSelector = convertToCssSelector;
4
+ exports.generateJsPath = generateJsPath;
5
+ //Converts XPath or FireFlink selector fragment to a clean CSS selector.
6
+ function convertToCssSelector(pathFragment) {
7
+ let s = pathFragment.trim().replace(/^\/+/, "");
8
+ // Remove redundant html/body prefix for top-level selectors
9
+ s = s.replace(/^html(\[\d+\])?\/body(\[\d+\])?\//i, "");
10
+ // Convert tag[n] -> tag or tag:nth-of-type(n)
11
+ s = s.replace(/([a-zA-Z0-9_-]+)\[(\d+)\]/g, (_, tag, index) => {
12
+ return index === "1" ? tag : `${tag}:nth-of-type(${index})`;
13
+ });
14
+ s = s.replace(/\//g, " > ");
15
+ return s;
16
+ }
17
+ //jspath generation
18
+ function generateJsPath(selector, finalSelectorOverride) {
19
+ if (typeof selector !== 'string' || selector.trim().length === 0) {
20
+ throw new TypeError('generateJsPath: "selector" must be a non-empty string.');
21
+ }
22
+ const SHADOW_TOKEN = '/shadow/';
23
+ const trimmedSelector = selector.trim();
24
+ if (trimmedSelector.startsWith(SHADOW_TOKEN)) {
25
+ throw new Error('generateJsPath: selector cannot start with "/shadow/" — a host element path is required before it.');
26
+ }
27
+ if (trimmedSelector.endsWith(SHADOW_TOKEN)) {
28
+ throw new Error('generateJsPath: selector cannot end with "/shadow/" — a path is required after it.');
29
+ }
30
+ const rawChunks = trimmedSelector
31
+ .split(SHADOW_TOKEN)
32
+ .map((c) => c.trim())
33
+ .filter((c) => c.length > 0);
34
+ if (rawChunks.length === 0) {
35
+ throw new Error('generateJsPath: no valid path segments found in selector.');
36
+ }
37
+ const cssChunks = rawChunks.map((chunk) => xpathChunkToCss(chunk));
38
+ if (typeof finalSelectorOverride === 'string' && finalSelectorOverride.trim().length > 0) {
39
+ cssChunks[cssChunks.length - 1] = finalSelectorOverride.trim();
40
+ }
41
+ let jsPath = `document.querySelector(${toQuotedSelector(cssChunks[0])})`;
42
+ for (let i = 1; i < cssChunks.length; i++) {
43
+ jsPath += `.shadowRoot.querySelector(${toQuotedSelector(cssChunks[i])})`;
44
+ }
45
+ return jsPath;
46
+ }
47
+ //Helpers
48
+ function toQuotedSelector(cssSelector) {
49
+ const cleanSelector = cssSelector.replace(/\[([\w\-:]+)\s*=\s*['"]([^'"]+)['"]\]/g, '[$1=$2]');
50
+ return `'${cleanSelector}'`;
51
+ }
52
+ //Nodes
53
+ function xpathChunkToCss(chunk) {
54
+ const { nodes, combinators } = tokenizeXPath(chunk);
55
+ if (nodes.length === 0) {
56
+ throw new Error(`generateJsPath: unable to parse path segment "${chunk}".`);
57
+ }
58
+ let css = xpathNodeToCss(nodes[0]);
59
+ for (let i = 1; i < nodes.length; i++) {
60
+ const combinator = combinators[i] === 'descendant' ? ' ' : ' > ';
61
+ css += combinator + xpathNodeToCss(nodes[i]);
62
+ }
63
+ return css;
64
+ }
65
+ /**
66
+ * Splits an XPath chunk into node segments plus the axis combinator
67
+ * ('child' | 'descendant') that precedes each node. Bracket contents are
68
+ * treated as opaque so a `/` inside a predicate does not cause an
69
+ */
70
+ function tokenizeXPath(path) {
71
+ const nodes = [];
72
+ const combinators = [];
73
+ let depth = 0;
74
+ let current = '';
75
+ let pendingCombinator;
76
+ let i = 0;
77
+ const pushCurrent = () => {
78
+ const trimmed = current.trim();
79
+ if (trimmed.length > 0 && trimmed !== '.') {
80
+ nodes.push(trimmed);
81
+ combinators.push(pendingCombinator);
82
+ }
83
+ current = '';
84
+ };
85
+ while (i < path.length) {
86
+ const ch = path[i];
87
+ if (ch === '[')
88
+ depth++;
89
+ if (ch === ']')
90
+ depth--;
91
+ if (ch === '/' && depth === 0) {
92
+ pushCurrent();
93
+ if (path[i + 1] === '/') {
94
+ pendingCombinator = 'descendant';
95
+ i += 2;
96
+ continue;
97
+ }
98
+ else {
99
+ pendingCombinator = 'child';
100
+ i += 1;
101
+ continue;
102
+ }
103
+ }
104
+ current += ch;
105
+ i += 1;
106
+ }
107
+ pushCurrent();
108
+ return { nodes, combinators };
109
+ }
110
+ /**
111
+ * Converts a single XPath node (e.g. `div[1]`, `input[@id='x']`,
112
+ * `span[contains(@class,'icon')]`) into an equivalent CSS selector
113
+ * fragment, always preserving positional predicates as `:nth-of-type()`.
114
+ */
115
+ function xpathNodeToCss(node) {
116
+ const match = node.match(/^(\*|[A-Za-z_][\w\-.:]*)((?:\[[^\]]*\])*)$/);
117
+ // Fallback: if the node doesn't match the expected shape, pass it
118
+ // through as-is rather than silently dropping information.
119
+ if (!match) {
120
+ return node;
121
+ }
122
+ const tag = match[1];
123
+ const predicatesStr = match[2] || '';
124
+ const predicates = predicatesStr.match(/\[[^\]]*\]/g) || [];
125
+ const attrSelectors = [];
126
+ let nthIndex = null;
127
+ for (const pred of predicates) {
128
+ const inner = pred.slice(1, -1).trim();
129
+ // [@attr='value'] or [@attr="value"]
130
+ const attrEqMatch = inner.match(/^@([\w\-:]+)\s*=\s*(['"])(.*)\2$/);
131
+ // contains(@attr, 'value')
132
+ const containsMatch = inner.match(/^contains\(\s*@([\w\-:]+)\s*,\s*(['"])(.*)\2\s*\)$/i);
133
+ // position()=n
134
+ const positionMatch = inner.match(/^position\(\)\s*=\s*(\d+)$/i);
135
+ if (attrEqMatch) {
136
+ const attrName = attrEqMatch[1];
137
+ const attrValue = escapeCssString(attrEqMatch[3]);
138
+ attrSelectors.push(`[${attrName}='${attrValue}']`);
139
+ }
140
+ else if (containsMatch) {
141
+ const attrName = containsMatch[1];
142
+ const attrValue = escapeCssString(containsMatch[3]);
143
+ attrSelectors.push(`[${attrName}*='${attrValue}']`);
144
+ }
145
+ else if (/^\d+$/.test(inner)) {
146
+ nthIndex = inner;
147
+ }
148
+ else if (positionMatch) {
149
+ nthIndex = positionMatch[1];
150
+ }
151
+ else if (/^last\(\)$/i.test(inner)) {
152
+ nthIndex = 'last';
153
+ }
154
+ // Unrecognized predicates (e.g. text()-based) are skipped: they have
155
+ // no reliable CSS equivalent and are not safe to guess at.
156
+ }
157
+ let css = tag;
158
+ css += attrSelectors.join('');
159
+ if (nthIndex === 'last') {
160
+ css += ':nth-last-of-type(1)';
161
+ }
162
+ else if (nthIndex) {
163
+ css += `:nth-of-type(${nthIndex})`;
164
+ }
165
+ return css;
166
+ }
167
+ function escapeCssString(value) {
168
+ return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
169
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ff-automationv2",
3
- "version": "2.2.34-beta.0",
3
+ "version": "2.2.34-beta.1",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "description": "This lib is used to automate the manual testcase",