@dev-blinq/cucumber_client 1.0.1364-dev → 1.0.1364-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 (48) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +107 -107
  2. package/bin/assets/preload/css_gen.js +10 -10
  3. package/bin/assets/preload/toolbar.js +27 -29
  4. package/bin/assets/preload/unique_locators.js +1 -1
  5. package/bin/assets/preload/yaml.js +288 -275
  6. package/bin/assets/scripts/aria_snapshot.js +223 -220
  7. package/bin/assets/scripts/dom_attr.js +329 -329
  8. package/bin/assets/scripts/dom_parent.js +169 -174
  9. package/bin/assets/scripts/event_utils.js +94 -94
  10. package/bin/assets/scripts/pw.js +2050 -1949
  11. package/bin/assets/scripts/recorder.js +13 -23
  12. package/bin/assets/scripts/snapshot_capturer.js +147 -147
  13. package/bin/assets/scripts/unique_locators.js +163 -44
  14. package/bin/assets/scripts/yaml.js +796 -783
  15. package/bin/assets/templates/_hooks_template.txt +41 -0
  16. package/bin/assets/templates/utils_template.txt +2 -45
  17. package/bin/client/apiTest/apiTest.js +6 -0
  18. package/bin/client/code_cleanup/utils.js +5 -1
  19. package/bin/client/code_gen/api_codegen.js +2 -2
  20. package/bin/client/code_gen/code_inversion.js +107 -2
  21. package/bin/client/code_gen/function_signature.js +4 -0
  22. package/bin/client/code_gen/page_reflection.js +846 -906
  23. package/bin/client/code_gen/playwright_codeget.js +45 -12
  24. package/bin/client/cucumber/feature.js +4 -0
  25. package/bin/client/cucumber/feature_data.js +2 -2
  26. package/bin/client/cucumber/project_to_document.js +9 -3
  27. package/bin/client/cucumber/steps_definitions.js +6 -3
  28. package/bin/client/cucumber_selector.js +17 -1
  29. package/bin/client/local_agent.js +4 -3
  30. package/bin/client/parse_feature_file.js +23 -26
  31. package/bin/client/playground/projects/env.json +2 -2
  32. package/bin/client/project.js +186 -196
  33. package/bin/client/recorderv3/bvt_recorder.js +238 -95
  34. package/bin/client/recorderv3/implemented_steps.js +8 -0
  35. package/bin/client/recorderv3/index.js +59 -54
  36. package/bin/client/recorderv3/scriptTest.js +1 -1
  37. package/bin/client/recorderv3/services.js +66 -16
  38. package/bin/client/recorderv3/step_runner.js +315 -205
  39. package/bin/client/recorderv3/step_utils.js +476 -16
  40. package/bin/client/recorderv3/update_feature.js +9 -5
  41. package/bin/client/recording.js +1 -0
  42. package/bin/client/upload-service.js +3 -2
  43. package/bin/client/utils/socket_logger.js +132 -0
  44. package/bin/index.js +4 -0
  45. package/bin/logger.js +3 -2
  46. package/bin/min/consoleApi.min.cjs +2 -3
  47. package/bin/min/injectedScript.min.cjs +16 -16
  48. package/package.json +20 -10
@@ -4,6 +4,9 @@ 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";
8
+ import fs from "fs";
9
+
7
10
  const findElementIdentifier = (node, step, userData, elements) => {
8
11
  if (node.key) {
9
12
  // incase of rerunning implemented steps
@@ -12,6 +15,13 @@ const findElementIdentifier = (node, step, userData, elements) => {
12
15
 
13
16
  let elementIdentifier = null;
14
17
  const keys = Object.keys(elements);
18
+
19
+ for (let i = 0; i < keys.length; i++) {
20
+ const element_key = keys[i];
21
+ const element = elements[element_key];
22
+ element["element_key"] = element_key;
23
+ }
24
+
15
25
  let name = node.name;
16
26
  if (!name && step.dataKey) {
17
27
  name = step.dataKey;
@@ -92,11 +102,16 @@ const splitToLocatorsGroups = (locators) => {
92
102
  const _generateCodeFromCommand = (step, elements, userData) => {
93
103
  let elementsChanged = false;
94
104
 
95
- let elementIdentifier = null;
105
+ let elementIdentifier = step.locators?.element_key;
96
106
  let locatorObject = step.locators;
97
107
  // handle element
98
108
  let element_name = null;
99
109
  let allStrategyLocators = null;
110
+ const codeLines = [];
111
+
112
+ if (_isCodeGenerationStep(step) === false)
113
+ return { codeLines, elements: elementsChanged ? elements : null, elementIdentifier, allStrategyLocators };
114
+
100
115
  if (_isCodeGenerationStep(step) && step.type !== Types.VERIFY_PAGE_SNAPSHOT) {
101
116
  allStrategyLocators = step.allStrategyLocators ?? splitToLocatorsGroups(step.locators);
102
117
  let node = null;
@@ -130,18 +145,20 @@ const _generateCodeFromCommand = (step, elements, userData) => {
130
145
  let locatorObjectClone = JSON.parse(JSON.stringify(locatorObject));
131
146
  element_name = locatorObjectClone.element_name;
132
147
  delete locatorObjectClone.element_name;
133
- keys.forEach((key) => {
134
- let elementClone = JSON.parse(JSON.stringify(elements[key]));
135
- delete elementClone.element_name;
136
- if (JSON.stringify(elementClone) === JSON.stringify(locatorObjectClone)) {
137
- elementIdentifier = key;
138
- }
139
- });
148
+ if (!elementIdentifier) {
149
+ keys.forEach((key) => {
150
+ let elementClone = JSON.parse(JSON.stringify(elements[key]));
151
+ delete elementClone.element_name;
152
+ if (JSON.stringify(elementClone) === JSON.stringify(locatorObjectClone)) {
153
+ elementIdentifier = key;
154
+ }
155
+ });
156
+ }
140
157
  if (!elementIdentifier) {
141
158
  elementIdentifier = findElementIdentifier(node, step, userData, elements);
142
159
  }
143
160
  const withoutAllStrategyLocators = excludeKey(locatorObject, "allStrategyLocators");
144
- elements[elementIdentifier] = withoutAllStrategyLocators;
161
+ elements[elementIdentifier] = { ...withoutAllStrategyLocators, element_key: elementIdentifier };
145
162
  elementsChanged = true;
146
163
  }
147
164
  let optionElement = null;
@@ -157,8 +174,6 @@ const _generateCodeFromCommand = (step, elements, userData) => {
157
174
  }
158
175
  }
159
176
 
160
- const codeLines = [];
161
-
162
177
  let line = null;
163
178
  let comment = null;
164
179
  let input = null;
@@ -353,7 +368,7 @@ const _generateCodeFromCommand = (step, elements, userData) => {
353
368
  codeLines.push(line);
354
369
  break;
355
370
  }
356
- case Types.VERIFY_PAGE_SNAPSHOT:
371
+ case Types.VERIFY_PAGE_SNAPSHOT: {
357
372
  comment = step.nestFrmLoc
358
373
  ? `// Verify page snapshot ${step.parameters[0]} in the frame ${step.selectors.iframe_src}`
359
374
  : `// Verify page snapshot ${step.parameters[0]}`;
@@ -362,7 +377,24 @@ const _generateCodeFromCommand = (step, elements, userData) => {
362
377
  codeLines.push(line);
363
378
  line = `await context.web.snapshotValidation(frameLocator, _param_0 , _params, ${JSON.stringify(options)}, this);`;
364
379
  codeLines.push(line);
380
+
381
+ const data = step.data;
382
+ if (data) {
383
+ try {
384
+ const { snapshot, fileName, filePath } = data;
385
+ const folderPath = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? filePath;
386
+ const filePathWithName = path.join(folderPath, fileName);
387
+ if (!fs.existsSync(folderPath)) {
388
+ fs.mkdirSync(folderPath, { recursive: true });
389
+ }
390
+ fs.writeFileSync(filePathWithName, snapshot, "utf-8");
391
+ } catch (e) {
392
+ console.log(`Error saving snapshot file: ${e}`);
393
+ throw e;
394
+ }
395
+ }
365
396
  break;
397
+ }
366
398
  case Types.VERIFY_PAGE_CONTAINS_TEXT:
367
399
  line = "await context.web.verifyTextExistInPage( ";
368
400
  input = `${escapeNonPrintables(JSON.stringify(step.parameters[0]))}`;
@@ -716,6 +748,7 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
716
748
  }
717
749
  let elements = {};
718
750
  if (!codePage) {
751
+ socketLogger.info("CodePage is null");
719
752
  console.log("codePage is null");
720
753
  } else {
721
754
  elements = codePage.getVariableDeclarationAsObject("elements");
@@ -8,6 +8,7 @@ import { parseStepTextParameters, toCucumberExpression, unEscapeNonPrintables }
8
8
  import stream from "stream";
9
9
  import { testStringForRegex } from "../recorderv3/update_feature.js";
10
10
  import { generateTestData } from "./feature_data.js";
11
+ import socketLogger from "../utils/socket_logger.js";
11
12
  class DataTable {
12
13
  constructor(dataTableDocument) {
13
14
  if (!dataTableDocument) {
@@ -544,6 +545,9 @@ const scenarioResolution = async (featureFilePath) => {
544
545
  fs.rmSync(tmpDir, { recursive: true });
545
546
  }
546
547
  } catch (e) {
548
+ socketLogger.error(
549
+ `An error has occurred while removing the temp folder at ${tmpDir}. Please remove it manually. Error: ${e}`
550
+ );
547
551
  console.error(
548
552
  `An error has occurred while removing the temp folder at ${tmpDir}. Please remove it manually. Error: ${e}`
549
553
  );
@@ -41,10 +41,10 @@ const generateTestData = (featureFile) => {
41
41
  try {
42
42
  const fake = faker.helpers.fake(match[0]);
43
43
 
44
- if(fake === match[0]){
44
+ if (fake === match[0]) {
45
45
  continue;
46
46
  }
47
-
47
+
48
48
  otherFakeData.push({
49
49
  var: match[0],
50
50
  fake,
@@ -4,7 +4,13 @@ import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherki
4
4
  import { Feature } from "./feature.js";
5
5
  import { StepsDefinitions } from "./steps_definitions.js";
6
6
 
7
- export const projectDocument = (folder, featureName = null, scenarioName = null, supressErrors = false, errors = []) => {
7
+ export const projectDocument = (
8
+ folder,
9
+ featureName = null,
10
+ scenarioName = null,
11
+ supressErrors = false,
12
+ errors = []
13
+ ) => {
8
14
  const uuidFn = () => (23212).toString(16);
9
15
  const builder = new AstBuilder(uuidFn);
10
16
  const matcher = new GherkinClassicTokenMatcher();
@@ -15,7 +21,7 @@ export const projectDocument = (folder, featureName = null, scenarioName = null,
15
21
  // read all the feature files in the project folder under features folder
16
22
  let featureFiles = [];
17
23
  const featuresDir = path.join(folder, "features");
18
- featureFiles = fs.readdirSync(featuresDir).filter((file) => {
24
+ featureFiles = fs.readdirSync(featuresDir, { recursive: true }).filter((file) => {
19
25
  return file.endsWith(".feature");
20
26
  });
21
27
  const documents = [];
@@ -59,4 +65,4 @@ export const projectDocument = (folder, featureName = null, scenarioName = null,
59
65
  // const documents = projectDocument(projectDir, featureName, scenarioName, true, errors);
60
66
  // const args = process.argv.slice(2);
61
67
 
62
- // console.log(JSON.stringify(documents));
68
+ // console.log(JSON.stringify(documents));
@@ -45,7 +45,10 @@ class StepsDefinitions {
45
45
  if (method && expression.keyword && pattern && expression.methodName) {
46
46
  this.steps[pattern] = {
47
47
  // file: path.join(this.isTemp ? "__temp_features" : "features", mjsFile),
48
- file: path.join(this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features", mjsFile),
48
+ file: path.join(
49
+ this.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features",
50
+ mjsFile
51
+ ),
49
52
  functionName: expression.methodName,
50
53
  name: pattern,
51
54
  source: this.findKey(method.codePart, "source"),
@@ -56,7 +59,7 @@ class StepsDefinitions {
56
59
  }
57
60
  load(print = true, supressErrors = false, errors = []) {
58
61
  const mjsFiles = findFilesWithExtension(
59
- path.join(this.baseFolder, this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features"),
62
+ path.join(this.baseFolder, this.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features"),
60
63
  "mjs"
61
64
  );
62
65
  // console.log({ mjsFiles });
@@ -64,7 +67,7 @@ class StepsDefinitions {
64
67
  const mjsFile = mjsFiles[i];
65
68
  const filePath = path.join(
66
69
  this.baseFolder,
67
- this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
70
+ this.isTemp ? (process.env.tempFeaturesFolderPath ?? "__temp_features") : "features",
68
71
  mjsFile
69
72
  );
70
73
  const codePage = new CodePage(filePath);
@@ -6,6 +6,7 @@ import crypto from "crypto";
6
6
  import { findFilesWithExtension } from "./cucumber/steps_definitions.js";
7
7
  import fs from "fs";
8
8
  //import { spawn } from "child_process";
9
+ const __dirname = path.dirname(new URL(import.meta.url).pathname);
9
10
 
10
11
  if (process.argv.length < 3) {
11
12
  console.log("Usage: node cucumber_selector.js <projectsDir> --all");
@@ -100,8 +101,8 @@ if (process.argv.includes("--scenario-name")) {
100
101
  }
101
102
  const projectName = selectedScenario.featureFile.split(path.sep)[0];
102
103
  let deleteMjsFiles = readlineSync.keyInYN("Delete mjs file?");
104
+ let stepsPath = path.join(selectedScenario.projectsDir, projectName, "features", "step_definitions");
103
105
  if (deleteMjsFiles) {
104
- let stepsPath = path.join(selectedScenario.projectsDir, projectName, "features", "step_definitions");
105
106
  if (fs.existsSync(stepsPath)) {
106
107
  // delete all the *.mjs files
107
108
  let files = fs.readdirSync(stepsPath);
@@ -112,6 +113,21 @@ if (deleteMjsFiles) {
112
113
  }
113
114
  }
114
115
  }
116
+ // copy utils file to the steps directory
117
+ const utilsPath = path.join(__dirname, "../assets/templates/utils_template.txt");
118
+ const utilsContent = fs.readFileSync(utilsPath, "utf8");
119
+ const utilsFileName = stepsPath + "/utils.mjs";
120
+ // if stepsPath directory doesn't exist create it
121
+ if (!fs.existsSync(stepsPath)) {
122
+ fs.mkdirSync(stepsPath, { recursive: true });
123
+ }
124
+ fs.writeFileSync(utilsFileName, utilsContent, "utf8");
125
+ // copy hooks file to the steps directory
126
+ const hooksTemplateFilePath = path.join(__dirname, "../assets/templates/_hooks_template.txt");
127
+ const hooksContent = fs.readFileSync(hooksTemplateFilePath, "utf8");
128
+ const hooksFilePath = path.join(stepsPath, "_hooks.mjs");
129
+ fs.writeFileSync(hooksFilePath, hooksContent, "utf8");
130
+
115
131
  process.env.PROJECT_PATH = path.join(selectedScenario.projectsDir, projectName);
116
132
  //chanks[currentChank][index];
117
133
  console.log("Running scenario:");
@@ -267,7 +267,7 @@ class LocalAgent {
267
267
  null,
268
268
  true,
269
269
  null,
270
- 450,
270
+ -1,
271
271
  null,
272
272
  initScript
273
273
  );
@@ -696,6 +696,7 @@ class LocalAgent {
696
696
  stepDiff: stepResult,
697
697
  });
698
698
  page.removeUnusedElements();
699
+ page.mergeSimilarElements();
699
700
  agent.scenarioReport.updateLastStep({
700
701
  code: {
701
702
  cleanFileNames: page.cleanFileNames,
@@ -809,12 +810,12 @@ class LocalAgent {
809
810
  } catch (err) {
810
811
  logger.error("Error reading memory file: " + err.message);
811
812
  }
812
- const screenshot = await agent.getScreenshot(agent.context);
813
+ //const screenshot = await agent.getScreenshot(agent.context);
813
814
  // check if the popup is open
814
815
  const popupResult = await agent.context.web.closeUnexpectedPopups(null, null);
815
816
  if (nodes) {
816
817
  nodes.memory = memory;
817
- nodes.screenshot = screenshot;
818
+ //nodes.screenshot = screenshot;
818
819
  if (popupResult.rerun === true) {
819
820
  nodes.rerun = true;
820
821
  }
@@ -1,37 +1,34 @@
1
- import {
2
- AstBuilder,
3
- GherkinClassicTokenMatcher,
4
- Parser,
5
- } from '@cucumber/gherkin'
6
- import { readFileSync, writeFileSync } from 'fs'
7
- import { loadArgs, showUsage, validateCLIArg } from './cli_helpers.js'
8
- let id =0;
9
- const uuidFn = () => (++id).toString(16)
10
- const builder = new AstBuilder(uuidFn)
11
- const matcher = new GherkinClassicTokenMatcher()
1
+ import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
2
+ import { readFileSync, writeFileSync } from "fs";
3
+ import { loadArgs, showUsage, validateCLIArg } from "./cli_helpers.js";
4
+ let id = 0;
5
+ const uuidFn = () => (++id).toString(16);
6
+ const builder = new AstBuilder(uuidFn);
7
+ const matcher = new GherkinClassicTokenMatcher();
12
8
 
13
- const parser = new Parser(builder, matcher)
9
+ const parser = new Parser(builder, matcher);
14
10
 
15
11
  const parseFeatureFile = (featureFilePath) => {
16
- const source = readFileSync(featureFilePath, 'utf8')
17
- const gherkinDocument = parser.parse(source)
18
- return gherkinDocument
19
- }
12
+ const source = readFileSync(featureFilePath, "utf8");
13
+ const gherkinDocument = parser.parse(source);
14
+ return gherkinDocument;
15
+ };
20
16
 
21
- const args = loadArgs()
22
- const featureFilePath = args[0]
23
- const outputFilePath = args[1]
24
- const usage = 'Usage: node parse_feature_file.js <featureFilePath> [<outputFilePath>] \n\nExample: node parseFeatureFile.js ./features/my.feature ./features/my.feature.json\n\n'
17
+ const args = loadArgs();
18
+ const featureFilePath = args[0];
19
+ const outputFilePath = args[1];
20
+ const usage =
21
+ "Usage: node parse_feature_file.js <featureFilePath> [<outputFilePath>] \n\nExample: node parseFeatureFile.js ./features/my.feature ./features/my.feature.json\n\n";
25
22
  try {
26
- validateCLIArg(featureFilePath, 'featureFilePath')
23
+ validateCLIArg(featureFilePath, "featureFilePath");
27
24
  } catch (error) {
28
- showUsage(error, usage)
25
+ showUsage(error, usage);
29
26
  }
30
- const gherkinDocument = parseFeatureFile(featureFilePath)
27
+ const gherkinDocument = parseFeatureFile(featureFilePath);
31
28
  if (outputFilePath) {
32
- writeFileSync(outputFilePath, JSON.stringify(gherkinDocument, null, 2))
29
+ writeFileSync(outputFilePath, JSON.stringify(gherkinDocument, null, 2));
33
30
  } else {
34
- console.log(JSON.stringify(gherkinDocument, null, 2))
31
+ console.log(JSON.stringify(gherkinDocument, null, 2));
35
32
  }
36
33
 
37
- export { parseFeatureFile }
34
+ export { parseFeatureFile };
@@ -1,3 +1,3 @@
1
1
  {
2
- "baseUrl": "https://google.com"
3
- }
2
+ "baseUrl": "https://google.com"
3
+ }