@dev-blinq/cucumber_client 1.0.1450-dev → 1.0.1450-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 -48
- 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 -57
- 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 +25 -8
- package/bin/client/recorderv3/step_utils.js +569 -75
- 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,16 @@ 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 (
|
|
488
|
+
if (
|
|
489
|
+
stepDef &&
|
|
490
|
+
(stepDef?.file.endsWith("utils.mjs") || stepDef?.file.endsWith("default_page.mjs")) &&
|
|
491
|
+
!step.shouldMakeStepTextUnique
|
|
492
|
+
) {
|
|
63
493
|
return true;
|
|
64
494
|
}
|
|
65
495
|
while (stepDef) {
|
|
@@ -70,7 +500,18 @@ function makeStepTextUnique(step, stepsDefinitions) {
|
|
|
70
500
|
step.text = stepText;
|
|
71
501
|
}
|
|
72
502
|
|
|
73
|
-
export async function saveRecording({
|
|
503
|
+
export async function saveRecording({
|
|
504
|
+
step,
|
|
505
|
+
cucumberStep,
|
|
506
|
+
codePage,
|
|
507
|
+
projectDir,
|
|
508
|
+
stepsDefinitions,
|
|
509
|
+
parametersMap,
|
|
510
|
+
logger,
|
|
511
|
+
}) {
|
|
512
|
+
if (step.commands && Array.isArray(step.commands)) {
|
|
513
|
+
step.commands = step.commands.map((cmd) => toRecordingStep(cmd, parametersMap, step.text));
|
|
514
|
+
}
|
|
74
515
|
let routesPath = path.join(tmpdir(), "blinq_temp_routes");
|
|
75
516
|
|
|
76
517
|
if (process.env.TEMP_RUN === "true") {
|
|
@@ -78,21 +519,19 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
78
519
|
rmSync(routesPath, { recursive: true });
|
|
79
520
|
}
|
|
80
521
|
mkdirSync(routesPath, { recursive: true });
|
|
81
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
522
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
82
523
|
} else {
|
|
83
524
|
if (existsSync(routesPath)) {
|
|
84
|
-
// remove the folder
|
|
85
525
|
try {
|
|
86
526
|
rmSync(routesPath, { recursive: true });
|
|
87
|
-
console.log("Removed temp_routes_folder:", routesPath);
|
|
88
527
|
} catch (error) {
|
|
89
|
-
|
|
528
|
+
logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
|
|
90
529
|
}
|
|
91
530
|
routesPath = path.join(projectDir, "data", "routes");
|
|
92
531
|
if (!existsSync(routesPath)) {
|
|
93
532
|
mkdirSync(routesPath, { recursive: true });
|
|
94
533
|
}
|
|
95
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
534
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
96
535
|
}
|
|
97
536
|
}
|
|
98
537
|
|
|
@@ -100,48 +539,63 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
100
539
|
return;
|
|
101
540
|
}
|
|
102
541
|
|
|
542
|
+
let isUtilStep = false;
|
|
543
|
+
let isChangedUtilStepName = false;
|
|
544
|
+
|
|
103
545
|
if (step.isImplemented && step.shouldOverride) {
|
|
104
|
-
|
|
546
|
+
const stepDef = stepsDefinitions.findMatchingStep(step.text);
|
|
105
547
|
codePage = getCodePage(stepDef.file);
|
|
106
548
|
} else {
|
|
107
|
-
|
|
549
|
+
isUtilStep = makeStepTextUnique(step, stepsDefinitions);
|
|
108
550
|
|
|
109
551
|
if (isUtilStep) {
|
|
110
|
-
|
|
552
|
+
isChangedUtilStepName =
|
|
553
|
+
step.renamedText && stepsDefinitions.findMatchingStep(step.renamedText)?.file.endsWith("default_page.mjs");
|
|
554
|
+
|
|
555
|
+
if (!isChangedUtilStepName) {
|
|
556
|
+
const isUtilStep = stepsDefinitions.findMatchingStep(step.text)?.file.endsWith("utils.mjs");
|
|
557
|
+
if (!step.renamedText || step.renamedText === step.text || isUtilStep) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
step.text = step.text.trim();
|
|
561
|
+
const { functionName } = stepsDefinitions.findMatchingStep(step.renamedText);
|
|
562
|
+
step.renamedText = functionName;
|
|
563
|
+
const newImportLine = `import { ${functionName} } from "./utils.mjs";\n`;
|
|
564
|
+
|
|
565
|
+
if (!codePage.fileContent.includes(newImportLine)) {
|
|
566
|
+
codePage.fileContent = newImportLine + codePage.fileContent;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
111
569
|
}
|
|
112
570
|
}
|
|
113
571
|
|
|
572
|
+
const renamedUtil = step.renamedText && isUtilStep;
|
|
573
|
+
|
|
114
574
|
routesPath = path.join(tmpdir(), "blinq_temp_routes");
|
|
115
575
|
if (process.env.TEMP_RUN === "true") {
|
|
116
|
-
console.log("Save routes in temp folder for running:", routesPath);
|
|
117
576
|
if (existsSync(routesPath)) {
|
|
118
|
-
console.log("Removing existing temp_routes_folder:", routesPath);
|
|
119
577
|
rmSync(routesPath, { recursive: true });
|
|
120
578
|
}
|
|
121
579
|
mkdirSync(routesPath, { recursive: true });
|
|
122
|
-
|
|
123
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
580
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
124
581
|
} else {
|
|
125
|
-
console.log("Saving routes in project directory:", projectDir);
|
|
126
582
|
if (existsSync(routesPath)) {
|
|
127
|
-
// remove the folder
|
|
128
583
|
try {
|
|
129
584
|
rmSync(routesPath, { recursive: true });
|
|
130
|
-
console.log("Removed temp_routes_folder:", routesPath);
|
|
131
585
|
} catch (error) {
|
|
132
|
-
|
|
586
|
+
logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
|
|
133
587
|
}
|
|
134
588
|
}
|
|
135
589
|
routesPath = path.join(projectDir, "data", "routes");
|
|
136
|
-
console.log("Saving routes to:", routesPath);
|
|
137
590
|
if (!existsSync(routesPath)) {
|
|
138
591
|
mkdirSync(routesPath, { recursive: true });
|
|
139
592
|
}
|
|
140
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
593
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
141
594
|
}
|
|
142
595
|
|
|
143
596
|
cucumberStep.text = step.text;
|
|
144
597
|
const recording = new Recording();
|
|
598
|
+
|
|
145
599
|
const steps = step.commands;
|
|
146
600
|
|
|
147
601
|
recording.loadFromObject({ steps, step: cucumberStep });
|
|
@@ -192,7 +646,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
192
646
|
stepsDefinitions
|
|
193
647
|
);
|
|
194
648
|
|
|
195
|
-
if (!step.isImplemented) {
|
|
649
|
+
if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
|
|
196
650
|
stepsDefinitions.addStep({
|
|
197
651
|
name: step.text,
|
|
198
652
|
file: result.codePage.sourceFileName,
|
|
@@ -204,13 +658,16 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
204
658
|
return result.codePage;
|
|
205
659
|
} else {
|
|
206
660
|
const generateCodeResult = generateCode(recording, codePage, userData, projectDir, methodName);
|
|
207
|
-
|
|
208
|
-
|
|
661
|
+
console.log("Generated code for step:", step.text);
|
|
662
|
+
if (!isUtilStep && generateCodeResult.noCode === true) {
|
|
209
663
|
return generateCodeResult.page;
|
|
210
664
|
}
|
|
211
665
|
codePage = generateCodeResult.page;
|
|
212
666
|
methodName = generateCodeResult.methodName;
|
|
213
|
-
|
|
667
|
+
|
|
668
|
+
if (!renamedUtil) {
|
|
669
|
+
codePage.insertElements(generateCodeResult.elements);
|
|
670
|
+
}
|
|
214
671
|
|
|
215
672
|
const description = cucumberStep.text;
|
|
216
673
|
let path = null;
|
|
@@ -223,15 +680,43 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
223
680
|
protect = true;
|
|
224
681
|
}
|
|
225
682
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
683
|
+
|
|
684
|
+
if (renamedUtil) {
|
|
685
|
+
if (isChangedUtilStepName) {
|
|
686
|
+
const newFileContent = codePage.fileContent
|
|
687
|
+
.replace(`async function ${step.renamedText}`, `async function ${methodName}`)
|
|
688
|
+
.replace(`Then("${step.renamedText}"`, `// Then("${step.renamedText}"`)
|
|
689
|
+
.replace(`When("${step.renamedText}"`, `// When("${step.renamedText}"`)
|
|
690
|
+
.replace(`Given("${step.renamedText}"`, `// Given("${step.renamedText}"`);
|
|
691
|
+
|
|
692
|
+
codePage._init();
|
|
693
|
+
codePage.generateModel(newFileContent);
|
|
694
|
+
} else {
|
|
695
|
+
codePage.addInfraCommandUtil(
|
|
696
|
+
methodName,
|
|
697
|
+
description,
|
|
698
|
+
cucumberStep.parameters,
|
|
699
|
+
generateCodeResult.codeLines,
|
|
700
|
+
step.renamedText,
|
|
701
|
+
step.text,
|
|
702
|
+
parametersMap,
|
|
703
|
+
protect,
|
|
704
|
+
"recorder",
|
|
705
|
+
path
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
} else {
|
|
709
|
+
codePage.addInfraCommand(
|
|
710
|
+
methodName,
|
|
711
|
+
description,
|
|
712
|
+
cucumberStep.getVariablesList(),
|
|
713
|
+
generateCodeResult.codeLines,
|
|
714
|
+
protect,
|
|
715
|
+
"recorder",
|
|
716
|
+
path
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
|
|
235
720
|
const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
|
|
236
721
|
const stepResult = codePage.addCucumberStep(
|
|
237
722
|
keyword,
|
|
@@ -241,7 +726,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
241
726
|
step.finalTimeout
|
|
242
727
|
);
|
|
243
728
|
|
|
244
|
-
if (!step.isImplemented) {
|
|
729
|
+
if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
|
|
245
730
|
stepsDefinitions.addStep({
|
|
246
731
|
name: step.text,
|
|
247
732
|
file: codePage.sourceFileName,
|
|
@@ -250,6 +735,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
250
735
|
}
|
|
251
736
|
|
|
252
737
|
codePage.removeUnusedElements();
|
|
738
|
+
codePage.mergeSimilarElements();
|
|
253
739
|
cucumberStep.methodName = methodName;
|
|
254
740
|
if (generateCodeResult.locatorsMetadata) {
|
|
255
741
|
codePage.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
|
|
@@ -293,7 +779,7 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
|
|
|
293
779
|
const file = step?.file;
|
|
294
780
|
const locatorsJson = getLocatorsJson(file);
|
|
295
781
|
if (!step) {
|
|
296
|
-
throw new Error(
|
|
782
|
+
throw new Error(`Step definition not found: ${stepName}`);
|
|
297
783
|
}
|
|
298
784
|
isImplemented = true;
|
|
299
785
|
const { codeCommands, codePage, elements, parametersNames, error } =
|
|
@@ -301,26 +787,35 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
|
|
|
301
787
|
if (error) {
|
|
302
788
|
throw new Error(error);
|
|
303
789
|
}
|
|
790
|
+
isUtilStep = codePage.sourceFileName.endsWith("utils.mjs") || codePage.sourceFileName.endsWith("default_page.mjs");
|
|
304
791
|
|
|
305
792
|
if (parametersNames.length !== stepParams.length) {
|
|
306
793
|
// console.log("Parameters mismatch", parametersNames, stepParams);
|
|
307
794
|
throw new Error("Parameters mismatch");
|
|
308
795
|
}
|
|
309
|
-
for (let i = 0; i < parametersNames.length; i++) {
|
|
310
|
-
stepParams[i].argumentName = parametersNames[i];
|
|
311
|
-
}
|
|
312
796
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
797
|
+
const pattern = step.name;
|
|
798
|
+
if (isUtilStep && pattern === "Verify the file {string} exists") {
|
|
799
|
+
commands.push({
|
|
800
|
+
type: "verify_file_exists",
|
|
801
|
+
parameters: [stepParams[0].text],
|
|
802
|
+
});
|
|
803
|
+
} else {
|
|
804
|
+
for (let i = 0; i < parametersNames.length; i++) {
|
|
805
|
+
stepParams[i].argumentName = parametersNames[i];
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
for (const { code } of codeCommands) {
|
|
809
|
+
const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
|
|
810
|
+
if (command === undefined || command.type === null) continue;
|
|
811
|
+
if (command.element) {
|
|
812
|
+
const key = command.element.key;
|
|
813
|
+
if (key && locatorsJson[key]) {
|
|
814
|
+
command.allStrategyLocators = locatorsJson[key];
|
|
815
|
+
}
|
|
321
816
|
}
|
|
817
|
+
commands.push(command);
|
|
322
818
|
}
|
|
323
|
-
commands.push(command);
|
|
324
819
|
}
|
|
325
820
|
} catch (error) {
|
|
326
821
|
console.error(error);
|
|
@@ -368,7 +863,7 @@ export async function executeStep({ stepsDefinitions, cucumberStep, context, cod
|
|
|
368
863
|
}
|
|
369
864
|
}
|
|
370
865
|
|
|
371
|
-
export async function updateStepDefinitions({ scenario, featureName, projectDir }) {
|
|
866
|
+
export async function updateStepDefinitions({ scenario, featureName, projectDir, logger }) {
|
|
372
867
|
// set the candidate step definition file name
|
|
373
868
|
// set the utils file path
|
|
374
869
|
const utilsFilePath = path.join(projectDir, "features", "step_definitions", "utils.mjs");
|
|
@@ -399,44 +894,46 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
399
894
|
step.isImplementedWhileRecording = true;
|
|
400
895
|
}
|
|
401
896
|
}
|
|
402
|
-
if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
|
|
897
|
+
if (!step.isUtilStep && ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0)) {
|
|
403
898
|
let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
404
899
|
if (process.env.TEMP_RUN === "true") {
|
|
405
|
-
console.log("Save routes in temp folder for running:", routesPath);
|
|
406
900
|
if (existsSync(routesPath)) {
|
|
407
|
-
console.log("Removing existing temp_routes_folder:", routesPath);
|
|
408
901
|
routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
409
902
|
rmSync(routesPath, { recursive: true });
|
|
410
903
|
}
|
|
411
904
|
mkdirSync(routesPath, { recursive: true });
|
|
412
|
-
|
|
413
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
905
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
414
906
|
} else {
|
|
415
|
-
console.log("Saving routes in project directory:", projectDir);
|
|
416
907
|
if (existsSync(routesPath)) {
|
|
417
|
-
// remove the folder
|
|
418
908
|
try {
|
|
419
909
|
rmSync(routesPath, { recursive: true });
|
|
420
|
-
console.log("Removed temp_routes_folder:", routesPath);
|
|
421
910
|
} catch (error) {
|
|
422
|
-
|
|
911
|
+
logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
|
|
423
912
|
}
|
|
424
913
|
}
|
|
425
914
|
routesPath = path.join(projectDir, "data", "routes");
|
|
426
|
-
console.log("Saving routes to:", routesPath);
|
|
427
915
|
if (!existsSync(routesPath)) {
|
|
428
916
|
mkdirSync(routesPath, { recursive: true });
|
|
429
917
|
}
|
|
430
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
918
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
919
|
+
}
|
|
920
|
+
if (step.commands && Array.isArray(step.commands)) {
|
|
921
|
+
step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
|
|
431
922
|
}
|
|
432
923
|
continue;
|
|
433
924
|
}
|
|
434
925
|
const cucumberStep = getCucumberStep({ step });
|
|
435
926
|
const pageName = generatePageName(step.startFrame?.url ?? "default");
|
|
436
927
|
const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
|
|
437
|
-
// path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
|
|
438
928
|
let codePage = getCodePage(stepDefsFilePath);
|
|
439
|
-
codePage = await saveRecording({
|
|
929
|
+
codePage = await saveRecording({
|
|
930
|
+
step,
|
|
931
|
+
cucumberStep,
|
|
932
|
+
codePage,
|
|
933
|
+
projectDir,
|
|
934
|
+
stepsDefinitions,
|
|
935
|
+
parametersMap: scenario.parametersMap,
|
|
936
|
+
});
|
|
440
937
|
if (!codePage) {
|
|
441
938
|
continue;
|
|
442
939
|
}
|
|
@@ -448,7 +945,7 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
448
945
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
449
946
|
}
|
|
450
947
|
|
|
451
|
-
export function saveRoutes({ step, folderPath }) {
|
|
948
|
+
export function saveRoutes({ step, folderPath }, logger) {
|
|
452
949
|
const routeItems = step.routeItems;
|
|
453
950
|
if (!routeItems || routeItems.length === 0) {
|
|
454
951
|
return;
|
|
@@ -471,21 +968,18 @@ export function saveRoutes({ step, folderPath }) {
|
|
|
471
968
|
};
|
|
472
969
|
});
|
|
473
970
|
|
|
474
|
-
const routesFilePath = path.join(folderPath, stepNameHash
|
|
475
|
-
console.log("Routes file path:", routesFilePath);
|
|
971
|
+
const routesFilePath = path.join(folderPath, `${stepNameHash}.json`);
|
|
476
972
|
const routesData = {
|
|
477
973
|
template,
|
|
478
974
|
routes: routeItemsWithFilters,
|
|
479
975
|
};
|
|
480
|
-
console.log("Routes data to save:", routesData);
|
|
481
976
|
|
|
482
977
|
if (!existsSync(folderPath)) {
|
|
483
978
|
mkdirSync(folderPath, { recursive: true });
|
|
484
979
|
}
|
|
485
980
|
try {
|
|
486
981
|
writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
|
|
487
|
-
console.log("Saved routes to", routesFilePath);
|
|
488
982
|
} catch (error) {
|
|
489
|
-
|
|
983
|
+
logger.error(`Error saving routes to ${routesFilePath}: ${getErrorMessage(error)}`);
|
|
490
984
|
}
|
|
491
985
|
}
|