@dev-blinq/cucumber_client 1.0.1184-dev → 1.0.1184-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 +220 -0
- package/bin/assets/preload/accessibility.js +1 -1
- package/bin/assets/preload/find_context.js +1 -1
- package/bin/assets/preload/generateSelector.js +24 -0
- package/bin/assets/preload/locators.js +18 -0
- package/bin/assets/preload/recorderv3.js +80 -9
- package/bin/assets/preload/unique_locators.js +24 -3
- package/bin/assets/scripts/aria_snapshot.js +235 -0
- package/bin/assets/scripts/dom_attr.js +372 -0
- package/bin/assets/scripts/dom_element.js +0 -0
- package/bin/assets/scripts/dom_parent.js +185 -0
- package/bin/assets/scripts/event_utils.js +105 -0
- package/bin/assets/scripts/pw.js +7886 -0
- package/bin/assets/scripts/recorder.js +1147 -0
- package/bin/assets/scripts/snapshot_capturer.js +155 -0
- package/bin/assets/scripts/unique_locators.js +844 -0
- package/bin/assets/scripts/yaml.js +4770 -0
- package/bin/assets/templates/page_template.txt +2 -16
- package/bin/assets/templates/utils_template.txt +65 -12
- package/bin/client/cli_helpers.js +0 -1
- package/bin/client/code_cleanup/utils.js +43 -14
- package/bin/client/code_gen/code_inversion.js +44 -12
- package/bin/client/code_gen/index.js +3 -0
- package/bin/client/code_gen/page_reflection.js +37 -20
- package/bin/client/code_gen/playwright_codeget.js +149 -43
- package/bin/client/cucumber/feature.js +96 -42
- package/bin/client/cucumber/project_to_document.js +8 -7
- package/bin/client/cucumber/steps_definitions.js +49 -16
- package/bin/client/local_agent.js +9 -7
- package/bin/client/operations/dump_tree.js +159 -5
- package/bin/client/playground/playground.js +1 -1
- package/bin/client/project.js +6 -2
- package/bin/client/recorderv3/bvt_recorder.js +279 -81
- package/bin/client/recorderv3/cli.js +1 -0
- package/bin/client/recorderv3/implemented_steps.js +111 -11
- package/bin/client/recorderv3/index.js +48 -4
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +183 -13
- package/bin/client/recorderv3/step_utils.js +159 -14
- package/bin/client/recorderv3/update_feature.js +53 -28
- package/bin/client/recording.js +8 -0
- package/bin/client/run_cucumber.js +16 -2
- package/bin/client/scenario_report.js +112 -55
- package/bin/client/test_scenario.js +0 -1
- package/bin/index.js +1 -0
- package/package.json +15 -8
- package/bin/client/code_gen/get_implemented_steps.js +0 -27
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
import { Given, When, Then, After, setDefaultTimeout, Before} from "@dev-blinq/cucumber-js";
|
|
2
|
-
import { closeContext, initContext, navigate } from "automation_model";
|
|
3
|
-
setDefaultTimeout(60 * 1000);
|
|
4
|
-
|
|
5
|
-
const path = null;
|
|
1
|
+
import { Given, When, Then, After, setDefaultTimeout, Before } from "@dev-blinq/cucumber-js";
|
|
2
|
+
import { closeContext, initContext, navigate, TestContext as context } from "automation_model";
|
|
6
3
|
|
|
7
4
|
const elements = {
|
|
8
5
|
};
|
|
9
6
|
|
|
10
|
-
let context = null;
|
|
11
|
-
Before(async function () {
|
|
12
|
-
if (!context) {
|
|
13
|
-
context = await initContext(path, false, false, this);
|
|
14
|
-
}
|
|
15
|
-
await navigate(path);
|
|
16
|
-
});
|
|
17
|
-
After(async function () {
|
|
18
|
-
await closeContext();
|
|
19
|
-
context = null;
|
|
20
|
-
});
|
|
@@ -19,9 +19,7 @@ const elements = {};
|
|
|
19
19
|
|
|
20
20
|
let context = null;
|
|
21
21
|
Before(async function (scenario) {
|
|
22
|
-
|
|
23
|
-
context = await initContext(url, false, false, this);
|
|
24
|
-
}
|
|
22
|
+
context = await initContext(url, false, false, this);
|
|
25
23
|
await navigate(url);
|
|
26
24
|
await context.web.beforeScenario(this, scenario);
|
|
27
25
|
});
|
|
@@ -33,20 +31,21 @@ After(async function (scenario) {
|
|
|
33
31
|
|
|
34
32
|
BeforeStep(async function (step) {
|
|
35
33
|
if (context) {
|
|
36
|
-
await context.
|
|
34
|
+
await context.stable.beforeStep(this, step);
|
|
37
35
|
}
|
|
38
36
|
});
|
|
39
37
|
|
|
40
38
|
AfterStep(async function (step) {
|
|
41
39
|
if (context) {
|
|
42
|
-
await context.
|
|
40
|
+
await context.stable.afterStep(this, step);
|
|
43
41
|
}
|
|
44
42
|
});
|
|
45
43
|
|
|
44
|
+
|
|
46
45
|
/**
|
|
47
46
|
* Load test data for a user
|
|
48
47
|
* @param {string} user name of the user to load test data for
|
|
49
|
-
* @
|
|
48
|
+
* @returns
|
|
50
49
|
*/
|
|
51
50
|
async function loadUserData(user) {
|
|
52
51
|
await context.web.loadTestDataAsync("users", user, this);
|
|
@@ -60,7 +59,6 @@ async function loadUserData(user) {
|
|
|
60
59
|
async function verifyTextExistsInPage(text) {
|
|
61
60
|
await context.web.verifyTextExistInPage(text, null, this);
|
|
62
61
|
}
|
|
63
|
-
|
|
64
62
|
Then("Verify the text {string} can be found in the page", verifyTextExistsInPage);
|
|
65
63
|
|
|
66
64
|
/**
|
|
@@ -69,7 +67,7 @@ Then("Verify the text {string} can be found in the page", verifyTextExistsInPage
|
|
|
69
67
|
* @protect
|
|
70
68
|
*/
|
|
71
69
|
async function clickOnElement(elementDescription) {
|
|
72
|
-
await context.
|
|
70
|
+
await context.stable.simpleClick(elementDescription, null, null, this);
|
|
73
71
|
}
|
|
74
72
|
When("click on {string}", clickOnElement);
|
|
75
73
|
When("click {string}", clickOnElement);
|
|
@@ -83,10 +81,11 @@ When("Click {string}", clickOnElement);
|
|
|
83
81
|
* @protect
|
|
84
82
|
*/
|
|
85
83
|
async function fillElement(elementDescription, value) {
|
|
86
|
-
await context.
|
|
84
|
+
await context.stable.simpleClickType(elementDescription, value, null, null, this);
|
|
87
85
|
}
|
|
88
86
|
When("fill {string} with {string}", fillElement);
|
|
89
87
|
When("Fill {string} with {string}", fillElement);
|
|
88
|
+
|
|
90
89
|
/**
|
|
91
90
|
* Verify text does not exist in page
|
|
92
91
|
* @param {string} text the text to verify does not exist in page
|
|
@@ -107,6 +106,24 @@ async function navigateTo(url) {
|
|
|
107
106
|
}
|
|
108
107
|
When("Navigate to {string}", navigateTo);
|
|
109
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Navigate to the current page
|
|
111
|
+
* @protect
|
|
112
|
+
*/
|
|
113
|
+
async function browserNavigateBack() {
|
|
114
|
+
await context.web.goBack({}, this);
|
|
115
|
+
}
|
|
116
|
+
Then("Browser navigate back", browserNavigateBack);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Navigate forward in browser history
|
|
120
|
+
* @protect
|
|
121
|
+
*/
|
|
122
|
+
async function browserNavigateForward() {
|
|
123
|
+
await context.web.goForward({}, this);
|
|
124
|
+
}
|
|
125
|
+
Then("Browser navigate forward", browserNavigateForward);
|
|
126
|
+
|
|
110
127
|
/**
|
|
111
128
|
* Store browser session "<path>"
|
|
112
129
|
* @param {string} filePath the file path or empty to store in the test data file
|
|
@@ -141,6 +158,7 @@ Then(
|
|
|
141
158
|
"Identify the text {string}, climb {string} levels in the page, validate text {string} can be found in the context",
|
|
142
159
|
verifyTextRelatedToText
|
|
143
160
|
);
|
|
161
|
+
|
|
144
162
|
/**
|
|
145
163
|
* execute bruno single request given the bruno project is placed in a folder called bruno under the root of the cucumber project
|
|
146
164
|
* @requestName the name of the bruno request file
|
|
@@ -149,7 +167,7 @@ Then(
|
|
|
149
167
|
async function runBrunoRequest(requestName) {
|
|
150
168
|
await executeBrunoRequest(requestName, {}, context, this);
|
|
151
169
|
}
|
|
152
|
-
|
|
170
|
+
When("Bruno - {string}", runBrunoRequest);
|
|
153
171
|
When("bruno - {string}", runBrunoRequest);
|
|
154
172
|
|
|
155
173
|
/**
|
|
@@ -162,6 +180,41 @@ async function verify_the_downloaded_file_exists(fileName) {
|
|
|
162
180
|
const downloadFile = path.join(downloadFolder, fileName);
|
|
163
181
|
await verifyFileExists(downloadFile, {}, context, this);
|
|
164
182
|
}
|
|
165
|
-
|
|
166
183
|
Then("Verify the file {string} exists", { timeout: 60000 }, verify_the_downloaded_file_exists);
|
|
167
|
-
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Noop step for running only hooks
|
|
187
|
+
*/
|
|
188
|
+
When("Noop", async function () {});
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Verify the page url is "<url>"
|
|
192
|
+
* @param {string} url URL to be verified against current URL
|
|
193
|
+
* @protect
|
|
194
|
+
*/
|
|
195
|
+
async function verify_page_url(url) {
|
|
196
|
+
await context.web.verifyPagePath(url, {}, this);
|
|
197
|
+
}
|
|
198
|
+
Then("Verify the page url is {string}", verify_page_url);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Verify the page title is "<title>"
|
|
202
|
+
* @param {string} title Title to be verified against current Title
|
|
203
|
+
* @protect
|
|
204
|
+
*/
|
|
205
|
+
async function verify_page_title(title) {
|
|
206
|
+
await context.web.verifyPageTitle(title, {}, this);
|
|
207
|
+
}
|
|
208
|
+
Then("Verify the page title is {string}", verify_page_title);
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Explicit wait/sleep function that pauses execution for a specified duration
|
|
212
|
+
* @param {duration} - Duration to sleep in milliseconds (default: 1000ms)
|
|
213
|
+
* @param {options} - Optional configuration object
|
|
214
|
+
* @param {world} - Optional world context
|
|
215
|
+
* @returns Promise that resolves after the specified duration
|
|
216
|
+
*/
|
|
217
|
+
async function sleep(duration) {
|
|
218
|
+
await context.web.sleep(duration, {}, null);
|
|
219
|
+
}
|
|
220
|
+
Then("Sleep for {string} ms", { timeout: -1 }, sleep);
|
|
@@ -10,6 +10,8 @@ import * as t from "@babel/types";
|
|
|
10
10
|
|
|
11
11
|
import { CucumberExpression, ParameterTypeRegistry } from "@cucumber/cucumber-expressions";
|
|
12
12
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
13
|
+
|
|
14
|
+
import { getAiConfig } from "../code_gen/page_reflection.js";
|
|
13
15
|
const STEP_KEYWORDS = new Set(["Given", "When", "Then"]);
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -286,14 +288,50 @@ export function removeUnusedElementsKeys(ast, supportFilePath) {
|
|
|
286
288
|
}
|
|
287
289
|
}
|
|
288
290
|
}
|
|
289
|
-
|
|
291
|
+
export function getDefaultPrettierConfig() {
|
|
292
|
+
let prettierConfig = {
|
|
293
|
+
parser: "babel",
|
|
294
|
+
trailingComma: "es5",
|
|
295
|
+
tabWidth: 2,
|
|
296
|
+
semi: true,
|
|
297
|
+
singleQuote: false,
|
|
298
|
+
bracketSpacing: true,
|
|
299
|
+
arrowParens: "always",
|
|
300
|
+
embeddedLanguageFormatting: "auto",
|
|
301
|
+
endOfLine: "lf",
|
|
302
|
+
printWidth: 120,
|
|
303
|
+
};
|
|
304
|
+
// check if .prettierrc file exists
|
|
305
|
+
const prettierConfigPath = ".prettierrc";
|
|
306
|
+
if (existsSync(prettierConfigPath)) {
|
|
307
|
+
try {
|
|
308
|
+
const configContent = readFileSync(prettierConfigPath, "utf-8");
|
|
309
|
+
prettierConfig = JSON.parse(configContent);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error(`Error parsing Prettier config file: ${error}`);
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
// save the default config to .prettierrc
|
|
315
|
+
try {
|
|
316
|
+
writeFileSync(prettierConfigPath, JSON.stringify(prettierConfig, null, 2), "utf-8");
|
|
317
|
+
// console.log(`Created default Prettier config at ${prettierConfigPath}`);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error(`Error writing Prettier config file: ${error}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return prettierConfig;
|
|
323
|
+
}
|
|
290
324
|
/**
|
|
291
325
|
* Remove unused step definitions from a file.
|
|
292
326
|
* @param {string} filePath
|
|
293
327
|
* @param {Array<{keyword: string, pattern: string}>} stepDefinitions
|
|
294
328
|
*/
|
|
295
329
|
export async function removeUnusedStepDefinitions(filePath, stepDefinitions) {
|
|
296
|
-
|
|
330
|
+
let supportFilePath = filePath.replace(".mjs", ".json");
|
|
331
|
+
const config = getAiConfig();
|
|
332
|
+
if (config && config.locatorsMetadataDir) {
|
|
333
|
+
supportFilePath = path.join(config.locatorsMetadataDir, path.basename(supportFilePath));
|
|
334
|
+
}
|
|
297
335
|
const ast = await parse(filePath);
|
|
298
336
|
removeStepDefinitions(stepDefinitions, ast);
|
|
299
337
|
removeUnusedDeclarations(ast);
|
|
@@ -301,19 +339,10 @@ export async function removeUnusedStepDefinitions(filePath, stepDefinitions) {
|
|
|
301
339
|
// removeUnusedImports(ast);
|
|
302
340
|
let code = generateCode(ast);
|
|
303
341
|
|
|
342
|
+
// configuration object
|
|
343
|
+
const prettierConfig = getDefaultPrettierConfig();
|
|
304
344
|
// Format the code using Prettier
|
|
305
|
-
code = await prettier.format(code,
|
|
306
|
-
parser: "babel",
|
|
307
|
-
trailingComma: "es5",
|
|
308
|
-
tabWidth: 2,
|
|
309
|
-
semi: true,
|
|
310
|
-
singleQuote: false,
|
|
311
|
-
bracketSpacing: true,
|
|
312
|
-
arrowParens: "always",
|
|
313
|
-
embeddedLanguageFormatting: "auto",
|
|
314
|
-
endOfLine: "lf",
|
|
315
|
-
printWidth: 120,
|
|
316
|
-
});
|
|
345
|
+
code = await prettier.format(code, prettierConfig);
|
|
317
346
|
|
|
318
347
|
await fs.writeFile(filePath, code, "utf-8");
|
|
319
348
|
console.log(`Removed unused step definitions from ${filePath}`);
|
|
@@ -109,13 +109,13 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
109
109
|
|
|
110
110
|
case "click":
|
|
111
111
|
// Handle different click scenarios
|
|
112
|
+
step.type = Types.CLICK;
|
|
113
|
+
step.element = extractElement(call.arguments[0]);
|
|
112
114
|
if (call.arguments.length > 2 && call.arguments[2]?.type === "ObjectExpression") {
|
|
113
115
|
// Context click
|
|
114
|
-
step.type = "context_click";
|
|
115
|
-
step.element = extractElement(call.arguments[0]);
|
|
116
|
-
|
|
117
116
|
const contextProp = call.arguments[2].properties.find((prop) => prop.key.name === "context");
|
|
118
117
|
if (contextProp) {
|
|
118
|
+
step.type = "context_click";
|
|
119
119
|
const contextValue = parseDataSource(contextProp.value, stepParams);
|
|
120
120
|
if (contextValue.type === "literal") {
|
|
121
121
|
step.value = contextValue.value;
|
|
@@ -125,9 +125,10 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
125
125
|
step.value = toVariableName(contextValue.dataKey);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
const clickCountProp = call.arguments[2].properties.find((prop) => prop.key.name === "clickCount");
|
|
129
|
+
if (clickCountProp) {
|
|
130
|
+
step.count = clickCountProp.value.value;
|
|
131
|
+
}
|
|
131
132
|
}
|
|
132
133
|
break;
|
|
133
134
|
|
|
@@ -309,6 +310,14 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
309
310
|
break;
|
|
310
311
|
}
|
|
311
312
|
|
|
313
|
+
case "goBack":
|
|
314
|
+
step.type = Types.GO_BACK;
|
|
315
|
+
break;
|
|
316
|
+
|
|
317
|
+
case "goForward":
|
|
318
|
+
step.type = Types.GO_FORWARD;
|
|
319
|
+
break;
|
|
320
|
+
|
|
312
321
|
case "reloadPage":
|
|
313
322
|
step.type = Types.RELOAD;
|
|
314
323
|
break;
|
|
@@ -320,14 +329,14 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
320
329
|
case "simpleClick":
|
|
321
330
|
// step.type = Types.CLICK_SIMPLE;
|
|
322
331
|
// step.elementDescription = call.arguments[0].value;
|
|
323
|
-
throw new Error("
|
|
332
|
+
throw new Error("Action is not supported in the recorder");
|
|
324
333
|
|
|
325
334
|
case "simpleClickType":
|
|
326
335
|
// step.type = Types.FILL_SIMPLE;
|
|
327
336
|
// step.elementDescription = call.arguments[0].value;
|
|
328
337
|
// step.value = call.arguments[1].value;
|
|
329
338
|
// break;
|
|
330
|
-
throw new Error("
|
|
339
|
+
throw new Error("Action is not supported in the recorder");
|
|
331
340
|
|
|
332
341
|
case "hover":
|
|
333
342
|
step.type = Types.HOVER;
|
|
@@ -384,7 +393,6 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
384
393
|
}
|
|
385
394
|
break;
|
|
386
395
|
}
|
|
387
|
-
|
|
388
396
|
case "snapshotValidation": {
|
|
389
397
|
step.type = Types.VERIFY_PAGE_SNAPSHOT;
|
|
390
398
|
const inputParam = parseDataSource(call.arguments[1], stepParams);
|
|
@@ -394,6 +402,30 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
394
402
|
step.selectors = call.arguments[0].value;
|
|
395
403
|
break;
|
|
396
404
|
}
|
|
405
|
+
case "verifyPageTitle": {
|
|
406
|
+
step.type = Types.VERIFY_PAGE_TITLE;
|
|
407
|
+
const text = parseDataSource(call.arguments[0], stepParams);
|
|
408
|
+
if (text.type === "literal") {
|
|
409
|
+
step.parameters = [text.value];
|
|
410
|
+
} else {
|
|
411
|
+
step.dataSource = text.dataSource;
|
|
412
|
+
step.dataKey = text.dataKey;
|
|
413
|
+
step.parameters = [toVariableName(text.dataKey)];
|
|
414
|
+
}
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
case "verifyPagePath": {
|
|
418
|
+
step.type = Types.VERIFY_PAGE_PATH;
|
|
419
|
+
const path = parseDataSource(call.arguments[0], stepParams);
|
|
420
|
+
if (path.type === "literal") {
|
|
421
|
+
step.parameters = [path.value];
|
|
422
|
+
} else {
|
|
423
|
+
step.dataSource = path.dataSource;
|
|
424
|
+
step.dataKey = path.dataKey;
|
|
425
|
+
step.parameters = [toVariableName(path.dataKey)];
|
|
426
|
+
}
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
397
429
|
default:
|
|
398
430
|
return; // Skip if no matching method
|
|
399
431
|
}
|
|
@@ -521,9 +553,9 @@ const invertCodeToCommand = (codeString, elements = {}, stepParams, stepsDefinit
|
|
|
521
553
|
if (propName === "web" || propName === "stable") {
|
|
522
554
|
const step = invertStableCommand(call, elements, stepParams);
|
|
523
555
|
if (step) steps.push(step);
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
556
|
+
} else if (propName === "api") {
|
|
557
|
+
const step = invertApiCommand(stepsDefinitions, codePage, stepName);
|
|
558
|
+
if (step) steps.push(step);
|
|
527
559
|
} else {
|
|
528
560
|
return;
|
|
529
561
|
}
|
|
@@ -58,6 +58,9 @@ for (let i = 0; i < scenarioReport.stepsProgress.length; i++) {
|
|
|
58
58
|
page.addCucumberStep(keyword, step.cucumberLine, methodName, step.recording.steps.length);
|
|
59
59
|
|
|
60
60
|
page.removeUnusedElements();
|
|
61
|
+
if (generateCodeResult.locatorsMetadata) {
|
|
62
|
+
page.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
|
|
63
|
+
}
|
|
61
64
|
await page.save();
|
|
62
65
|
set.add(page.sourceFileName);
|
|
63
66
|
}
|
|
@@ -6,6 +6,7 @@ import logger from "../../logger.js";
|
|
|
6
6
|
import { convertToIdentifier } from "./utils.js";
|
|
7
7
|
import prettier from "prettier";
|
|
8
8
|
import url from "url";
|
|
9
|
+
import { getDefaultPrettierConfig } from "../code_cleanup/utils.js";
|
|
9
10
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
10
11
|
const CodeStatus = {
|
|
11
12
|
ADD: "add",
|
|
@@ -22,6 +23,21 @@ function unescapeFromComment(text) {
|
|
|
22
23
|
.replace(/\*\\/g, "*/") // Unescape comment-closing sequence
|
|
23
24
|
.replace(/\\\\/g, "\\"); // Unescape backslashes
|
|
24
25
|
}
|
|
26
|
+
let ai_config = null;
|
|
27
|
+
export function getAiConfig() {
|
|
28
|
+
if (ai_config) {
|
|
29
|
+
return ai_config;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
ai_config = JSON.parse(readFileSync("ai_config.json", "utf8"));
|
|
33
|
+
} catch (e) {
|
|
34
|
+
ai_config = {};
|
|
35
|
+
}
|
|
36
|
+
if (!ai_config.locatorsMetadataDir) {
|
|
37
|
+
ai_config.locatorsMetadataDir = "features/step_definitions/locators";
|
|
38
|
+
}
|
|
39
|
+
return ai_config;
|
|
40
|
+
}
|
|
25
41
|
class CodePage {
|
|
26
42
|
constructor(sourceFileName = null) {
|
|
27
43
|
this.sourceFileName = sourceFileName;
|
|
@@ -45,18 +61,7 @@ class CodePage {
|
|
|
45
61
|
if (this.sourceFileName !== null) {
|
|
46
62
|
// format the code before saving
|
|
47
63
|
try {
|
|
48
|
-
const fileContentNew = await prettier.format(this.fileContent,
|
|
49
|
-
parser: "babel",
|
|
50
|
-
trailingComma: "es5",
|
|
51
|
-
tabWidth: 2,
|
|
52
|
-
semi: true,
|
|
53
|
-
singleQuote: false,
|
|
54
|
-
bracketSpacing: true,
|
|
55
|
-
arrowParens: "always",
|
|
56
|
-
embeddedLanguageFormatting: "auto",
|
|
57
|
-
endOfLine: "lf",
|
|
58
|
-
printWidth: 120,
|
|
59
|
-
});
|
|
64
|
+
const fileContentNew = await prettier.format(this.fileContent, getDefaultPrettierConfig());
|
|
60
65
|
this._init();
|
|
61
66
|
this.generateModel(fileContentNew);
|
|
62
67
|
} catch (e) {
|
|
@@ -224,25 +229,28 @@ this.imports[2].node.source.value
|
|
|
224
229
|
params = paramsObj.map((param) => param.name);
|
|
225
230
|
}
|
|
226
231
|
firstFind = false;
|
|
227
|
-
}
|
|
232
|
+
}
|
|
228
233
|
stepPaths.push(method.path);
|
|
229
|
-
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
236
|
if (foundMethod) {
|
|
233
|
-
templates.push({ pattern, methodName, params, stepType, paths: stepPaths});
|
|
237
|
+
templates.push({ pattern, methodName, params, stepType, paths: stepPaths });
|
|
234
238
|
}
|
|
235
239
|
}
|
|
236
240
|
}
|
|
237
241
|
return templates;
|
|
238
242
|
}
|
|
239
|
-
getExpectedTimeout(expectedNumofCmds) {
|
|
243
|
+
getExpectedTimeout(expectedNumofCmds, finalTimeout) {
|
|
244
|
+
const timeoutNum = parseFloat(finalTimeout);
|
|
245
|
+
if (finalTimeout && !isNaN(timeoutNum)) {
|
|
246
|
+
return -1;
|
|
247
|
+
}
|
|
240
248
|
return expectedNumofCmds * 60 * 1000;
|
|
241
249
|
}
|
|
242
|
-
addCucumberStep(type, cucumberLine, method, expectedNumofCmds) {
|
|
250
|
+
addCucumberStep(type, cucumberLine, method, expectedNumofCmds, finalTimeout) {
|
|
243
251
|
const result = {};
|
|
244
252
|
let code = "\n";
|
|
245
|
-
code += `${type}(${JSON.stringify(cucumberLine)}, ${expectedNumofCmds ? `{ timeout: ${this.getExpectedTimeout(expectedNumofCmds)}}, ` : ""}${method});\n`;
|
|
253
|
+
code += `${type}(${JSON.stringify(cucumberLine)}, ${expectedNumofCmds ? `{ timeout: ${this.getExpectedTimeout(expectedNumofCmds, finalTimeout)}}, ` : ""}${method});\n`;
|
|
246
254
|
let existCodePart = null;
|
|
247
255
|
for (let i = 0; i < this.cucumberCalls.length; i++) {
|
|
248
256
|
if (
|
|
@@ -481,7 +489,16 @@ this.imports[2].node.source.value
|
|
|
481
489
|
}
|
|
482
490
|
addLocatorsMetadata(locatorsMetadata) {
|
|
483
491
|
// create a file name based on the source file name replace .mjs with .json
|
|
484
|
-
|
|
492
|
+
let locatorsMetadataFileName = this.sourceFileName.replace(".mjs", ".json");
|
|
493
|
+
const config = getAiConfig();
|
|
494
|
+
if (config && config.locatorsMetadataDir) {
|
|
495
|
+
// if config.locatorsMetadataDir is set, use it to create the file path
|
|
496
|
+
locatorsMetadataFileName = path.join(config.locatorsMetadataDir, path.basename(locatorsMetadataFileName));
|
|
497
|
+
// check if the directory exists, if not create it
|
|
498
|
+
if (!existsSync(path.dirname(locatorsMetadataFileName))) {
|
|
499
|
+
mkdirSync(path.dirname(locatorsMetadataFileName), { recursive: true });
|
|
500
|
+
}
|
|
501
|
+
}
|
|
485
502
|
let metadata = {};
|
|
486
503
|
// try to read the file to metadata, protect with try catch
|
|
487
504
|
try {
|
|
@@ -782,7 +799,7 @@ function getPath(comment) {
|
|
|
782
799
|
if (index === -1) {
|
|
783
800
|
return null;
|
|
784
801
|
}
|
|
785
|
-
return comment.substring(index).split(
|
|
802
|
+
return comment.substring(index).split("\n")[0].substring(6);
|
|
786
803
|
}
|
|
787
804
|
|
|
788
805
|
class CodePart {
|