@dev-blinq/cucumber_client 1.0.1456-dev → 1.0.1456-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/scripts/recorder.js +87 -49
- package/bin/assets/scripts/snapshot_capturer.js +10 -17
- package/bin/assets/scripts/unique_locators.js +169 -47
- package/bin/assets/templates/_hooks_template.txt +6 -2
- package/bin/assets/templates/utils_template.txt +16 -16
- package/bin/client/code_cleanup/utils.js +16 -7
- package/bin/client/code_gen/code_inversion.js +115 -0
- 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 +164 -75
- package/bin/client/cucumber/feature.js +4 -17
- package/bin/client/cucumber/steps_definitions.js +13 -0
- package/bin/client/local_agent.js +1 -0
- package/bin/client/recorderv3/bvt_init.js +305 -0
- package/bin/client/recorderv3/bvt_recorder.js +1031 -61
- package/bin/client/recorderv3/implemented_steps.js +2 -0
- package/bin/client/recorderv3/index.js +3 -286
- package/bin/client/recorderv3/services.js +818 -142
- package/bin/client/recorderv3/step_runner.js +21 -4
- package/bin/client/recorderv3/step_utils.js +194 -118
- 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 +2 -0
- 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
|
@@ -12,7 +12,7 @@ import path from "path";
|
|
|
12
12
|
* @param {string} text the text to verify exists in page
|
|
13
13
|
* @protect
|
|
14
14
|
*/
|
|
15
|
-
async function verifyTextExistsInPage(text) {
|
|
15
|
+
export async function verifyTextExistsInPage(text) {
|
|
16
16
|
await context.web.verifyTextExistInPage(text, null, this);
|
|
17
17
|
}
|
|
18
18
|
Then("Verify the text {string} can be found in the page", verifyTextExistsInPage);
|
|
@@ -22,7 +22,7 @@ Then("Verify the text {string} can be found in the page", verifyTextExistsInPage
|
|
|
22
22
|
* @param {string} elementDescription element description
|
|
23
23
|
* @protect
|
|
24
24
|
*/
|
|
25
|
-
async function clickOnElement(elementDescription) {
|
|
25
|
+
export async function clickOnElement(elementDescription) {
|
|
26
26
|
await context.web.simpleClick(elementDescription, null, null, this);
|
|
27
27
|
}
|
|
28
28
|
When("click on {string}", clickOnElement);
|
|
@@ -36,7 +36,7 @@ When("Click {string}", clickOnElement);
|
|
|
36
36
|
* @param {string} value value to fill the element with
|
|
37
37
|
* @protect
|
|
38
38
|
*/
|
|
39
|
-
async function fillElement(elementDescription, value) {
|
|
39
|
+
export async function fillElement(elementDescription, value) {
|
|
40
40
|
await context.web.simpleClickType(elementDescription, value, null, null, this);
|
|
41
41
|
}
|
|
42
42
|
When("fill {string} with {string}", fillElement);
|
|
@@ -47,7 +47,7 @@ When("Fill {string} with {string}", fillElement);
|
|
|
47
47
|
* @param {string} text the text to verify does not exist in page
|
|
48
48
|
* @protect
|
|
49
49
|
*/
|
|
50
|
-
async function verifyTextNotExistsInPage(text) {
|
|
50
|
+
export async function verifyTextNotExistsInPage(text) {
|
|
51
51
|
await context.web.waitForTextToDisappear(text, null, this);
|
|
52
52
|
}
|
|
53
53
|
Then("Verify the text {string} cannot be found in the page", verifyTextNotExistsInPage);
|
|
@@ -57,7 +57,7 @@ Then("Verify the text {string} cannot be found in the page", verifyTextNotExists
|
|
|
57
57
|
* @param {string} url URL to navigate
|
|
58
58
|
* @protect
|
|
59
59
|
*/
|
|
60
|
-
async function navigateTo(url) {
|
|
60
|
+
export async function navigateTo(url) {
|
|
61
61
|
await context.web.goto(url, this);
|
|
62
62
|
}
|
|
63
63
|
When("Navigate to {string}", navigateTo);
|
|
@@ -66,7 +66,7 @@ When("Navigate to {string}", navigateTo);
|
|
|
66
66
|
* Navigate to the current page
|
|
67
67
|
* @protect
|
|
68
68
|
*/
|
|
69
|
-
async function browserNavigateBack() {
|
|
69
|
+
export async function browserNavigateBack() {
|
|
70
70
|
await context.web.goBack({}, this);
|
|
71
71
|
}
|
|
72
72
|
Then("Browser navigate back", browserNavigateBack);
|
|
@@ -75,7 +75,7 @@ Then("Browser navigate back", browserNavigateBack);
|
|
|
75
75
|
* Navigate forward in browser history
|
|
76
76
|
* @protect
|
|
77
77
|
*/
|
|
78
|
-
async function browserNavigateForward() {
|
|
78
|
+
export async function browserNavigateForward() {
|
|
79
79
|
await context.web.goForward({}, this);
|
|
80
80
|
}
|
|
81
81
|
Then("Browser navigate forward", browserNavigateForward);
|
|
@@ -85,7 +85,7 @@ Then("Browser navigate forward", browserNavigateForward);
|
|
|
85
85
|
* @param {string} filePath the file path or empty to store in the test data file
|
|
86
86
|
* @protect
|
|
87
87
|
*/
|
|
88
|
-
async function storeBrowserSession(filePath) {
|
|
88
|
+
export async function storeBrowserSession(filePath) {
|
|
89
89
|
await context.web.saveStoreState(filePath, this);
|
|
90
90
|
}
|
|
91
91
|
When("Store browser session {string}", storeBrowserSession);
|
|
@@ -95,7 +95,7 @@ When("Store browser session {string}", storeBrowserSession);
|
|
|
95
95
|
* @param {string} filePath the file path or empty
|
|
96
96
|
* @protect
|
|
97
97
|
*/
|
|
98
|
-
async function resetBrowserSession(filePath) {
|
|
98
|
+
export async function resetBrowserSession(filePath) {
|
|
99
99
|
await context.web.restoreSaveState(filePath, this);
|
|
100
100
|
}
|
|
101
101
|
When("Reset browser session {string}", resetBrowserSession);
|
|
@@ -107,7 +107,7 @@ When("Reset browser session {string}", resetBrowserSession);
|
|
|
107
107
|
* @param {string} textToVerify the target text to verify
|
|
108
108
|
* @protect
|
|
109
109
|
*/
|
|
110
|
-
async function verifyTextRelatedToText(textAnchor, climb, textToVerify) {
|
|
110
|
+
export async function verifyTextRelatedToText(textAnchor, climb, textToVerify) {
|
|
111
111
|
await context.web.verifyTextRelatedToText(textAnchor, climb, textToVerify, null, this);
|
|
112
112
|
}
|
|
113
113
|
Then(
|
|
@@ -120,7 +120,7 @@ Then(
|
|
|
120
120
|
* @requestName the name of the bruno request file
|
|
121
121
|
* @protect
|
|
122
122
|
*/
|
|
123
|
-
async function runBrunoRequest(requestName) {
|
|
123
|
+
export async function runBrunoRequest(requestName) {
|
|
124
124
|
await executeBrunoRequest(requestName, {}, context, this);
|
|
125
125
|
}
|
|
126
126
|
When("Bruno - {string}", runBrunoRequest);
|
|
@@ -131,7 +131,7 @@ When("bruno - {string}", runBrunoRequest);
|
|
|
131
131
|
* @param {string} fileName the downloaded file to verify
|
|
132
132
|
* @protect
|
|
133
133
|
*/
|
|
134
|
-
async function verify_the_downloaded_file_exists(fileName) {
|
|
134
|
+
export async function verify_the_downloaded_file_exists(fileName) {
|
|
135
135
|
const downloadFolder = path.join(context.reportFolder, "downloads");
|
|
136
136
|
const downloadFile = path.join(downloadFolder, fileName);
|
|
137
137
|
await verifyFileExists(downloadFile, {}, context, this);
|
|
@@ -148,7 +148,7 @@ When("Noop", async function () {});
|
|
|
148
148
|
* @param {string} url URL to be verified against current URL
|
|
149
149
|
* @protect
|
|
150
150
|
*/
|
|
151
|
-
async function verify_page_url(url) {
|
|
151
|
+
export async function verify_page_url(url) {
|
|
152
152
|
await context.web.verifyPagePath(url, {}, this);
|
|
153
153
|
}
|
|
154
154
|
Then("Verify the page url is {string}", verify_page_url);
|
|
@@ -158,7 +158,7 @@ Then("Verify the page url is {string}", verify_page_url);
|
|
|
158
158
|
* @param {string} title Title to be verified against current Title
|
|
159
159
|
* @protect
|
|
160
160
|
*/
|
|
161
|
-
async function verify_page_title(title) {
|
|
161
|
+
export async function verify_page_title(title) {
|
|
162
162
|
await context.web.verifyPageTitle(title, {}, this);
|
|
163
163
|
}
|
|
164
164
|
Then("Verify the page title is {string}", verify_page_title);
|
|
@@ -170,7 +170,7 @@ Then("Verify the page title is {string}", verify_page_title);
|
|
|
170
170
|
* @param {world} - Optional world context
|
|
171
171
|
* @returns Promise that resolves after the specified duration
|
|
172
172
|
*/
|
|
173
|
-
async function sleep(duration) {
|
|
174
|
-
await context.web.sleep(duration, {},
|
|
173
|
+
export async function sleep(duration) {
|
|
174
|
+
await context.web.sleep(duration, {}, this);
|
|
175
175
|
}
|
|
176
176
|
Then("Sleep for {string} ms", { timeout: -1 }, sleep);
|
|
@@ -10,7 +10,7 @@ import * as t from "@babel/types";
|
|
|
10
10
|
import { CucumberExpression, ParameterTypeRegistry } from "@cucumber/cucumber-expressions";
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
12
12
|
import { getAiConfig } from "../code_gen/page_reflection.js";
|
|
13
|
-
import socketLogger from "../utils/socket_logger.js";
|
|
13
|
+
import socketLogger, { getErrorMessage } from "../utils/socket_logger.js";
|
|
14
14
|
|
|
15
15
|
const STEP_KEYWORDS = new Set(["Given", "When", "Then"]);
|
|
16
16
|
|
|
@@ -308,18 +308,27 @@ export function getDefaultPrettierConfig() {
|
|
|
308
308
|
const configContent = readFileSync(prettierConfigPath, "utf-8");
|
|
309
309
|
prettierConfig = JSON.parse(configContent);
|
|
310
310
|
} catch (error) {
|
|
311
|
-
socketLogger.error(
|
|
312
|
-
|
|
311
|
+
socketLogger.error(
|
|
312
|
+
`Error parsing Prettier config file: ${getErrorMessage(error)}`,
|
|
313
|
+
undefined,
|
|
314
|
+
"getDefaultPrettierConfig"
|
|
315
|
+
);
|
|
313
316
|
}
|
|
314
317
|
} else {
|
|
315
318
|
// save the default config to .prettierrc
|
|
316
319
|
try {
|
|
317
320
|
writeFileSync(prettierConfigPath, JSON.stringify(prettierConfig, null, 2), "utf-8");
|
|
318
|
-
socketLogger.info(
|
|
319
|
-
|
|
321
|
+
socketLogger.info(
|
|
322
|
+
`Created default Prettier config at ${prettierConfigPath}`,
|
|
323
|
+
undefined,
|
|
324
|
+
"getDefaultPrettierConfig"
|
|
325
|
+
);
|
|
320
326
|
} catch (error) {
|
|
321
|
-
socketLogger.error(
|
|
322
|
-
|
|
327
|
+
socketLogger.error(
|
|
328
|
+
`Error writing Prettier config file: ${getErrorMessage(error)}`,
|
|
329
|
+
undefined,
|
|
330
|
+
"getDefaultPrettierConfig"
|
|
331
|
+
);
|
|
323
332
|
}
|
|
324
333
|
}
|
|
325
334
|
return prettierConfig;
|
|
@@ -86,6 +86,30 @@ const parseDataSource = (paramNode, stepParams) => {
|
|
|
86
86
|
// }
|
|
87
87
|
throw new Error("Unknown parameter type");
|
|
88
88
|
};
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
const parseOptions = (optionsNode) => {
|
|
92
|
+
const properties = optionsNode.properties;
|
|
93
|
+
const options = {}
|
|
94
|
+
for (const prop of properties) {
|
|
95
|
+
let key = "";
|
|
96
|
+
if (prop.key.type === 'Identifier') {
|
|
97
|
+
key = prop.key.name;
|
|
98
|
+
} else if (prop.key.type === 'StringLiteral') {
|
|
99
|
+
key = prop.key.value;
|
|
100
|
+
}
|
|
101
|
+
if (key === 'context') continue;
|
|
102
|
+
options[key] = prop.value.value;
|
|
103
|
+
}
|
|
104
|
+
return options;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parseOptionsFromCallNode = (call, index) => {
|
|
108
|
+
if (call.arguments[index] && call.arguments[index].type === "ObjectExpression") {
|
|
109
|
+
return parseOptions(call.arguments[index]);
|
|
110
|
+
}
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
89
113
|
const invertStableCommand = (call, elements, stepParams) => {
|
|
90
114
|
const methodName = call.callee.property.name;
|
|
91
115
|
const step = { type: null, parameters: [], element: null, locators: null };
|
|
@@ -104,6 +128,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
104
128
|
step.dataSource = inputParam.dataSource;
|
|
105
129
|
step.dataKey = inputParam.dataKey;
|
|
106
130
|
}
|
|
131
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
107
132
|
break;
|
|
108
133
|
}
|
|
109
134
|
|
|
@@ -130,6 +155,8 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
130
155
|
step.count = clickCountProp.value.value;
|
|
131
156
|
}
|
|
132
157
|
}
|
|
158
|
+
|
|
159
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
133
160
|
break;
|
|
134
161
|
|
|
135
162
|
case "setDateTime": {
|
|
@@ -147,6 +174,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
147
174
|
step.dataKey = dateParam.dataKey;
|
|
148
175
|
step.parameters = [toVariableName(dateParam.dataKey), format, enterParam];
|
|
149
176
|
}
|
|
177
|
+
step.options = parseOptionsFromCallNode(call, 5);
|
|
150
178
|
break;
|
|
151
179
|
}
|
|
152
180
|
|
|
@@ -163,6 +191,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
163
191
|
step.dataKey = selectParam.dataKey;
|
|
164
192
|
step.parameters = [toVariableName(selectParam.dataKey)];
|
|
165
193
|
}
|
|
194
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
166
195
|
break;
|
|
167
196
|
}
|
|
168
197
|
|
|
@@ -176,6 +205,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
176
205
|
step.dataKey = text.dataKey;
|
|
177
206
|
step.parameters = [toVariableName(text.dataKey)];
|
|
178
207
|
}
|
|
208
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
179
209
|
break;
|
|
180
210
|
}
|
|
181
211
|
case "waitForTextToDisappear": {
|
|
@@ -188,6 +218,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
188
218
|
step.dataKey = text.dataKey;
|
|
189
219
|
step.parameters = [toVariableName(text.dataKey)];
|
|
190
220
|
}
|
|
221
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
191
222
|
break;
|
|
192
223
|
}
|
|
193
224
|
|
|
@@ -203,6 +234,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
203
234
|
step.dataKey = text.dataKey;
|
|
204
235
|
step.parameters = [toVariableName(text.dataKey), climb ?? null];
|
|
205
236
|
}
|
|
237
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
206
238
|
break;
|
|
207
239
|
}
|
|
208
240
|
case "extractAttribute": {
|
|
@@ -233,6 +265,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
233
265
|
step.regex = "";
|
|
234
266
|
step.trimSpaces = false;
|
|
235
267
|
}
|
|
268
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
236
269
|
break;
|
|
237
270
|
}
|
|
238
271
|
case "verifyAttribute": {
|
|
@@ -249,6 +282,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
249
282
|
step.dataKey = value.dataKey;
|
|
250
283
|
step.parameters = [attribute, toVariableName(value.dataKey)];
|
|
251
284
|
}
|
|
285
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
252
286
|
break;
|
|
253
287
|
}
|
|
254
288
|
|
|
@@ -277,6 +311,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
277
311
|
step.parameters = [toVariableName(fillParam.dataKey), enterValue];
|
|
278
312
|
}
|
|
279
313
|
}
|
|
314
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
280
315
|
break;
|
|
281
316
|
|
|
282
317
|
case "loadTestDataAsync": {
|
|
@@ -307,23 +342,28 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
307
342
|
step.dataKey = url.dataKey;
|
|
308
343
|
step.parameters = [toVariableName(url.dataKey)];
|
|
309
344
|
}
|
|
345
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
310
346
|
break;
|
|
311
347
|
}
|
|
312
348
|
|
|
313
349
|
case "goBack":
|
|
314
350
|
step.type = Types.GO_BACK;
|
|
351
|
+
step.options = parseOptionsFromCallNode(call, 0);
|
|
315
352
|
break;
|
|
316
353
|
|
|
317
354
|
case "goForward":
|
|
318
355
|
step.type = Types.GO_FORWARD;
|
|
356
|
+
step.options = parseOptionsFromCallNode(call, 0);
|
|
319
357
|
break;
|
|
320
358
|
|
|
321
359
|
case "reloadPage":
|
|
322
360
|
step.type = Types.RELOAD;
|
|
361
|
+
step.options = parseOptionsFromCallNode(call, 0);
|
|
323
362
|
break;
|
|
324
363
|
|
|
325
364
|
case "closePage":
|
|
326
365
|
step.type = Types.CLOSE_PAGE;
|
|
366
|
+
step.options = parseOptionsFromCallNode(call, 0);
|
|
327
367
|
break;
|
|
328
368
|
|
|
329
369
|
case "simpleClick":
|
|
@@ -341,12 +381,15 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
341
381
|
case "hover":
|
|
342
382
|
step.type = Types.HOVER;
|
|
343
383
|
step.element = extractElement(call.arguments[0]);
|
|
384
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
344
385
|
break;
|
|
345
386
|
|
|
346
387
|
case "setCheck":
|
|
347
388
|
step.type = Types.CHECK;
|
|
348
389
|
step.element = extractElement(call.arguments[0]);
|
|
349
390
|
step.check = call.arguments[1].value;
|
|
391
|
+
///
|
|
392
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
350
393
|
break;
|
|
351
394
|
|
|
352
395
|
case "setViewportSize": {
|
|
@@ -354,12 +397,14 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
354
397
|
const width = call.arguments[0].value;
|
|
355
398
|
const height = call.arguments[1].value;
|
|
356
399
|
step.parameters = [width, height];
|
|
400
|
+
step.options = parseOptionsFromCallNode(call, 2);
|
|
357
401
|
break;
|
|
358
402
|
}
|
|
359
403
|
|
|
360
404
|
case "visualVerification": {
|
|
361
405
|
step.type = Types.VISUAL_VERIFICATION;
|
|
362
406
|
step.parameters = [call.arguments[0].value];
|
|
407
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
363
408
|
break;
|
|
364
409
|
}
|
|
365
410
|
case "waitForUserInput": {
|
|
@@ -370,6 +415,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
370
415
|
case "extractEmailData": {
|
|
371
416
|
step.type = Types.MAIL_GET_CODE_OR_URL;
|
|
372
417
|
step.parameters = [call.arguments[0].value];
|
|
418
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
373
419
|
break;
|
|
374
420
|
}
|
|
375
421
|
case "verifyTextRelatedToText": {
|
|
@@ -383,6 +429,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
383
429
|
const textToVerify =
|
|
384
430
|
textToVerifyParse.type === "literal" ? textToVerifyParse.value : toVariableName(textToVerifyParse.dataKey);
|
|
385
431
|
step.parameters = [textAnchor, climb, textToVerify];
|
|
432
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
386
433
|
break;
|
|
387
434
|
}
|
|
388
435
|
case "setInputFiles": {
|
|
@@ -396,6 +443,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
396
443
|
step.dataKey = fileParam.dataKey;
|
|
397
444
|
step.parameters = [toVariableName(fileParam.dataKey)];
|
|
398
445
|
}
|
|
446
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
399
447
|
break;
|
|
400
448
|
}
|
|
401
449
|
case "snapshotValidation": {
|
|
@@ -405,6 +453,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
405
453
|
step.parameters = [inputParam.value];
|
|
406
454
|
}
|
|
407
455
|
step.selectors = call.arguments[0].value;
|
|
456
|
+
step.options = parseOptionsFromCallNode(call, 3);
|
|
408
457
|
break;
|
|
409
458
|
}
|
|
410
459
|
case "verifyPageTitle": {
|
|
@@ -417,6 +466,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
417
466
|
step.dataKey = text.dataKey;
|
|
418
467
|
step.parameters = [toVariableName(text.dataKey)];
|
|
419
468
|
}
|
|
469
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
420
470
|
break;
|
|
421
471
|
}
|
|
422
472
|
case "verifyPagePath": {
|
|
@@ -429,6 +479,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
429
479
|
step.dataKey = path.dataKey;
|
|
430
480
|
step.parameters = [toVariableName(path.dataKey)];
|
|
431
481
|
}
|
|
482
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
432
483
|
break;
|
|
433
484
|
}
|
|
434
485
|
case "verifyProperty": {
|
|
@@ -443,6 +494,7 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
443
494
|
step.dataKey = value.dataKey;
|
|
444
495
|
step.parameters = [property, toVariableName(value.dataKey)];
|
|
445
496
|
}
|
|
497
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
446
498
|
break;
|
|
447
499
|
}
|
|
448
500
|
case "extractProperty": {
|
|
@@ -473,8 +525,71 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
473
525
|
step.regex = "";
|
|
474
526
|
step.trimSpaces = false;
|
|
475
527
|
}
|
|
528
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
case "conditionalWait": {
|
|
533
|
+
step.type = Types.CONDITIONAL_WAIT;
|
|
534
|
+
step.element = extractElement(call.arguments[0]);
|
|
535
|
+
const condition = call.arguments[1].value;
|
|
536
|
+
|
|
537
|
+
const _timeout = parseDataSource(call.arguments[2], stepParams);
|
|
538
|
+
let timeout = 30;
|
|
539
|
+
if (_timeout.type === "literal") {
|
|
540
|
+
if (isNaN(_timeout.value)) {
|
|
541
|
+
throw new Error(`Timeout value must be a number, got ${_timeout.value}`);
|
|
542
|
+
}
|
|
543
|
+
timeout = Number(_timeout.value) * 1000;
|
|
544
|
+
} else {
|
|
545
|
+
step.dataSource = _timeout.dataSource;
|
|
546
|
+
step.dataKey = _timeout.dataKey;
|
|
547
|
+
timeout = toVariableName(_timeout.dataKey);
|
|
548
|
+
}
|
|
549
|
+
// step.timeout = timeout;
|
|
550
|
+
// step.value = "true";
|
|
551
|
+
step.parameters = [timeout, condition, step.value];
|
|
552
|
+
// step.condition = condition;
|
|
553
|
+
step.options = parseOptionsFromCallNode(call, 4);
|
|
476
554
|
break;
|
|
477
555
|
}
|
|
556
|
+
|
|
557
|
+
case "sleep": {
|
|
558
|
+
step.type = Types.SLEEP;
|
|
559
|
+
const duration = parseDataSource(call.arguments[0], stepParams);
|
|
560
|
+
if (duration.type === "literal") {
|
|
561
|
+
if (isNaN(duration.value)) {
|
|
562
|
+
throw new Error(`Sleep duration must be a number, got ${duration.value}`);
|
|
563
|
+
}
|
|
564
|
+
step.parameters = [Number(duration.value)];
|
|
565
|
+
} else {
|
|
566
|
+
step.dataSource = duration.dataSource;
|
|
567
|
+
step.dataKey = duration.dataKey;
|
|
568
|
+
step.parameters = [toVariableName(duration.dataKey)];
|
|
569
|
+
}
|
|
570
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
case "verify_file_exists": {
|
|
574
|
+
step.type = Types.VERIFY_FILE_EXISTS;
|
|
575
|
+
const filePath = parseDataSource(call.arguments[0], stepParams);
|
|
576
|
+
if (filePath.type === "literal") {
|
|
577
|
+
step.parameters = [filePath.value];
|
|
578
|
+
} else {
|
|
579
|
+
step.dataSource = filePath.dataSource;
|
|
580
|
+
step.dataKey = filePath.dataKey;
|
|
581
|
+
step.parameters = [toVariableName(filePath.dataKey)];
|
|
582
|
+
}
|
|
583
|
+
step.options = parseOptionsFromCallNode(call, 1);
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// case "verifyPagePath":
|
|
588
|
+
// {
|
|
589
|
+
// step.type = Types.VERIFY_PAGE_PATH;
|
|
590
|
+
// step.parameters = [call.arguments[0].value];
|
|
591
|
+
// break;
|
|
592
|
+
// }
|
|
478
593
|
default:
|
|
479
594
|
return; // Skip if no matching method
|
|
480
595
|
}
|
|
@@ -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,8 @@ async function verify_the_opportunity_name_was_created(_name) {
|
|
|
11
11
|
}
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import strip from "strip-comments";
|
|
15
|
+
|
|
14
16
|
function generateSignature(page, functionName) {
|
|
15
17
|
let functionCode = null;
|
|
16
18
|
let method = null;
|
|
@@ -28,6 +30,8 @@ function generateSignature(page, functionName) {
|
|
|
28
30
|
method.node.body.parent.params.forEach((param) => {
|
|
29
31
|
parameters.push(param.name);
|
|
30
32
|
});
|
|
33
|
+
const strippedFunctionCode = strip(functionCode);
|
|
34
|
+
functionCode = strippedFunctionCode !== "" ? strippedFunctionCode : functionCode;
|
|
31
35
|
let lines = functionCode.split("\n");
|
|
32
36
|
// trim all lines
|
|
33
37
|
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";
|
|
@@ -528,12 +568,13 @@ export class CodePage {
|
|
|
528
568
|
for (let j = i - 1; j >= 0; j--) {
|
|
529
569
|
const nextElement = elements[keys[j]];
|
|
530
570
|
if (this._isSimilarElement(currentElement, nextElement)) {
|
|
531
|
-
mergedElements[currentElement.element_key] = currentElement;
|
|
532
|
-
replacedKeys[nextElement.element_key] = currentElement.element_key
|
|
571
|
+
// mergedElements[currentElement.element_key!] = currentElement;
|
|
572
|
+
// replacedKeys[nextElement.element_key!] = currentElement.element_key!;
|
|
533
573
|
foundMatch = true;
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
574
|
+
break;
|
|
575
|
+
// keys.splice(j, 1);
|
|
576
|
+
// i--;
|
|
577
|
+
// j++;
|
|
537
578
|
}
|
|
538
579
|
}
|
|
539
580
|
if (!foundMatch) {
|
|
@@ -543,7 +584,7 @@ export class CodePage {
|
|
|
543
584
|
if (Object.keys(replacedKeys).length === 0)
|
|
544
585
|
return;
|
|
545
586
|
for (const key in replacedKeys) {
|
|
546
|
-
const regexp = new RegExp(`elements
|
|
587
|
+
const regexp = new RegExp(`elements\\[\\s*["']${key}["']\\s*\\]`, "g");
|
|
547
588
|
this.fileContent = this.fileContent.replace(regexp, () => `elements["${replacedKeys[key]}"]`);
|
|
548
589
|
}
|
|
549
590
|
this.insertElements(mergedElements);
|
|
@@ -620,7 +661,7 @@ export class CodePage {
|
|
|
620
661
|
let locatorsMetadataFileName = this.sourceFileName.replace(".mjs", ".json");
|
|
621
662
|
const config = getAiConfig();
|
|
622
663
|
if (config && config.locatorsMetadataDir) {
|
|
623
|
-
locatorsMetadataFileName = path.join(
|
|
664
|
+
locatorsMetadataFileName = locatorsMetadataFileName.replace(path.join("features", "step_definitions"), path.join(config.locatorsMetadataDir));
|
|
624
665
|
if (!existsSync(path.dirname(locatorsMetadataFileName))) {
|
|
625
666
|
mkdirSync(path.dirname(locatorsMetadataFileName), { recursive: true });
|
|
626
667
|
}
|
|
@@ -632,7 +673,7 @@ export class CodePage {
|
|
|
632
673
|
}
|
|
633
674
|
}
|
|
634
675
|
catch {
|
|
635
|
-
logger.
|
|
676
|
+
logger.error("failed to read locators metadata file", locatorsMetadataFileName);
|
|
636
677
|
}
|
|
637
678
|
const keys = Object.keys(locatorsMetadata);
|
|
638
679
|
keys.forEach((key) => {
|
|
@@ -642,7 +683,7 @@ export class CodePage {
|
|
|
642
683
|
writeFileSync(locatorsMetadataFileName, JSON.stringify(metadata, null, 2), "utf8");
|
|
643
684
|
}
|
|
644
685
|
catch {
|
|
645
|
-
logger.
|
|
686
|
+
logger.error("failed to write locators metadata file", locatorsMetadataFileName);
|
|
646
687
|
}
|
|
647
688
|
}
|
|
648
689
|
_getVariableStartEnd(variableName) {
|