ff-automationv2 2.2.32-beta.0 → 2.2.32-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.
@@ -5,31 +5,41 @@ async function fileVerifyActionExtractorPrompt({ extractedDomJson, priorAndNextS
5
5
  const nlpList = `VerifyPDF,CompareTwoPdf,VerifyPDFExist,CheckAccessibilityForPdfUsingPython,CheckIfPdfIsXfaFrom,VerifyElementIsDisplayed,VerifyElementIsNotDisplayed,VerifyElementIsEnabled,VerifyElementIsDisabled,VerifyElementIsClickable,VerifyNavigateURL,VerifyIfLinkIsWorking`;
6
6
  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'];
7
7
  const prompt = `You are an intelligent assistant that extracts structured UI action data for file and element verification.
8
- Given the step, the simplified DOM: ${extractedDomJson}, and the list of NLP names: ${nlpList} don't provide the other nlp names which are not present in the list in any manner.
8
+ Given the step, the simplified DOM: ${extractedDomJson}, and the list of NLP names: ${nlpList} don't provide the other nlp names which are not present in the list in any manner.
9
9
 
10
- Select the perfect matching NLP name from the list that best fits the step's intent.
11
- **The NLP name must match exactly one value from the approved NLP list. Do not generate or suggest any new NLP names under any condition.**
12
- step may give any way, you need to get most matching and most related nlp name from the list that relates to step.
10
+ Select the perfect matching NLP name from the list that best fits the step's intent.
11
+ **The NLP name must match exactly one value from the approved NLP list. Do not generate or suggest any new NLP names under any condition.**
12
+ step may give any way, you need to get most matching and most related nlp name from the list that relates to step.
13
13
 
14
- - Return ONLY a valid JSON object; the response must start with '{' and end with '}', with no markdown, code fences, explanations, or extra text.
15
- Return **only valid JSON** in the following format:
16
- {
17
- "attributeValue": "Fire-Flink-x",
18
- "nlpName": "x",
19
- "inputText": "x",
20
- "elementType": "x"
21
- }
14
+ - Return ONLY a valid JSON object; the response must start with '{' and end with '}', with no markdown, code fences, explanations, or extra text.
15
+ Return **only valid JSON** in the following format:
16
+ {
17
+ "attributeValue": "Fire-Flink-x",
18
+ "nlpName": "x",
19
+ "inputText": "x",
20
+ "elementType": "x",
21
+ "isDownload": true,
22
+ "path": null
23
+ }
22
24
 
23
- Rules:
24
- - PDF steps: if step specifies checking/verifying text, content, or phrases inside a PDF → choose VerifyPDF and extract the expected text into inputText | if step only verifies the presence, creation, or download of the file without checking inner text → choose VerifyPDFExist with inputText: "".
25
- - nlpName must be exactly one from the provided list.
26
- - While matching nlpName with step (ignore spaces, case differences). If multiple NLPs are similar, choose the MOST SPECIFIC and EXACT match.
27
- - Use context from the steps: ${priorAndNextSteps}, keyword and json to search for FF-inspecter.
28
- - **Find the FF-inspecter attribute value of the element in the Simplified JSON whose text or any other attributes best match the step in the simplified DOM.**
29
- - If no matching element found for the step or if step is file/pdf verification, return attributeValue as 'Fire-Flink-0'.
30
- - Extract inputText from the step that is being verified (e.g. expected text to verify in PDF or element). If no input text in step, return keyword or empty string "" as inputText.
31
- - Based on step give most relevant type of element. Use this list to choose elementType: ${elementType}. If elementType is not there in list return 'link'.
32
- - **Respond with valid JSON only. don't return any other text or don't return response in list format**
33
- `;
25
+ Rules:
26
+ - PDF steps: if step specifies checking/verifying text, content, or phrases inside a PDF → choose VerifyPDF and extract the expected text into inputText | if step only verifies the presence, creation, or download of the file without checking inner text → choose VerifyPDFExist with inputText: "".
27
+ - nlpName must be exactly one from the provided list.
28
+ - While matching nlpName with step (ignore spaces, case differences). If multiple NLPs are similar, choose the MOST SPECIFIC and EXACT match.
29
+ - Use context from the steps: ${priorAndNextSteps}, keyword and json to search for FF-inspecter.
30
+ - **Find the FF-inspecter attribute value of the element in the Simplified JSON whose text or any other attributes best match the step in the simplified DOM.**
31
+ - If no matching element found for the step or if step is file/pdf verification, return attributeValue as 'Fire-Flink-0'.
32
+ - **inputText rules**: Extract ONLY the expected text phrase to verify inside the PDF or element. Strip any wrapping quotes (' or "). **NEVER include the file path or file name in inputText**. If no text is being verified, return "".
33
+ - Based on step give most relevant type of element. Use this list to choose elementType: ${elementType}. If elementType is not there in list return 'link'.
34
+ - **isDownload & path rules**:
35
+ - If the step contains both a file path and expected text (e.g. "Verify existing PDF contains text 'path/to/doc.pdf',Expected Text" or "Verify PDF 'path/to/doc.pdf' contains 'Expected Text'"):
36
+ • Extract the file path into "path" (stripped of surrounding quotes).
37
+ • Extract ONLY the search phrase into "inputText" (do NOT include the file path in inputText).
38
+ • Set isDownload: false.
39
+ - If verifying an existing file at a specified path or location (e.g. contains drive letter C:, /, or file extension .pdf) → set isDownload: false and path to the extracted file path string.
40
+ - If the step is about verifying a file downloaded via the browser (e.g. "verify downloaded pdf", "verify pdf", or no explicit file path is given) → set isDownload: true and path: null.
41
+ - **path formatting**: Extract the file path from the step and add extra //// for path like "C:////Users/////User/////Downloads/////file.pdf" (strip surrounding quotes).
42
+ - **Respond with valid JSON only. don't return any other text or don't return response in list format**
43
+ `;
34
44
  return prompt;
35
45
  }
@@ -377,5 +377,5 @@ export declare class ActionExecutor implements IActionExecutor {
377
377
  otp: string;
378
378
  regex: string;
379
379
  } | null>, returnTypeVariableName?: string): Promise<void>;
380
- verifyPDF(value: string): Promise<void>;
380
+ verifyPDF(value: string, isDownload?: boolean, filePath?: string | null): Promise<void>;
381
381
  }
@@ -6692,11 +6692,13 @@ class ActionExecutor {
6692
6692
  throw error;
6693
6693
  }
6694
6694
  }
6695
- async verifyPDF(value) {
6695
+ async verifyPDF(value, isDownload = true, filePath = null) {
6696
6696
  try {
6697
6697
  await (0, VerifyPDF_js_1.verifyPDF)({
6698
6698
  driver: this.driver,
6699
6699
  value,
6700
+ isDownload,
6701
+ filePath,
6700
6702
  platform: this.platform,
6701
6703
  scriptDataAppender: this.scriptDataAppender
6702
6704
  });
@@ -6,166 +6,117 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.verifyPDF = verifyPDF;
7
7
  const appendScriptData_js_1 = require("../../../../fireflinkData/fireflinkScript/appendScriptData.js");
8
8
  const logData_js_1 = require("../../../../utils/logger/logData.js");
9
+ const pdfDownloadWatcher_js_1 = require("../../../../utils/downloadWatcher/pdfDownloadWatcher.js");
10
+ const pdfTextExtractor_js_1 = require("../../../../utils/textExtractor/pdfTextExtractor.js");
9
11
  const fs_1 = __importDefault(require("fs"));
10
12
  const path_1 = __importDefault(require("path"));
11
- //Normalizing
12
- function normalizeText(text) {
13
- return text.replace(/\s+/g, " ").trim();
14
- }
15
- //Extracting
16
- async function extractTextFromPdf(buffer) {
13
+ async function verifyPDF(args) {
14
+ let calculatedDuration = 2;
15
+ let fileName;
17
16
  try {
18
- const dynamicImport = new Function("modulePath", "return import(modulePath)");
19
- const pdfjs = await dynamicImport("pdfjs-dist/legacy/build/pdf.mjs").catch(() => dynamicImport("pdfjs-dist"));
20
- const data = new Uint8Array(buffer);
21
- const loadingTask = pdfjs.getDocument({
22
- data,
23
- useSystemFonts: true,
24
- disableFontFace: true,
25
- });
26
- const pdfDoc = await loadingTask.promise;
27
- const pageTexts = [];
28
- for (let i = 1; i <= pdfDoc.numPages; i++) {
29
- const page = await pdfDoc.getPage(i);
30
- const textContent = await page.getTextContent();
31
- const pageString = textContent.items
32
- .map((item) => ("str" in item ? item.str : ""))
33
- .join(" ");
34
- pageTexts.push(pageString);
17
+ let buffer;
18
+ if (args.isDownload) {
19
+ // isDownload = true → poll downloadDir until a PDF lands
20
+ const fileInfo = await (0, pdfDownloadWatcher_js_1.waitForDownloadComplete)(logData_js_1.downloadDir);
21
+ calculatedDuration = fileInfo.durationSeconds;
22
+ fileName = fileInfo.file.name;
23
+ buffer = fs_1.default.readFileSync(fileInfo.file.fullPath);
24
+ await logData_js_1.logger.info("VerifyPDF: found latest file", { file: fileName, dir: logData_js_1.downloadDir, durationSeconds: fileInfo.durationSeconds });
35
25
  }
36
- return pageTexts.join("\n");
37
- }
38
- catch (err) {
39
- logData_js_1.logger.error("pdfjs-dist extraction failed, falling back to raw buffer string scan", err);
40
- return buffer.toString("latin1");
41
- }
42
- }
43
- //Downloading time
44
- const DOWNLOAD_TIMEOUT_MS = 30000;
45
- const POLL_INTERVAL_MS = 400;
46
- const STABILITY_WAIT_MS = 250;
47
- async function waitForDownloadComplete(tempFileDir, timeoutMs = DOWNLOAD_TIMEOUT_MS) {
48
- const startTime = Date.now();
49
- while (Date.now() - startTime < timeoutMs) {
50
- if (fs_1.default.existsSync(tempFileDir)) {
51
- const allEntries = fs_1.default.readdirSync(tempFileDir);
52
- let hasActiveDownload = false;
53
- const pdfFiles = [];
54
- // Single pass: early exit if active download is present, stat only PDF files
55
- for (const file of allEntries) {
56
- const lower = file.toLowerCase();
57
- if (lower.endsWith(".crdownload") || lower.endsWith(".part") || lower.endsWith(".tmp")) {
58
- hasActiveDownload = true;
59
- break; // Download still in progress, skip remaining work this cycle
60
- }
61
- if (lower.endsWith(".pdf")) {
62
- const fullPath = path_1.default.join(tempFileDir, file);
63
- try {
64
- const stat = fs_1.default.statSync(fullPath);
65
- if (stat.isFile()) {
66
- pdfFiles.push({ name: file, fullPath, mtime: stat.mtimeMs, size: stat.size });
67
- }
68
- }
69
- catch {
70
- // File locked or in transition, skip for this cycle
71
- }
72
- }
26
+ else {
27
+ // isDownload = false → use the explicit path directly
28
+ if (!args.filePath) {
29
+ throw new Error("VerifyPDF: isDownload is false but no filePath was provided.");
73
30
  }
74
- if (!hasActiveDownload && pdfFiles.length > 0) {
75
- pdfFiles.sort((a, b) => b.mtime - a.mtime);
76
- const latest = pdfFiles[0];
77
- if (latest.size > 0) {
78
- await new Promise(res => setTimeout(res, STABILITY_WAIT_MS));
79
- try {
80
- const newStat = fs_1.default.statSync(latest.fullPath);
81
- if (newStat.size === latest.size && newStat.size > 0) {
82
- const durationSeconds = Math.max(1, Math.ceil((Date.now() - startTime) / 1000));
83
- return { file: latest, durationSeconds };
84
- }
85
- }
86
- catch {
87
- // Retry on next cycle
88
- }
89
- }
31
+ const normalizedPath = path_1.default.normalize(args.filePath);
32
+ if (!fs_1.default.existsSync(normalizedPath)) {
33
+ throw new Error(`VerifyPDF: file not found at path: ${normalizedPath}`);
90
34
  }
35
+ buffer = fs_1.default.readFileSync(normalizedPath);
36
+ fileName = normalizedPath;
91
37
  }
92
- await new Promise(res => setTimeout(res, POLL_INTERVAL_MS));
93
- }
94
- throw new Error(`PDF download did not complete within ${timeoutMs / 1000} seconds in directory: ${tempFileDir}. ` +
95
- `Ensure the download was triggered prior to VerifyPDF.`);
96
- }
97
- async function verifyPDF(args) {
98
- const tempFileDir = path_1.default.resolve(process.cwd(), "session", "temp_file");
99
- let calculatedDuration = 2;
100
- try {
101
- const { file: latestFile, durationSeconds } = await waitForDownloadComplete(tempFileDir);
102
- calculatedDuration = durationSeconds;
103
- logData_js_1.logger.info("VerifyPDF: found latest file", { file: latestFile.name, dir: tempFileDir, durationSeconds });
104
- const buffer = fs_1.default.readFileSync(latestFile.fullPath);
38
+ // Validate PDF header
105
39
  const header = buffer.subarray(0, 5).toString("ascii");
106
40
  if (!header.startsWith("%PDF")) {
107
- throw new Error(`File does not have a valid PDF header (%PDF): ${latestFile.name}`);
41
+ throw new Error(`File does not have a valid PDF header (%PDF): ${fileName}`);
108
42
  }
43
+ // Optional text content verification
109
44
  const expectedText = args.value?.trim();
110
45
  if (expectedText) {
111
- const extractedText = await extractTextFromPdf(buffer);
112
- const normalizedExtracted = normalizeText(extractedText).toLowerCase();
113
- const normalizedExpected = normalizeText(expectedText).toLowerCase();
46
+ const extractedText = await (0, pdfTextExtractor_js_1.extractTextFromPdf)(buffer);
47
+ const normalizedExtracted = (0, pdfTextExtractor_js_1.normalizeText)(extractedText).toLowerCase();
48
+ const normalizedExpected = (0, pdfTextExtractor_js_1.normalizeText)(expectedText).toLowerCase();
114
49
  if (!normalizedExtracted.includes(normalizedExpected)) {
115
- // Secondary check on raw buffer
50
+ // Secondary check directly on raw buffer bytes
116
51
  const rawContent = buffer.toString("latin1").toLowerCase();
117
52
  if (!rawContent.includes(normalizedExpected)) {
118
- throw new Error(`PDF "${latestFile.name}" does not contain expected text: "${expectedText}"`);
53
+ throw new Error(`PDF "${fileName}" does not contain expected text: "${expectedText}"`);
119
54
  }
120
55
  }
121
56
  }
122
- logData_js_1.logger.info("VerifyPDF passed", {
123
- file: latestFile.name,
57
+ await logData_js_1.logger.info("VerifyPDF passed", {
58
+ file: fileName,
124
59
  expectedText: expectedText || "(none — structural check only)",
125
- sizeBytes: buffer.length
126
- });
127
- (0, appendScriptData_js_1.appendScriptData)({
128
- scriptDataAppender: args.scriptDataAppender,
129
- nlpName: "Sleep",
130
- stepInputs: [calculatedDuration],
131
- });
132
- (0, appendScriptData_js_1.appendScriptData)({
133
- scriptDataAppender: args.scriptDataAppender,
134
- nlpName: "GetUserHomeDirectory",
135
- stepInputs: [],
136
- returnTypeVariableName: "userHomeDirectory"
137
- });
138
- (0, appendScriptData_js_1.appendScriptData)({
139
- scriptDataAppender: args.scriptDataAppender,
140
- nlpName: "ConcatString",
141
- stepInputs: ["variable:userHomeDirectory", "/Downloads"],
142
- returnTypeVariableName: "finalpath"
143
- });
144
- (0, appendScriptData_js_1.appendScriptData)({
145
- scriptDataAppender: args.scriptDataAppender,
146
- nlpName: "GetLastModifiedFile",
147
- stepInputs: ["variable:finalpath"],
148
- returnTypeVariableName: "lastModifiedFilePath"
149
- });
150
- (0, appendScriptData_js_1.appendScriptData)({
151
- scriptDataAppender: args.scriptDataAppender,
152
- nlpName: "VerifyPDF",
153
- stepInputs: ["variable:lastModifiedFilePath", args.value ?? ""],
60
+ sizeBytes: buffer.length,
154
61
  });
62
+ if (args.isDownload) {
63
+ // Download flow: emit full chain so the generated script can replay the wait
64
+ const downloadsFolder = process.platform === "win32" ? "\\Downloads" : "/Downloads";
65
+ (0, appendScriptData_js_1.appendScriptData)({
66
+ scriptDataAppender: args.scriptDataAppender,
67
+ nlpName: "Sleep",
68
+ stepInputs: [calculatedDuration],
69
+ });
70
+ (0, appendScriptData_js_1.appendScriptData)({
71
+ scriptDataAppender: args.scriptDataAppender,
72
+ nlpName: "GetUserHomeDirectory",
73
+ stepInputs: [],
74
+ returnTypeVariableName: "userHomeDirectory",
75
+ });
76
+ (0, appendScriptData_js_1.appendScriptData)({
77
+ scriptDataAppender: args.scriptDataAppender,
78
+ nlpName: "ConcatString",
79
+ stepInputs: ["variable:userHomeDirectory", downloadsFolder],
80
+ returnTypeVariableName: "finalpath",
81
+ });
82
+ (0, appendScriptData_js_1.appendScriptData)({
83
+ scriptDataAppender: args.scriptDataAppender,
84
+ nlpName: "GetLastModifiedFile",
85
+ stepInputs: ["variable:finalpath"],
86
+ returnTypeVariableName: "lastModifiedFilePath",
87
+ });
88
+ (0, appendScriptData_js_1.appendScriptData)({
89
+ scriptDataAppender: args.scriptDataAppender,
90
+ nlpName: "VerifyPDF",
91
+ stepInputs: ["variable:lastModifiedFilePath", args.value ?? ""],
92
+ });
93
+ }
94
+ else {
95
+ // Explicit path flow: emit VerifyPDF with the literal path the user provided
96
+ (0, appendScriptData_js_1.appendScriptData)({
97
+ scriptDataAppender: args.scriptDataAppender,
98
+ nlpName: "VerifyPDF",
99
+ stepInputs: [args.filePath ?? "", args.value ?? ""],
100
+ });
101
+ }
155
102
  }
156
103
  catch (error) {
157
104
  args.scriptDataAppender.setErrorNLP("VerifyPDF");
105
+ await logData_js_1.logger.error("VerifyPDF action failed:", error);
158
106
  throw new Error("VerifyPDF action failed", { cause: error });
159
107
  }
160
108
  finally {
161
- try {
162
- if (fs_1.default.existsSync(tempFileDir)) {
163
- fs_1.default.rmSync(tempFileDir, { recursive: true, force: true });
164
- logData_js_1.logger.info(`Cleaned up temp_file directory: ${tempFileDir}`);
109
+ // Clean up temp download directory if download flow was used
110
+ if (args.isDownload) {
111
+ try {
112
+ if (fs_1.default.existsSync(logData_js_1.downloadDir)) {
113
+ fs_1.default.rmSync(logData_js_1.downloadDir, { recursive: true, force: true });
114
+ await logData_js_1.logger.info(`Cleaned up temp download directory: ${logData_js_1.downloadDir}`);
115
+ }
116
+ }
117
+ catch (cleanupErr) {
118
+ await logData_js_1.logger.error("Error cleaning up temp download directory after VerifyPDF:", cleanupErr);
165
119
  }
166
- }
167
- catch (cleanupErr) {
168
- logData_js_1.logger.error("Error cleaning up temp_file directory after VerifyPDF:", cleanupErr);
169
120
  }
170
121
  }
171
122
  }
@@ -29,6 +29,14 @@ export declare const WebVerifySchema: z.ZodObject<{
29
29
  inputText: z.ZodString;
30
30
  elementType: z.ZodString;
31
31
  }, z.core.$strip>;
32
+ export declare const WebFileVerifySchema: z.ZodObject<{
33
+ attributeValue: z.ZodString;
34
+ nlpName: z.ZodString;
35
+ inputText: z.ZodString;
36
+ elementType: z.ZodString;
37
+ isDownload: z.ZodBoolean;
38
+ path: z.ZodNullable<z.ZodString>;
39
+ }, z.core.$strip>;
32
40
  export declare const WebGetSchema: z.ZodObject<{
33
41
  attributeValue: z.ZodString;
34
42
  nlpName: z.ZodString;
@@ -43,6 +51,15 @@ export declare const MobileVerifySchema: z.ZodObject<{
43
51
  elementType: z.ZodString;
44
52
  locator_type: z.ZodString;
45
53
  }, z.core.$strip>;
54
+ export declare const MobileFileVerifySchema: z.ZodObject<{
55
+ attributeValue: z.ZodString;
56
+ nlpName: z.ZodString;
57
+ inputText: z.ZodString;
58
+ elementType: z.ZodString;
59
+ locator_type: z.ZodString;
60
+ isDownload: z.ZodBoolean;
61
+ path: z.ZodNullable<z.ZodString>;
62
+ }, z.core.$strip>;
46
63
  export declare const MobileGetSchema: z.ZodObject<{
47
64
  attributeValue: z.ZodString;
48
65
  nlpName: z.ZodString;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- 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.MobileVerifySchema = exports.WebGetSchema = exports.WebVerifySchema = exports.AlertShema = exports.KeywordSchema = exports.StepItemSchema = void 0;
3
+ 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;
4
4
  exports.getSchemaForPrompt = getSchemaForPrompt;
5
5
  const zod_1 = require("zod");
6
6
  const promptType_js_1 = require("../../../core/types/promptType.js");
@@ -26,6 +26,14 @@ exports.WebVerifySchema = zod_1.z.object({
26
26
  inputText: zod_1.z.string(),
27
27
  elementType: zod_1.z.string(),
28
28
  });
29
+ exports.WebFileVerifySchema = zod_1.z.object({
30
+ attributeValue: zod_1.z.string().describe("FF-inspecter attribute value, e.g. 'Fire-Flink-0'"),
31
+ nlpName: zod_1.z.string().describe("Exact NLP name from the approved list, e.g. 'VerifyPDF' or 'VerifyPDFExist'"),
32
+ inputText: zod_1.z.string().describe("The expected text to verify inside the file. Must NEVER include the file path or file name. Only the search/verification phrase."),
33
+ elementType: zod_1.z.string().describe("Type of element from the allowed element list, default 'link'"),
34
+ isDownload: zod_1.z.boolean().describe("true if verifying a file downloaded via the browser; false if verifying an existing file at a local/server file path."),
35
+ path: zod_1.z.string().nullable().describe("The extracted file path string if isDownload is false with extra 4 slashes //// (e.g. 'C:////Users/////User/////Downloads/////file.pdf'). Must be null if isDownload is true. Never include search/input text here."),
36
+ });
29
37
  exports.WebGetSchema = zod_1.z.object({
30
38
  attributeValue: zod_1.z.string(),
31
39
  nlpName: zod_1.z.string(),
@@ -40,6 +48,15 @@ exports.MobileVerifySchema = zod_1.z.object({
40
48
  elementType: zod_1.z.string(),
41
49
  locator_type: zod_1.z.string(),
42
50
  });
51
+ exports.MobileFileVerifySchema = zod_1.z.object({
52
+ attributeValue: zod_1.z.string().describe("FF-inspecter attribute value, e.g. 'Fire-Flink-0'"),
53
+ nlpName: zod_1.z.string().describe("Exact NLP name from the approved list, e.g. 'VerifyPDF' or 'VerifyPDFExist'"),
54
+ inputText: zod_1.z.string().describe("The expected text to verify inside the file. Must NEVER include the file path or file name. Only the search/verification phrase."),
55
+ elementType: zod_1.z.string().describe("Type of element from the allowed element list, default 'link'"),
56
+ locator_type: zod_1.z.string().describe("Locator type"),
57
+ isDownload: zod_1.z.boolean().describe("true if verifying a file downloaded via the browser; false if verifying an existing file at a local/server file path."),
58
+ path: zod_1.z.string().nullable().describe("The extracted file path string if isDownload is false with extra 4 slashes //// (e.g. 'C:////Users/////User/////Downloads/////file.pdf'). Must be null if isDownload is true. Never include search/input text here."),
59
+ });
43
60
  exports.MobileGetSchema = zod_1.z.object({
44
61
  attributeValue: zod_1.z.string(),
45
62
  nlpName: zod_1.z.string(),
@@ -190,9 +207,12 @@ function getSchemaForPrompt(type, platform, args) {
190
207
  if (type === promptType_js_1.PromptType.COMBINED_ACTION_EXTRACTOR_PROMPT) {
191
208
  return platform === "web" ? exports.CombinedActionSchema : exports.MobileCombinedSchema;
192
209
  }
193
- if (type === promptType_js_1.PromptType.VERIFY_PROMPT || type === promptType_js_1.PromptType.FILE_VERIFY_PROMPT) {
210
+ if (type === promptType_js_1.PromptType.VERIFY_PROMPT) {
194
211
  return platform === "web" ? exports.WebVerifySchema : exports.MobileVerifySchema;
195
212
  }
213
+ if (type === promptType_js_1.PromptType.FILE_VERIFY_PROMPT) {
214
+ return platform === "web" ? exports.WebFileVerifySchema : exports.MobileFileVerifySchema;
215
+ }
196
216
  if (type === promptType_js_1.PromptType.GET_PROMPT) {
197
217
  return platform === "web" ? exports.WebGetSchema : exports.MobileGetSchema;
198
218
  }
@@ -920,6 +920,8 @@ export interface verifyElementIsSelectedInSpecifiedTimeInterface {
920
920
  export interface VerifyPDFInterface {
921
921
  driver: WebdriverIO.Browser;
922
922
  value: string;
923
+ isDownload: boolean;
924
+ filePath: string | null;
923
925
  platform: string;
924
926
  scriptDataAppender: ScriptDataAppender;
925
927
  }
@@ -72,6 +72,7 @@ exports.ELEMENTLESS_ACTION = [
72
72
  "ConcatString",
73
73
  "GetLastModifiedFile",
74
74
  "VerifyPDF",
75
+ "VerifyPDFExist",
75
76
  "MOB_GetCurrentWindowHandle",
76
77
  "MOB_GetDriverInstance",
77
78
  "MOB_GetADBLogsForGivenAppPackage",
@@ -41,6 +41,7 @@ const executor_js_1 = require("../../automation/actions/executor.js");
41
41
  const logData_js_1 = require("../../utils/logger/logData.js");
42
42
  const capability_js_1 = require("../../utils/browserCap/capability.js");
43
43
  const path_1 = __importDefault(require("path"));
44
+ const systemCapabilities_js_1 = require("../../utils/browserCap/systemCapabilities.js");
44
45
  function createActionHandlers(context) {
45
46
  let isAndroidInitialized = false;
46
47
  const requireExecutor = () => {
@@ -99,6 +100,14 @@ function createActionHandlers(context) {
99
100
  resolvedBrowserName = "safari";
100
101
  }
101
102
  const capsObj = (0, capability_js_1.getCapabilities)(resolvedBrowserName, pathValue, profileValue, capsKeywords);
103
+ for (const value of Object.values(capsObj)) {
104
+ if (value && typeof value === "object" && "prefs" in value) {
105
+ const prefs = value.prefs;
106
+ for (const key of systemCapabilities_js_1.systemCapability[resolvedBrowserName]) {
107
+ delete prefs[key];
108
+ }
109
+ }
110
+ }
102
111
  let exactCommands = [];
103
112
  let exactPrefs = {};
104
113
  if (resolvedBrowserName === "chrome") {
@@ -1469,7 +1478,9 @@ function createActionHandlers(context) {
1469
1478
  }),
1470
1479
  VerifyPDF: withExecutor(async (executor, result) => {
1471
1480
  const expectedText = result?.value ?? "";
1472
- await executor.verifyPDF(expectedText);
1481
+ const isDownload = result?.isDownload ?? true;
1482
+ const filePath = result?.path ?? null;
1483
+ await executor.verifyPDF(expectedText, isDownload, filePath);
1473
1484
  })
1474
1485
  };
1475
1486
  }
@@ -112,14 +112,14 @@ class AutomationRunner {
112
112
  fireflinkServiceInstance = null;
113
113
  delete AutomationRunner.sessionTerminationDetails[request.testCaseId];
114
114
  try {
115
- const tempFileDir = path_1.default.resolve(process.cwd(), "session", "temp_file");
115
+ const tempFileDir = path_1.default.resolve(process.cwd(), "session", "tempFile");
116
116
  if (fs_1.default.existsSync(tempFileDir)) {
117
117
  fs_1.default.rmSync(tempFileDir, { recursive: true, force: true });
118
- logData_js_1.logger.info(`Cleaned up temp_file directory: ${tempFileDir}`);
118
+ logData_js_1.logger.info(`Cleaned up tempFile directory: ${tempFileDir}`);
119
119
  }
120
120
  }
121
121
  catch (err) {
122
- logData_js_1.logger.error("Error cleaning up temp_file directory:", err);
122
+ logData_js_1.logger.error("Error cleaning up tempFile directory:", err);
123
123
  }
124
124
  if (global.gc) {
125
125
  global.gc();
@@ -452,6 +452,8 @@ class AutomationRunner {
452
452
  manualStep: step,
453
453
  stepsInputData,
454
454
  returnTypeVariableName: currentReturnTypeVariableName,
455
+ isDownload: stepResult.response.isDownload,
456
+ path: stepResult.response.path,
455
457
  ...(currentVariableName ? { variableName: currentVariableName } : {}),
456
458
  });
457
459
  if (currentDriver && currentPlatform === "web") {
@@ -1,10 +1,7 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.getCapabilities = getCapabilities;
7
- const path_1 = __importDefault(require("path"));
4
+ const logData_js_1 = require("../logger/logData.js");
8
5
  function getCapabilities(browserName, path, profile, customCaps = []) {
9
6
  const isLinux = process.platform === 'linux';
10
7
  const caps = customCaps.map(c => c.toLowerCase());
@@ -43,9 +40,7 @@ function getCapabilities(browserName, path, profile, customCaps = []) {
43
40
  };
44
41
  const getChromiumPrefs = () => {
45
42
  const prefs = {};
46
- // Always redirect downloads to session/temp_file so VerifyPDF can reliably find them
47
- const downloadDir = path_1.default.resolve(process.cwd(), "session", "temp_file");
48
- prefs["download.default_directory"] = downloadDir;
43
+ prefs["download.default_directory"] = logData_js_1.downloadDir;
49
44
  prefs["download.prompt_for_download"] = false;
50
45
  prefs["download.directory_upgrade"] = true;
51
46
  prefs["safebrowsing.enabled"] = true;
@@ -77,9 +72,8 @@ function getCapabilities(browserName, path, profile, customCaps = []) {
77
72
  if (hasCap("disableExtensions"))
78
73
  args.push('-safe-mode');
79
74
  const prefs = {};
80
- // Always redirect downloads to session/temp_file so VerifyPDF can reliably find them
81
- const downloadDir = path_1.default.resolve(process.cwd(), "session", "temp_file");
82
- prefs["browser.download.dir"] = downloadDir;
75
+ // Always redirect downloads to session/tempFile so VerifyPDF can reliably find them
76
+ prefs["browser.download.dir"] = logData_js_1.downloadDir;
83
77
  prefs["browser.download.folderList"] = 2; // 2 = use custom download dir
84
78
  prefs["browser.download.useDownloadDir"] = true;
85
79
  prefs["browser.helperApps.neverAsk.saveToDisk"] = "application/pdf,application/octet-stream";
@@ -0,0 +1,6 @@
1
+ export declare const systemCapability: {
2
+ chrome: string[];
3
+ edge: string[];
4
+ firefox: string[];
5
+ safari: never[];
6
+ };
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.systemCapability = void 0;
4
+ exports.systemCapability = {
5
+ chrome: [
6
+ "download.default_directory",
7
+ "download.prompt_for_download",
8
+ "download.directory_upgrade",
9
+ "safebrowsing.enabled"
10
+ ],
11
+ edge: [
12
+ "download.default_directory",
13
+ "download.prompt_for_download",
14
+ "download.directory_upgrade",
15
+ "safebrowsing.enabled"
16
+ ],
17
+ firefox: [
18
+ "browser.download.dir",
19
+ "browser.download.folderList",
20
+ "browser.download.useDownloadDir",
21
+ "browser.helperApps.neverAsk.saveToDisk",
22
+ "pdfjs.disabled"
23
+ ],
24
+ safari: []
25
+ };
@@ -0,0 +1,8 @@
1
+ export declare function waitForDownloadComplete(downloadDir: string, timeoutMs?: number): Promise<{
2
+ file: {
3
+ name: string;
4
+ fullPath: string;
5
+ mtime: number;
6
+ };
7
+ durationSeconds: number;
8
+ }>;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.waitForDownloadComplete = waitForDownloadComplete;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const DOWNLOAD_TIMEOUT_MS = 30000;
10
+ const POLL_INTERVAL_MS = 400;
11
+ const STABILITY_WAIT_MS = 250;
12
+ async function waitForDownloadComplete(downloadDir, timeoutMs = DOWNLOAD_TIMEOUT_MS) {
13
+ const startTime = Date.now();
14
+ while (Date.now() - startTime < timeoutMs) {
15
+ if (fs_1.default.existsSync(downloadDir)) {
16
+ const allEntries = fs_1.default.readdirSync(downloadDir);
17
+ let hasActiveDownload = false;
18
+ const pdfFiles = [];
19
+ // Single pass: early exit if active download is present, stat only PDF files
20
+ for (const file of allEntries) {
21
+ const lower = file.toLowerCase();
22
+ if (lower.endsWith(".crdownload") || lower.endsWith(".part") || lower.endsWith(".tmp")) {
23
+ hasActiveDownload = true;
24
+ break;
25
+ }
26
+ if (lower.endsWith(".pdf")) {
27
+ const fullPath = path_1.default.join(downloadDir, file);
28
+ try {
29
+ const stat = fs_1.default.statSync(fullPath);
30
+ if (stat.isFile()) {
31
+ pdfFiles.push({ name: file, fullPath, mtime: stat.mtimeMs, size: stat.size });
32
+ }
33
+ }
34
+ catch {
35
+ // File locked or in transition, skip for this cycle
36
+ }
37
+ }
38
+ }
39
+ if (!hasActiveDownload && pdfFiles.length > 0) {
40
+ pdfFiles.sort((a, b) => b.mtime - a.mtime);
41
+ const latest = pdfFiles[0];
42
+ if (latest.size > 0) {
43
+ await new Promise(res => setTimeout(res, STABILITY_WAIT_MS));
44
+ try {
45
+ const newStat = fs_1.default.statSync(latest.fullPath);
46
+ if (newStat.size === latest.size && newStat.size > 0) {
47
+ const durationSeconds = Math.max(1, Math.ceil((Date.now() - startTime) / 1000));
48
+ return { file: latest, durationSeconds };
49
+ }
50
+ }
51
+ catch {
52
+ // Retry on next cycle
53
+ }
54
+ }
55
+ }
56
+ }
57
+ await new Promise(res => setTimeout(res, POLL_INTERVAL_MS));
58
+ }
59
+ throw new Error(`PDF download did not complete within ${timeoutMs / 1000} seconds in directory: ${downloadDir}. ` +
60
+ `Ensure the download was triggered prior to VerifyPDF.`);
61
+ }
@@ -1,4 +1,6 @@
1
+ export declare let downloadDir: string;
1
2
  export declare function initLogger(basePath: string): void;
3
+ export declare function ensureDirs(): Promise<void>;
2
4
  export declare const logger: {
3
5
  info: (...args: any[]) => Promise<void>;
4
6
  error: (...args: any[]) => Promise<void>;
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.logger = void 0;
6
+ exports.logger = exports.downloadDir = void 0;
7
7
  exports.initLogger = initLogger;
8
+ exports.ensureDirs = ensureDirs;
8
9
  const promises_1 = __importDefault(require("fs/promises"));
9
10
  const path_1 = __importDefault(require("path"));
10
11
  const js_beautify_1 = __importDefault(require("js-beautify"));
@@ -17,6 +18,7 @@ function initLogger(basePath) {
17
18
  sessionPath = basePath;
18
19
  logsDir = path_1.default.resolve(sessionPath, "logs");
19
20
  domDir = path_1.default.resolve(sessionPath, "dom");
21
+ exports.downloadDir = path_1.default.resolve(sessionPath, "tempFile");
20
22
  screenshotsDir = path_1.default.resolve(sessionPath, "screenshots");
21
23
  logFilePath = path_1.default.join(logsDir, "ai-execution-logs.txt");
22
24
  }
@@ -26,6 +28,7 @@ async function ensureDirs() {
26
28
  await promises_1.default.mkdir(logsDir, { recursive: true });
27
29
  await promises_1.default.mkdir(domDir, { recursive: true });
28
30
  await promises_1.default.mkdir(screenshotsDir, { recursive: true });
31
+ await promises_1.default.mkdir(exports.downloadDir, { recursive: true });
29
32
  }
30
33
  function getISTDate() {
31
34
  return new Intl.DateTimeFormat("sv-SE", {
@@ -0,0 +1,2 @@
1
+ export declare function normalizeText(text: string): string;
2
+ export declare function extractTextFromPdf(buffer: Buffer): Promise<string>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeText = normalizeText;
4
+ exports.extractTextFromPdf = extractTextFromPdf;
5
+ const logData_js_1 = require("../logger/logData.js");
6
+ function normalizeText(text) {
7
+ return text.replace(/\s+/g, " ").trim();
8
+ }
9
+ async function extractTextFromPdf(buffer) {
10
+ try {
11
+ const dynamicImport = new Function("modulePath", "return import(modulePath)");
12
+ const pdfjs = await dynamicImport("pdfjs-dist/legacy/build/pdf.mjs").catch(() => dynamicImport("pdfjs-dist"));
13
+ const data = new Uint8Array(buffer);
14
+ const loadingTask = pdfjs.getDocument({
15
+ data,
16
+ useSystemFonts: true,
17
+ disableFontFace: true,
18
+ });
19
+ const pdfDoc = await loadingTask.promise;
20
+ const pageTexts = [];
21
+ for (let i = 1; i <= pdfDoc.numPages; i++) {
22
+ const page = await pdfDoc.getPage(i);
23
+ const textContent = await page.getTextContent();
24
+ const pageString = textContent.items
25
+ .map((item) => ("str" in item ? item.str : ""))
26
+ .join(" ");
27
+ pageTexts.push(pageString);
28
+ }
29
+ return pageTexts.join("\n");
30
+ }
31
+ catch (err) {
32
+ logData_js_1.logger.error("pdfjs-dist extraction failed, falling back to raw buffer string scan", err);
33
+ return buffer.toString("latin1");
34
+ }
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ff-automationv2",
3
- "version": "2.2.32-beta.0",
3
+ "version": "2.2.32-beta.1",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "description": "This lib is used to automate the manual testcase",