ff-automationv2 2.1.3-beta.5 → 2.1.3-beta.7
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/RELEASE_GUIDE.md +1 -1
- package/eslint.config.ts +4 -0
- package/package.json +2 -2
- package/src/ai/llmprompts/systemPrompts/verifyActionExtractorPrompt.ts +3 -5
- package/src/automation/actions/executor.ts +28 -9
- package/src/automation/actions/interaction/clear/clear.ts +0 -1
- package/src/automation/actions/interaction/clear/clearAndEnter.ts +0 -3
- package/src/automation/actions/interaction/click.ts +1 -1
- package/src/automation/actions/interaction/enterInput.ts +1 -1
- package/src/core/constants/allAction.ts +14 -0
- package/src/core/interfaces/automationRunnerInterface.ts +2 -1
- package/src/core/main/actionHandlerFactory.ts +10 -5
- package/src/core/main/runAutomationScript.ts +42 -59
- package/src/core/types/promptType.ts +6 -0
- package/src/domAnalysis/searchBest.ts +1 -0
- package/src/utils/DomExtraction/jsForAttributeInjection.ts +9 -7
- package/src/utils/helpers/enterActionHelper.ts +1 -1
- package/src/utils/logger/logData.ts +30 -7
package/RELEASE_GUIDE.md
CHANGED
package/eslint.config.ts
CHANGED
|
@@ -4,6 +4,9 @@ import tseslint from "typescript-eslint";
|
|
|
4
4
|
import { defineConfig } from "eslint/config";
|
|
5
5
|
|
|
6
6
|
export default defineConfig([
|
|
7
|
+
{
|
|
8
|
+
ignores: ["src/tests/**", "dist/**", "node_modules/**"]
|
|
9
|
+
},
|
|
7
10
|
{
|
|
8
11
|
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
9
12
|
plugins: { js },
|
|
@@ -15,6 +18,7 @@ export default defineConfig([
|
|
|
15
18
|
|
|
16
19
|
{
|
|
17
20
|
rules: {
|
|
21
|
+
"no-useless-escape": "off",
|
|
18
22
|
"no-console": ["error", { allow: ["warn", "error"] }],
|
|
19
23
|
"@typescript-eslint/no-explicit-any": "off",
|
|
20
24
|
"@typescript-eslint/no-unused-vars": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ff-automationv2",
|
|
3
|
-
"version": "2.1.3-beta.
|
|
3
|
+
"version": "2.1.3-beta.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "This lib is used to automate the manual testcase",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"builds": "npm run lint && tsc",
|
|
17
|
-
"build": "tsc --pretty --listEmittedFiles && echo Build Successful",
|
|
17
|
+
"build": "npm run lint && tsc --pretty --listEmittedFiles && echo Build Successful",
|
|
18
18
|
"prepare": "npm run build",
|
|
19
19
|
"lint": "eslint src --max-warnings=0",
|
|
20
20
|
"test": "npm run build && node dist/tests/test12.js",
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
priorAndNextSteps: string[];
|
|
4
|
-
}
|
|
1
|
+
import { verifyActionArgs } from "../../../core/types/promptType.js"
|
|
2
|
+
|
|
5
3
|
|
|
6
4
|
export async function verifyActionExtractorPrompt({
|
|
7
5
|
extractedDomJson,
|
|
@@ -41,7 +39,7 @@ export async function verifyActionExtractorPrompt({
|
|
|
41
39
|
|
|
42
40
|
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'];
|
|
43
41
|
const prompt = `You are an intelligent assistant that extracts structured UI action data.
|
|
44
|
-
Given the step, the simplified DOM: ${extractedDomJson}, and the list of NLP names: {
|
|
42
|
+
Given the step, the simplified DOM: ${extractedDomJson}, and the list of NLP names: ${nlpList}.
|
|
45
43
|
|
|
46
44
|
Select the perfect matching NLP name from the list that best fits the step's intent.
|
|
47
45
|
**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.**
|
|
@@ -10,6 +10,7 @@ import { clear } from "./interaction/clear/clear.js";
|
|
|
10
10
|
import { clearAndEnter } from "./interaction/clear/clearAndEnter.js";
|
|
11
11
|
import { ScriptDataAppender } from "../../fireflinkData/fireflinkScript/scriptGenrationData.js";
|
|
12
12
|
import { ElementGetter } from "../../fireflinkData/fireflinkLocators/getListOfLocators.js";
|
|
13
|
+
import { logger } from "../../utils/logger/logData.js"
|
|
13
14
|
export class ActionExecutor implements IActionExecutor {
|
|
14
15
|
constructor(private readonly browser: WebdriverIO.Browser,
|
|
15
16
|
private scriptDataAppender: ScriptDataAppender,
|
|
@@ -27,7 +28,9 @@ export class ActionExecutor implements IActionExecutor {
|
|
|
27
28
|
});
|
|
28
29
|
}
|
|
29
30
|
catch (error) {
|
|
30
|
-
|
|
31
|
+
logger.error("Error in [ActionExecutor]", error)
|
|
32
|
+
|
|
33
|
+
throw error;
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -39,7 +42,9 @@ export class ActionExecutor implements IActionExecutor {
|
|
|
39
42
|
});
|
|
40
43
|
}
|
|
41
44
|
catch (error) {
|
|
42
|
-
|
|
45
|
+
logger.error("Error in [ActionExecutor]", error)
|
|
46
|
+
|
|
47
|
+
throw error;
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -51,7 +56,9 @@ export class ActionExecutor implements IActionExecutor {
|
|
|
51
56
|
});
|
|
52
57
|
}
|
|
53
58
|
catch (error) {
|
|
54
|
-
|
|
59
|
+
logger.error("Error in [ActionExecutor]", error)
|
|
60
|
+
|
|
61
|
+
throw error;
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
|
|
@@ -65,7 +72,9 @@ export class ActionExecutor implements IActionExecutor {
|
|
|
65
72
|
});
|
|
66
73
|
}
|
|
67
74
|
catch (error) {
|
|
68
|
-
|
|
75
|
+
logger.error("Error in [ActionExecutor]", error)
|
|
76
|
+
|
|
77
|
+
throw error;
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
80
|
async click(pageDOM: string, selector: string, fireflinkIndex: string, elementName: string, elementType: string): Promise<void> {
|
|
@@ -83,7 +92,9 @@ export class ActionExecutor implements IActionExecutor {
|
|
|
83
92
|
});
|
|
84
93
|
}
|
|
85
94
|
catch (error) {
|
|
86
|
-
|
|
95
|
+
logger.error("Error in [ActionExecutor]", error)
|
|
96
|
+
|
|
97
|
+
throw error;
|
|
87
98
|
}
|
|
88
99
|
}
|
|
89
100
|
async enterInput(selector: string, value: string, fireflinkIndex: string, pageDOM: string, elementName: string, elementType: string): Promise<void> {
|
|
@@ -102,7 +113,9 @@ export class ActionExecutor implements IActionExecutor {
|
|
|
102
113
|
});
|
|
103
114
|
}
|
|
104
115
|
catch (error) {
|
|
105
|
-
|
|
116
|
+
logger.error("Error in [ActionExecutor]", error)
|
|
117
|
+
|
|
118
|
+
throw error;
|
|
106
119
|
}
|
|
107
120
|
}
|
|
108
121
|
async maximize(): Promise<void> {
|
|
@@ -113,7 +126,9 @@ export class ActionExecutor implements IActionExecutor {
|
|
|
113
126
|
});
|
|
114
127
|
}
|
|
115
128
|
catch (error) {
|
|
116
|
-
|
|
129
|
+
logger.error("Error in [ActionExecutor]", error)
|
|
130
|
+
|
|
131
|
+
throw error;
|
|
117
132
|
}
|
|
118
133
|
}
|
|
119
134
|
|
|
@@ -132,7 +147,9 @@ export class ActionExecutor implements IActionExecutor {
|
|
|
132
147
|
});
|
|
133
148
|
}
|
|
134
149
|
catch (error) {
|
|
135
|
-
|
|
150
|
+
logger.error("Error in [ActionExecutor]", error)
|
|
151
|
+
|
|
152
|
+
throw error;
|
|
136
153
|
}
|
|
137
154
|
}
|
|
138
155
|
|
|
@@ -152,7 +169,9 @@ export class ActionExecutor implements IActionExecutor {
|
|
|
152
169
|
});
|
|
153
170
|
}
|
|
154
171
|
catch (error) {
|
|
155
|
-
|
|
172
|
+
logger.error("Error in [ActionExecutor]", error)
|
|
173
|
+
|
|
174
|
+
throw error;
|
|
156
175
|
}
|
|
157
176
|
}
|
|
158
177
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { clearAndEnterActionInterface } from "../../interface/clearActionInterface.js";
|
|
2
|
-
import { logger } from "../../../../utils/logger/logData.js";
|
|
3
|
-
|
|
4
2
|
export async function clearAndEnter(args: clearAndEnterActionInterface): Promise<void> {
|
|
5
3
|
try {
|
|
6
4
|
|
|
@@ -25,7 +23,6 @@ export async function clearAndEnter(args: clearAndEnterActionInterface): Promise
|
|
|
25
23
|
});
|
|
26
24
|
|
|
27
25
|
} catch (error: any) {
|
|
28
|
-
console.log(error);
|
|
29
26
|
throw new Error("clear and enter action failed", { cause: error });
|
|
30
27
|
}
|
|
31
28
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ExecutionContext } from "../main/executionContext.js";
|
|
2
2
|
import { ActionExecutor } from "../../automation/actions/executor.js";
|
|
3
|
-
|
|
3
|
+
import { logger } from "../../utils/logger/logData.js"
|
|
4
4
|
type ActionHandler = (result?: any) => Promise<void>;
|
|
5
5
|
|
|
6
6
|
export function createActionHandlers(
|
|
@@ -24,12 +24,12 @@ export function createActionHandlers(
|
|
|
24
24
|
await fn(executor, result);
|
|
25
25
|
}
|
|
26
26
|
catch (error) {
|
|
27
|
-
|
|
27
|
+
logger.error("Error in createActionHandlers", error)
|
|
28
|
+
throw error
|
|
28
29
|
}
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
return {
|
|
32
|
-
|
|
33
33
|
open: async () => {
|
|
34
34
|
try {
|
|
35
35
|
await context.session.open({
|
|
@@ -52,7 +52,10 @@ export function createActionHandlers(
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
catch (error) {
|
|
55
|
-
|
|
55
|
+
|
|
56
|
+
logger.error("Error in createActionHandlers", error)
|
|
57
|
+
|
|
58
|
+
throw error;
|
|
56
59
|
}
|
|
57
60
|
},
|
|
58
61
|
|
|
@@ -61,7 +64,9 @@ export function createActionHandlers(
|
|
|
61
64
|
await context.session.close();
|
|
62
65
|
}
|
|
63
66
|
catch (error) {
|
|
64
|
-
|
|
67
|
+
logger.error("Error in createActionHandlers", error)
|
|
68
|
+
|
|
69
|
+
throw error;
|
|
65
70
|
}
|
|
66
71
|
},
|
|
67
72
|
|
|
@@ -13,15 +13,14 @@ import { INPUTLESS_ACTIONS, ELEMENTLESS_ACTION } from "../constants/supportedAct
|
|
|
13
13
|
import { DomProcessingEngine } from "../../domAnalysis/getRelaventElements.js"
|
|
14
14
|
import { getAnnotatedDOM } from "../../utils/DomExtraction/jsForAttributeInjection.js"
|
|
15
15
|
import { logger } from "../../utils/logger/logData.js"
|
|
16
|
+
import { allKeywordAction } from "../constants/allAction.js";
|
|
16
17
|
export class AutomationRunner implements IAutomationRunner {
|
|
17
18
|
static sessionTerminationDetails: Record<string, boolean> = {};
|
|
18
19
|
constructor(
|
|
19
|
-
private readonly request: AutomationRequest,
|
|
20
20
|
private pageLoad: number = 20000,
|
|
21
21
|
private implicit: number = 15000,
|
|
22
|
-
private tokensConsumed: number = 0
|
|
23
22
|
|
|
24
|
-
) {
|
|
23
|
+
) { }
|
|
25
24
|
|
|
26
25
|
static getSessionTerminationInfo(testCaseId: string): boolean {
|
|
27
26
|
return AutomationRunner.sessionTerminationDetails[testCaseId]
|
|
@@ -35,18 +34,17 @@ export class AutomationRunner implements IAutomationRunner {
|
|
|
35
34
|
domInfo: any,
|
|
36
35
|
extractedRelevantDom: any,
|
|
37
36
|
stepProcessor: StepProcessor,
|
|
38
|
-
scriptRunner: ScriptRunner
|
|
37
|
+
scriptRunner: ScriptRunner,
|
|
38
|
+
request: AutomationRequest
|
|
39
39
|
) {
|
|
40
40
|
try {
|
|
41
41
|
logger.info("Starting cleanup process...");
|
|
42
42
|
if (context?.session) {
|
|
43
43
|
try {
|
|
44
44
|
const browser = await context.session.getCurrentBrowser();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} catch (e) {
|
|
49
|
-
logger.error("Browser cleanup failed:", e);
|
|
45
|
+
logger.info("Still browser session is active, session id :", browser.sessionId)
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.error("Browser cleanup failed:", error);
|
|
50
48
|
}
|
|
51
49
|
}
|
|
52
50
|
|
|
@@ -59,7 +57,7 @@ export class AutomationRunner implements IAutomationRunner {
|
|
|
59
57
|
|
|
60
58
|
scriptRunner = null as any;
|
|
61
59
|
|
|
62
|
-
delete AutomationRunner.sessionTerminationDetails[
|
|
60
|
+
delete AutomationRunner.sessionTerminationDetails[request.testCaseId];
|
|
63
61
|
|
|
64
62
|
if (global.gc) {
|
|
65
63
|
global.gc();
|
|
@@ -73,18 +71,19 @@ export class AutomationRunner implements IAutomationRunner {
|
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
|
|
76
|
-
async run(): Promise<void> {
|
|
74
|
+
async run(request: AutomationRequest): Promise<void> {
|
|
75
|
+
AutomationRunner.sessionTerminationDetails[request.testCaseId] = false;
|
|
77
76
|
const apiService = new FireFlinkApiService();
|
|
78
77
|
const scriptRunner = new ScriptRunner(apiService);
|
|
79
|
-
const context = new ExecutionContext(
|
|
78
|
+
const context = new ExecutionContext(request);
|
|
80
79
|
const configProvider = new ServiceProviderBaseUrlProvider();
|
|
81
|
-
const baseUrl = configProvider.getBaseUrl(
|
|
80
|
+
const baseUrl = configProvider.getBaseUrl(request.serviceProvider);
|
|
82
81
|
let domInfo: any = null;
|
|
83
82
|
let extractedRelevantDom: any = null;
|
|
84
|
-
const llm = new llmAction(
|
|
83
|
+
const llm = new llmAction(request.apiKey, baseUrl, request.model, request.visionApikey);
|
|
85
84
|
const stepProcessor = new StepProcessor(llm);
|
|
86
85
|
|
|
87
|
-
const stepResult = await stepProcessor.getLLMResponse({ type: PromptType.USER_STORY_TO_LIST, args: {}, input: { userStory:
|
|
86
|
+
const stepResult = await stepProcessor.getLLMResponse({ type: PromptType.USER_STORY_TO_LIST, args: {}, input: { userStory: request.userStory } });
|
|
88
87
|
|
|
89
88
|
const actionHandlers = createActionHandlers(
|
|
90
89
|
context,
|
|
@@ -97,40 +96,25 @@ export class AutomationRunner implements IAutomationRunner {
|
|
|
97
96
|
if (listOfSteps.length == 0) {
|
|
98
97
|
throw new Error("No executable manual steps were returned by the LLM.");
|
|
99
98
|
}
|
|
100
|
-
|
|
101
|
-
logger.info(listOfSteps)
|
|
102
|
-
const allKeywordAction = ["enter", "wait", "verify", "scroll", "navigate", "click", "maximize", "get", "upload", "close", "open", "drag_and_drop", "switch", "cleartext"];
|
|
103
|
-
|
|
99
|
+
logger.info("Test steps for execution ", listOfSteps)
|
|
104
100
|
for (const step of listOfSteps) {
|
|
105
101
|
try {
|
|
106
|
-
if (AutomationRunner.getSessionTerminationInfo(
|
|
107
|
-
|
|
108
|
-
logger.info
|
|
109
|
-
(
|
|
110
|
-
`Processing step ${stepCount}: "${step}"`
|
|
111
|
-
);
|
|
112
|
-
|
|
102
|
+
if (AutomationRunner.getSessionTerminationInfo(request.testCaseId)) { break; }
|
|
103
|
+
logger.info(`Processing step ${stepCount}: ${step}`);
|
|
113
104
|
const start = Math.max(0, stepCount - 3);
|
|
114
105
|
const end = Math.min(listOfSteps.length, stepCount + 3);
|
|
115
106
|
const priorAndNextSteps = listOfSteps.slice(start, end);
|
|
116
|
-
|
|
117
107
|
const result = await stepProcessor.getLLMResponse({
|
|
118
108
|
type: PromptType.KEYWORD_EXTRACTOR,
|
|
119
109
|
args: { priorAndNextSteps },
|
|
120
110
|
input: { currentStep: step }
|
|
121
111
|
});
|
|
122
|
-
|
|
123
112
|
logger.info(JSON.stringify(result, null, 2))
|
|
124
|
-
|
|
125
113
|
const action = result.response.action?.toLowerCase();
|
|
126
|
-
|
|
127
114
|
if (!allKeywordAction.includes(action)) {
|
|
128
115
|
throw new Error(`Unsupported action at action or keyword exracter: ${action}`);
|
|
129
116
|
}
|
|
130
|
-
|
|
131
117
|
let handler = actionHandlers[action];
|
|
132
|
-
|
|
133
|
-
|
|
134
118
|
if (INPUTLESS_ACTIONS.includes(action)) {
|
|
135
119
|
await handler(result);
|
|
136
120
|
stepCount++;
|
|
@@ -156,18 +140,16 @@ export class AutomationRunner implements IAutomationRunner {
|
|
|
156
140
|
}
|
|
157
141
|
const driver = await context.session.getCurrentBrowser();
|
|
158
142
|
domInfo = await getAnnotatedDOM(driver);
|
|
159
|
-
|
|
160
|
-
|
|
143
|
+
logger.saveDOM(domInfo.dom, `annotated-dom-${stepCount}`);
|
|
161
144
|
extractedRelevantDom = domProcessor.process({
|
|
162
145
|
keywords: result.response.keywords,
|
|
163
146
|
rawDom: domInfo.dom,
|
|
164
147
|
stepCount: stepCount
|
|
165
148
|
});
|
|
166
|
-
|
|
167
|
-
await logger.saveJSON(extractedRelevantDom, `relevant-dom-${stepCount}`);
|
|
149
|
+
logger.saveJSON(extractedRelevantDom, `relevant-dom-${stepCount}`);
|
|
168
150
|
let stepResult: any;
|
|
169
151
|
if (action == "verify") {
|
|
170
|
-
|
|
152
|
+
stepResult = await stepProcessor.getLLMResponse({
|
|
171
153
|
type: PromptType.VERIFY_PROMPT,
|
|
172
154
|
args: {
|
|
173
155
|
extractedDomJson: JSON.stringify(extractedRelevantDom, null, 2),
|
|
@@ -224,35 +206,35 @@ export class AutomationRunner implements IAutomationRunner {
|
|
|
224
206
|
elementsData: [],
|
|
225
207
|
stepInputs: []
|
|
226
208
|
});
|
|
227
|
-
|
|
228
209
|
logger.error(`Error executing step "${step}":`, error);
|
|
229
210
|
break;
|
|
230
211
|
}
|
|
231
212
|
}
|
|
232
213
|
const payload: IPayload = {
|
|
233
|
-
scriptName:
|
|
234
|
-
scriptType:
|
|
235
|
-
projectId:
|
|
236
|
-
testCaseId:
|
|
237
|
-
promptId:
|
|
238
|
-
pageDetails:
|
|
239
|
-
generatedBy:
|
|
240
|
-
webSocketId:
|
|
241
|
-
licenseType:
|
|
242
|
-
licenseId:
|
|
243
|
-
userId:
|
|
244
|
-
topic:
|
|
245
|
-
projectType:
|
|
214
|
+
scriptName: request.scriptName,
|
|
215
|
+
scriptType: request.scriptType,
|
|
216
|
+
projectId: request.projectId,
|
|
217
|
+
testCaseId: request.testCaseId,
|
|
218
|
+
promptId: request.promptId,
|
|
219
|
+
pageDetails: request.pageDetails,
|
|
220
|
+
generatedBy: request.generatedBy,
|
|
221
|
+
webSocketId: request.webSocketId,
|
|
222
|
+
licenseType: request.licenseType,
|
|
223
|
+
licenseId: request.licenseId,
|
|
224
|
+
userId: request.userId,
|
|
225
|
+
topic: request.topic,
|
|
226
|
+
projectType: request.projectType,
|
|
246
227
|
tokensConsumed: (await stepProcessor.getResultTokenUsage()).totalTokens,
|
|
247
228
|
}
|
|
248
229
|
try {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
230
|
+
await scriptRunner.runScriptFromPayload(
|
|
231
|
+
context.scriptAppender.getData(),
|
|
232
|
+
payload,
|
|
233
|
+
request.token,
|
|
234
|
+
request.serverHost
|
|
235
|
+
);
|
|
255
236
|
} catch (error: any) {
|
|
237
|
+
|
|
256
238
|
throw new Error(
|
|
257
239
|
"Failed to send payload to FireFlink API:", { cause: error }
|
|
258
240
|
);
|
|
@@ -263,7 +245,8 @@ export class AutomationRunner implements IAutomationRunner {
|
|
|
263
245
|
domInfo,
|
|
264
246
|
extractedRelevantDom,
|
|
265
247
|
stepProcessor,
|
|
266
|
-
scriptRunner
|
|
248
|
+
scriptRunner,
|
|
249
|
+
request
|
|
267
250
|
);
|
|
268
251
|
}
|
|
269
252
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { logger } from "../../utils/logger/logData.js"
|
|
3
2
|
export interface AnnotatedDOMResult {
|
|
4
3
|
dom: string;
|
|
5
4
|
selectors: Record<string, string>;
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
export async function getAnnotatedDOM(
|
|
9
|
-
browser: Browser
|
|
8
|
+
browser: WebdriverIO.Browser
|
|
10
9
|
): Promise<AnnotatedDOMResult> {
|
|
11
10
|
|
|
12
11
|
return await browser.execute<AnnotatedDOMResult, []>(() => {
|
|
@@ -32,9 +31,9 @@ export async function getAnnotatedDOM(
|
|
|
32
31
|
[rect.left + rect.width / 2, rect.top + rect.height / 2]
|
|
33
32
|
];
|
|
34
33
|
|
|
35
|
-
for (
|
|
34
|
+
for (const [x, y] of testPoints) {
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
const topEl = document.elementFromPoint(x, y);
|
|
38
37
|
if (!topEl) continue;
|
|
39
38
|
|
|
40
39
|
if (topEl === el || el.contains(topEl)) continue;
|
|
@@ -115,7 +114,7 @@ export async function getAnnotatedDOM(
|
|
|
115
114
|
|
|
116
115
|
if (!node || node.nodeType !== 1) return "";
|
|
117
116
|
|
|
118
|
-
|
|
117
|
+
const segments: string[] = [];
|
|
119
118
|
let current: Element | null = node;
|
|
120
119
|
|
|
121
120
|
while (current && current.nodeType === 1) {
|
|
@@ -300,7 +299,10 @@ export async function getAnnotatedDOM(
|
|
|
300
299
|
doc.documentElement
|
|
301
300
|
);
|
|
302
301
|
}
|
|
303
|
-
} catch {
|
|
302
|
+
} catch (error) {
|
|
303
|
+
|
|
304
|
+
logger.error("Attribute injection failed", error)
|
|
305
|
+
}
|
|
304
306
|
}
|
|
305
307
|
|
|
306
308
|
el.childNodes.forEach(child => {
|
|
@@ -40,15 +40,38 @@ export const logger = {
|
|
|
40
40
|
|
|
41
41
|
fs.appendFileSync(logFilePath, message, "utf-8");
|
|
42
42
|
},
|
|
43
|
-
|
|
44
43
|
error: (...args: any[]) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
const formatted = args.map(arg => {
|
|
45
|
+
if (arg instanceof Error) {
|
|
46
|
+
let output = arg.stack || `${arg.name}: ${arg.message}`;
|
|
47
|
+
output = output.replace(
|
|
48
|
+
/invalid session id:\s*invalid session id:/gi,
|
|
49
|
+
"invalid session id:"
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (arg.cause instanceof Error) {
|
|
53
|
+
let causeStack = arg.cause.stack || "";
|
|
54
|
+
|
|
55
|
+
causeStack = causeStack.replace(
|
|
56
|
+
/invalid session id:\s*invalid session id:/gi,
|
|
57
|
+
"invalid session id:"
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
output += `\nCaused by: ${causeStack}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return output;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof arg === "object") {
|
|
67
|
+
return JSON.stringify(arg, null, 2);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return String(arg);
|
|
71
|
+
});
|
|
50
72
|
|
|
51
|
-
const message =
|
|
73
|
+
const message =
|
|
74
|
+
`[ERROR] ${new Date().toISOString()} - ${formatted.join(" ")}\n`;
|
|
52
75
|
|
|
53
76
|
fs.appendFileSync(logFilePath, message, "utf-8");
|
|
54
77
|
},
|