ff-automationv2 1.0.0
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/ARCHITECTURE.md +112 -0
- package/FireFlink_Architecture.drawio +68 -0
- package/ONBOARDING.md +29 -0
- package/README.md +28 -0
- package/TECHNICAL_DEEP_DIVE.md +26 -0
- package/eslint.config.ts +29 -0
- package/package.json +51 -0
- package/src/ai/llmcalls/decodeApiKey.ts +14 -0
- package/src/ai/llmcalls/llmAction.ts +89 -0
- package/src/ai/llmcalls/parseLlmOputput.ts +69 -0
- package/src/ai/llmprompts/promptRegistry.ts +16 -0
- package/src/ai/llmprompts/systemPrompts/actionExtractorPrompt.ts +70 -0
- package/src/ai/llmprompts/systemPrompts/errorDescriptionPrompt.ts +23 -0
- package/src/ai/llmprompts/systemPrompts/fireflinkElementIndexExtactors.ts +198 -0
- package/src/ai/llmprompts/systemPrompts/userStoryToListPrompt.ts +24 -0
- package/src/ai/llmprompts/systemPrompts/visionPrompt.ts +28 -0
- package/src/ai/llmprompts/userPrompts/userPrompt.ts +41 -0
- package/src/automation/actions/executor.ts +75 -0
- package/src/automation/actions/interaction/click.ts +25 -0
- package/src/automation/actions/interaction/enterInput.ts +27 -0
- package/src/automation/actions/interface/interactionActionInterface.ts +27 -0
- package/src/automation/actions/interface/navigationActionInterface.ts +22 -0
- package/src/automation/actions/interface/waitActionInterface.ts +6 -0
- package/src/automation/actions/navigation/getTitle.ts +9 -0
- package/src/automation/actions/navigation/goBack.ts +9 -0
- package/src/automation/actions/navigation/navigate.ts +10 -0
- package/src/automation/actions/navigation/refresh.ts +9 -0
- package/src/automation/actions/wait/wait.ts +10 -0
- package/src/automation/browserSession/initiateBrowserSession.ts +81 -0
- package/src/core/constants/supportedActions.ts +8 -0
- package/src/core/interfaces/StableDomInterface.ts +6 -0
- package/src/core/interfaces/actionInterface.ts +13 -0
- package/src/core/interfaces/automationRunnerInterface.ts +3 -0
- package/src/core/interfaces/browserCapabilitiesInterface.ts +5 -0
- package/src/core/interfaces/browserConfigurationInterface.ts +3 -0
- package/src/core/interfaces/domAnalysisInterface.ts +34 -0
- package/src/core/interfaces/executionDetails.ts +29 -0
- package/src/core/interfaces/fireflinkScriptPayloadInterface.ts +39 -0
- package/src/core/interfaces/llmConfigurationInterface.ts +3 -0
- package/src/core/interfaces/llmResponseInterface.ts +38 -0
- package/src/core/interfaces/promptInterface.ts +21 -0
- package/src/core/interfaces/scriptGenrationDataInterface.ts +16 -0
- package/src/core/interfaces/toolsInterface.ts +5 -0
- package/src/core/main/actionHandlerFactory.ts +86 -0
- package/src/core/main/executionContext.ts +18 -0
- package/src/core/main/runAutomationScript.ts +177 -0
- package/src/core/main/stepProcessor.ts +28 -0
- package/src/core/types/llmResponseType.ts +11 -0
- package/src/core/types/promptMap.ts +7 -0
- package/src/core/types/promptType.ts +7 -0
- package/src/core/types/visionllmInputType.ts +4 -0
- package/src/domAnalysis/getRelaventElements.ts +24 -0
- package/src/domAnalysis/relativeElementsFromDom.ts +94 -0
- package/src/domAnalysis/searchBest.ts +159 -0
- package/src/domAnalysis/simplifyAndFlatten.ts +118 -0
- package/src/fireflinkData/fireflinkLocators/elementsFromHTML.ts +656 -0
- package/src/fireflinkData/fireflinkLocators/getListOfLocators.ts +31 -0
- package/src/fireflinkData/fireflinkLocators/typeList.ts +36 -0
- package/src/fireflinkData/fireflinkScript/scriptGenrationData.ts +30 -0
- package/src/index.ts +5 -0
- package/src/llmConfig/llmConfiguration.ts +26 -0
- package/src/service/fireflinkApi.service.ts +46 -0
- package/src/service/scriptRunner.service.ts +83 -0
- package/src/utils/DomExtraction/jsForAttributeInjection.ts +254 -0
- package/src/utils/javascript/jsFindElement.ts +161 -0
- package/src/utils/javascript/jsForShadowRoot.ts +216 -0
- package/src/utils/javascript/jsForToaster.ts +60 -0
- package/src/utils/logger/logData.ts +36 -0
- package/tsconfig.json +26 -0
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# 📊 FireFlink System Architecture
|
|
2
|
+
|
|
3
|
+
## Layered Architecture
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌────────────────────────────────────────────┐
|
|
7
|
+
│ User Story Layer │
|
|
8
|
+
└────────────────────────────────────────────┘
|
|
9
|
+
↓
|
|
10
|
+
┌────────────────────────────────────────────┐
|
|
11
|
+
│ LLM Reasoning Layer │
|
|
12
|
+
│ - USER_STORY_TO_LIST │
|
|
13
|
+
│ - KEYWORD_EXTRACTOR │
|
|
14
|
+
│ - FF_INSPECTOR │
|
|
15
|
+
└────────────────────────────────────────────┘
|
|
16
|
+
↓
|
|
17
|
+
┌────────────────────────────────────────────┐
|
|
18
|
+
│ Orchestration Layer │
|
|
19
|
+
│ (AutomationRunner) │
|
|
20
|
+
└────────────────────────────────────────────┘
|
|
21
|
+
↓
|
|
22
|
+
┌────────────────────────────────────────────┐
|
|
23
|
+
│ DOM Abstraction Layer │
|
|
24
|
+
│ - Inject FF-inspecter attributes │
|
|
25
|
+
│ - Serialize DOM │
|
|
26
|
+
│ - Build FF → XPath mapping │
|
|
27
|
+
└────────────────────────────────────────────┘
|
|
28
|
+
↓
|
|
29
|
+
┌────────────────────────────────────────────┐
|
|
30
|
+
│ DOM Processing Engine │
|
|
31
|
+
│ - Keyword-based DOM filtering │
|
|
32
|
+
└────────────────────────────────────────────┘
|
|
33
|
+
↓
|
|
34
|
+
┌────────────────────────────────────────────┐
|
|
35
|
+
│ Action Execution Layer │
|
|
36
|
+
│ - Click Handler │
|
|
37
|
+
│ - Enter Handler │
|
|
38
|
+
│ - Navigate Handler │
|
|
39
|
+
└────────────────────────────────────────────┘
|
|
40
|
+
↓
|
|
41
|
+
┌────────────────────────────────────────────┐
|
|
42
|
+
│ Script Generation Layer │
|
|
43
|
+
│ - ScriptAppender │
|
|
44
|
+
│ - Payload Builder │
|
|
45
|
+
└────────────────────────────────────────────┘
|
|
46
|
+
↓
|
|
47
|
+
┌────────────────────────────────────────────┐
|
|
48
|
+
│ FireFlink Backend API │
|
|
49
|
+
└────────────────────────────────────────────┘
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Execution Sequence
|
|
54
|
+
|
|
55
|
+
AutomationRunner
|
|
56
|
+
→ StepProcessor (LLM)
|
|
57
|
+
→ Browser (Inject DOM)
|
|
58
|
+
→ DomProcessingEngine
|
|
59
|
+
→ StepProcessor (FF selection)
|
|
60
|
+
→ ActionHandler
|
|
61
|
+
→ ScriptAppender
|
|
62
|
+
→ ScriptRunner
|
|
63
|
+
→ FireFlink API
|
|
64
|
+
|
|
65
|
+
## high Level flow
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
User Story
|
|
69
|
+
↓
|
|
70
|
+
LLM → Manual Steps
|
|
71
|
+
↓
|
|
72
|
+
FOR EACH STEP
|
|
73
|
+
↓
|
|
74
|
+
LLM → Action + Keywords
|
|
75
|
+
↓
|
|
76
|
+
Inject Annotated DOM
|
|
77
|
+
↓
|
|
78
|
+
Filter Relevant DOM (DomProcessingEngine)
|
|
79
|
+
↓
|
|
80
|
+
LLM → Choose FF-inspecter Index
|
|
81
|
+
↓
|
|
82
|
+
Map FF Index → XPath
|
|
83
|
+
↓
|
|
84
|
+
Execute Action
|
|
85
|
+
↓
|
|
86
|
+
Append Script Data
|
|
87
|
+
↓
|
|
88
|
+
┌─────────────────────────────┐
|
|
89
|
+
│ Error Occurred? │
|
|
90
|
+
└──────────────┬──────────────┘
|
|
91
|
+
│
|
|
92
|
+
┌──────┴──────┐
|
|
93
|
+
│ │
|
|
94
|
+
NO YES
|
|
95
|
+
│ │
|
|
96
|
+
│ Append CloseBrowser
|
|
97
|
+
│ ↓
|
|
98
|
+
│ Stop Execution
|
|
99
|
+
│ |
|
|
100
|
+
↓ |
|
|
101
|
+
Continue Next Step |
|
|
102
|
+
↓ |
|
|
103
|
+
(all Steps Complete) |
|
|
104
|
+
| |
|
|
105
|
+
| |
|
|
106
|
+
└──────┬──────┘
|
|
107
|
+
Collect Token Usage
|
|
108
|
+
↓
|
|
109
|
+
Build Payload
|
|
110
|
+
↓
|
|
111
|
+
Send to FireFlink API
|
|
112
|
+
```
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<mxfile host="app.diagrams.net">
|
|
3
|
+
<diagram name="FireFlink Architecture">
|
|
4
|
+
<mxGraphModel dx="1000" dy="1000" grid="1" gridSize="10" guides="1" tooltips="1"
|
|
5
|
+
connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100"
|
|
6
|
+
math="0" shadow="0">
|
|
7
|
+
<root>
|
|
8
|
+
<mxCell id="0"/>
|
|
9
|
+
<mxCell id="1" parent="0"/>
|
|
10
|
+
|
|
11
|
+
<mxCell id="2" value="User Story" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
12
|
+
<mxGeometry x="300" y="20" width="200" height="60" as="geometry"/>
|
|
13
|
+
</mxCell>
|
|
14
|
+
|
|
15
|
+
<mxCell id="3" value="LLM Reasoning Layer" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
16
|
+
<mxGeometry x="300" y="120" width="200" height="60" as="geometry"/>
|
|
17
|
+
</mxCell>
|
|
18
|
+
|
|
19
|
+
<mxCell id="4" value="AutomationRunner (Orchestration)" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
20
|
+
<mxGeometry x="300" y="220" width="200" height="60" as="geometry"/>
|
|
21
|
+
</mxCell>
|
|
22
|
+
|
|
23
|
+
<mxCell id="5" value="DOM Abstraction Layer (FF-inspecter Injection)" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
24
|
+
<mxGeometry x="300" y="320" width="200" height="60" as="geometry"/>
|
|
25
|
+
</mxCell>
|
|
26
|
+
|
|
27
|
+
<mxCell id="6" value="DOM Processing Engine" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
28
|
+
<mxGeometry x="300" y="420" width="200" height="60" as="geometry"/>
|
|
29
|
+
</mxCell>
|
|
30
|
+
|
|
31
|
+
<mxCell id="7" value="Action Execution Layer" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
32
|
+
<mxGeometry x="300" y="520" width="200" height="60" as="geometry"/>
|
|
33
|
+
</mxCell>
|
|
34
|
+
|
|
35
|
+
<mxCell id="8" value="Script Generation Layer" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
36
|
+
<mxGeometry x="300" y="620" width="200" height="60" as="geometry"/>
|
|
37
|
+
</mxCell>
|
|
38
|
+
|
|
39
|
+
<mxCell id="9" value="FireFlink Backend API" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
40
|
+
<mxGeometry x="300" y="720" width="200" height="60" as="geometry"/>
|
|
41
|
+
</mxCell>
|
|
42
|
+
|
|
43
|
+
<mxCell id="10" style="endArrow=block;html=1;" edge="1" parent="1" source="2" target="3">
|
|
44
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
45
|
+
</mxCell>
|
|
46
|
+
<mxCell id="11" style="endArrow=block;html=1;" edge="1" parent="1" source="3" target="4">
|
|
47
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
48
|
+
</mxCell>
|
|
49
|
+
<mxCell id="12" style="endArrow=block;html=1;" edge="1" parent="1" source="4" target="5">
|
|
50
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
51
|
+
</mxCell>
|
|
52
|
+
<mxCell id="13" style="endArrow=block;html=1;" edge="1" parent="1" source="5" target="6">
|
|
53
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
54
|
+
</mxCell>
|
|
55
|
+
<mxCell id="14" style="endArrow=block;html=1;" edge="1" parent="1" source="6" target="7">
|
|
56
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
57
|
+
</mxCell>
|
|
58
|
+
<mxCell id="15" style="endArrow=block;html=1;" edge="1" parent="1" source="7" target="8">
|
|
59
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
60
|
+
</mxCell>
|
|
61
|
+
<mxCell id="16" style="endArrow=block;html=1;" edge="1" parent="1" source="8" target="9">
|
|
62
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
63
|
+
</mxCell>
|
|
64
|
+
|
|
65
|
+
</root>
|
|
66
|
+
</mxGraphModel>
|
|
67
|
+
</diagram>
|
|
68
|
+
</mxfile>
|
package/ONBOARDING.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 🧪 Developer Onboarding Guide
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
Entry Point: AutomationRunner.ts
|
|
6
|
+
|
|
7
|
+
Run: new AutomationRunner(request).run();
|
|
8
|
+
|
|
9
|
+
## Execution Flow
|
|
10
|
+
|
|
11
|
+
1. Convert user story to steps
|
|
12
|
+
2. Extract action & keywords
|
|
13
|
+
3. Inject annotated DOM
|
|
14
|
+
4. Filter relevant DOM
|
|
15
|
+
5. Resolve FF-index
|
|
16
|
+
6. Execute action
|
|
17
|
+
7. Send script to backend
|
|
18
|
+
|
|
19
|
+
## Adding a New Action
|
|
20
|
+
|
|
21
|
+
- Create handler
|
|
22
|
+
- Register in actionHandlerFactory
|
|
23
|
+
- Add to INPUTLESS_ACTIONS if applicable
|
|
24
|
+
|
|
25
|
+
## Debugging
|
|
26
|
+
|
|
27
|
+
- Log DOM using saveDomToFile()
|
|
28
|
+
- Validate FF-index mapping
|
|
29
|
+
- Check token usage tracking
|
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 🔥 FireFlink AI Automation Platform
|
|
2
|
+
|
|
3
|
+
Enterprise-grade AI-powered automation engine that transforms natural
|
|
4
|
+
language user stories into executable browser automation scripts using
|
|
5
|
+
LLM-driven reasoning and intelligent DOM abstraction.
|
|
6
|
+
|
|
7
|
+
## Executive Summary
|
|
8
|
+
|
|
9
|
+
FireFlink enables organizations to: - Convert user stories into
|
|
10
|
+
automated test scripts - Reduce manual scripting effort - Improve
|
|
11
|
+
element resilience using AI-based DOM interpretation - Track LLM token
|
|
12
|
+
usage and automation generation cost - Integrate automation results
|
|
13
|
+
directly into backend infrastructure
|
|
14
|
+
|
|
15
|
+
## Core Capabilities
|
|
16
|
+
|
|
17
|
+
- AI Step Generation
|
|
18
|
+
- Intelligent Element Resolution
|
|
19
|
+
- DOM Annotation Engine (FF-inspecter system)
|
|
20
|
+
- Modular Action Handler Framework
|
|
21
|
+
- Failure Isolation & Safe Termination
|
|
22
|
+
- Token Usage Monitoring
|
|
23
|
+
- API Integration Layer
|
|
24
|
+
|
|
25
|
+
## High-Level Execution Lifecycle
|
|
26
|
+
|
|
27
|
+
User Story → LLM Steps → DOM Annotation → Element Resolution → Action
|
|
28
|
+
Execution → Script Generation → API Submission
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 🧠 Technical Deep Dive
|
|
2
|
+
|
|
3
|
+
## Core Concept
|
|
4
|
+
|
|
5
|
+
Each DOM element is assigned: FF-inspecter="Fire-Flink-X"
|
|
6
|
+
|
|
7
|
+
This allows deterministic mapping from AI selection to XPath.
|
|
8
|
+
|
|
9
|
+
## Token Optimization Strategy
|
|
10
|
+
|
|
11
|
+
Instead of sending full DOM to LLM, FireFlink filters relevant nodes
|
|
12
|
+
using DomProcessingEngine to reduce token usage.
|
|
13
|
+
|
|
14
|
+
## Failure Handling Model
|
|
15
|
+
|
|
16
|
+
Execution stops on: - Unsupported action - Invalid FF-index - XPath
|
|
17
|
+
resolution failure - Handler execution failure
|
|
18
|
+
|
|
19
|
+
CloseBrowser step is appended before termination.
|
|
20
|
+
|
|
21
|
+
## Extensibility
|
|
22
|
+
|
|
23
|
+
- Add new PromptType
|
|
24
|
+
- Extend StepProcessor
|
|
25
|
+
- Add ActionHandler
|
|
26
|
+
- Modify payload schema
|
package/eslint.config.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import tseslint from "typescript-eslint";
|
|
4
|
+
import { defineConfig } from "eslint/config";
|
|
5
|
+
|
|
6
|
+
export default defineConfig([
|
|
7
|
+
{
|
|
8
|
+
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
9
|
+
plugins: { js },
|
|
10
|
+
extends: ["js/recommended"],
|
|
11
|
+
languageOptions: { globals: globals.browser },
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
...tseslint.configs.recommended,
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
rules: {
|
|
18
|
+
"no-console": ["error", { allow: ["warn", "error"] }],
|
|
19
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
20
|
+
"@typescript-eslint/no-unused-vars": [
|
|
21
|
+
"error",
|
|
22
|
+
{
|
|
23
|
+
argsIgnorePattern: "^_",
|
|
24
|
+
varsIgnorePattern: "^_",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
]);
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ff-automationv2",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "This lib is used to automate the manual testcase",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"builds": "npm run lint && tsc",
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepare": "npm run build",
|
|
19
|
+
"lint": "eslint src --max-warnings=0",
|
|
20
|
+
"test": "npm run build && node dist/tests/test12.js",
|
|
21
|
+
"llm": "npm run build && node dist/tests/testllm.js",
|
|
22
|
+
"dev": "npm run build && node dist/index.js",
|
|
23
|
+
"start": "node dist/index.js",
|
|
24
|
+
"file": "node dist/domAnalysis/getXpath.js"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [],
|
|
27
|
+
"author": "Diwahar R",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@eslint/js": "10.0.1",
|
|
31
|
+
"@types/jsdom": "27.0.0",
|
|
32
|
+
"@types/node": "25.2.2",
|
|
33
|
+
"eslint": "10.0.0",
|
|
34
|
+
"globals": "17.3.0",
|
|
35
|
+
"jiti": "2.6.1",
|
|
36
|
+
"nodemon": "3.1.11",
|
|
37
|
+
"ts-node": "10.9.2",
|
|
38
|
+
"typescript": "5.9.3",
|
|
39
|
+
"typescript-eslint": "8.56.0"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"axios": "1.13.5",
|
|
43
|
+
"cheerio": "1.2.0",
|
|
44
|
+
"fs-extra": "11.3.3",
|
|
45
|
+
"fuzzball": "2.2.3",
|
|
46
|
+
"jsdom": "27.4.0",
|
|
47
|
+
"openai": "6.18.0",
|
|
48
|
+
"uuid": "13.0.0",
|
|
49
|
+
"webdriverio": "9.23.3"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
export function decodeApiKey(apiKey: string): string {
|
|
3
|
+
if (!apiKey) {
|
|
4
|
+
throw new Error("API key is required");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
return Buffer.from(apiKey, "base64").toString("utf-8");
|
|
9
|
+
} catch (error) {
|
|
10
|
+
throw new Error("Failed to decode API key", { cause: error });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { prompts } from "../llmprompts/promptRegistry.js";
|
|
2
|
+
import OpenAI from "openai";
|
|
3
|
+
import { decodeApiKey } from "./decodeApiKey.js";
|
|
4
|
+
import { FireFlinkLLMResponse } from "../../core/types/llmResponseType.js"
|
|
5
|
+
import { PromptArgsMap } from "../../core/interfaces/promptInterface.js"
|
|
6
|
+
import { userInputFormatters } from "../../ai/llmprompts/userPrompts/userPrompt.js"
|
|
7
|
+
import { visionPromptMessage } from "../../core/types/visionllmInputType.js"
|
|
8
|
+
import { PromptType } from "../../core/types/promptType.js";
|
|
9
|
+
export class llmAction {
|
|
10
|
+
private client: OpenAI;
|
|
11
|
+
private model: string;
|
|
12
|
+
private visionClient: OpenAI
|
|
13
|
+
constructor(
|
|
14
|
+
apiKey: string,
|
|
15
|
+
baseURL: string,
|
|
16
|
+
model: string,
|
|
17
|
+
visionApiKey: string
|
|
18
|
+
) {
|
|
19
|
+
this.model = model;
|
|
20
|
+
this.client = new OpenAI({
|
|
21
|
+
apiKey: decodeApiKey(apiKey),
|
|
22
|
+
baseURL: baseURL,
|
|
23
|
+
});
|
|
24
|
+
this.visionClient = new OpenAI({
|
|
25
|
+
apiKey: decodeApiKey(visionApiKey),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async getLLMResponseText(system: string, user: string): Promise<FireFlinkLLMResponse> {
|
|
31
|
+
const response = await this.client.chat.completions.create({
|
|
32
|
+
model: this.model,
|
|
33
|
+
messages: [
|
|
34
|
+
{
|
|
35
|
+
role: "system",
|
|
36
|
+
content: system
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
role: "user",
|
|
40
|
+
content: user
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
response_format: { type: "json_object" }
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
|
|
47
|
+
return response;
|
|
48
|
+
} catch {
|
|
49
|
+
throw new Error(`Invalid JSON returned from LLM `);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async getLLMResponse<K extends keyof PromptArgsMap>(
|
|
53
|
+
type: K,
|
|
54
|
+
args: PromptArgsMap[K],
|
|
55
|
+
userInput: Record<string, any>
|
|
56
|
+
): Promise<FireFlinkLLMResponse> {
|
|
57
|
+
const promptBuilder: (args: PromptArgsMap[K]) => Promise<string> =
|
|
58
|
+
prompts[type];
|
|
59
|
+
const systemPrompt = await promptBuilder(args);
|
|
60
|
+
const userPrompt: string | visionPromptMessage = userInputFormatters[type](userInput);
|
|
61
|
+
if (type === PromptType.VISION_PROMPT && Array.isArray(userPrompt)) {
|
|
62
|
+
return this.getLLMResponseWithVision(systemPrompt, userPrompt);
|
|
63
|
+
}
|
|
64
|
+
if (typeof userPrompt !== "string") {
|
|
65
|
+
throw new Error("Invalid non-vision user prompt format");
|
|
66
|
+
}
|
|
67
|
+
return this.getLLMResponseText(systemPrompt, userPrompt);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private async getLLMResponseWithVision(system: any, userPrompt: visionPromptMessage): Promise<string> {
|
|
71
|
+
const response = await this.visionClient.chat.completions.create({
|
|
72
|
+
model: "gpt-4.1",
|
|
73
|
+
messages: [
|
|
74
|
+
{
|
|
75
|
+
role: "system",
|
|
76
|
+
content: system
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
role: "user",
|
|
80
|
+
content: userPrompt
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
response_format: { type: "json_object" }
|
|
84
|
+
});
|
|
85
|
+
return response.choices[0].message.content || "";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { FireFlinkLLMResponse } from "../../core/types/llmResponseType.js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export class LLMResultParser {
|
|
5
|
+
private inputTokens: number = 0;
|
|
6
|
+
private outputTokens: number = 0;
|
|
7
|
+
private totalTokens: number = 0;
|
|
8
|
+
|
|
9
|
+
private async extractJsonObject(response: string) {
|
|
10
|
+
const match = response.match(/\{[\s\S]*\}/);
|
|
11
|
+
if (!match) return null;
|
|
12
|
+
return JSON.parse(match[0]);
|
|
13
|
+
}
|
|
14
|
+
public async fetchResult(llmOutput: any): Promise<{
|
|
15
|
+
response: FireFlinkLLMResponse
|
|
16
|
+
}> {
|
|
17
|
+
try {
|
|
18
|
+
const content = llmOutput?.choices?.[0]?.message?.content;
|
|
19
|
+
|
|
20
|
+
if (!content) {
|
|
21
|
+
throw new Error("No content in LLM output");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let parsedContent: FireFlinkLLMResponse;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Try direct parse first
|
|
28
|
+
parsedContent = JSON.parse(content);
|
|
29
|
+
} catch {
|
|
30
|
+
// If LLM added explanation text, extract JSON block
|
|
31
|
+
const extracted = this.extractJsonObject(content);
|
|
32
|
+
|
|
33
|
+
if (!extracted) {
|
|
34
|
+
throw new Error("Could not extract valid JSON from LLM response");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
parsedContent = extracted as FireFlinkLLMResponse;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const usage = llmOutput?.usage ?? {};
|
|
41
|
+
|
|
42
|
+
const inputTokens = Number(usage.prompt_tokens ?? 0);
|
|
43
|
+
const outputTokens = Number(usage.completion_tokens ?? 0);
|
|
44
|
+
const totalTokens = Number(usage.total_tokens ?? 0);
|
|
45
|
+
|
|
46
|
+
this.inputTokens += inputTokens;
|
|
47
|
+
this.outputTokens += outputTokens;
|
|
48
|
+
this.totalTokens += totalTokens;
|
|
49
|
+
|
|
50
|
+
return { response: parsedContent };
|
|
51
|
+
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"Failed to parse LLM output",
|
|
55
|
+
{ cause: error }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
public getTokenUsage() {
|
|
63
|
+
return {
|
|
64
|
+
inputTokens: this.inputTokens,
|
|
65
|
+
outputTokens: this.outputTokens,
|
|
66
|
+
totalTokens: this.totalTokens,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { keywordExtractor } from "./systemPrompts/actionExtractorPrompt.js";
|
|
2
|
+
import { buildStepExtractionPrompt } from "./systemPrompts/userStoryToListPrompt.js";
|
|
3
|
+
import { buildErrorDescriptionPrompt } from "./systemPrompts/errorDescriptionPrompt.js";
|
|
4
|
+
import { ffInspectorNumExtractor } from "./systemPrompts/fireflinkElementIndexExtactors.js";
|
|
5
|
+
import { visionPrompt } from "./systemPrompts/visionPrompt.js";
|
|
6
|
+
import { PromptMap } from "../../core/types/promptMap.js";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export const prompts: PromptMap = {
|
|
10
|
+
userStoryToList: buildStepExtractionPrompt,
|
|
11
|
+
keywordExtractorPrompt: keywordExtractor,
|
|
12
|
+
errorDescriptionPrompt: buildErrorDescriptionPrompt,
|
|
13
|
+
ffInspectorNumExtractor: ffInspectorNumExtractor,
|
|
14
|
+
visionPrompt: visionPrompt,
|
|
15
|
+
};
|
|
16
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
|
|
2
|
+
type keywordExtractor = {
|
|
3
|
+
priorAndNextSteps: string[];
|
|
4
|
+
|
|
5
|
+
};
|
|
6
|
+
export async function keywordExtractor(
|
|
7
|
+
{ priorAndNextSteps }: keywordExtractor
|
|
8
|
+
): Promise<string> {
|
|
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
|
+
];
|
|
25
|
+
|
|
26
|
+
const prompt = `
|
|
27
|
+
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.
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
return prompt;
|
|
70
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export async function buildErrorDescriptionPrompt(): Promise<string> {
|
|
2
|
+
const description_prompt = `
|
|
3
|
+
|
|
4
|
+
Analyze the error and execution history to provide diagnostics in JSON format.
|
|
5
|
+
|
|
6
|
+
Return a JSON object with the following keys:
|
|
7
|
+
1. "error": A concise, single-line, selenium type(error).__name__ (e.g., "InvalidSessionIdException", "InvalidElementStateException", "ElementNotInteractableException", "NoSuchElementException" etc).
|
|
8
|
+
2. "errorDescription": A very simple, non-technical, and user-friendly explanation of why the automation might have failed.
|
|
9
|
+
- Use plain English.
|
|
10
|
+
- All the lines should contain FireFlink. example: "FireFlink was unable to find the element..."
|
|
11
|
+
- Do not mention code, XPaths, or internal technical terms.
|
|
12
|
+
- The output MUST be a single paragraph of exactly 10 to 15 lines of text.
|
|
13
|
+
- Focus on potential real-world causes.
|
|
14
|
+
|
|
15
|
+
Respond only with JSON using this format:
|
|
16
|
+
{
|
|
17
|
+
"error": ",
|
|
18
|
+
"errorDescription": "",
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
return description_prompt;
|
|
23
|
+
}
|