@dev-blinq/cucumber_client 1.0.1446-dev → 1.0.1446-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 +572 -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,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
|
+
|
|
574
|
+
routesPath = path.join(tmpdir(), "blinq_temp_routes");
|
|
114
575
|
if (process.env.TEMP_RUN === "true") {
|
|
115
|
-
console.log("Save routes in temp folder for running:", routesPath);
|
|
116
576
|
if (existsSync(routesPath)) {
|
|
117
|
-
console.log("Removing existing temp_routes_folder:", routesPath);
|
|
118
577
|
rmSync(routesPath, { recursive: true });
|
|
119
578
|
}
|
|
120
579
|
mkdirSync(routesPath, { recursive: true });
|
|
121
|
-
|
|
122
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
580
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
123
581
|
} else {
|
|
124
|
-
console.log("Saving routes in project directory:", projectDir);
|
|
125
582
|
if (existsSync(routesPath)) {
|
|
126
|
-
// remove the folder
|
|
127
583
|
try {
|
|
128
584
|
rmSync(routesPath, { recursive: true });
|
|
129
|
-
console.log("Removed temp_routes_folder:", routesPath);
|
|
130
585
|
} catch (error) {
|
|
131
|
-
|
|
586
|
+
logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
|
|
132
587
|
}
|
|
133
588
|
}
|
|
134
589
|
routesPath = path.join(projectDir, "data", "routes");
|
|
135
|
-
console.log("Saving routes to:", routesPath);
|
|
136
590
|
if (!existsSync(routesPath)) {
|
|
137
591
|
mkdirSync(routesPath, { recursive: true });
|
|
138
592
|
}
|
|
139
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
593
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
140
594
|
}
|
|
141
595
|
|
|
142
596
|
cucumberStep.text = step.text;
|
|
143
597
|
const recording = new Recording();
|
|
598
|
+
|
|
144
599
|
const steps = step.commands;
|
|
145
600
|
|
|
146
601
|
recording.loadFromObject({ steps, step: cucumberStep });
|
|
@@ -191,7 +646,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
191
646
|
stepsDefinitions
|
|
192
647
|
);
|
|
193
648
|
|
|
194
|
-
if (!step.isImplemented) {
|
|
649
|
+
if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
|
|
195
650
|
stepsDefinitions.addStep({
|
|
196
651
|
name: step.text,
|
|
197
652
|
file: result.codePage.sourceFileName,
|
|
@@ -203,13 +658,16 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
203
658
|
return result.codePage;
|
|
204
659
|
} else {
|
|
205
660
|
const generateCodeResult = generateCode(recording, codePage, userData, projectDir, methodName);
|
|
206
|
-
|
|
207
|
-
|
|
661
|
+
console.log("Generated code for step:", step.text);
|
|
662
|
+
if (!isUtilStep && generateCodeResult.noCode === true) {
|
|
208
663
|
return generateCodeResult.page;
|
|
209
664
|
}
|
|
210
665
|
codePage = generateCodeResult.page;
|
|
211
666
|
methodName = generateCodeResult.methodName;
|
|
212
|
-
|
|
667
|
+
|
|
668
|
+
if (!renamedUtil) {
|
|
669
|
+
codePage.insertElements(generateCodeResult.elements);
|
|
670
|
+
}
|
|
213
671
|
|
|
214
672
|
const description = cucumberStep.text;
|
|
215
673
|
let path = null;
|
|
@@ -222,15 +680,43 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
222
680
|
protect = true;
|
|
223
681
|
}
|
|
224
682
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
+
|
|
234
720
|
const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
|
|
235
721
|
const stepResult = codePage.addCucumberStep(
|
|
236
722
|
keyword,
|
|
@@ -240,7 +726,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
240
726
|
step.finalTimeout
|
|
241
727
|
);
|
|
242
728
|
|
|
243
|
-
if (!step.isImplemented) {
|
|
729
|
+
if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
|
|
244
730
|
stepsDefinitions.addStep({
|
|
245
731
|
name: step.text,
|
|
246
732
|
file: codePage.sourceFileName,
|
|
@@ -249,6 +735,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
249
735
|
}
|
|
250
736
|
|
|
251
737
|
codePage.removeUnusedElements();
|
|
738
|
+
codePage.mergeSimilarElements();
|
|
252
739
|
cucumberStep.methodName = methodName;
|
|
253
740
|
if (generateCodeResult.locatorsMetadata) {
|
|
254
741
|
codePage.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
|
|
@@ -292,7 +779,7 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
|
|
|
292
779
|
const file = step?.file;
|
|
293
780
|
const locatorsJson = getLocatorsJson(file);
|
|
294
781
|
if (!step) {
|
|
295
|
-
throw new Error(
|
|
782
|
+
throw new Error(`Step definition not found: ${stepName}`);
|
|
296
783
|
}
|
|
297
784
|
isImplemented = true;
|
|
298
785
|
const { codeCommands, codePage, elements, parametersNames, error } =
|
|
@@ -300,26 +787,35 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
|
|
|
300
787
|
if (error) {
|
|
301
788
|
throw new Error(error);
|
|
302
789
|
}
|
|
790
|
+
isUtilStep = codePage.sourceFileName.endsWith("utils.mjs") || codePage.sourceFileName.endsWith("default_page.mjs");
|
|
303
791
|
|
|
304
792
|
if (parametersNames.length !== stepParams.length) {
|
|
305
793
|
// console.log("Parameters mismatch", parametersNames, stepParams);
|
|
306
794
|
throw new Error("Parameters mismatch");
|
|
307
795
|
}
|
|
308
|
-
for (let i = 0; i < parametersNames.length; i++) {
|
|
309
|
-
stepParams[i].argumentName = parametersNames[i];
|
|
310
|
-
}
|
|
311
796
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
+
}
|
|
320
816
|
}
|
|
817
|
+
commands.push(command);
|
|
321
818
|
}
|
|
322
|
-
commands.push(command);
|
|
323
819
|
}
|
|
324
820
|
} catch (error) {
|
|
325
821
|
console.error(error);
|
|
@@ -367,7 +863,7 @@ export async function executeStep({ stepsDefinitions, cucumberStep, context, cod
|
|
|
367
863
|
}
|
|
368
864
|
}
|
|
369
865
|
|
|
370
|
-
export async function updateStepDefinitions({ scenario, featureName, projectDir }) {
|
|
866
|
+
export async function updateStepDefinitions({ scenario, featureName, projectDir, logger }) {
|
|
371
867
|
// set the candidate step definition file name
|
|
372
868
|
// set the utils file path
|
|
373
869
|
const utilsFilePath = path.join(projectDir, "features", "step_definitions", "utils.mjs");
|
|
@@ -398,43 +894,46 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
398
894
|
step.isImplementedWhileRecording = true;
|
|
399
895
|
}
|
|
400
896
|
}
|
|
401
|
-
if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
|
|
897
|
+
if (!step.isUtilStep && ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0)) {
|
|
402
898
|
let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
403
899
|
if (process.env.TEMP_RUN === "true") {
|
|
404
|
-
console.log("Save routes in temp folder for running:", routesPath);
|
|
405
900
|
if (existsSync(routesPath)) {
|
|
406
|
-
|
|
901
|
+
routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
407
902
|
rmSync(routesPath, { recursive: true });
|
|
408
903
|
}
|
|
409
904
|
mkdirSync(routesPath, { recursive: true });
|
|
410
|
-
|
|
411
|
-
saveRoutes({ step, folderPath: routesPath });
|
|
905
|
+
saveRoutes({ step, folderPath: routesPath }, logger);
|
|
412
906
|
} else {
|
|
413
|
-
console.log("Saving routes in project directory:", projectDir);
|
|
414
907
|
if (existsSync(routesPath)) {
|
|
415
|
-
// remove the folder
|
|
416
908
|
try {
|
|
417
909
|
rmSync(routesPath, { recursive: true });
|
|
418
|
-
console.log("Removed temp_routes_folder:", routesPath);
|
|
419
910
|
} catch (error) {
|
|
420
|
-
|
|
911
|
+
logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
|
|
421
912
|
}
|
|
422
913
|
}
|
|
423
914
|
routesPath = path.join(projectDir, "data", "routes");
|
|
424
|
-
console.log("Saving routes to:", routesPath);
|
|
425
915
|
if (!existsSync(routesPath)) {
|
|
426
916
|
mkdirSync(routesPath, { recursive: true });
|
|
427
917
|
}
|
|
428
|
-
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));
|
|
429
922
|
}
|
|
430
923
|
continue;
|
|
431
924
|
}
|
|
432
925
|
const cucumberStep = getCucumberStep({ step });
|
|
433
926
|
const pageName = generatePageName(step.startFrame?.url ?? "default");
|
|
434
927
|
const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
|
|
435
|
-
// path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
|
|
436
928
|
let codePage = getCodePage(stepDefsFilePath);
|
|
437
|
-
codePage = await saveRecording({
|
|
929
|
+
codePage = await saveRecording({
|
|
930
|
+
step,
|
|
931
|
+
cucumberStep,
|
|
932
|
+
codePage,
|
|
933
|
+
projectDir,
|
|
934
|
+
stepsDefinitions,
|
|
935
|
+
parametersMap: scenario.parametersMap,
|
|
936
|
+
});
|
|
438
937
|
if (!codePage) {
|
|
439
938
|
continue;
|
|
440
939
|
}
|
|
@@ -446,7 +945,7 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
446
945
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
447
946
|
}
|
|
448
947
|
|
|
449
|
-
export function saveRoutes({ step, folderPath }) {
|
|
948
|
+
export function saveRoutes({ step, folderPath }, logger) {
|
|
450
949
|
const routeItems = step.routeItems;
|
|
451
950
|
if (!routeItems || routeItems.length === 0) {
|
|
452
951
|
return;
|
|
@@ -469,21 +968,18 @@ export function saveRoutes({ step, folderPath }) {
|
|
|
469
968
|
};
|
|
470
969
|
});
|
|
471
970
|
|
|
472
|
-
const routesFilePath = path.join(folderPath, stepNameHash
|
|
473
|
-
console.log("Routes file path:", routesFilePath);
|
|
971
|
+
const routesFilePath = path.join(folderPath, `${stepNameHash}.json`);
|
|
474
972
|
const routesData = {
|
|
475
973
|
template,
|
|
476
974
|
routes: routeItemsWithFilters,
|
|
477
975
|
};
|
|
478
|
-
console.log("Routes data to save:", routesData);
|
|
479
976
|
|
|
480
977
|
if (!existsSync(folderPath)) {
|
|
481
978
|
mkdirSync(folderPath, { recursive: true });
|
|
482
979
|
}
|
|
483
980
|
try {
|
|
484
981
|
writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
|
|
485
|
-
console.log("Saved routes to", routesFilePath);
|
|
486
982
|
} catch (error) {
|
|
487
|
-
|
|
983
|
+
logger.error(`Error saving routes to ${routesFilePath}: ${getErrorMessage(error)}`);
|
|
488
984
|
}
|
|
489
985
|
}
|