@dev-blinq/cucumber_client 1.0.1253-dev → 1.0.1253-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.
Files changed (42) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +220 -0
  2. package/bin/assets/preload/recorderv3.js +4 -2
  3. package/bin/assets/scripts/aria_snapshot.js +235 -0
  4. package/bin/assets/scripts/dom_attr.js +372 -0
  5. package/bin/assets/scripts/dom_element.js +0 -0
  6. package/bin/assets/scripts/dom_parent.js +185 -0
  7. package/bin/assets/scripts/event_utils.js +105 -0
  8. package/bin/assets/scripts/pw.js +7886 -0
  9. package/bin/assets/scripts/recorder.js +1147 -0
  10. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  11. package/bin/assets/scripts/unique_locators.js +852 -0
  12. package/bin/assets/scripts/yaml.js +4770 -0
  13. package/bin/assets/templates/_hooks_template.txt +37 -0
  14. package/bin/assets/templates/page_template.txt +2 -16
  15. package/bin/assets/templates/utils_template.txt +44 -71
  16. package/bin/client/apiTest/apiTest.js +6 -0
  17. package/bin/client/cli_helpers.js +11 -13
  18. package/bin/client/code_cleanup/utils.js +5 -1
  19. package/bin/client/code_gen/code_inversion.js +61 -4
  20. package/bin/client/code_gen/page_reflection.js +10 -3
  21. package/bin/client/code_gen/playwright_codeget.js +55 -16
  22. package/bin/client/cucumber/feature.js +89 -27
  23. package/bin/client/cucumber/project_to_document.js +1 -1
  24. package/bin/client/cucumber/steps_definitions.js +84 -76
  25. package/bin/client/cucumber_selector.js +13 -1
  26. package/bin/client/local_agent.js +3 -3
  27. package/bin/client/project.js +7 -1
  28. package/bin/client/recorderv3/bvt_recorder.js +298 -123
  29. package/bin/client/recorderv3/implemented_steps.js +74 -16
  30. package/bin/client/recorderv3/index.js +47 -25
  31. package/bin/client/recorderv3/network.js +299 -0
  32. package/bin/client/recorderv3/services.js +4 -16
  33. package/bin/client/recorderv3/step_runner.js +326 -67
  34. package/bin/client/recorderv3/step_utils.js +152 -5
  35. package/bin/client/recorderv3/update_feature.js +66 -34
  36. package/bin/client/recording.js +3 -0
  37. package/bin/client/run_cucumber.js +5 -1
  38. package/bin/client/scenario_report.js +0 -5
  39. package/bin/client/test_scenario.js +0 -1
  40. package/bin/client/utils/socket_logger.js +132 -0
  41. package/bin/index.js +1 -0
  42. package/package.json +17 -9
@@ -0,0 +1,37 @@
1
+ import {
2
+ After,
3
+ setDefaultTimeout,
4
+ Before,
5
+ AfterStep,
6
+ BeforeStep,
7
+ } from "@dev-blinq/cucumber-js";
8
+ import { closeContext, initContext, navigate, executeBrunoRequest, verifyFileExists } from "automation_model";
9
+ setDefaultTimeout(60 * 1000);
10
+
11
+ const url = null;
12
+
13
+ const elements = {};
14
+
15
+ let context = null;
16
+ Before(async function (scenario) {
17
+ context = await initContext(url, false, false, this);
18
+ await navigate(url);
19
+ await context.web.beforeScenario(this, scenario);
20
+ });
21
+ After(async function (scenario) {
22
+ await context.web.afterScenario(this, scenario);
23
+ await closeContext();
24
+ context = null;
25
+ });
26
+
27
+ BeforeStep(async function (step) {
28
+ if (context) {
29
+ await context.web.beforeStep(this, step);
30
+ }
31
+ });
32
+
33
+ AfterStep(async function (step) {
34
+ if (context) {
35
+ await context.web.afterStep(this, step);
36
+ }
37
+ });
@@ -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
- });
@@ -2,55 +2,10 @@ import {
2
2
  Given,
3
3
  When,
4
4
  Then,
5
- After,
6
- setDefaultTimeout,
7
- Before,
8
5
  defineStep,
9
- AfterStep,
10
- BeforeStep,
11
6
  } from "@dev-blinq/cucumber-js";
12
- import { closeContext, initContext, navigate, executeBrunoRequest, verifyFileExists } from "automation_model";
7
+ import { closeContext, initContext, navigate, executeBrunoRequest, verifyFileExists, TestContext as context } from "automation_model";
13
8
  import path from "path";
14
- setDefaultTimeout(60 * 1000);
15
-
16
- const url = null;
17
-
18
- const elements = {};
19
-
20
- let context = null;
21
- Before(async function (scenario) {
22
- if (!context) {
23
- context = await initContext(url, false, false, this);
24
- }
25
- await navigate(url);
26
- await context.web.beforeScenario(this, scenario);
27
- });
28
- After(async function (scenario) {
29
- await context.web.afterScenario(this, scenario);
30
- await closeContext();
31
- context = null;
32
- });
33
-
34
- BeforeStep(async function (step) {
35
- if (context) {
36
- await context.web.beforeStep(this, step);
37
- }
38
- });
39
-
40
- AfterStep(async function (step) {
41
- if (context) {
42
- await context.web.afterStep(this, step);
43
- }
44
- });
45
-
46
- /**
47
- * Load test data for a user
48
- * @param {string} user name of the user to load test data for
49
- * @protect
50
- */
51
- async function loadUserData(user) {
52
- await context.web.loadTestDataAsync("users", user, this);
53
- }
54
9
 
55
10
  /**
56
11
  * Verify text exsits in page
@@ -60,7 +15,6 @@ async function loadUserData(user) {
60
15
  async function verifyTextExistsInPage(text) {
61
16
  await context.web.verifyTextExistInPage(text, null, this);
62
17
  }
63
-
64
18
  Then("Verify the text {string} can be found in the page", verifyTextExistsInPage);
65
19
 
66
20
  /**
@@ -87,6 +41,7 @@ async function fillElement(elementDescription, value) {
87
41
  }
88
42
  When("fill {string} with {string}", fillElement);
89
43
  When("Fill {string} with {string}", fillElement);
44
+
90
45
  /**
91
46
  * Verify text does not exist in page
92
47
  * @param {string} text the text to verify does not exist in page
@@ -107,6 +62,24 @@ async function navigateTo(url) {
107
62
  }
108
63
  When("Navigate to {string}", navigateTo);
109
64
 
65
+ /**
66
+ * Navigate to the current page
67
+ * @protect
68
+ */
69
+ async function browserNavigateBack() {
70
+ await context.web.goBack({}, this);
71
+ }
72
+ Then("Browser navigate back", browserNavigateBack);
73
+
74
+ /**
75
+ * Navigate forward in browser history
76
+ * @protect
77
+ */
78
+ async function browserNavigateForward() {
79
+ await context.web.goForward({}, this);
80
+ }
81
+ Then("Browser navigate forward", browserNavigateForward);
82
+
110
83
  /**
111
84
  * Store browser session "<path>"
112
85
  * @param {string} filePath the file path or empty to store in the test data file
@@ -141,6 +114,7 @@ Then(
141
114
  "Identify the text {string}, climb {string} levels in the page, validate text {string} can be found in the context",
142
115
  verifyTextRelatedToText
143
116
  );
117
+
144
118
  /**
145
119
  * execute bruno single request given the bruno project is placed in a folder called bruno under the root of the cucumber project
146
120
  * @requestName the name of the bruno request file
@@ -149,7 +123,6 @@ Then(
149
123
  async function runBrunoRequest(requestName) {
150
124
  await executeBrunoRequest(requestName, {}, context, this);
151
125
  }
152
-
153
126
  When("Bruno - {string}", runBrunoRequest);
154
127
  When("bruno - {string}", runBrunoRequest);
155
128
 
@@ -163,41 +136,41 @@ async function verify_the_downloaded_file_exists(fileName) {
163
136
  const downloadFile = path.join(downloadFolder, fileName);
164
137
  await verifyFileExists(downloadFile, {}, context, this);
165
138
  }
166
-
167
139
  Then("Verify the file {string} exists", { timeout: 60000 }, verify_the_downloaded_file_exists);
168
140
 
169
- When("Noop", async function(){})
170
-
171
141
  /**
172
- * Verify the page url is "<url>"
173
- * @param {string} url URL to be verified against current URL
174
- * @protect
175
- */
142
+ * Noop step for running only hooks
143
+ */
144
+ When("Noop", async function () {});
176
145
 
177
- async function verify_page_url(url){
178
- await context.web.verifyPagePath(url,{}, this);
146
+ /**
147
+ * Verify the page url is "<url>"
148
+ * @param {string} url URL to be verified against current URL
149
+ * @protect
150
+ */
151
+ async function verify_page_url(url) {
152
+ await context.web.verifyPagePath(url, {}, this);
179
153
  }
180
154
  Then("Verify the page url is {string}", verify_page_url);
181
155
 
182
156
  /**
183
- * Verify the page title is "<title>"
184
- * @param {string} title Title to be verified against current Title
185
- * @protect
186
- */
187
- async function verify_page_title(title){
157
+ * Verify the page title is "<title>"
158
+ * @param {string} title Title to be verified against current Title
159
+ * @protect
160
+ */
161
+ async function verify_page_title(title) {
188
162
  await context.web.verifyPageTitle(title, {}, this);
189
163
  }
190
- Then("Verify the page title is {string}",verify_page_title)
164
+ Then("Verify the page title is {string}", verify_page_title);
191
165
 
192
166
  /**
193
- * Explicit wait/sleep function that pauses execution for a specified duration
194
- * @param {duration} - Duration to sleep in milliseconds (default: 1000ms)
195
- * @param {options} - Optional configuration object
196
- * @param {world} - Optional world context
197
- * @returns Promise that resolves after the specified duration
198
- */
167
+ * Explicit wait/sleep function that pauses execution for a specified duration
168
+ * @param {duration} - Duration to sleep in milliseconds (default: 1000ms)
169
+ * @param {options} - Optional configuration object
170
+ * @param {world} - Optional world context
171
+ * @returns Promise that resolves after the specified duration
172
+ */
199
173
  async function sleep(duration) {
200
174
  await context.web.sleep(duration, {}, null);
201
175
  }
202
-
203
- Then("Sleep for {string} ms", {timeout: -1}, sleep);
176
+ Then("Sleep for {string} ms", { timeout: -1 }, sleep);
@@ -75,6 +75,12 @@ class ApiTest {
75
75
  const utilsTemplateFilePath = path.join(__dirname, "../../assets", "templates", "utils_template.txt");
76
76
  const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
77
77
  writeFileSync(utilsFilePath, utilsContent, "utf8");
78
+ const hooksTemplateFilePath = path.join(__dirname, "../../assets", "templates", "_hooks_template.txt");
79
+ if (existsSync(hooksTemplateFilePath)) {
80
+ const hooksFilePath = path.join(stepDefinitionFolderPath, "_hooks.mjs");
81
+ const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
82
+ writeFileSync(hooksFilePath, hooksContent, "utf8");
83
+ }
78
84
  } catch (error) {
79
85
  console.log("error", error);
80
86
  }
@@ -1,17 +1,15 @@
1
-
2
1
  const validateCLIArg = (arg, name) => {
3
2
  if (!arg) {
4
- throw new Error(`${name} is required`)
3
+ throw new Error(`${name} is required`);
5
4
  }
6
- }
5
+ };
7
6
  const showUsage = (error, usage) => {
8
- console.error(error.message)
9
- console.info(usage)
10
- process.exit(1)
11
- }
12
- const loadArgs = ()=>{
13
- const args = process.argv.slice(2)
14
- return args
15
- }
16
-
17
- export { validateCLIArg, loadArgs, showUsage };
7
+ console.error(error.message);
8
+ console.info(usage);
9
+ process.exit(1);
10
+ };
11
+ const loadArgs = () => {
12
+ const args = process.argv.slice(2);
13
+ return args;
14
+ };
15
+ export { validateCLIArg, loadArgs, showUsage };
@@ -7,10 +7,11 @@ const { default: traverse } = pkg;
7
7
  import pkg1 from "@babel/generator";
8
8
  const { default: generate } = pkg1;
9
9
  import * as t from "@babel/types";
10
-
11
10
  import { CucumberExpression, ParameterTypeRegistry } from "@cucumber/cucumber-expressions";
12
11
  import { existsSync, readFileSync, writeFileSync } from "fs";
13
12
  import { getAiConfig } from "../code_gen/page_reflection.js";
13
+ import socketLogger from "../utils/socket_logger.js";
14
+
14
15
  const STEP_KEYWORDS = new Set(["Given", "When", "Then"]);
15
16
 
16
17
  /**
@@ -307,14 +308,17 @@ export function getDefaultPrettierConfig() {
307
308
  const configContent = readFileSync(prettierConfigPath, "utf-8");
308
309
  prettierConfig = JSON.parse(configContent);
309
310
  } catch (error) {
311
+ socketLogger.error("Error parsing Prettier config file", error);
310
312
  console.error(`Error parsing Prettier config file: ${error}`);
311
313
  }
312
314
  } else {
313
315
  // save the default config to .prettierrc
314
316
  try {
315
317
  writeFileSync(prettierConfigPath, JSON.stringify(prettierConfig, null, 2), "utf-8");
318
+ socketLogger.info(`Created default Prettier config at ${prettierConfigPath}`);
316
319
  console.log(`Created default Prettier config at ${prettierConfigPath}`);
317
320
  } catch (error) {
321
+ socketLogger.error(`Error writing Prettier config file: ${error}`);
318
322
  console.error(`Error writing Prettier config file: ${error}`);
319
323
  }
320
324
  }
@@ -310,6 +310,14 @@ const invertStableCommand = (call, elements, stepParams) => {
310
310
  break;
311
311
  }
312
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
+
313
321
  case "reloadPage":
314
322
  step.type = Types.RELOAD;
315
323
  break;
@@ -366,10 +374,15 @@ const invertStableCommand = (call, elements, stepParams) => {
366
374
  }
367
375
  case "verifyTextRelatedToText": {
368
376
  step.type = Types.VERIFY_TEXT_RELATED_TO_TEXT;
369
- const textAnchor = parseDataSource(call.arguments[0], stepParams);
370
- const climb = parseDataSource(call.arguments[1], stepParams);
371
- const textToVerify = parseDataSource(call.arguments[2], stepParams);
372
- step.parameters = [textAnchor.value, +climb.value, textToVerify.value];
377
+ const textAnchorParse = parseDataSource(call.arguments[0], stepParams);
378
+ const textAnchor =
379
+ textAnchorParse.type === "literal" ? textAnchorParse.value : toVariableName(textAnchorParse.dataKey);
380
+ const climbParse = parseDataSource(call.arguments[1], stepParams);
381
+ const climb = climbParse.type === "literal" ? climbParse.value : toVariableName(climbParse.dataKey);
382
+ const textToVerifyParse = parseDataSource(call.arguments[2], stepParams);
383
+ const textToVerify =
384
+ textToVerifyParse.type === "literal" ? textToVerifyParse.value : toVariableName(textToVerifyParse.dataKey);
385
+ step.parameters = [textAnchor, climb, textToVerify];
373
386
  break;
374
387
  }
375
388
  case "setInputFiles": {
@@ -418,6 +431,50 @@ const invertStableCommand = (call, elements, stepParams) => {
418
431
  }
419
432
  break;
420
433
  }
434
+ case "verifyProperty": {
435
+ step.type = Types.VERIFY_PROPERTY;
436
+ step.element = extractElement(call.arguments[0]);
437
+ const property = call.arguments[1].value;
438
+ const value = parseDataSource(call.arguments[2], stepParams);
439
+ if (value.type === "literal") {
440
+ step.parameters = [property, value.value];
441
+ } else {
442
+ step.dataSource = value.dataSource;
443
+ step.dataKey = value.dataKey;
444
+ step.parameters = [property, toVariableName(value.dataKey)];
445
+ }
446
+ break;
447
+ }
448
+ case "extractProperty": {
449
+ step.type = Types.EXTRACT_PROPERTY;
450
+ step.element = extractElement(call.arguments[0]);
451
+ const attribute = call.arguments[1].value;
452
+ const variable = parseDataSource(call.arguments[2], stepParams);
453
+ if (variable.type === "literal") {
454
+ step.parameters = [attribute, variable.value];
455
+ } else {
456
+ step.dataSource = variable.dataSource;
457
+ step.dataKey = variable.dataKey;
458
+ step.parameters = [attribute, toVariableName(variable.dataKey)];
459
+ }
460
+ if (call.arguments[4].type === "ObjectExpression") {
461
+ const properties = call.arguments[4].properties;
462
+ const regexProp = properties.findIndex((prop) => prop.key.name === "regex");
463
+ if (regexProp !== -1) {
464
+ const regex = properties[regexProp].value.value;
465
+ step.regex = regex;
466
+ }
467
+ const trimSpacesProp = properties.findIndex((prop) => prop.key.name === "trimSpaces");
468
+ if (trimSpacesProp !== -1) {
469
+ const trimSpaces = properties[trimSpacesProp].value.value;
470
+ step.trimSpaces = trimSpaces;
471
+ }
472
+ } else {
473
+ step.regex = "";
474
+ step.trimSpaces = false;
475
+ }
476
+ break;
477
+ }
421
478
  default:
422
479
  return; // Skip if no matching method
423
480
  }
@@ -33,6 +33,9 @@ export function getAiConfig() {
33
33
  } catch (e) {
34
34
  ai_config = {};
35
35
  }
36
+ if (!ai_config.locatorsMetadataDir) {
37
+ ai_config.locatorsMetadataDir = "features/step_definitions/locators";
38
+ }
36
39
  return ai_config;
37
40
  }
38
41
  class CodePage {
@@ -237,13 +240,17 @@ this.imports[2].node.source.value
237
240
  }
238
241
  return templates;
239
242
  }
240
- getExpectedTimeout(expectedNumofCmds) {
243
+ getExpectedTimeout(expectedNumofCmds, finalTimeout) {
244
+ const timeoutNum = parseFloat(finalTimeout);
245
+ if (finalTimeout && !isNaN(timeoutNum)) {
246
+ return -1;
247
+ }
241
248
  return expectedNumofCmds * 60 * 1000;
242
249
  }
243
- addCucumberStep(type, cucumberLine, method, expectedNumofCmds) {
250
+ addCucumberStep(type, cucumberLine, method, expectedNumofCmds, finalTimeout) {
244
251
  const result = {};
245
252
  let code = "\n";
246
- 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`;
247
254
  let existCodePart = null;
248
255
  for (let i = 0; i < this.cucumberCalls.length; i++) {
249
256
  if (
@@ -4,6 +4,7 @@ import { StepsDefinitions } from "../cucumber/steps_definitions.js";
4
4
  import path from "path";
5
5
  import { CodePage } from "./page_reflection.js";
6
6
  import { convertToIdentifier, escapeNonPrintables } from "./utils.js";
7
+ import socketLogger from "../utils/socket_logger.js";
7
8
  const findElementIdentifier = (node, step, userData, elements) => {
8
9
  if (node.key) {
9
10
  // incase of rerunning implemented steps
@@ -12,6 +13,13 @@ const findElementIdentifier = (node, step, userData, elements) => {
12
13
 
13
14
  let elementIdentifier = null;
14
15
  const keys = Object.keys(elements);
16
+
17
+ for (let i = 0; i < keys.length; i++) {
18
+ const element_key = keys[i];
19
+ const element = elements[element_key];
20
+ element["element_key"] = element_key;
21
+ }
22
+
15
23
  let name = node.name;
16
24
  if (!name && step.dataKey) {
17
25
  name = step.dataKey;
@@ -58,7 +66,8 @@ const _isCodeGenerationStep = (step) => {
58
66
  step.type === Types.VERIFY_ATTRIBUTE ||
59
67
  step.type === Types.VERIFY_PROPERTY ||
60
68
  step.type === Types.SET_INPUT_FILES ||
61
- step.type === Types.VERIFY_PAGE_SNAPSHOT
69
+ step.type === Types.VERIFY_PAGE_SNAPSHOT ||
70
+ step.type === Types.CONDITIONAL_WAIT
62
71
  ) {
63
72
  return true;
64
73
  }
@@ -91,7 +100,7 @@ const splitToLocatorsGroups = (locators) => {
91
100
  const _generateCodeFromCommand = (step, elements, userData) => {
92
101
  let elementsChanged = false;
93
102
 
94
- let elementIdentifier = null;
103
+ let elementIdentifier = step.locators?.element_key;
95
104
  let locatorObject = step.locators;
96
105
  // handle element
97
106
  let element_name = null;
@@ -129,18 +138,20 @@ const _generateCodeFromCommand = (step, elements, userData) => {
129
138
  let locatorObjectClone = JSON.parse(JSON.stringify(locatorObject));
130
139
  element_name = locatorObjectClone.element_name;
131
140
  delete locatorObjectClone.element_name;
132
- keys.forEach((key) => {
133
- let elementClone = JSON.parse(JSON.stringify(elements[key]));
134
- delete elementClone.element_name;
135
- if (JSON.stringify(elementClone) === JSON.stringify(locatorObjectClone)) {
136
- elementIdentifier = key;
137
- }
138
- });
141
+ if (!elementIdentifier) {
142
+ keys.forEach((key) => {
143
+ let elementClone = JSON.parse(JSON.stringify(elements[key]));
144
+ delete elementClone.element_name;
145
+ if (JSON.stringify(elementClone) === JSON.stringify(locatorObjectClone)) {
146
+ elementIdentifier = key;
147
+ }
148
+ });
149
+ }
139
150
  if (!elementIdentifier) {
140
151
  elementIdentifier = findElementIdentifier(node, step, userData, elements);
141
152
  }
142
153
  const withoutAllStrategyLocators = excludeKey(locatorObject, "allStrategyLocators");
143
- elements[elementIdentifier] = withoutAllStrategyLocators;
154
+ elements[elementIdentifier] = { ...withoutAllStrategyLocators, element_key: elementIdentifier };
144
155
  elementsChanged = true;
145
156
  }
146
157
  let optionElement = null;
@@ -578,7 +589,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
578
589
  case 1:
579
590
  comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
580
591
  codeLines.push(escapeNonPrintables(comment));
581
- input = `"${escapeNonPrintables(step.value)}"`;
592
+ input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
582
593
  if (step.dataSource === "userData" || step.dataSource === "parameters") {
583
594
  input = "_" + step.dataKey;
584
595
  }
@@ -588,7 +599,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
588
599
  case 2:
589
600
  comment = `// Double click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
590
601
  codeLines.push(escapeNonPrintables(comment));
591
- input = `"${escapeNonPrintables(step.value)}"`;
602
+ input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
592
603
  if (step.dataSource === "userData" || step.dataSource === "parameters") {
593
604
  input = "_" + step.dataKey;
594
605
  }
@@ -598,7 +609,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
598
609
  default:
599
610
  comment = `// Click on ${elementIdentifier ? elementIdentifier : step.label} in the context of ${escapeNonPrintables(step.value)}`;
600
611
  codeLines.push(escapeNonPrintables(comment));
601
- input = `"${escapeNonPrintables(step.value)}"`;
612
+ input = `"${escapeNonPrintables(step.value.replaceAll('"', '\\"'))}"`;
602
613
  if (step.dataSource === "userData" || step.dataSource === "parameters") {
603
614
  input = "_" + step.dataKey;
604
615
  }
@@ -641,6 +652,13 @@ const _generateCodeFromCommand = (step, elements, userData) => {
641
652
  codeLines.push(line);
642
653
  break;
643
654
  }
655
+ case Types.CONDITIONAL_WAIT: {
656
+ line = `await context.web.conditionalWait(elements["${elementIdentifier}"], `;
657
+ input = "_param_0";
658
+ line += `"${step.parameters[1]}", ${input}, _params, null, this);`;
659
+ codeLines.push(line);
660
+ break;
661
+ }
644
662
  case Types.SET_INPUT_FILES: {
645
663
  line = `await context.web.setInputFiles(elements["${elementIdentifier}"], `;
646
664
  let files = step.parameters[0];
@@ -666,6 +684,22 @@ const _generateCodeFromCommand = (step, elements, userData) => {
666
684
  return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
667
685
  };
668
686
 
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
+
669
703
  const generateCode = (recording, codePage, userData, projectDir, methodName) => {
670
704
  const stepsDefinitions = new StepsDefinitions(projectDir);
671
705
  stepsDefinitions.load(false);
@@ -692,6 +726,7 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
692
726
  }
693
727
  let elements = {};
694
728
  if (!codePage) {
729
+ socketLogger.info("CodePage is null");
695
730
  console.log("codePage is null");
696
731
  } else {
697
732
  elements = codePage.getVariableDeclarationAsObject("elements");
@@ -706,12 +741,16 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
706
741
  if (recordingStep.type === Types.COMPLETE) {
707
742
  return;
708
743
  }
709
- // if (!recordingStep.element) {
710
- // logger.info(`Step ${recordingStep.type} doesn't have an element, ignoring`);
711
- // return;
744
+
745
+ // if (process.env.TEMP_RUN === "true") {
746
+ // codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
712
747
  // }
713
748
  const result = _generateCodeFromCommand(recordingStep, elements, userData);
749
+
714
750
  codeLines.push(...result.codeLines);
751
+ // if (process.env.TEMP_RUN === "true") {
752
+ // codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
753
+ // }
715
754
  if (result.elements) {
716
755
  elements = result.elements;
717
756
  }