@dev-blinq/cucumber_client 1.0.1443-dev → 1.0.1443-stage
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/bin/assets/bundled_scripts/recorder.js +73 -73
- package/bin/assets/preload/css_gen.js +10 -10
- package/bin/assets/preload/toolbar.js +27 -29
- package/bin/assets/preload/unique_locators.js +1 -1
- package/bin/assets/preload/yaml.js +288 -275
- package/bin/assets/scripts/aria_snapshot.js +223 -220
- package/bin/assets/scripts/dom_attr.js +329 -329
- package/bin/assets/scripts/dom_parent.js +169 -174
- package/bin/assets/scripts/event_utils.js +94 -94
- package/bin/assets/scripts/pw.js +2050 -1949
- package/bin/assets/scripts/recorder.js +70 -45
- package/bin/assets/scripts/snapshot_capturer.js +147 -147
- package/bin/assets/scripts/unique_locators.js +170 -49
- package/bin/assets/scripts/yaml.js +796 -783
- package/bin/assets/templates/_hooks_template.txt +6 -2
- package/bin/assets/templates/utils_template.txt +16 -16
- package/bin/client/code_cleanup/find_step_definition_references.js +0 -1
- package/bin/client/code_cleanup/utils.js +16 -7
- package/bin/client/code_gen/api_codegen.js +2 -2
- package/bin/client/code_gen/code_inversion.js +119 -2
- package/bin/client/code_gen/duplication_analysis.js +2 -1
- package/bin/client/code_gen/function_signature.js +4 -0
- package/bin/client/code_gen/page_reflection.js +52 -11
- package/bin/client/code_gen/playwright_codeget.js +163 -75
- package/bin/client/cucumber/feature.js +4 -17
- package/bin/client/cucumber/feature_data.js +2 -2
- package/bin/client/cucumber/project_to_document.js +8 -2
- package/bin/client/cucumber/steps_definitions.js +19 -3
- package/bin/client/local_agent.js +1 -0
- package/bin/client/parse_feature_file.js +23 -26
- package/bin/client/playground/projects/env.json +2 -2
- package/bin/client/recorderv3/bvt_init.js +305 -0
- package/bin/client/recorderv3/bvt_recorder.js +1024 -58
- package/bin/client/recorderv3/implemented_steps.js +2 -0
- package/bin/client/recorderv3/index.js +3 -283
- package/bin/client/recorderv3/services.js +818 -142
- package/bin/client/recorderv3/step_runner.js +20 -6
- package/bin/client/recorderv3/step_utils.js +542 -73
- package/bin/client/recorderv3/update_feature.js +87 -39
- package/bin/client/recorderv3/wbr_entry.js +61 -0
- package/bin/client/recording.js +1 -0
- package/bin/client/upload-service.js +4 -2
- package/bin/client/utils/app_dir.js +21 -0
- package/bin/client/utils/socket_logger.js +87 -125
- package/bin/index.js +4 -1
- package/package.json +11 -5
- package/bin/client/recorderv3/app_dir.js +0 -23
- package/bin/client/recorderv3/network.js +0 -299
- package/bin/client/recorderv3/scriptTest.js +0 -5
- package/bin/client/recorderv3/ws_server.js +0 -72
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import url from "url";
|
|
4
|
-
import logger from "../../logger.js";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import url from "node:url";
|
|
5
4
|
import { CodePage, getAiConfig } from "../code_gen/page_reflection.js";
|
|
6
5
|
import { generateCode, generatePageName } from "../code_gen/playwright_codeget.js";
|
|
7
6
|
import { invertCodeToCommand } from "../code_gen/code_inversion.js";
|
|
@@ -9,11 +8,438 @@ import { Step } from "../cucumber/feature.js";
|
|
|
9
8
|
import { locateDefinitionPath, StepsDefinitions } from "../cucumber/steps_definitions.js";
|
|
10
9
|
import { Recording } from "../recording.js";
|
|
11
10
|
import { generateApiCode } from "../code_gen/api_codegen.js";
|
|
12
|
-
import { tmpdir } from "os";
|
|
13
|
-
import { createHash } from "crypto";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { getErrorMessage } from "../utils/socket_logger.js";
|
|
14
14
|
|
|
15
15
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
16
16
|
|
|
17
|
+
const convertToIdentifier = (text) => {
|
|
18
|
+
// replace all invalid characters with _
|
|
19
|
+
return text.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const isVariable = (text) => {
|
|
23
|
+
if (typeof text !== "string") return false;
|
|
24
|
+
const isParametric = text.startsWith("<") && text.endsWith(">");
|
|
25
|
+
if (!isParametric) return false;
|
|
26
|
+
const l = text.length;
|
|
27
|
+
if (l < 2) return false;
|
|
28
|
+
const leftindex = text.indexOf("<");
|
|
29
|
+
const rightindex = text.indexOf(">");
|
|
30
|
+
return leftindex === 0 && rightindex === l - 1;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const extractQuotes = (text) => {
|
|
34
|
+
const stringRegex = /"([^"]*)"/g;
|
|
35
|
+
const matches = text.match(stringRegex);
|
|
36
|
+
if (!matches) return [];
|
|
37
|
+
const quotes = [];
|
|
38
|
+
for (const match of matches) {
|
|
39
|
+
const value = match.slice(1, -1);
|
|
40
|
+
quotes.push(value);
|
|
41
|
+
}
|
|
42
|
+
return quotes;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const replaceLastOccurence = (str, search, replacement) => {
|
|
46
|
+
const lastIndex = str.lastIndexOf(search);
|
|
47
|
+
if (lastIndex === -1) return str;
|
|
48
|
+
return str.substring(0, lastIndex) + replacement + str.substring(lastIndex + search.length);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const _toRecordingStep = (cmd, stepText) => {
|
|
52
|
+
switch (cmd.type) {
|
|
53
|
+
case "hover_element": {
|
|
54
|
+
return {
|
|
55
|
+
type: "hover_element",
|
|
56
|
+
element: {
|
|
57
|
+
role: cmd.role,
|
|
58
|
+
name: cmd.label,
|
|
59
|
+
},
|
|
60
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
case "click_element": {
|
|
64
|
+
return {
|
|
65
|
+
type: "click_element",
|
|
66
|
+
element: {
|
|
67
|
+
role: cmd.role,
|
|
68
|
+
name: cmd.label,
|
|
69
|
+
},
|
|
70
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
71
|
+
count: cmd.count ?? 1,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
case "context_click": {
|
|
75
|
+
return {
|
|
76
|
+
type: "context_click",
|
|
77
|
+
element: {
|
|
78
|
+
role: cmd.role,
|
|
79
|
+
name: cmd.label,
|
|
80
|
+
},
|
|
81
|
+
label: cmd.label,
|
|
82
|
+
value: cmd.value,
|
|
83
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
84
|
+
text: cmd.text,
|
|
85
|
+
count: cmd.count ?? 1,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
case "parameterized_click": {
|
|
89
|
+
return {
|
|
90
|
+
type: "parameterized_click",
|
|
91
|
+
element: {
|
|
92
|
+
role: cmd.role,
|
|
93
|
+
name: cmd.label,
|
|
94
|
+
},
|
|
95
|
+
label: cmd.label,
|
|
96
|
+
value: cmd.value,
|
|
97
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
98
|
+
count: cmd.count ?? 1,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
case "fill_element": {
|
|
102
|
+
return {
|
|
103
|
+
type: "fill_element",
|
|
104
|
+
element: {
|
|
105
|
+
role: cmd.role,
|
|
106
|
+
name: cmd.label,
|
|
107
|
+
},
|
|
108
|
+
parameters: [cmd.value, cmd.enter ?? false],
|
|
109
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
case "select_combobox": {
|
|
113
|
+
return {
|
|
114
|
+
type: "select_combobox",
|
|
115
|
+
element: {
|
|
116
|
+
role: "combobox",
|
|
117
|
+
name: cmd.label,
|
|
118
|
+
},
|
|
119
|
+
selectMode: "select",
|
|
120
|
+
parameters: [cmd.value],
|
|
121
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
case "verify_page_contains_text": {
|
|
125
|
+
return {
|
|
126
|
+
type: "verify_page_contains_text",
|
|
127
|
+
parameters: [cmd.value, cmd.isRegex],
|
|
128
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
case "verify_element_contains_text": {
|
|
132
|
+
return {
|
|
133
|
+
type: "verify_element_contains_text",
|
|
134
|
+
element: {
|
|
135
|
+
role: cmd.role,
|
|
136
|
+
name: cmd.label,
|
|
137
|
+
},
|
|
138
|
+
parameters: [cmd.value, cmd.climb],
|
|
139
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
case "close_page": {
|
|
143
|
+
return {
|
|
144
|
+
type: "close_page",
|
|
145
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
case "check_element": {
|
|
149
|
+
return {
|
|
150
|
+
type: "check_element",
|
|
151
|
+
element: {
|
|
152
|
+
role: cmd.role,
|
|
153
|
+
name: cmd.label,
|
|
154
|
+
},
|
|
155
|
+
check: cmd.check,
|
|
156
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
case "press_key": {
|
|
160
|
+
return {
|
|
161
|
+
type: "press_key",
|
|
162
|
+
element: {
|
|
163
|
+
role: cmd.role,
|
|
164
|
+
name: cmd.label,
|
|
165
|
+
},
|
|
166
|
+
key: cmd.value,
|
|
167
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
case "load_user": {
|
|
171
|
+
return {
|
|
172
|
+
type: "load_data",
|
|
173
|
+
parameters: ["users", cmd.value],
|
|
174
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
case "load_csv": {
|
|
178
|
+
return {
|
|
179
|
+
type: "load_data",
|
|
180
|
+
parameters: ["csv", `${cmd.label}:${cmd.value}`],
|
|
181
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
case "set_date_time": {
|
|
185
|
+
return {
|
|
186
|
+
type: "set_date_time",
|
|
187
|
+
element: {
|
|
188
|
+
role: cmd.role,
|
|
189
|
+
name: cmd.label,
|
|
190
|
+
},
|
|
191
|
+
parameters: [cmd.value],
|
|
192
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
case "set_input": {
|
|
196
|
+
return {
|
|
197
|
+
type: "set_input",
|
|
198
|
+
element: {
|
|
199
|
+
role: cmd.role,
|
|
200
|
+
name: cmd.label,
|
|
201
|
+
},
|
|
202
|
+
value: cmd.value,
|
|
203
|
+
parameters: [cmd.value],
|
|
204
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
case "extract_attribute": {
|
|
208
|
+
return {
|
|
209
|
+
type: "extract_attribute",
|
|
210
|
+
element: {
|
|
211
|
+
role: cmd.role,
|
|
212
|
+
name: cmd.label,
|
|
213
|
+
},
|
|
214
|
+
parameters: [cmd.selectedField, cmd.variableName],
|
|
215
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
216
|
+
regex: cmd.regex,
|
|
217
|
+
trimSpaces: cmd.trimSpaces,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
case "extract_property": {
|
|
221
|
+
return {
|
|
222
|
+
type: "extract_property",
|
|
223
|
+
element: {
|
|
224
|
+
role: cmd.role,
|
|
225
|
+
name: cmd.label,
|
|
226
|
+
},
|
|
227
|
+
parameters: [cmd.selectedField, cmd.variableName],
|
|
228
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
229
|
+
regex: cmd.regex,
|
|
230
|
+
trimSpaces: cmd.trimSpaces,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
case "verify_element_attribute": {
|
|
234
|
+
return {
|
|
235
|
+
type: "verify_element_attribute",
|
|
236
|
+
element: {
|
|
237
|
+
role: cmd.role,
|
|
238
|
+
name: cmd.label,
|
|
239
|
+
},
|
|
240
|
+
parameters: [cmd.selectedField, cmd.value],
|
|
241
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
case "verify_element_property": {
|
|
245
|
+
return {
|
|
246
|
+
type: "verify_element_property",
|
|
247
|
+
element: {
|
|
248
|
+
role: cmd.role,
|
|
249
|
+
name: cmd.label,
|
|
250
|
+
},
|
|
251
|
+
parameters: [cmd.selectedField, cmd.value],
|
|
252
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
253
|
+
valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
case "conditional_wait": {
|
|
257
|
+
return {
|
|
258
|
+
type: "conditional_wait",
|
|
259
|
+
element: {
|
|
260
|
+
role: cmd.role,
|
|
261
|
+
name: cmd.label,
|
|
262
|
+
},
|
|
263
|
+
parameters: [cmd.timeout, cmd.selectedField, cmd.value],
|
|
264
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
265
|
+
valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
case "navigate": {
|
|
269
|
+
return {
|
|
270
|
+
type: "navigate",
|
|
271
|
+
parameters: [cmd.value],
|
|
272
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
case "browser_go_back": {
|
|
276
|
+
return {
|
|
277
|
+
type: "browser_go_back",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
case "browser_go_forward": {
|
|
281
|
+
return {
|
|
282
|
+
type: "browser_go_forward",
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
case "set_input_files": {
|
|
286
|
+
return {
|
|
287
|
+
type: "set_input_files",
|
|
288
|
+
element: {
|
|
289
|
+
role: cmd.role,
|
|
290
|
+
name: cmd.label,
|
|
291
|
+
},
|
|
292
|
+
parameters: [cmd.files],
|
|
293
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
case "verify_page_snapshot": {
|
|
297
|
+
return {
|
|
298
|
+
type: "verify_page_snapshot",
|
|
299
|
+
parameters: [cmd.value],
|
|
300
|
+
selectors: cmd.selectors,
|
|
301
|
+
data: cmd.data,
|
|
302
|
+
valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
default: {
|
|
306
|
+
return {
|
|
307
|
+
type: cmd.type,
|
|
308
|
+
parameters: [cmd.value],
|
|
309
|
+
lastKnownUrlPath: cmd.lastKnownUrlPath,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
function getBestStrategy(allStrategyLocators) {
|
|
316
|
+
const orderedPriorities = ["custom", "context", "basic", "text_with_index", "ignore_digit", "no_text"];
|
|
317
|
+
for (const strategy of orderedPriorities) {
|
|
318
|
+
if (allStrategyLocators[strategy] && allStrategyLocators[strategy].length > 0) {
|
|
319
|
+
return strategy;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const _parameterizeLocators = (locators, replacementFromValue, replacementToValue) => {
|
|
326
|
+
for (const loc of locators) {
|
|
327
|
+
if (loc?.css?.includes(replacementFromValue)) {
|
|
328
|
+
loc.css = loc.css.replaceAll(replacementFromValue, replacementToValue);
|
|
329
|
+
}
|
|
330
|
+
if (loc?.text?.includes(replacementFromValue)) {
|
|
331
|
+
loc.text = loc.text.replaceAll(replacementFromValue, replacementToValue);
|
|
332
|
+
}
|
|
333
|
+
if (loc?.climb && typeof loc.climb === "string" && loc.climb?.includes(replacementFromValue)) {
|
|
334
|
+
loc.climb = loc.climb.replaceAll(replacementFromValue, replacementToValue);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return locators;
|
|
338
|
+
};
|
|
339
|
+
const parameterizeLocators = ({ cmd, locs, isValueVariable, isTargetValueVariable, parametersMap }) => {
|
|
340
|
+
if (isValueVariable) {
|
|
341
|
+
const variable = cmd.value.slice(1, -1);
|
|
342
|
+
// const val = parametersMap[variable];
|
|
343
|
+
if (typeof cmd.text === "string") {
|
|
344
|
+
const replacementFromValue = cmd.text.trim().replace(/\s+/g, " ") ?? ""; // val.trim();
|
|
345
|
+
if (replacementFromValue.length > 0) {
|
|
346
|
+
const replacementToValue = `{${variable}}`;
|
|
347
|
+
locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (isTargetValueVariable) {
|
|
352
|
+
const variable = cmd.targetValue.slice(1, -1);
|
|
353
|
+
// const val = parametersMap[variable];
|
|
354
|
+
if (typeof cmd.targetText === "string") {
|
|
355
|
+
const replacementFromValue = cmd.targetText.trim().replace(/\s+/g, " ") ?? ""; // val.trim();
|
|
356
|
+
if (replacementFromValue.length > 0) {
|
|
357
|
+
const replacementToValue = `{${variable}}`;
|
|
358
|
+
locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return locs;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
//TODO: IMPORTAN
|
|
366
|
+
export const toRecordingStep = (cmd, parametersMap, stepText) => {
|
|
367
|
+
if (cmd.type === "api") {
|
|
368
|
+
return {
|
|
369
|
+
type: "api",
|
|
370
|
+
value: cmd.value,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
const step = _toRecordingStep(cmd, stepText);
|
|
374
|
+
const cmdID = {
|
|
375
|
+
cmdId: cmd.id,
|
|
376
|
+
options: cmd.options ?? null,
|
|
377
|
+
};
|
|
378
|
+
Object.assign(step, cmdID);
|
|
379
|
+
|
|
380
|
+
const locatorsObject = JSON.parse(JSON.stringify(cmd.locators ?? null));
|
|
381
|
+
|
|
382
|
+
if (!locatorsObject) return step;
|
|
383
|
+
|
|
384
|
+
const element_name = cmd?.locators?.element_name ?? `${cmd.label} ${cmd.role ?? "Text"}`;
|
|
385
|
+
locatorsObject.element_name = element_name;
|
|
386
|
+
|
|
387
|
+
const isValueVariable = isVariable(cmd.value);
|
|
388
|
+
const isTargetValueVariable = isVariable(cmd.targetValue);
|
|
389
|
+
const allStrategyLocators = JSON.parse(JSON.stringify(cmd?.allStrategyLocators ?? null));
|
|
390
|
+
step.locators = locatorsObject;
|
|
391
|
+
step.allStrategyLocators = allStrategyLocators;
|
|
392
|
+
step.isLocatorsAssigned = true;
|
|
393
|
+
|
|
394
|
+
if (!isValueVariable && !isTargetValueVariable) {
|
|
395
|
+
return step;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (isValueVariable) {
|
|
399
|
+
step.dataSource = "parameters";
|
|
400
|
+
step.dataKey = convertToIdentifier(cmd.value.slice(1, -1));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (!allStrategyLocators) {
|
|
404
|
+
let locs = locatorsObject.locators;
|
|
405
|
+
locs = parameterizeLocators({
|
|
406
|
+
cmd,
|
|
407
|
+
locs,
|
|
408
|
+
isValueVariable,
|
|
409
|
+
isTargetValueVariable,
|
|
410
|
+
parametersMap,
|
|
411
|
+
});
|
|
412
|
+
locatorsObject.locators = locs;
|
|
413
|
+
return {
|
|
414
|
+
...step,
|
|
415
|
+
locators: locatorsObject,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
for (const key in allStrategyLocators) {
|
|
420
|
+
if (key === "strategy") continue;
|
|
421
|
+
if (key === "no_text" || key === "custom") continue;
|
|
422
|
+
const locators = allStrategyLocators[key];
|
|
423
|
+
if (locators.length === 0) continue;
|
|
424
|
+
parameterizeLocators({
|
|
425
|
+
cmd,
|
|
426
|
+
locs: locators,
|
|
427
|
+
isValueVariable,
|
|
428
|
+
isTargetValueVariable,
|
|
429
|
+
parametersMap,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
locatorsObject.locators = allStrategyLocators[allStrategyLocators.strategy] ?? locatorsObject.locators;
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
...step,
|
|
437
|
+
locators: locatorsObject,
|
|
438
|
+
allStrategyLocators,
|
|
439
|
+
isLocatorsAssigned: true,
|
|
440
|
+
};
|
|
441
|
+
};
|
|
442
|
+
|
|
17
443
|
export const toMethodName = (str) => {
|
|
18
444
|
// Remove any non-word characters (excluding underscore) and trim spaces
|
|
19
445
|
let cleanStr = str.trim().replace(/[^\w\s]/gi, "");
|
|
@@ -36,7 +462,7 @@ export function getCodePage(stepDefsFilePath) {
|
|
|
36
462
|
export function getCucumberStep({ step }) {
|
|
37
463
|
const cucumberStep = new Step();
|
|
38
464
|
cucumberStep.loadFromJson({
|
|
39
|
-
text: step.text,
|
|
465
|
+
text: step.renamedText ? step.renamedText : step.text,
|
|
40
466
|
keyword: step.keyword,
|
|
41
467
|
keywordType: step.keywordType,
|
|
42
468
|
parameters: [],
|
|
@@ -54,12 +480,12 @@ export function getCucumberStep({ step }) {
|
|
|
54
480
|
|
|
55
481
|
function makeStepTextUnique(step, stepsDefinitions) {
|
|
56
482
|
// const utilsFilePath = path.join("features", "step_definitions", "utils.mjs");
|
|
57
|
-
let stepText = step.text;
|
|
483
|
+
let stepText = step.renamedText ? step.renamedText : step.text;
|
|
58
484
|
let stepIndex = 1;
|
|
59
485
|
// console.log("makeStepTextUnique", step.text);
|
|
60
486
|
let stepDef = stepsDefinitions.findMatchingStep(stepText);
|
|
61
487
|
// console.log({ stepDef });
|
|
62
|
-
if (stepDef && stepDef?.file.endsWith("utils.mjs")) {
|
|
488
|
+
if (stepDef && stepDef?.file.endsWith("utils.mjs") && !step.shouldMakeStepTextUnique) {
|
|
63
489
|
return true;
|
|
64
490
|
}
|
|
65
491
|
while (stepDef) {
|
|
@@ -70,29 +496,38 @@ function makeStepTextUnique(step, stepsDefinitions) {
|
|
|
70
496
|
step.text = stepText;
|
|
71
497
|
}
|
|
72
498
|
|
|
73
|
-
export async function saveRecording({
|
|
499
|
+
export async function saveRecording({
|
|
500
|
+
step,
|
|
501
|
+
cucumberStep,
|
|
502
|
+
codePage,
|
|
503
|
+
projectDir,
|
|
504
|
+
stepsDefinitions,
|
|
505
|
+
parametersMap,
|
|
506
|
+
logger,
|
|
507
|
+
}) {
|
|
508
|
+
if (step.commands && Array.isArray(step.commands)) {
|
|
509
|
+
step.commands = step.commands.map((cmd) => toRecordingStep(cmd, parametersMap, step.text));
|
|
510
|
+
}
|
|
74
511
|
let routesPath = path.join(tmpdir(), "blinq_temp_routes");
|
|
75
512
|
|
|
76
|
-
if (process.env.TEMP_RUN) {
|
|
513
|
+
if (process.env.TEMP_RUN === "true") {
|
|
77
514
|
if (existsSync(routesPath)) {
|
|
78
515
|
rmSync(routesPath, { recursive: true });
|
|
79
516
|
}
|
|
80
517
|
mkdirSync(routesPath, { recursive: true });
|
|
81
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
518
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
82
519
|
} else {
|
|
83
520
|
if (existsSync(routesPath)) {
|
|
84
|
-
// remove the folder
|
|
85
521
|
try {
|
|
86
522
|
rmSync(routesPath, { recursive: true });
|
|
87
|
-
console.log("Removed temp_routes_folder:", routesPath);
|
|
88
523
|
} catch (error) {
|
|
89
|
-
|
|
524
|
+
logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
|
|
90
525
|
}
|
|
91
526
|
routesPath = path.join(projectDir, "data", "routes");
|
|
92
527
|
if (!existsSync(routesPath)) {
|
|
93
528
|
mkdirSync(routesPath, { recursive: true });
|
|
94
529
|
}
|
|
95
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
530
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
96
531
|
}
|
|
97
532
|
}
|
|
98
533
|
|
|
@@ -101,46 +536,51 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
101
536
|
}
|
|
102
537
|
|
|
103
538
|
if (step.isImplemented && step.shouldOverride) {
|
|
104
|
-
|
|
539
|
+
const stepDef = stepsDefinitions.findMatchingStep(step.text);
|
|
105
540
|
codePage = getCodePage(stepDef.file);
|
|
106
541
|
} else {
|
|
107
542
|
const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
|
|
108
543
|
|
|
109
544
|
if (isUtilStep) {
|
|
110
|
-
|
|
545
|
+
if (!step.renamedText) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
step.text = step.text.trim();
|
|
549
|
+
const { functionName } = stepsDefinitions.findMatchingStep(step.renamedText);
|
|
550
|
+
step.renamedText = functionName;
|
|
551
|
+
const newImportLine = `import { ${functionName} } from "./utils.mjs";\n`;
|
|
552
|
+
|
|
553
|
+
if (!codePage.fileContent.includes(newImportLine)) {
|
|
554
|
+
codePage.fileContent = newImportLine + codePage.fileContent;
|
|
555
|
+
}
|
|
111
556
|
}
|
|
112
557
|
}
|
|
113
558
|
|
|
559
|
+
routesPath = path.join(tmpdir(), "blinq_temp_routes");
|
|
114
560
|
if (process.env.TEMP_RUN === "true") {
|
|
115
|
-
console.log("Save routes in temp folder for running:", routesPath);
|
|
116
561
|
if (existsSync(routesPath)) {
|
|
117
|
-
console.log("Removing existing temp_routes_folder:", routesPath);
|
|
118
562
|
rmSync(routesPath, { recursive: true });
|
|
119
563
|
}
|
|
120
564
|
mkdirSync(routesPath, { recursive: true });
|
|
121
|
-
|
|
122
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
565
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
123
566
|
} else {
|
|
124
|
-
console.log("Saving routes in project directory:", projectDir);
|
|
125
567
|
if (existsSync(routesPath)) {
|
|
126
|
-
// remove the folder
|
|
127
568
|
try {
|
|
128
569
|
rmSync(routesPath, { recursive: true });
|
|
129
|
-
console.log("Removed temp_routes_folder:", routesPath);
|
|
130
570
|
} catch (error) {
|
|
131
|
-
|
|
571
|
+
logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
|
|
132
572
|
}
|
|
133
573
|
}
|
|
134
574
|
routesPath = path.join(projectDir, "data", "routes");
|
|
135
|
-
console.log("Saving routes to:", routesPath);
|
|
136
575
|
if (!existsSync(routesPath)) {
|
|
137
576
|
mkdirSync(routesPath, { recursive: true });
|
|
138
577
|
}
|
|
139
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
578
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
140
579
|
}
|
|
141
580
|
|
|
142
581
|
cucumberStep.text = step.text;
|
|
143
582
|
const recording = new Recording();
|
|
583
|
+
|
|
144
584
|
const steps = step.commands;
|
|
145
585
|
|
|
146
586
|
recording.loadFromObject({ steps, step: cucumberStep });
|
|
@@ -191,7 +631,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
191
631
|
stepsDefinitions
|
|
192
632
|
);
|
|
193
633
|
|
|
194
|
-
if (!step.isImplemented) {
|
|
634
|
+
if (!step.renamedText && !(step.isImplemented && step.shouldOverride)) {
|
|
195
635
|
stepsDefinitions.addStep({
|
|
196
636
|
name: step.text,
|
|
197
637
|
file: result.codePage.sourceFileName,
|
|
@@ -204,12 +644,14 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
204
644
|
} else {
|
|
205
645
|
const generateCodeResult = generateCode(recording, codePage, userData, projectDir, methodName);
|
|
206
646
|
if (generateCodeResult.noCode === true) {
|
|
207
|
-
logger.log("No code generated for step: " + step.text);
|
|
208
647
|
return generateCodeResult.page;
|
|
209
648
|
}
|
|
210
649
|
codePage = generateCodeResult.page;
|
|
211
650
|
methodName = generateCodeResult.methodName;
|
|
212
|
-
|
|
651
|
+
|
|
652
|
+
if (!step.renamedText) {
|
|
653
|
+
codePage.insertElements(generateCodeResult.elements);
|
|
654
|
+
}
|
|
213
655
|
|
|
214
656
|
const description = cucumberStep.text;
|
|
215
657
|
let path = null;
|
|
@@ -222,15 +664,32 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
222
664
|
protect = true;
|
|
223
665
|
}
|
|
224
666
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
667
|
+
|
|
668
|
+
if (step.renamedText) {
|
|
669
|
+
codePage.addInfraCommandUtil(
|
|
670
|
+
methodName,
|
|
671
|
+
description,
|
|
672
|
+
cucumberStep.parameters,
|
|
673
|
+
generateCodeResult.codeLines,
|
|
674
|
+
step.renamedText,
|
|
675
|
+
step.text,
|
|
676
|
+
parametersMap,
|
|
677
|
+
protect,
|
|
678
|
+
"recorder",
|
|
679
|
+
path
|
|
680
|
+
);
|
|
681
|
+
} else {
|
|
682
|
+
codePage.addInfraCommand(
|
|
683
|
+
methodName,
|
|
684
|
+
description,
|
|
685
|
+
cucumberStep.getVariablesList(),
|
|
686
|
+
generateCodeResult.codeLines,
|
|
687
|
+
protect,
|
|
688
|
+
"recorder",
|
|
689
|
+
path
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
234
693
|
const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
|
|
235
694
|
const stepResult = codePage.addCucumberStep(
|
|
236
695
|
keyword,
|
|
@@ -240,7 +699,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
240
699
|
step.finalTimeout
|
|
241
700
|
);
|
|
242
701
|
|
|
243
|
-
if (!step.isImplemented) {
|
|
702
|
+
if (!step.renamedText && !(step.isImplemented && step.shouldOverride)) {
|
|
244
703
|
stepsDefinitions.addStep({
|
|
245
704
|
name: step.text,
|
|
246
705
|
file: codePage.sourceFileName,
|
|
@@ -249,6 +708,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
249
708
|
}
|
|
250
709
|
|
|
251
710
|
codePage.removeUnusedElements();
|
|
711
|
+
codePage.mergeSimilarElements();
|
|
252
712
|
cucumberStep.methodName = methodName;
|
|
253
713
|
if (generateCodeResult.locatorsMetadata) {
|
|
254
714
|
codePage.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
|
|
@@ -292,7 +752,7 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
|
|
|
292
752
|
const file = step?.file;
|
|
293
753
|
const locatorsJson = getLocatorsJson(file);
|
|
294
754
|
if (!step) {
|
|
295
|
-
throw new Error(
|
|
755
|
+
throw new Error(`Step definition not found: ${stepName}`);
|
|
296
756
|
}
|
|
297
757
|
isImplemented = true;
|
|
298
758
|
const { codeCommands, codePage, elements, parametersNames, error } =
|
|
@@ -300,26 +760,35 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
|
|
|
300
760
|
if (error) {
|
|
301
761
|
throw new Error(error);
|
|
302
762
|
}
|
|
763
|
+
isUtilStep = codePage.sourceFileName.endsWith("utils.mjs");
|
|
303
764
|
|
|
304
765
|
if (parametersNames.length !== stepParams.length) {
|
|
305
766
|
// console.log("Parameters mismatch", parametersNames, stepParams);
|
|
306
767
|
throw new Error("Parameters mismatch");
|
|
307
768
|
}
|
|
308
|
-
for (let i = 0; i < parametersNames.length; i++) {
|
|
309
|
-
stepParams[i].argumentName = parametersNames[i];
|
|
310
|
-
}
|
|
311
769
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
770
|
+
const pattern = step.name;
|
|
771
|
+
if (isUtilStep && pattern === "Verify the file {string} exists") {
|
|
772
|
+
commands.push({
|
|
773
|
+
type: "verify_file_exists",
|
|
774
|
+
parameters: [stepParams[0].text],
|
|
775
|
+
});
|
|
776
|
+
} else {
|
|
777
|
+
for (let i = 0; i < parametersNames.length; i++) {
|
|
778
|
+
stepParams[i].argumentName = parametersNames[i];
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
for (const { code } of codeCommands) {
|
|
782
|
+
const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
|
|
783
|
+
if (command === undefined || command.type === null) continue;
|
|
784
|
+
if (command.element) {
|
|
785
|
+
const key = command.element.key;
|
|
786
|
+
if (key && locatorsJson[key]) {
|
|
787
|
+
command.allStrategyLocators = locatorsJson[key];
|
|
788
|
+
}
|
|
320
789
|
}
|
|
790
|
+
commands.push(command);
|
|
321
791
|
}
|
|
322
|
-
commands.push(command);
|
|
323
792
|
}
|
|
324
793
|
} catch (error) {
|
|
325
794
|
console.error(error);
|
|
@@ -367,7 +836,7 @@ export async function executeStep({ stepsDefinitions, cucumberStep, context, cod
|
|
|
367
836
|
}
|
|
368
837
|
}
|
|
369
838
|
|
|
370
|
-
export async function updateStepDefinitions({ scenario, featureName, projectDir }) {
|
|
839
|
+
export async function updateStepDefinitions({ scenario, featureName, projectDir, logger }) {
|
|
371
840
|
// set the candidate step definition file name
|
|
372
841
|
// set the utils file path
|
|
373
842
|
const utilsFilePath = path.join(projectDir, "features", "step_definitions", "utils.mjs");
|
|
@@ -401,40 +870,43 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
401
870
|
if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
|
|
402
871
|
let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
403
872
|
if (process.env.TEMP_RUN === "true") {
|
|
404
|
-
console.log("Save routes in temp folder for running:", routesPath);
|
|
405
873
|
if (existsSync(routesPath)) {
|
|
406
|
-
|
|
874
|
+
routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
407
875
|
rmSync(routesPath, { recursive: true });
|
|
408
876
|
}
|
|
409
877
|
mkdirSync(routesPath, { recursive: true });
|
|
410
|
-
|
|
411
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
878
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
412
879
|
} else {
|
|
413
|
-
console.log("Saving routes in project directory:", projectDir);
|
|
414
880
|
if (existsSync(routesPath)) {
|
|
415
|
-
// remove the folder
|
|
416
881
|
try {
|
|
417
882
|
rmSync(routesPath, { recursive: true });
|
|
418
|
-
console.log("Removed temp_routes_folder:", routesPath);
|
|
419
883
|
} catch (error) {
|
|
420
|
-
|
|
884
|
+
logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
|
|
421
885
|
}
|
|
422
886
|
}
|
|
423
887
|
routesPath = path.join(projectDir, "data", "routes");
|
|
424
|
-
console.log("Saving routes to:", routesPath);
|
|
425
888
|
if (!existsSync(routesPath)) {
|
|
426
889
|
mkdirSync(routesPath, { recursive: true });
|
|
427
890
|
}
|
|
428
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
891
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
892
|
+
}
|
|
893
|
+
if (step.commands && Array.isArray(step.commands)) {
|
|
894
|
+
step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
|
|
429
895
|
}
|
|
430
896
|
continue;
|
|
431
897
|
}
|
|
432
898
|
const cucumberStep = getCucumberStep({ step });
|
|
433
899
|
const pageName = generatePageName(step.startFrame?.url ?? "default");
|
|
434
900
|
const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
|
|
435
|
-
// path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
|
|
436
901
|
let codePage = getCodePage(stepDefsFilePath);
|
|
437
|
-
codePage = await saveRecording({
|
|
902
|
+
codePage = await saveRecording({
|
|
903
|
+
step,
|
|
904
|
+
cucumberStep,
|
|
905
|
+
codePage,
|
|
906
|
+
projectDir,
|
|
907
|
+
stepsDefinitions,
|
|
908
|
+
parametersMap: scenario.parametersMap,
|
|
909
|
+
});
|
|
438
910
|
if (!codePage) {
|
|
439
911
|
continue;
|
|
440
912
|
}
|
|
@@ -446,7 +918,7 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
446
918
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
447
919
|
}
|
|
448
920
|
|
|
449
|
-
export function saveRoutes({ step, folderPath }) {
|
|
921
|
+
export function saveRoutes({ step, folderPath }, logger) {
|
|
450
922
|
const routeItems = step.routeItems;
|
|
451
923
|
if (!routeItems || routeItems.length === 0) {
|
|
452
924
|
return;
|
|
@@ -469,21 +941,18 @@ export function saveRoutes({ step, folderPath }) {
|
|
|
469
941
|
};
|
|
470
942
|
});
|
|
471
943
|
|
|
472
|
-
const routesFilePath = path.join(folderPath, stepNameHash
|
|
473
|
-
console.log("Routes file path:", routesFilePath);
|
|
944
|
+
const routesFilePath = path.join(folderPath, `${stepNameHash}.json`);
|
|
474
945
|
const routesData = {
|
|
475
946
|
template,
|
|
476
947
|
routes: routeItemsWithFilters,
|
|
477
948
|
};
|
|
478
|
-
console.log("Routes data to save:", routesData);
|
|
479
949
|
|
|
480
950
|
if (!existsSync(folderPath)) {
|
|
481
951
|
mkdirSync(folderPath, { recursive: true });
|
|
482
952
|
}
|
|
483
953
|
try {
|
|
484
954
|
writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
|
|
485
|
-
console.log("Saved routes to", routesFilePath);
|
|
486
955
|
} catch (error) {
|
|
487
|
-
|
|
956
|
+
logger.error(`Error saving routes to ${routesFilePath}: ${getErrorMessage(error)}`);
|
|
488
957
|
}
|
|
489
958
|
}
|