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