@dev-blinq/cucumber_client 1.0.1438-dev → 1.0.1438-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 +63 -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 +46 -28
- 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 +3 -2
- 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 +1025 -57
- package/bin/client/recorderv3/implemented_steps.js +2 -0
- package/bin/client/recorderv3/index.js +3 -283
- package/bin/client/recorderv3/services.js +818 -142
- package/bin/client/recorderv3/step_runner.js +21 -7
- package/bin/client/recorderv3/step_utils.js +540 -72
- package/bin/client/recorderv3/update_feature.js +86 -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,3 +1,7 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
// Load .env into process.env
|
|
3
|
+
dotenv.config();
|
|
4
|
+
|
|
1
5
|
import {
|
|
2
6
|
After,
|
|
3
7
|
setDefaultTimeout,
|
|
@@ -30,8 +34,8 @@ BeforeStep(async function (step) {
|
|
|
30
34
|
}
|
|
31
35
|
});
|
|
32
36
|
|
|
33
|
-
AfterStep(async function (
|
|
37
|
+
AfterStep(async function ({ result, pickleStep }) {
|
|
34
38
|
if (context) {
|
|
35
|
-
await context.web.afterStep(this,
|
|
39
|
+
await context.web.afterStep(this, pickleStep, result);
|
|
36
40
|
}
|
|
37
41
|
});
|
|
@@ -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;
|
|
@@ -50,7 +50,7 @@ const generateApiCode = async (
|
|
|
50
50
|
// create new codePage
|
|
51
51
|
mjsFullPath = path.join(
|
|
52
52
|
projectDir,
|
|
53
|
-
stepsDefinitions.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
|
|
53
|
+
stepsDefinitions.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features",
|
|
54
54
|
"step_definitions",
|
|
55
55
|
`api_tests_page.mjs`
|
|
56
56
|
);
|
|
@@ -60,7 +60,7 @@ const generateApiCode = async (
|
|
|
60
60
|
if (!codePage) {
|
|
61
61
|
mjsFullPath = path.join(
|
|
62
62
|
projectDir,
|
|
63
|
-
stepsDefinitions.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
|
|
63
|
+
stepsDefinitions.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features",
|
|
64
64
|
"step_definitions",
|
|
65
65
|
`api_tests_page.mjs`
|
|
66
66
|
);
|
|
@@ -375,11 +375,13 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
375
375
|
case "verifyTextRelatedToText": {
|
|
376
376
|
step.type = Types.VERIFY_TEXT_RELATED_TO_TEXT;
|
|
377
377
|
const textAnchorParse = parseDataSource(call.arguments[0], stepParams);
|
|
378
|
-
const textAnchor =
|
|
378
|
+
const textAnchor =
|
|
379
|
+
textAnchorParse.type === "literal" ? textAnchorParse.value : toVariableName(textAnchorParse.dataKey);
|
|
379
380
|
const climbParse = parseDataSource(call.arguments[1], stepParams);
|
|
380
381
|
const climb = climbParse.type === "literal" ? climbParse.value : toVariableName(climbParse.dataKey);
|
|
381
382
|
const textToVerifyParse = parseDataSource(call.arguments[2], stepParams);
|
|
382
|
-
const textToVerify =
|
|
383
|
+
const textToVerify =
|
|
384
|
+
textToVerifyParse.type === "literal" ? textToVerifyParse.value : toVariableName(textToVerifyParse.dataKey);
|
|
383
385
|
step.parameters = [textAnchor, climb, textToVerify];
|
|
384
386
|
break;
|
|
385
387
|
}
|
|
@@ -473,6 +475,65 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
473
475
|
}
|
|
474
476
|
break;
|
|
475
477
|
}
|
|
478
|
+
|
|
479
|
+
case "conditionalWait": {
|
|
480
|
+
step.type = Types.CONDITIONAL_WAIT;
|
|
481
|
+
step.element = extractElement(call.arguments[0]);
|
|
482
|
+
const condition = call.arguments[1].value;
|
|
483
|
+
|
|
484
|
+
const _timeout = parseDataSource(call.arguments[2], stepParams);
|
|
485
|
+
let timeout = 30;
|
|
486
|
+
if (_timeout.type === "literal") {
|
|
487
|
+
if (isNaN(_timeout.value)) {
|
|
488
|
+
throw new Error(`Timeout value must be a number, got ${_timeout.value}`);
|
|
489
|
+
}
|
|
490
|
+
timeout = Number(_timeout.value) * 1000;
|
|
491
|
+
} else {
|
|
492
|
+
step.dataSource = _timeout.dataSource;
|
|
493
|
+
step.dataKey = _timeout.dataKey;
|
|
494
|
+
timeout = toVariableName(_timeout.dataKey);
|
|
495
|
+
}
|
|
496
|
+
// step.timeout = timeout;
|
|
497
|
+
// step.value = "true";
|
|
498
|
+
step.parameters = [timeout, condition, step.value];
|
|
499
|
+
// step.condition = condition;
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
case "sleep": {
|
|
504
|
+
step.type = Types.SLEEP;
|
|
505
|
+
const duration = parseDataSource(call.arguments[0], stepParams);
|
|
506
|
+
if (duration.type === "literal") {
|
|
507
|
+
if (isNaN(duration.value)) {
|
|
508
|
+
throw new Error(`Sleep duration must be a number, got ${duration.value}`);
|
|
509
|
+
}
|
|
510
|
+
step.parameters = [Number(duration.value)];
|
|
511
|
+
} else {
|
|
512
|
+
step.dataSource = duration.dataSource;
|
|
513
|
+
step.dataKey = duration.dataKey;
|
|
514
|
+
step.parameters = [toVariableName(duration.dataKey)];
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
case "verify_file_exists": {
|
|
519
|
+
step.type = Types.VERIFY_FILE_EXISTS;
|
|
520
|
+
const filePath = parseDataSource(call.arguments[0], stepParams);
|
|
521
|
+
if (filePath.type === "literal") {
|
|
522
|
+
step.parameters = [filePath.value];
|
|
523
|
+
} else {
|
|
524
|
+
step.dataSource = filePath.dataSource;
|
|
525
|
+
step.dataKey = filePath.dataKey;
|
|
526
|
+
step.parameters = [toVariableName(filePath.dataKey)];
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// case "verifyPagePath":
|
|
532
|
+
// {
|
|
533
|
+
// step.type = Types.VERIFY_PAGE_PATH;
|
|
534
|
+
// step.parameters = [call.arguments[0].value];
|
|
535
|
+
// break;
|
|
536
|
+
// }
|
|
476
537
|
default:
|
|
477
538
|
return; // Skip if no matching method
|
|
478
539
|
}
|
|
@@ -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) {
|
|
@@ -5,6 +5,8 @@ import path from "path";
|
|
|
5
5
|
import { CodePage } from "./page_reflection.js";
|
|
6
6
|
import { convertToIdentifier, escapeNonPrintables } from "./utils.js";
|
|
7
7
|
import socketLogger from "../utils/socket_logger.js";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
|
|
8
10
|
const findElementIdentifier = (node, step, userData, elements) => {
|
|
9
11
|
if (node.key) {
|
|
10
12
|
// incase of rerunning implemented steps
|
|
@@ -67,12 +69,23 @@ const _isCodeGenerationStep = (step) => {
|
|
|
67
69
|
step.type === Types.VERIFY_PROPERTY ||
|
|
68
70
|
step.type === Types.SET_INPUT_FILES ||
|
|
69
71
|
step.type === Types.VERIFY_PAGE_SNAPSHOT ||
|
|
70
|
-
step.type === Types.CONDITIONAL_WAIT
|
|
72
|
+
step.type === Types.CONDITIONAL_WAIT ||
|
|
73
|
+
step.type === Types.CLOSE_PAGE
|
|
71
74
|
) {
|
|
72
75
|
return true;
|
|
73
76
|
}
|
|
74
77
|
return false;
|
|
75
78
|
};
|
|
79
|
+
|
|
80
|
+
const _isLocatorStrategyStep = (step) => {
|
|
81
|
+
switch (step.type) {
|
|
82
|
+
case Types.VERIFY_PAGE_SNAPSHOT:
|
|
83
|
+
case Types.CLOSE_PAGE:
|
|
84
|
+
return false;
|
|
85
|
+
default:
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
76
89
|
// Note: this function is used to exclude a key from an object
|
|
77
90
|
// Please move it to utils and use it as a reusable function ...
|
|
78
91
|
function excludeKey(obj, keyToRemove) {
|
|
@@ -105,7 +118,12 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
105
118
|
// handle element
|
|
106
119
|
let element_name = null;
|
|
107
120
|
let allStrategyLocators = null;
|
|
108
|
-
|
|
121
|
+
const codeLines = [];
|
|
122
|
+
|
|
123
|
+
if (_isCodeGenerationStep(step) === false)
|
|
124
|
+
return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
|
|
125
|
+
|
|
126
|
+
if (_isCodeGenerationStep(step) && _isLocatorStrategyStep(step)) {
|
|
109
127
|
allStrategyLocators = step.allStrategyLocators ?? splitToLocatorsGroups(step.locators);
|
|
110
128
|
let node = null;
|
|
111
129
|
|
|
@@ -167,8 +185,6 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
167
185
|
}
|
|
168
186
|
}
|
|
169
187
|
|
|
170
|
-
const codeLines = [];
|
|
171
|
-
|
|
172
188
|
let line = null;
|
|
173
189
|
let comment = null;
|
|
174
190
|
let input = null;
|
|
@@ -363,16 +379,33 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
363
379
|
codeLines.push(line);
|
|
364
380
|
break;
|
|
365
381
|
}
|
|
366
|
-
case Types.VERIFY_PAGE_SNAPSHOT:
|
|
382
|
+
case Types.VERIFY_PAGE_SNAPSHOT: {
|
|
367
383
|
comment = step.nestFrmLoc
|
|
368
384
|
? `// Verify page snapshot ${step.parameters[0]} in the frame ${step.selectors.iframe_src}`
|
|
369
385
|
: `// Verify page snapshot ${step.parameters[0]}`;
|
|
370
386
|
codeLines.push(escapeNonPrintables(comment));
|
|
371
387
|
line = `const frameLocator = ${JSON.stringify(step.selectors ?? null)}`;
|
|
372
388
|
codeLines.push(line);
|
|
373
|
-
line = `await context.web.snapshotValidation(frameLocator
|
|
389
|
+
line = `await context.web.snapshotValidation(frameLocator,${step.valueInStepText ? "_param_0" : `"${step.parameters[0]}"`}, _params, ${JSON.stringify(options)}, this);`;
|
|
374
390
|
codeLines.push(line);
|
|
391
|
+
|
|
392
|
+
const data = step.data;
|
|
393
|
+
if (data) {
|
|
394
|
+
try {
|
|
395
|
+
const { snapshot, fileName, filePath } = data;
|
|
396
|
+
const folderPath = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? filePath;
|
|
397
|
+
const filePathWithName = path.join(folderPath, fileName);
|
|
398
|
+
if (!fs.existsSync(folderPath)) {
|
|
399
|
+
fs.mkdirSync(folderPath, { recursive: true });
|
|
400
|
+
}
|
|
401
|
+
fs.writeFileSync(filePathWithName, snapshot, "utf-8");
|
|
402
|
+
} catch (e) {
|
|
403
|
+
console.log(`Error saving snapshot file: ${e}`);
|
|
404
|
+
throw e;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
375
407
|
break;
|
|
408
|
+
}
|
|
376
409
|
case Types.VERIFY_PAGE_CONTAINS_TEXT:
|
|
377
410
|
line = "await context.web.verifyTextExistInPage( ";
|
|
378
411
|
input = `${escapeNonPrintables(JSON.stringify(step.parameters[0]))}`;
|
|
@@ -644,7 +677,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
644
677
|
}
|
|
645
678
|
case Types.VERIFY_PROPERTY: {
|
|
646
679
|
line = `await context.web.verifyProperty(elements["${elementIdentifier}"], `;
|
|
647
|
-
input = "_param_0"
|
|
680
|
+
input = step.valueInStepText ? "_param_0" : `"${escapeNonPrintables(step.parameters[1].replaceAll('"', '\\"'))}"`;
|
|
648
681
|
if (step.dataSource === "userData" || step.dataSource === "parameters") {
|
|
649
682
|
input = "_" + step.dataKey;
|
|
650
683
|
}
|
|
@@ -654,7 +687,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
654
687
|
}
|
|
655
688
|
case Types.CONDITIONAL_WAIT: {
|
|
656
689
|
line = `await context.web.conditionalWait(elements["${elementIdentifier}"], `;
|
|
657
|
-
input = "_param_0"
|
|
690
|
+
input = step.valueInStepText ? "_param_0" : `"${escapeNonPrintables(step.parameters[1].replaceAll('"', '\\"'))}"`;
|
|
658
691
|
line += `"${step.parameters[1]}", ${input}, _params, null, this);`;
|
|
659
692
|
codeLines.push(line);
|
|
660
693
|
break;
|
|
@@ -684,22 +717,6 @@ const _generateCodeFromCommand = (step, elements, userData) => {
|
|
|
684
717
|
return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
|
|
685
718
|
};
|
|
686
719
|
|
|
687
|
-
/**
|
|
688
|
-
* Generates a report command based on the given position.
|
|
689
|
-
* @param {"start"|"end"} position
|
|
690
|
-
*/
|
|
691
|
-
const generateReportCommand = (position, cmdId) => {
|
|
692
|
-
const codeLines = [];
|
|
693
|
-
if (position === "start") {
|
|
694
|
-
const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"start","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
|
|
695
|
-
codeLines.push(line);
|
|
696
|
-
} else if (position === "end") {
|
|
697
|
-
const line = `await context.web.addCommandToReport("${cmdId}", "PASSED", '{"status":"end","cmdId":"${cmdId}"}', {type:"cmdReport"},this);`;
|
|
698
|
-
codeLines.push(line);
|
|
699
|
-
}
|
|
700
|
-
return codeLines;
|
|
701
|
-
};
|
|
702
|
-
|
|
703
720
|
const generateCode = (recording, codePage, userData, projectDir, methodName) => {
|
|
704
721
|
const stepsDefinitions = new StepsDefinitions(projectDir);
|
|
705
722
|
stepsDefinitions.load(false);
|
|
@@ -722,12 +739,10 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
|
|
|
722
739
|
codePage = new CodePage(mjsFullPath);
|
|
723
740
|
codePage.generateModel();
|
|
724
741
|
}
|
|
725
|
-
//console.log("step found");
|
|
726
742
|
}
|
|
727
743
|
let elements = {};
|
|
728
744
|
if (!codePage) {
|
|
729
|
-
socketLogger.info("CodePage is null");
|
|
730
|
-
console.log("codePage is null");
|
|
745
|
+
socketLogger.info("CodePage is null", undefined, "generateCode");
|
|
731
746
|
} else {
|
|
732
747
|
elements = codePage.getVariableDeclarationAsObject("elements");
|
|
733
748
|
}
|
|
@@ -741,6 +756,7 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
|
|
|
741
756
|
if (recordingStep.type === Types.COMPLETE) {
|
|
742
757
|
return;
|
|
743
758
|
}
|
|
759
|
+
socketLogger.info(`Generating code for step: ${recordingStep.type}`, undefined, "generateCode");
|
|
744
760
|
|
|
745
761
|
// if (process.env.TEMP_RUN === "true") {
|
|
746
762
|
// codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
|
|
@@ -752,9 +768,11 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
|
|
|
752
768
|
// codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
|
|
753
769
|
// }
|
|
754
770
|
if (result.elements) {
|
|
771
|
+
socketLogger.info(`Updating elements with ${Object.keys(result.elements)}`, undefined, "generateCode");
|
|
755
772
|
elements = result.elements;
|
|
756
773
|
}
|
|
757
774
|
if (result.elementIdentifier) {
|
|
775
|
+
socketLogger.info(`Adding locator metadata for ${result.elementIdentifier}`, undefined, "generateCode");
|
|
758
776
|
locatorsMetadata[result.elementIdentifier] = result.allStrategyLocators;
|
|
759
777
|
}
|
|
760
778
|
});
|
|
@@ -791,7 +809,7 @@ const generatePageName = (url) => {
|
|
|
791
809
|
}
|
|
792
810
|
// join by _
|
|
793
811
|
return lowerCaseParts.join("_");
|
|
794
|
-
} catch
|
|
812
|
+
} catch {
|
|
795
813
|
return "default";
|
|
796
814
|
}
|
|
797
815
|
};
|