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.
- package/dist/ai/llmprompts/llmPromptTypes/systemPromptTypes.d.ts +2 -0
- package/dist/ai/llmprompts/promptRegistry.js +4 -0
- package/dist/ai/llmprompts/systemPrompts/multichannelKeywordExtractor.d.ts +6 -0
- package/dist/ai/llmprompts/systemPrompts/multichannelKeywordExtractor.js +86 -0
- package/dist/ai/llmprompts/userPrompts/userPrompt.js +1 -0
- package/dist/automation/actions/interaction/shadowDom/shadowDomUpload.js +88 -10
- package/dist/automation/actions/interface/FireFlinkResponseSchema.d.ts +30 -0
- package/dist/automation/actions/interface/FireFlinkResponseSchema.js +8 -1
- package/dist/core/interfaces/executionDetails.d.ts +2 -1
- package/dist/core/interfaces/fireflinkScriptPayloadInterface.d.ts +1 -3
- package/dist/core/interfaces/promptInterface.d.ts +5 -0
- package/dist/core/main/actionHandlerFactory.js +3 -3
- package/dist/core/main/executionContext.d.ts +2 -0
- package/dist/core/main/executionContext.js +18 -3
- package/dist/core/main/runAutomationScript.d.ts +2 -0
- package/dist/core/main/runAutomationScript.js +65 -17
- package/dist/core/types/promptType.d.ts +1 -0
- package/dist/core/types/promptType.js +1 -0
- package/dist/fireflinkData/fireflinkScript/appendScriptData.js +28 -17
- package/dist/tests/test12.d.ts +0 -1
- package/dist/tests/test12.js +33 -34
- package/dist/utils/JsPathGenerator/generateJsPath.d.ts +2 -0
- package/dist/utils/JsPathGenerator/generateJsPath.js +169 -0
- package/package.json +1 -1
|
@@ -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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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: "
|
|
19
|
-
stepInputs: [
|
|
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("
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
24
|
+
this.activePlatform = (request.platform?.toLowerCase() || "web");
|
|
24
25
|
}
|
|
25
26
|
async initializeMobileDriver(caps) {
|
|
26
|
-
const isIOS = this.
|
|
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.
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
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 =
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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: "
|
|
65
|
-
type: "
|
|
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,
|
package/dist/tests/test12.d.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/tests/test12.js
CHANGED
|
@@ -1,35 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
// import
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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,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
|
+
}
|