ff-automationv2 2.1.1-beta.3 → 2.1.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.
@@ -2,8 +2,7 @@ image: node:20
2
2
 
3
3
  pipelines:
4
4
  branches:
5
-
6
- "feature/*":
5
+ "feature/automation_V2":
7
6
  - step:
8
7
  name: Publish Beta
9
8
  caches:
@@ -19,8 +18,7 @@ pipelines:
19
18
 
20
19
  - echo "Publishing as beta"
21
20
  - npm publish --tag beta --access public
22
-
23
- release:
21
+ "release/automation_V2":
24
22
  - step:
25
23
  name: Publish Production
26
24
  caches:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ff-automationv2",
3
- "version": "2.1.1-beta.3",
3
+ "version": "2.1.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "This lib is used to automate the manual testcase",
@@ -28,6 +28,7 @@
28
28
  "license": "ISC",
29
29
  "devDependencies": {
30
30
  "@eslint/js": "10.0.1",
31
+ "@types/js-beautify": "^1.14.3",
31
32
  "@types/jsdom": "27.0.0",
32
33
  "@types/node": "25.2.2",
33
34
  "eslint": "10.0.0",
@@ -43,6 +44,7 @@
43
44
  "cheerio": "1.2.0",
44
45
  "fs-extra": "11.3.3",
45
46
  "fuzzball": "2.2.3",
47
+ "js-beautify": "1.15.1",
46
48
  "jsdom": "27.4.0",
47
49
  "openai": "6.18.0",
48
50
  "uuid": "13.0.0",
@@ -7,63 +7,44 @@ export async function keywordExtractor(
7
7
  { priorAndNextSteps }: keywordExtractor
8
8
  ): Promise<string> {
9
9
 
10
- const allowedActions: string[] = [
11
- "enter",
12
- "wait",
13
- "verify",
14
- "scroll",
15
- "navigate",
16
- "click",
17
- "maximize",
18
- "get",
19
- "upload",
20
- "close",
21
- "open",
22
- "drag_and_drop",
23
- "switch"
24
- ];
10
+ const allowedActions: string[] = ["enter", "wait", "verify", "scroll", "navigate", "click", "maximize", "get", "upload", "close", "open", "drag_and_drop", "switch", "cleartext"];
25
11
 
26
12
  const prompt = `
27
13
  You are an expert in Web application testing.
28
- From the step: step, extract ONLY the meaningful keywords so that i can search for the element in the dom.
29
-
30
- Rules:
31
- - Only give response for the current step .
32
- - understand the step and context from the ${JSON.stringify(priorAndNextSteps)} and keywords should be from step.
33
- - 3 to 5 keywords maximum.
34
- - If the step is about entering text or Uploading file, Should NOT include input value from the step into keywords.
35
- - If the step has words like tag name audio, video, image, svg, checkbox etc, include them in the keywords.
36
- - If icon is mentioned in step then 'svg' should add in keywords and for Upload action first keyword should be 'file'.
37
- - First keyword should be from step. Next keywords must be distinct and based on element label meaning only.
38
- - If keyword is two words, then create an additional keyword by combining them.
39
- Example: "Sign In" "Sign In", "SignIn"
40
- - Keywords can be string or number.
41
- - Do NOT include generic UI words or action words.
42
- - Do NOT include status or technical words.
43
- - If element label contains multiple words, keep them together as ONE keyword.
44
- - element_name: extract name of the element from step.
45
- Capitalize first letter.
46
- If not mentioned, return action as element_name.
47
- - action rules:
48
- click, enter, wait, scroll, navigate, get, maximize, close, open,
49
- upload, drag_and_drop, switch
50
- - action must be one of ${JSON.stringify(allowedActions)}.
51
- If not, return "0".
52
- - For navigate:
53
- - keywords must contain ONLY full URL
54
- - element_name must be "URL"
55
- - navigate allowed ONLY if step intent is purely navigation
56
- - If verification intent exists, MUST return "verify"
57
-
58
- Respond only with JSON using this format:
59
- {
60
- "keywords": [],
61
- "elementName": "",
62
- "elementType":""
63
- "action": ""
64
- }
65
-
66
- No other text.
14
+ From the step, extract ONLY the meaningful keywords so that i can search for the element in the dom.
15
+ Rules:
16
+ - Only give response for the current step.
17
+ - understand the step and context from the ${JSON.stringify(priorAndNextSteps)}.and keywords should be from step. it should not be related to other steps.
18
+ - 3 to 5 keywords maximum.
19
+ - If the step is about entering text or Uploading file, Should NOT include input value from the step into keywords.
20
+ - If the step has words like tag name audio, video, image,svg, checkbox etc, include them in the keywords.
21
+ - If icon is metioned in step than 'svg' should add in keywords and for Upload action first keyword should be 'file'.
22
+ - First keywords should be from step next Keywords must be distinct and based on the element's label meaning only.
23
+ - If a keyword contains exactly two words, you MUST always include both the spaced and concatenated (no-space) versions; omission of either is invalid.
24
+ - Do NOT split single-word keywords and do NOT include relation terms (above, below, next to, etc.) in keywords.
25
+ - Treat each keyword independently—never merge different keywords or combine them with relation words.
26
+ - Example: Click on Sign In above Create Account button → ["Sign In", "SignIn", "Create Account", "CreateAccount"]
27
+ - Keywords can be string or number if the step contains a number ,add it also as keyword.
28
+ - Do Not include any other unrelated keywords for step.
29
+ - Do NOT include generic UI words (button, field, etc) and action words (tap, click, press, etc).
30
+ - Do NOT include status/technical words (displayed, enabled, authenticate, visible).
31
+ - If an element label contains multiple words (e.g., "Sign In", "Add to Cart"), keep them together as ONE keyword and do not split them and also for keywords you generated, do not split them.
32
+ - element_name: extract name of the element that mentioned in the the step.(eg:tap on x -> element_name:x) keep element_name as short as possible and make the first letter of first word of the element_name as capital. beacuse element_name is also used to find element in the dom. and if element_name is not mentioned in step than return action of the step as element_name.
33
+ - action: click for taping, clicking or selecting, enter for entering input, wait for waiting or sleeping,scroll for scrolling and swiping, navigate for navigating to page using url or navigateing back to previous page, get for getting,fetching element,maximize for maximizing browser window, close for closing browser window,open for opening browser window, upload for uploading file using path, drag_and_drop for dragging and dropping element, switch is for switching to tab or window or frame,cleartext for clearing or removing text from element.
34
+ - action must be one of from this list ${JSON.stringify(allowedActions)}.if not one of them, return '0'. if step about set or find action return '0'
35
+ - For navigate action, keywords should contain only one keyword which is full url from the step and should not include any other text. and if step has another actions, including navigate action, don't return navigate action return another action witch is in the step. and element_name should be "URL".
36
+ Navigate action is allowed only when the step intent is purely navigation (e.g., "Navigate to URL").
37
+ If the step includes verification intent (verify/check/confirm/etc), you MUST return "verify" and MUST NOT return "navigate".
38
+ only give navigate action if step has only navigate action.
39
+
40
+ Respond only with JSON using this format:
41
+ {
42
+ "keywords": [key1,key2,key3,key4,key5],
43
+ "element_name": "x",
44
+ "action": "x"
45
+ }
46
+
47
+ No other text.
67
48
  `;
68
49
 
69
50
  return prompt;
@@ -26,8 +26,8 @@ export async function ffInspectorNumExtractor({
26
26
  "WaitTillAlertIsPresent",
27
27
  "VerifyTextPresentOnAlertPopup"
28
28
  ];
29
- const elementType = ["button", "link"]
30
-
29
+ const elementType = ['link', 'textfield', 'icon', 'button', 'radioButton', 'text', 'textarea', 'image', 'dropdown', 'checkbox', 'tab', 'action overflow button', 'hamburger icon', 'toggle button', 'suggestion', 'time picker', 'date picker', 'toaster message', 'card', 'tooltip', 'option', 'calender', 'sliders', 'visual testing'];
30
+ const enterActions = ["enter", "clearandenter"];
31
31
  let prompt;
32
32
 
33
33
  // ---------------- ALERT ----------------
@@ -123,75 +123,50 @@ Return ONLY valid JSON:
123
123
  // ---------------- DEFAULT (CLICK / ENTER / UPLOAD etc.) ----------------
124
124
  else {
125
125
  prompt = `
126
- You are a deterministic UI action extraction engine.
127
-
128
- OBJECTIVE:
129
- Identify the SINGLE best matching element from the Simplified JSON
130
- for the given step and return structured automation data.
131
-
132
- -----------------------------------------
133
-
134
- CONTEXT STEPS:
135
- ${JSON.stringify(priorAndNextSteps)}
136
-
137
- CLICK ACTION WORDS:
138
- ${JSON.stringify(clickActions)}
139
-
140
- ALLOWED ELEMENT TYPES:
141
- ${elementType}
142
- -----------------------------------------
143
-
144
- CRITICAL MATCHING LOGIC:
145
-
146
- 1. First understand intent:
147
- - click words → click
148
- - enter/type/write → enter_text
149
- - upload → upload
150
- - drag_and_drop → drag_and_drop
151
-
152
- 2. For ENTER actions:
153
- DO NOT pick first input blindly.
154
- Score candidates using priority:
155
-
156
- Priority Order:
157
- (1) Exact label text match
158
- (2) Placeholder match
159
- (3) Name attribute semantic match
160
- (4) ID semantic match
161
- (5) type="email" if step contains "email"
162
- (6) DOM sibling label relationship
163
-
164
- Choose highest scoring match.
165
-
166
- If NO strong semantic match → return:
167
- attribute_value = "Fire-Flink-0"
168
-
169
- 3. For CLICK actions:
170
- - Prefer exact text match
171
- - If multiple, choose closest contextual match
172
- - If icon + svg exists, prefer svg element
173
-
174
- 4. Never hallucinate.
175
- 5. Only use FF values from provided JSON.
176
- 6. No null values. Use empty string "".
177
- 7. Output must be valid JSON only.
178
-
179
- -----------------------------------------
180
- SIMPLIFIED JSON:
181
- ${extractedDomJson}
182
- -----------------------------------------
183
-
184
- Respond ONLY with JSON:
185
126
 
127
+ -You are an intelligent assistant that extracts structured UI action data.
128
+ -Given a structured UI JSON representation with uniquely identified elements (ff-inspect values like Fire-Flink-1, Fire-Flink-2, Fire-Flink-3... in DOM order),
129
+ -locate the most appropriate element for an automation step by performing keyword-based matching using exact, partial, and fuzzy strategies, and
130
+ return the identifier of the best match.
131
+
132
+ Return **only valid JSON** in the following format:
186
133
  {
187
- "attribute_value": "Fire-Flink-x",
188
- "action": "click | enter | upload | drag_and_drop",
189
- "input_text": "",
190
- "keyword": "",
191
- "num_of_scrolls": "0",
192
- "direction": "down",
193
- "elementType": ""
134
+ "attribute_value": "Fire-Flink-x",
135
+ "action": "x",
136
+ "input_text": "x",
137
+ "keyword": "x",
138
+ "num_of_scrolls": "0",
139
+ "direction": "down",
140
+ "element_type": "x"
194
141
  }
142
+
143
+ Rules:
144
+ You are an AI assistant. For the step, extract the element keyword from the step (the field name like 'Leaving from', 'Going To').
145
+ - Use context from the ${JSON.stringify(priorAndNextSteps)}, keyword and filtered dom to search for FF-inspecter.
146
+ - **Find the FF-inspecter attribute value of the element in the Simplified JSON whose text or attributes best match the atep.**
147
+ - If multiple matches exist, use the reference element and choose the closest match by ff-inspect distance.
148
+ **important example: step: "click on login below user login"
149
+ simplified json:
150
+ login - ff-inspect-300
151
+ user login - ff-inspect-790
152
+ login - ff-inspect-803
153
+ then should return login - ff-inspect-803 witch is near to user reference element user login - ff-inspect-790**
154
+ - Respect direction (up/down), default is down.
155
+ - Do not return elements far away from the reference.
156
+ - Select the closest semantic match to step and return only its attribute_value, else Fire-Flink-0.
157
+ - Action: ${clickActions} for clicking or selecting, ${enterActions} for entering text, 'upload' for uploading file, 'drag_and_drop' for dragging and dropping, 'cleartext' for clearing text.
158
+ - For click action, if only step involving 'clicking on icon' then try to return related svg tags attribute_value, if they are available in simplified json.
159
+ - For Action ${enterActions} extract input text from step.
160
+ - **For ${enterActions} actions, never directly select a input tag if should be realted to step; if u can't find input tag related to step then prefer the closest label/span/div/b/i etc, it can be any tag related to the step.**
161
+ - **For ${enterActions} step if you can't find perfect matching element return attribute_value as Fire-Flink-0 for the step. it should be exact match for the step.**
162
+ - For ${enterActions} step, If the step implies autogenerated data or random data(email, phone, credentials, identifiers, etc.), generate a valid dummy input_text suitable for the field.
163
+ example: step: "Enter sss.@gmail.com in email field" and step :
164
+ you should check only for email in Simplified JSON and input_text should be "sss.@gmail.com" dont add any extra text.
165
+ - For Action "upload" extract file path from step.
166
+ - if u cant find any input_text or any other dont give null just return them "" empty.
167
+ - Based on step give most relevant type of element. use this list to choose element_type: ${elementType} and Never change syntax of element_type, follow the syntax of element_type in list.if element_type is not there in list return 'link'.
168
+ Simplified JSON: ${extractedDomJson}
169
+
195
170
  `;
196
171
  }
197
172
  return prompt;
@@ -40,7 +40,7 @@ export class AutomationRunner implements IAutomationRunner {
40
40
  this.implicit
41
41
  );
42
42
  const domProcessor = new DomProcessingEngine();
43
- let stepCount = 0;
43
+ let stepCount = 1;
44
44
  const listOfSteps = stepResult.response.manualSteps
45
45
  if (listOfSteps.length === 0) {
46
46
  throw new Error("No executable manual steps were returned by the LLM.");
@@ -57,12 +57,14 @@ export class AutomationRunner implements IAutomationRunner {
57
57
  input: { currentStep: step }
58
58
  });
59
59
 
60
+ logger.info(JSON.stringify(result, null, 2))
61
+
60
62
  const action = result.response.action?.toLowerCase();
61
63
  const handler = actionHandlers[action];
62
64
 
63
65
  logger.info
64
66
  (
65
- `Processing step: "${step}" with action: "${action}" and keywords: ${JSON.stringify(result.response.keywords)}`
67
+ `Processing step ${stepCount}: "${step}" with action: "${action}" and keywords: ${JSON.stringify(result.response.keywords)}`
66
68
  );
67
69
 
68
70
  if (!handler) {
@@ -93,15 +95,19 @@ export class AutomationRunner implements IAutomationRunner {
93
95
  continue;
94
96
  }
95
97
 
98
+
96
99
  const browser = await context.session.getCurrentBrowser();
97
100
  domInfo = await getAnnotatedDOM(browser);
101
+ await logger.saveDOM(domInfo.dom, `annotated-dom-${stepCount}`);
98
102
 
99
103
  extractedRelevantDom = domProcessor.process({
100
104
  keywords: result.response.keywords,
101
105
  rawDom: domInfo.dom,
102
- stepIndex: stepCount
106
+ stepCount: stepCount
103
107
  });
104
108
 
109
+ await logger.saveJSON(extractedRelevantDom, `relevant-dom-${stepCount}`);
110
+
105
111
  const stepResult = await stepProcessor.getLLMResponse({
106
112
  type: PromptType.FF_INSPECTOR,
107
113
  args: {
@@ -114,12 +120,17 @@ export class AutomationRunner implements IAutomationRunner {
114
120
  input: { currentStep: step }
115
121
  });
116
122
 
123
+ logger.info(JSON.stringify(stepResult, null, 2))
124
+
117
125
  const fireflinkIndex = stepResult.response.attribute_value;
118
126
  const xpath = domInfo.selectors[fireflinkIndex];
119
127
 
120
128
  if (!xpath) {
121
129
  throw new Error(`Unable to resolve xpath for ${fireflinkIndex}`);
122
130
  }
131
+ else if (fireflinkIndex == 0) {
132
+ throw new Error(`Unable to find element for ${step}`);
133
+ }
123
134
 
124
135
  await handler({
125
136
  selector: xpath,
@@ -1,12 +1,15 @@
1
1
  import { DomSimplifier } from "./simplifyAndFlatten.js";
2
2
  import { DomSearcher } from "./searchBest.js";
3
3
  import { DomRelationshipBuilder } from "./relativeElementsFromDom.js";
4
+ import { logger } from "../utils/logger/logData.js";
5
+
4
6
  export interface DomProcessingRequest {
5
7
  keywords: string[] | string;
6
8
  rawDom: string;
7
- stepIndex: number;
9
+ stepCount: number;
8
10
  }
9
11
 
12
+
10
13
  export class DomProcessingEngine {
11
14
 
12
15
  private simplifier = new DomSimplifier();
@@ -16,6 +19,7 @@ export class DomProcessingEngine {
16
19
  public process(request: DomProcessingRequest) {
17
20
 
18
21
  const flat = this.simplifier.simplify(request.rawDom);
22
+ logger.saveJSON(flat, `flat-dom-${request.stepCount}`);
19
23
  const searched = this.searcher.search(flat, request.keywords);
20
24
  const structured = this.relationshipBuilder.build(searched);
21
25
 
@@ -123,13 +123,19 @@ export class DomSearcher {
123
123
  }
124
124
 
125
125
  if (collected.length < topN * 4) {
126
- fuzzyList.sort((a, b) => b[0] - a[0])
127
- .forEach(([_, idx]) => addWithContext(idx, collected));
126
+ fuzzyList
127
+ .sort((a, b) => b[0] - a[0])
128
+ .forEach(([_, idx]) => {
129
+ if (collected.length < topN * 4) {
130
+ addWithContext(idx, collected);
131
+ }
132
+ });
128
133
  }
129
134
 
130
135
  if (collected.length < topN * 4) {
131
136
  for (const idx of tagMatches) {
132
137
  addWithContext(idx, collected);
138
+ if (collected.length >= topN * 4) break;
133
139
  }
134
140
  }
135
141
 
@@ -1,12 +1,18 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
+ import beautify from "js-beautify";
3
4
 
4
5
  const logsDir = path.resolve(process.cwd(), "logs");
6
+ const domDir = path.resolve(process.cwd(), "dom");
5
7
 
6
8
  if (!fs.existsSync(logsDir)) {
7
9
  fs.mkdirSync(logsDir, { recursive: true });
8
10
  }
9
11
 
12
+ if (!fs.existsSync(domDir)) {
13
+ fs.mkdirSync(domDir, { recursive: true });
14
+ }
15
+
10
16
  const logFilePath = path.join(logsDir, "ai-execution-logs.txt");
11
17
 
12
18
  export const logger = {
@@ -32,5 +38,44 @@ export const logger = {
32
38
  const message = `[ERROR] ${new Date().toISOString()} - ${formattedArgs.join(" ")}\n`;
33
39
 
34
40
  fs.appendFileSync(logFilePath, message, "utf-8");
41
+ },
42
+
43
+ saveDOM: (domContent: string, fileName?: string) => {
44
+
45
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
46
+ const finalFileName = fileName
47
+ ? `${fileName}.html`
48
+ : `dom-${timestamp}.html`;
49
+
50
+ const filePath = path.join(domDir, finalFileName);
51
+
52
+ const formattedHTML = beautify.html(domContent, {
53
+ indent_size: 2,
54
+ preserve_newlines: false,
55
+ wrap_line_length: 120
56
+ });
57
+
58
+ fs.writeFileSync(filePath, formattedHTML, "utf-8");
59
+ },
60
+
61
+ saveJSON: (data: unknown, fileName?: string) => {
62
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
63
+
64
+ const finalFileName = fileName
65
+ ? `${fileName}.json`
66
+ : `data-${timestamp}.json`;
67
+
68
+ const filePath = path.join(domDir, finalFileName);
69
+
70
+ fs.writeFileSync(
71
+ filePath,
72
+ JSON.stringify(data, null, 2),
73
+ "utf-8"
74
+ );
75
+
76
+
35
77
  }
78
+
79
+
36
80
  };
81
+