@dev-blinq/cucumber_client 1.0.1475-dev → 1.0.1475-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 +49 -49
- package/bin/assets/scripts/recorder.js +87 -34
- package/bin/assets/scripts/snapshot_capturer.js +10 -17
- package/bin/assets/scripts/unique_locators.js +78 -28
- package/bin/assets/templates/_hooks_template.txt +6 -2
- package/bin/assets/templates/utils_template.txt +16 -16
- package/bin/client/code_cleanup/codemod/find_harcoded_locators.js +173 -0
- package/bin/client/code_cleanup/codemod/fix_hardcoded_locators.js +247 -0
- package/bin/client/code_cleanup/utils.js +16 -7
- package/bin/client/code_gen/code_inversion.js +125 -1
- package/bin/client/code_gen/duplication_analysis.js +2 -1
- package/bin/client/code_gen/function_signature.js +8 -0
- package/bin/client/code_gen/index.js +4 -0
- package/bin/client/code_gen/page_reflection.js +90 -9
- package/bin/client/code_gen/playwright_codeget.js +173 -77
- package/bin/client/codemod/find_harcoded_locators.js +173 -0
- package/bin/client/codemod/fix_hardcoded_locators.js +247 -0
- package/bin/client/codemod/index.js +8 -0
- package/bin/client/codemod/locators_array/find_misstructured_elements.js +148 -0
- package/bin/client/codemod/locators_array/fix_misstructured_elements.js +144 -0
- package/bin/client/codemod/locators_array/index.js +114 -0
- package/bin/client/codemod/types.js +1 -0
- package/bin/client/cucumber/feature.js +4 -17
- package/bin/client/cucumber/steps_definitions.js +17 -12
- package/bin/client/recorderv3/bvt_init.js +310 -0
- package/bin/client/recorderv3/bvt_recorder.js +1560 -1183
- package/bin/client/recorderv3/constants.js +45 -0
- package/bin/client/recorderv3/implemented_steps.js +2 -0
- package/bin/client/recorderv3/index.js +3 -293
- package/bin/client/recorderv3/mixpanel.js +39 -0
- package/bin/client/recorderv3/services.js +839 -142
- package/bin/client/recorderv3/step_runner.js +36 -7
- package/bin/client/recorderv3/step_utils.js +316 -98
- package/bin/client/recorderv3/update_feature.js +85 -37
- package/bin/client/recorderv3/utils.js +80 -0
- package/bin/client/recorderv3/wbr_entry.js +61 -0
- package/bin/client/recording.js +1 -0
- package/bin/client/types/locators.js +2 -0
- package/bin/client/upload-service.js +2 -0
- package/bin/client/utils/app_dir.js +21 -0
- package/bin/client/utils/socket_logger.js +100 -125
- package/bin/index.js +5 -0
- package/package.json +21 -6
- 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,5 +1,9 @@
|
|
|
1
1
|
import { Types } from "../recording.js";
|
|
2
2
|
import Walker from "node-source-walk";
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("../types/locators").Locator} Locator
|
|
5
|
+
* @typedef {import("../types/locators").LocatorBundle} LocatorBundle
|
|
6
|
+
*/
|
|
3
7
|
|
|
4
8
|
const extractElementIdentifier = (node) => {
|
|
5
9
|
// Check for elements["someIdentifier"] pattern
|
|
@@ -86,6 +90,34 @@ const parseDataSource = (paramNode, stepParams) => {
|
|
|
86
90
|
// }
|
|
87
91
|
throw new Error("Unknown parameter type");
|
|
88
92
|
};
|
|
93
|
+
|
|
94
|
+
const parseOptions = (optionsNode) => {
|
|
95
|
+
const properties = optionsNode.properties;
|
|
96
|
+
const options = {};
|
|
97
|
+
for (const prop of properties) {
|
|
98
|
+
let key = "";
|
|
99
|
+
if (prop.key.type === "Identifier") {
|
|
100
|
+
key = prop.key.name;
|
|
101
|
+
} else if (prop.key.type === "StringLiteral") {
|
|
102
|
+
key = prop.key.value;
|
|
103
|
+
}
|
|
104
|
+
if (key === "context") continue;
|
|
105
|
+
options[key] = prop.value.value;
|
|
106
|
+
}
|
|
107
|
+
return options;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const parseOptionsFromCallNode = (call, index) => {
|
|
111
|
+
if (call.arguments[index] && call.arguments[index].type === "ObjectExpression") {
|
|
112
|
+
return parseOptions(call.arguments[index]);
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* @param {any} call
|
|
118
|
+
* @param {Record<string, { locators?: Locator[] | LocatorBundle }>} elements
|
|
119
|
+
* @param {any[]} stepParams
|
|
120
|
+
*/
|
|
89
121
|
const invertStableCommand = (call, elements, stepParams) => {
|
|
90
122
|
const methodName = call.callee.property.name;
|
|
91
123
|
const step = { type: null, parameters: [], element: null, locators: null };
|
|
@@ -104,6 +136,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
104
136
|
step.dataSource = inputParam.dataSource;
|
|
105
137
|
step.dataKey = inputParam.dataKey;
|
|
106
138
|
}
|
|
139
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
107
140
|
break;
|
|
108
141
|
}
|
|
109
142
|
|
|
@@ -130,6 +163,8 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
130
163
|
step.count = clickCountProp.value.value;
|
|
131
164
|
}
|
|
132
165
|
}
|
|
166
|
+
|
|
167
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
133
168
|
break;
|
|
134
169
|
|
|
135
170
|
case "setDateTime": {
|
|
@@ -147,6 +182,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
147
182
|
step.dataKey = dateParam.dataKey;
|
|
148
183
|
step.parameters = [toVariableName(dateParam.dataKey), format, enterParam];
|
|
149
184
|
}
|
|
185
|
+
step.options = parseOptionsFromCallNode(call, 5);
|
|
150
186
|
break;
|
|
151
187
|
}
|
|
152
188
|
|
|
@@ -163,6 +199,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
163
199
|
step.dataKey = selectParam.dataKey;
|
|
164
200
|
step.parameters = [toVariableName(selectParam.dataKey)];
|
|
165
201
|
}
|
|
202
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
166
203
|
break;
|
|
167
204
|
}
|
|
168
205
|
|
|
@@ -176,6 +213,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
176
213
|
step.dataKey = text.dataKey;
|
|
177
214
|
step.parameters = [toVariableName(text.dataKey)];
|
|
178
215
|
}
|
|
216
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
179
217
|
break;
|
|
180
218
|
}
|
|
181
219
|
case "waitForTextToDisappear": {
|
|
@@ -188,6 +226,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
188
226
|
step.dataKey = text.dataKey;
|
|
189
227
|
step.parameters = [toVariableName(text.dataKey)];
|
|
190
228
|
}
|
|
229
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
191
230
|
break;
|
|
192
231
|
}
|
|
193
232
|
|
|
@@ -203,6 +242,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
203
242
|
step.dataKey = text.dataKey;
|
|
204
243
|
step.parameters = [toVariableName(text.dataKey), climb ?? null];
|
|
205
244
|
}
|
|
245
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
206
246
|
break;
|
|
207
247
|
}
|
|
208
248
|
case "extractAttribute": {
|
|
@@ -233,6 +273,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
233
273
|
step.regex = "";
|
|
234
274
|
step.trimSpaces = false;
|
|
235
275
|
}
|
|
276
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
236
277
|
break;
|
|
237
278
|
}
|
|
238
279
|
case "verifyAttribute": {
|
|
@@ -249,6 +290,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
249
290
|
step.dataKey = value.dataKey;
|
|
250
291
|
step.parameters = [attribute, toVariableName(value.dataKey)];
|
|
251
292
|
}
|
|
293
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
252
294
|
break;
|
|
253
295
|
}
|
|
254
296
|
|
|
@@ -277,6 +319,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
277
319
|
step.parameters = [toVariableName(fillParam.dataKey), enterValue];
|
|
278
320
|
}
|
|
279
321
|
}
|
|
322
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
280
323
|
break;
|
|
281
324
|
|
|
282
325
|
case "loadTestDataAsync": {
|
|
@@ -307,23 +350,28 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
307
350
|
step.dataKey = url.dataKey;
|
|
308
351
|
step.parameters = [toVariableName(url.dataKey)];
|
|
309
352
|
}
|
|
353
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
310
354
|
break;
|
|
311
355
|
}
|
|
312
356
|
|
|
313
357
|
case "goBack":
|
|
314
358
|
step.type = Types.GO_BACK;
|
|
359
|
+
step.options = parseOptionsFromCallNode(call, 0);
|
|
315
360
|
break;
|
|
316
361
|
|
|
317
362
|
case "goForward":
|
|
318
363
|
step.type = Types.GO_FORWARD;
|
|
364
|
+
step.options = parseOptionsFromCallNode(call, 0);
|
|
319
365
|
break;
|
|
320
366
|
|
|
321
367
|
case "reloadPage":
|
|
322
368
|
step.type = Types.RELOAD;
|
|
369
|
+
step.options = parseOptionsFromCallNode(call, 0);
|
|
323
370
|
break;
|
|
324
371
|
|
|
325
372
|
case "closePage":
|
|
326
373
|
step.type = Types.CLOSE_PAGE;
|
|
374
|
+
step.options = parseOptionsFromCallNode(call, 0);
|
|
327
375
|
break;
|
|
328
376
|
|
|
329
377
|
case "simpleClick":
|
|
@@ -341,12 +389,15 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
341
389
|
case "hover":
|
|
342
390
|
step.type = Types.HOVER;
|
|
343
391
|
step.element = extractElement(call.arguments[0]);
|
|
392
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
344
393
|
break;
|
|
345
394
|
|
|
346
395
|
case "setCheck":
|
|
347
396
|
step.type = Types.CHECK;
|
|
348
397
|
step.element = extractElement(call.arguments[0]);
|
|
349
398
|
step.check = call.arguments[1].value;
|
|
399
|
+
///
|
|
400
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
350
401
|
break;
|
|
351
402
|
|
|
352
403
|
case "setViewportSize": {
|
|
@@ -354,12 +405,14 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
354
405
|
const width = call.arguments[0].value;
|
|
355
406
|
const height = call.arguments[1].value;
|
|
356
407
|
step.parameters = [width, height];
|
|
408
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
357
409
|
break;
|
|
358
410
|
}
|
|
359
411
|
|
|
360
412
|
case "visualVerification": {
|
|
361
413
|
step.type = Types.VISUAL_VERIFICATION;
|
|
362
414
|
step.parameters = [call.arguments[0].value];
|
|
415
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
363
416
|
break;
|
|
364
417
|
}
|
|
365
418
|
case "waitForUserInput": {
|
|
@@ -370,6 +423,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
370
423
|
case "extractEmailData": {
|
|
371
424
|
step.type = Types.MAIL_GET_CODE_OR_URL;
|
|
372
425
|
step.parameters = [call.arguments[0].value];
|
|
426
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
373
427
|
break;
|
|
374
428
|
}
|
|
375
429
|
case "verifyTextRelatedToText": {
|
|
@@ -383,6 +437,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
383
437
|
const textToVerify =
|
|
384
438
|
textToVerifyParse.type === "literal" ? textToVerifyParse.value : toVariableName(textToVerifyParse.dataKey);
|
|
385
439
|
step.parameters = [textAnchor, climb, textToVerify];
|
|
440
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
386
441
|
break;
|
|
387
442
|
}
|
|
388
443
|
case "setInputFiles": {
|
|
@@ -396,6 +451,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
396
451
|
step.dataKey = fileParam.dataKey;
|
|
397
452
|
step.parameters = [toVariableName(fileParam.dataKey)];
|
|
398
453
|
}
|
|
454
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
399
455
|
break;
|
|
400
456
|
}
|
|
401
457
|
case "snapshotValidation": {
|
|
@@ -405,6 +461,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
405
461
|
step.parameters = [inputParam.value];
|
|
406
462
|
}
|
|
407
463
|
step.selectors = call.arguments[0].value;
|
|
464
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
408
465
|
break;
|
|
409
466
|
}
|
|
410
467
|
case "verifyPageTitle": {
|
|
@@ -417,6 +474,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
417
474
|
step.dataKey = text.dataKey;
|
|
418
475
|
step.parameters = [toVariableName(text.dataKey)];
|
|
419
476
|
}
|
|
477
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
420
478
|
break;
|
|
421
479
|
}
|
|
422
480
|
case "verifyPagePath": {
|
|
@@ -429,6 +487,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
429
487
|
step.dataKey = path.dataKey;
|
|
430
488
|
step.parameters = [toVariableName(path.dataKey)];
|
|
431
489
|
}
|
|
490
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
432
491
|
break;
|
|
433
492
|
}
|
|
434
493
|
case "verifyProperty": {
|
|
@@ -443,6 +502,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
443
502
|
step.dataKey = value.dataKey;
|
|
444
503
|
step.parameters = [property, toVariableName(value.dataKey)];
|
|
445
504
|
}
|
|
505
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
446
506
|
break;
|
|
447
507
|
}
|
|
448
508
|
case "extractProperty": {
|
|
@@ -473,15 +533,79 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
473
533
|
step.regex = "";
|
|
474
534
|
step.trimSpaces = false;
|
|
475
535
|
}
|
|
536
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
case "conditionalWait": {
|
|
541
|
+
step.type = Types.CONDITIONAL_WAIT;
|
|
542
|
+
step.element = extractElement(call.arguments[0]);
|
|
543
|
+
const condition = call.arguments[1].value;
|
|
544
|
+
|
|
545
|
+
const _timeout = parseDataSource(call.arguments[2], stepParams);
|
|
546
|
+
let timeout = 30;
|
|
547
|
+
if (_timeout.type === "literal") {
|
|
548
|
+
if (isNaN(_timeout.value)) {
|
|
549
|
+
throw new Error(`Timeout value must be a number, got ${_timeout.value}`);
|
|
550
|
+
}
|
|
551
|
+
timeout = Number(_timeout.value) * 1000;
|
|
552
|
+
} else {
|
|
553
|
+
step.dataSource = _timeout.dataSource;
|
|
554
|
+
step.dataKey = _timeout.dataKey;
|
|
555
|
+
timeout = toVariableName(_timeout.dataKey);
|
|
556
|
+
}
|
|
557
|
+
// step.timeout = timeout;
|
|
558
|
+
// step.value = "true";
|
|
559
|
+
step.parameters = [timeout, condition, step.value];
|
|
560
|
+
// step.condition = condition;
|
|
561
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
case "sleep": {
|
|
566
|
+
step.type = Types.SLEEP;
|
|
567
|
+
const duration = parseDataSource(call.arguments[0], stepParams);
|
|
568
|
+
if (duration.type === "literal") {
|
|
569
|
+
if (isNaN(duration.value)) {
|
|
570
|
+
throw new Error(`Sleep duration must be a number, got ${duration.value}`);
|
|
571
|
+
}
|
|
572
|
+
step.parameters = [Number(duration.value)];
|
|
573
|
+
} else {
|
|
574
|
+
step.dataSource = duration.dataSource;
|
|
575
|
+
step.dataKey = duration.dataKey;
|
|
576
|
+
step.parameters = [toVariableName(duration.dataKey)];
|
|
577
|
+
}
|
|
578
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
476
579
|
break;
|
|
477
580
|
}
|
|
581
|
+
case "verify_file_exists": {
|
|
582
|
+
step.type = Types.VERIFY_FILE_EXISTS;
|
|
583
|
+
const filePath = parseDataSource(call.arguments[0], stepParams);
|
|
584
|
+
if (filePath.type === "literal") {
|
|
585
|
+
step.parameters = [filePath.value];
|
|
586
|
+
} else {
|
|
587
|
+
step.dataSource = filePath.dataSource;
|
|
588
|
+
step.dataKey = filePath.dataKey;
|
|
589
|
+
step.parameters = [toVariableName(filePath.dataKey)];
|
|
590
|
+
}
|
|
591
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// case "verifyPagePath":
|
|
596
|
+
// {
|
|
597
|
+
// step.type = Types.VERIFY_PAGE_PATH;
|
|
598
|
+
// step.parameters = [call.arguments[0].value];
|
|
599
|
+
// break;
|
|
600
|
+
// }
|
|
478
601
|
default:
|
|
479
602
|
return; // Skip if no matching method
|
|
480
603
|
}
|
|
481
604
|
|
|
482
605
|
// Set locators if element exists
|
|
483
606
|
if (step.element && elements[step.element.key]) {
|
|
484
|
-
|
|
607
|
+
const locBundle = elements[step.element.key];
|
|
608
|
+
step.locators = locBundle;
|
|
485
609
|
}
|
|
486
610
|
|
|
487
611
|
if (step.type === Types.CLICK) {
|
|
@@ -4,6 +4,7 @@ import path from "path";
|
|
|
4
4
|
import { CodePage } from "./page_reflection.js";
|
|
5
5
|
import { compareElements, generateSignature } from "./function_signature.js";
|
|
6
6
|
import { updateStepDefinitions } from "../recorderv3/step_utils.js";
|
|
7
|
+
import socketLoggerInstance from "../utils/socket_logger.js";
|
|
7
8
|
/**
|
|
8
9
|
* DuplicationAnalizer class
|
|
9
10
|
* How to use:
|
|
@@ -101,7 +102,7 @@ async function compareWithScenario(projectDir, scenario) {
|
|
|
101
102
|
analyzerExisting.load();
|
|
102
103
|
// generate a temporary folder
|
|
103
104
|
const folder = fs.mkdtempSync(path.join(os.tmpdir(), "blinq-"));
|
|
104
|
-
await updateStepDefinitions({ scenario, featureName: "temp", projectDir: folder });
|
|
105
|
+
await updateStepDefinitions({ scenario, featureName: "temp", projectDir: folder, logger: socketLoggerInstance });
|
|
105
106
|
const analyzerScenario = new DuplicationAnalizer(folder);
|
|
106
107
|
analyzerScenario.load();
|
|
107
108
|
const compareResults = [];
|
|
@@ -11,6 +11,12 @@ async function verify_the_opportunity_name_was_created(_name) {
|
|
|
11
11
|
}
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import strip from "strip-comments";
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import("../types/locators").Locator} Locator
|
|
17
|
+
* @typedef {import("../types/locators").LocatorBundle} LocatorBundle
|
|
18
|
+
*/
|
|
19
|
+
|
|
14
20
|
function generateSignature(page, functionName) {
|
|
15
21
|
let functionCode = null;
|
|
16
22
|
let method = null;
|
|
@@ -28,6 +34,8 @@ function generateSignature(page, functionName) {
|
|
|
28
34
|
method.node.body.parent.params.forEach((param) => {
|
|
29
35
|
parameters.push(param.name);
|
|
30
36
|
});
|
|
37
|
+
const strippedFunctionCode = strip(functionCode);
|
|
38
|
+
functionCode = strippedFunctionCode !== "" ? strippedFunctionCode : functionCode;
|
|
31
39
|
let lines = functionCode.split("\n");
|
|
32
40
|
// trim all lines
|
|
33
41
|
lines = lines.map((line) => line.trim());
|
|
@@ -170,7 +170,7 @@ export class CodePage {
|
|
|
170
170
|
this.generateModel(fileContentNew);
|
|
171
171
|
}
|
|
172
172
|
catch (e) {
|
|
173
|
-
logger.
|
|
173
|
+
logger.error("failed to format the code");
|
|
174
174
|
logger.debug(e);
|
|
175
175
|
}
|
|
176
176
|
if (!existsSync(this.sourceFileName)) {
|
|
@@ -288,6 +288,11 @@ export class CodePage {
|
|
|
288
288
|
codePart.name = node.id.name;
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
|
+
findKey(funcString, key) {
|
|
292
|
+
const sourceRegex = new RegExp(`${key}:\\s*(.*)`);
|
|
293
|
+
const match = funcString.match(sourceRegex);
|
|
294
|
+
return match ? match[1] : null;
|
|
295
|
+
}
|
|
291
296
|
collectAllTemplates() {
|
|
292
297
|
const templates = [];
|
|
293
298
|
if (this.cucumberCalls.length > 0) {
|
|
@@ -300,10 +305,12 @@ export class CodePage {
|
|
|
300
305
|
const stepType = cucumberCall["stepType"];
|
|
301
306
|
let firstFind = true;
|
|
302
307
|
const stepPaths = [];
|
|
308
|
+
let source = null;
|
|
303
309
|
for (let j = 0; j < this.methods.length; j++) {
|
|
304
310
|
const method = this.methods[j];
|
|
305
311
|
if (method.name === methodName) {
|
|
306
312
|
if (firstFind) {
|
|
313
|
+
source = this.findKey(method.codePart, "source");
|
|
307
314
|
foundMethod = true;
|
|
308
315
|
const paramsObj = (method.node?.params ?? []);
|
|
309
316
|
if (paramsObj && paramsObj.length > 0) {
|
|
@@ -315,7 +322,7 @@ export class CodePage {
|
|
|
315
322
|
}
|
|
316
323
|
}
|
|
317
324
|
if (foundMethod) {
|
|
318
|
-
templates.push({ pattern, methodName, params, stepType, paths: stepPaths });
|
|
325
|
+
templates.push({ pattern, methodName, params, stepType, paths: stepPaths, source });
|
|
319
326
|
}
|
|
320
327
|
}
|
|
321
328
|
}
|
|
@@ -363,6 +370,39 @@ export class CodePage {
|
|
|
363
370
|
return result;
|
|
364
371
|
}
|
|
365
372
|
}
|
|
373
|
+
addInfraCommandUtil(methodName, description, stepVariables, stepCodeLines, renamedText, previousText, parametersMap, protectStep = false, source = null, codePath = "") {
|
|
374
|
+
let code = "\n";
|
|
375
|
+
code += "/**\n";
|
|
376
|
+
code += ` * ${description}\n`;
|
|
377
|
+
const tags = [];
|
|
378
|
+
if (protectStep)
|
|
379
|
+
tags.push("@protect");
|
|
380
|
+
if (source)
|
|
381
|
+
tags.push(`@${source}`);
|
|
382
|
+
if (tags.length > 0)
|
|
383
|
+
code += ` * ${tags.join(" ")}\n`;
|
|
384
|
+
if (codePath !== null)
|
|
385
|
+
code += ` * @path=${escapeForComment(codePath)}\n`;
|
|
386
|
+
const matches = previousText.match(/"[^"]*"/g);
|
|
387
|
+
const countInMethodName = matches ? matches.length : 0;
|
|
388
|
+
code += " */\n";
|
|
389
|
+
code += `async function ${methodName} (${new Array(countInMethodName)
|
|
390
|
+
.fill(0)
|
|
391
|
+
.map((v, index) => `param${index}`)
|
|
392
|
+
.join(", ")}){\n`;
|
|
393
|
+
code += `// source: ${source}\n`;
|
|
394
|
+
code += `// implemented_at: ${new Date().toISOString()}\n`;
|
|
395
|
+
stepCodeLines.forEach((line) => (code += ` ${line}\n`));
|
|
396
|
+
if (renamedText === "verify_page_title" || renamedText === "verify_page_url") {
|
|
397
|
+
const parameters = stepVariables.map((v) => parametersMap[v.text.replace(/[<>]/g, "")]);
|
|
398
|
+
code += `await ${renamedText}(${parameters.map((v) => (isNaN(Number(v)) ? `"${v}"` : Number(v))).join(", ")});\n`;
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
code += `await ${renamedText}(${stepVariables.map((v) => (isNaN(Number(v.text)) ? `"${v.text}"` : Number(v.text))).join(", ")});\n`;
|
|
402
|
+
}
|
|
403
|
+
code += "}\n";
|
|
404
|
+
return this._injectMethod(methodName, code);
|
|
405
|
+
}
|
|
366
406
|
addInfraCommand(methodName, description, stepVarables, stepCodeLines, protectStep = false, source = null, codePath = "") {
|
|
367
407
|
let code = "\n";
|
|
368
408
|
code += "/**\n";
|
|
@@ -415,6 +455,46 @@ export class CodePage {
|
|
|
415
455
|
code += "}\n";
|
|
416
456
|
return this._injectMethod(methodName, code);
|
|
417
457
|
}
|
|
458
|
+
_replaceSelectedCode(code, injectIndexEnd) {
|
|
459
|
+
const newFileContent = this.fileContent.substring(0, injectIndexEnd - 2) +
|
|
460
|
+
"\n " +
|
|
461
|
+
code +
|
|
462
|
+
"\n}\n" +
|
|
463
|
+
this.fileContent.substring(injectIndexEnd);
|
|
464
|
+
this._init();
|
|
465
|
+
this.generateModel(newFileContent);
|
|
466
|
+
}
|
|
467
|
+
_injectOneCommand(methodName, code) {
|
|
468
|
+
const result = { methodName };
|
|
469
|
+
code = code.trim();
|
|
470
|
+
const existMethod = this.methods.find((m) => m.name === methodName);
|
|
471
|
+
if (!existMethod) {
|
|
472
|
+
throw new Error(`method ${methodName} not found for injection`);
|
|
473
|
+
}
|
|
474
|
+
const oldCode = existMethod.codePart.trim();
|
|
475
|
+
this._replaceSelectedCode(code, existMethod.end);
|
|
476
|
+
result.status = CodeStatus.UPDATED;
|
|
477
|
+
result.oldCode = oldCode;
|
|
478
|
+
result.newCode = code;
|
|
479
|
+
return result;
|
|
480
|
+
}
|
|
481
|
+
_removeSelectedCode(commands, injectIndexStart, injectIndexEnd) {
|
|
482
|
+
let newFileContent = this.fileContent.substring(injectIndexStart, injectIndexEnd);
|
|
483
|
+
commands.forEach((cmd) => {
|
|
484
|
+
newFileContent = newFileContent.replace(cmd, "");
|
|
485
|
+
});
|
|
486
|
+
newFileContent =
|
|
487
|
+
this.fileContent.substring(0, injectIndexStart) + newFileContent + this.fileContent.substring(injectIndexEnd);
|
|
488
|
+
this._init();
|
|
489
|
+
this.generateModel(newFileContent);
|
|
490
|
+
}
|
|
491
|
+
_removeCommands(methodName, commands) {
|
|
492
|
+
const existMethod = this.methods.find((m) => m.name === methodName);
|
|
493
|
+
if (!existMethod) {
|
|
494
|
+
throw new Error(`method ${methodName} not found for removal`);
|
|
495
|
+
}
|
|
496
|
+
this._removeSelectedCode(commands, existMethod.start, existMethod.end);
|
|
497
|
+
}
|
|
418
498
|
_injectMethod(methodName, code) {
|
|
419
499
|
const result = { methodName };
|
|
420
500
|
code = code.trim();
|
|
@@ -487,11 +567,12 @@ export class CodePage {
|
|
|
487
567
|
if (!element || !element.locators)
|
|
488
568
|
return element;
|
|
489
569
|
const clone = JSON.parse(JSON.stringify(element));
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (
|
|
493
|
-
delete
|
|
570
|
+
const locatorsArray = Array.isArray(clone.locators) ? clone.locators : [];
|
|
571
|
+
for (let i = 0; i < locatorsArray.length; i++) {
|
|
572
|
+
if (locatorsArray[i].score)
|
|
573
|
+
delete locatorsArray[i].score;
|
|
494
574
|
}
|
|
575
|
+
clone.locators = Array.isArray(clone.locators) ? locatorsArray : [];
|
|
495
576
|
return clone;
|
|
496
577
|
}
|
|
497
578
|
insertElements(elements) {
|
|
@@ -621,7 +702,7 @@ export class CodePage {
|
|
|
621
702
|
let locatorsMetadataFileName = this.sourceFileName.replace(".mjs", ".json");
|
|
622
703
|
const config = getAiConfig();
|
|
623
704
|
if (config && config.locatorsMetadataDir) {
|
|
624
|
-
locatorsMetadataFileName = path.join(
|
|
705
|
+
locatorsMetadataFileName = locatorsMetadataFileName.replace(path.join("features", "step_definitions"), path.join(config.locatorsMetadataDir));
|
|
625
706
|
if (!existsSync(path.dirname(locatorsMetadataFileName))) {
|
|
626
707
|
mkdirSync(path.dirname(locatorsMetadataFileName), { recursive: true });
|
|
627
708
|
}
|
|
@@ -633,7 +714,7 @@ export class CodePage {
|
|
|
633
714
|
}
|
|
634
715
|
}
|
|
635
716
|
catch {
|
|
636
|
-
logger.
|
|
717
|
+
logger.error("failed to read locators metadata file", locatorsMetadataFileName);
|
|
637
718
|
}
|
|
638
719
|
const keys = Object.keys(locatorsMetadata);
|
|
639
720
|
keys.forEach((key) => {
|
|
@@ -643,7 +724,7 @@ export class CodePage {
|
|
|
643
724
|
writeFileSync(locatorsMetadataFileName, JSON.stringify(metadata, null, 2), "utf8");
|
|
644
725
|
}
|
|
645
726
|
catch {
|
|
646
|
-
logger.
|
|
727
|
+
logger.error("failed to write locators metadata file", locatorsMetadataFileName);
|
|
647
728
|
}
|
|
648
729
|
}
|
|
649
730
|
_getVariableStartEnd(variableName) {
|