@dev-blinq/cucumber_client 1.0.1193-dev → 1.0.1193-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 (43) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +220 -0
  2. package/bin/assets/preload/find_context.js +1 -1
  3. package/bin/assets/preload/locators.js +18 -0
  4. package/bin/assets/preload/recorderv3.js +77 -6
  5. package/bin/assets/preload/unique_locators.js +24 -3
  6. package/bin/assets/scripts/aria_snapshot.js +235 -0
  7. package/bin/assets/scripts/dom_attr.js +372 -0
  8. package/bin/assets/scripts/dom_element.js +0 -0
  9. package/bin/assets/scripts/dom_parent.js +185 -0
  10. package/bin/assets/scripts/event_utils.js +105 -0
  11. package/bin/assets/scripts/pw.js +7886 -0
  12. package/bin/assets/scripts/recorder.js +1147 -0
  13. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  14. package/bin/assets/scripts/unique_locators.js +841 -0
  15. package/bin/assets/scripts/yaml.js +4770 -0
  16. package/bin/assets/templates/page_template.txt +2 -16
  17. package/bin/assets/templates/utils_template.txt +59 -7
  18. package/bin/client/cli_helpers.js +11 -13
  19. package/bin/client/code_cleanup/utils.js +42 -14
  20. package/bin/client/code_gen/code_inversion.js +51 -14
  21. package/bin/client/code_gen/index.js +3 -0
  22. package/bin/client/code_gen/page_reflection.js +37 -20
  23. package/bin/client/code_gen/playwright_codeget.js +153 -25
  24. package/bin/client/cucumber/feature.js +103 -28
  25. package/bin/client/cucumber/project_to_document.js +8 -7
  26. package/bin/client/cucumber/steps_definitions.js +118 -85
  27. package/bin/client/local_agent.js +6 -2
  28. package/bin/client/operations/dump_tree.js +157 -2
  29. package/bin/client/project.js +6 -2
  30. package/bin/client/recorderv3/bvt_recorder.js +273 -80
  31. package/bin/client/recorderv3/cli.js +1 -0
  32. package/bin/client/recorderv3/implemented_steps.js +70 -15
  33. package/bin/client/recorderv3/index.js +49 -7
  34. package/bin/client/recorderv3/network.js +299 -0
  35. package/bin/client/recorderv3/step_runner.js +183 -13
  36. package/bin/client/recorderv3/step_utils.js +163 -14
  37. package/bin/client/recorderv3/update_feature.js +58 -30
  38. package/bin/client/recording.js +8 -0
  39. package/bin/client/run_cucumber.js +16 -2
  40. package/bin/client/scenario_report.js +35 -8
  41. package/bin/client/test_scenario.js +0 -1
  42. package/bin/index.js +1 -0
  43. package/package.json +15 -8
@@ -54,7 +54,7 @@ class StepsDefinitions {
54
54
  }
55
55
  }
56
56
  }
57
- load(print = true) {
57
+ load(print = true, supressErrors = false, errors = []) {
58
58
  const mjsFiles = findFilesWithExtension(
59
59
  path.join(this.baseFolder, this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features"),
60
60
  "mjs"
@@ -62,18 +62,24 @@ class StepsDefinitions {
62
62
  // console.log({ mjsFiles });
63
63
  for (let i = 0; i < mjsFiles.length; i++) {
64
64
  const mjsFile = mjsFiles[i];
65
- const codePage = new CodePage(
66
- path.join(
67
- this.baseFolder,
68
- this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
69
- mjsFile
70
- )
65
+ const filePath = path.join(
66
+ this.baseFolder,
67
+ this.isTemp ? process.env.tempFeaturesFolderPath ?? "__temp_features" : "features",
68
+ mjsFile
71
69
  );
70
+ const codePage = new CodePage(filePath);
72
71
  try {
73
72
  codePage.generateModel();
74
73
  } catch (error) {
75
- logger.info("unable to generate model for file", mjsFile);
76
- throw error;
74
+ logger.info("unable to generate model for", mjsFile, error);
75
+ if (supressErrors) {
76
+ errors.push({
77
+ file: filePath,
78
+ error: error.message,
79
+ });
80
+ } else {
81
+ continue;
82
+ }
77
83
  }
78
84
  this.initPage(codePage, mjsFile);
79
85
  }
@@ -517,86 +523,94 @@ class StepsDefinitions {
517
523
  // }
518
524
  // );
519
525
  };
526
+ //! Deprecated, not removing until stepRunner.executeStepRemote is stable
527
+ // executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText, onCommandResult }, options) => {
528
+ // // const { skipAfter = true, skipBefore = true } = options || {};
529
+ // const environment = {
530
+ // ...process.env,
531
+ // };
520
532
 
521
- executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText }, options) => {
522
- if (!options) {
523
- options = {
524
- skipAfter: true,
525
- };
526
- }
527
- const environment = {
528
- ...process.env,
529
- };
533
+ // try {
534
+ // const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
535
+ // const { runConfiguration } = await loadConfiguration(
536
+ // {
537
+ // provided: {
538
+ // name: [scenario],
539
+ // paths: [feature_file_path],
540
+ // import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
541
+ // // format: ["bvt"],
542
+ // },
543
+ // },
544
+ // { cwd: process.cwd(), env: environment }
545
+ // );
546
+ // // const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
547
+ // // console.log("Files found:", files);
548
+ // const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
549
+ // // console.log("found ", support.stepDefinitions.length, "step definitions");
550
+ // // support.stepDefinitions.map((step) => {
551
+ // // console.log("step", step.pattern);
552
+ // // });
530
553
 
531
- try {
532
- const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
533
- const { runConfiguration } = await loadConfiguration(
534
- {
535
- provided: {
536
- name: [scenario],
537
- paths: [feature_file_path],
538
- import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
539
- // format: ["bvt"],
540
- },
541
- },
542
- { cwd: process.cwd(), env: environment }
543
- );
544
- // const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
545
- // console.log("Files found:", files);
546
- const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
547
- // console.log("found ", support.stepDefinitions.length, "step definitions");
548
- // support.stepDefinitions.map((step) => {
549
- // console.log("step", step.pattern);
550
- // });
554
+ // if (skipAfter) {
555
+ // // ignore afterAll/after hooks
556
+ // support.afterTestCaseHookDefinitions = [];
557
+ // support.afterTestRunHookDefinitions = [];
558
+ // }
559
+ // // if (skipBefore) {
560
+ // // // ignore beforeAll/before hooks
561
+ // // support.beforeTestCaseHookDefinitions = support.beforeTestCaseHookDefinitions.filter((hook) => {
562
+ // // return hook.uri.endsWith("utils.mjs")
563
+ // // });
564
+ // // support.beforeTestRunHookDefinitions = [];
565
+ // // }
551
566
 
552
- if (options.skipAfter) {
553
- // ignore afterAll/after hooks
554
- support.afterTestCaseHookDefinitions = [];
555
- support.afterTestRunHookDefinitions = [];
556
- }
567
+ // let errorMesssage = null;
568
+ // let info = null;
569
+ // let errInfo = null;
570
+ // const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
571
+ // if (message.testStepFinished) {
572
+ // const { testStepFinished } = message;
573
+ // const { testStepResult } = testStepFinished;
574
+ // if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
575
+ // if (!errorMesssage) {
576
+ // errorMesssage = testStepResult.message;
577
+ // if (info) {
578
+ // errInfo = info;
579
+ // }
580
+ // }
581
+ // }
582
+ // if (testStepResult.status === "UNDEFINED") {
583
+ // if (!errorMesssage) {
584
+ // errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
585
+ // if (info) {
586
+ // errInfo = info;
587
+ // }
588
+ // }
589
+ // }
590
+ // }
591
+ // if (message.attachment) {
592
+ // const attachment = message.attachment;
593
+ // if (attachment.mediaType === "application/json" && attachment.body) {
594
+ // const body = JSON.parse(attachment.body);
595
+ // info = body.info;
596
+ // }
597
+ // }
598
+ // });
599
+ // if (errorMesssage) {
600
+ // const bvtError = new Error(errorMesssage);
601
+ // Object.assign(bvtError, { info: errInfo });
602
+ // throw bvtError;
603
+ // }
557
604
 
558
- let errorMesssage = null;
559
- let info = null;
560
- let errInfo = null;
561
- const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
562
- if (message.testStepFinished) {
563
- const { testStepFinished } = message;
564
- const { testStepResult } = testStepFinished;
565
- if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
566
- if (!errorMesssage) {
567
- errorMesssage = testStepResult.message;
568
- if (info) {
569
- errInfo = info;
570
- }
571
- }
572
- }
573
- if (testStepResult.status === "UNDEFINED") {
574
- if (!errorMesssage) {
575
- errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
576
- if (info) {
577
- errInfo = info;
578
- }
579
- }
580
- }
581
- }
582
- if (message.attachment) {
583
- const attachment = message.attachment;
584
- if (attachment.mediaType === "application/json" && attachment.body) {
585
- const body = JSON.parse(attachment.body);
586
- info = body.info;
587
- }
588
- }
589
- });
590
- if (errorMesssage) {
591
- const bvtError = new Error(errorMesssage);
592
- Object.assign(bvtError, { info: errInfo });
593
- throw bvtError;
594
- }
595
- } catch (error) {
596
- console.error("Error running cucumber-js", error);
597
- throw error;
598
- }
599
- };
605
+ // return {
606
+ // result,
607
+ // info,
608
+ // };
609
+ // } catch (error) {
610
+ // console.error("Error running cucumber-js", error);
611
+ // throw error;
612
+ // }
613
+ // };
600
614
  }
601
615
  function findFilesWithExtension(folderPath, extension, rootFolder = null) {
602
616
  let result = [];
@@ -626,4 +640,23 @@ function findFilesWithExtension(folderPath, extension, rootFolder = null) {
626
640
 
627
641
  return result;
628
642
  }
643
+ export function locateDefinitionPath(featureFolder, pageName) {
644
+ if (!pageName) {
645
+ return null;
646
+ }
647
+ if (!pageName.endsWith("_page.mjs")) {
648
+ pageName = pageName + "_page.mjs";
649
+ }
650
+ // search for files with the pageName
651
+ const stepDefinitionFolderPath = path.join(featureFolder, "step_definitions");
652
+ const files = findFilesWithExtension(stepDefinitionFolderPath, "mjs");
653
+ for (let i = 0; i < files.length; i++) {
654
+ const file = files[i];
655
+ if (file.includes(pageName)) {
656
+ return path.join(stepDefinitionFolderPath, file);
657
+ }
658
+ }
659
+ return path.join(stepDefinitionFolderPath, pageName);
660
+ }
661
+
629
662
  export { StepsDefinitions, findFilesWithExtension };
@@ -655,6 +655,7 @@ class LocalAgent {
655
655
 
656
656
  let page = agent.project.getPage(agent.pageName, true);
657
657
  const generateCodeResult = generateCode(recording, page, userData, agent.project.rootFolder, methodName);
658
+
658
659
  if (generateCodeResult.simple === true) {
659
660
  agent.sendDone("generateStepCode", null);
660
661
  break;
@@ -707,6 +708,9 @@ class LocalAgent {
707
708
  cucumberStep: agent.cucumberStep,
708
709
  },
709
710
  });
711
+ if (generateCodeResult.locatorsMetadata) {
712
+ page.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
713
+ }
710
714
  await page.save();
711
715
  agent.scenarioReport.updateLastStep({
712
716
  recording: command.data.recording,
@@ -967,7 +971,7 @@ class LocalAgent {
967
971
  async createNewStepLocal(
968
972
  featureName,
969
973
  cucumberStep,
970
- feature,
974
+ comments,
971
975
  userData,
972
976
  firstStep,
973
977
  previousTasks,
@@ -1083,7 +1087,7 @@ class LocalAgent {
1083
1087
  previousTasks,
1084
1088
  scenarioDocument,
1085
1089
  scenarioStepIndex,
1086
- comments: feature.comments,
1090
+ comments,
1087
1091
  featureFileText,
1088
1092
  recover,
1089
1093
  dumpConfig: this.dumpConfig,
@@ -378,6 +378,81 @@ const findLocatorByText = async (context, texts, backendNodeId) => {
378
378
  await _closeCDP();
379
379
  }
380
380
  };
381
+ function classifyLocatorsByStrategy(locators) {
382
+ const locs = [];
383
+
384
+ const allStrategyLocators = {
385
+ basic: [],
386
+ no_text: [],
387
+ ignore_digit: [],
388
+ context: [],
389
+ strategy: "basic",
390
+ };
391
+
392
+ const regexRegex = /\/.*\\[d][+].*\//;
393
+ for (const locator of locators) {
394
+ if (locator.mode) {
395
+ const { mode, ...rest } = locator;
396
+ locs.push(rest);
397
+ allStrategyLocators[locator.mode.toLowerCase()].push(rest);
398
+ } else if (locator.text && locator.text.length > 0) {
399
+ locs.push({
400
+ ...locator,
401
+ mode: "CONTEXT",
402
+ });
403
+ allStrategyLocators["context"].push(locator);
404
+ } else if (regexRegex.test(locator.css)) {
405
+ locs.push({
406
+ ...locator,
407
+ mode: "IGNORE_DIGIT",
408
+ });
409
+ allStrategyLocators["ignore_digit"].push(locator);
410
+ } else if (locator.css.includes("text=") || locator.css.includes("[name=") || locator.css.includes("label=")) {
411
+ locs.push(locator);
412
+ allStrategyLocators["basic"].push(locator);
413
+ } else {
414
+ locs.push({
415
+ ...locator,
416
+ mode: "NO_TEXT",
417
+ });
418
+ allStrategyLocators["no_text"].push(locator);
419
+ }
420
+ }
421
+ if (allStrategyLocators["context"].length > 0) {
422
+ allStrategyLocators["strategy"] = "context";
423
+ } else if (allStrategyLocators["basic"].length === 0 && allStrategyLocators["no_text"].length > 0) {
424
+ allStrategyLocators["strategy"] = "no_text";
425
+ }
426
+
427
+ return {
428
+ locators: locs,
429
+ allStrategyLocators,
430
+ };
431
+ }
432
+
433
+ async function filterLocators(cssLocators, elObjectId) {
434
+ const kept = [];
435
+ for (const selector of cssLocators) {
436
+ // call window.findElement(selector) === el
437
+ const { result } = await cdp.send("Runtime.callFunctionOn", {
438
+ functionDeclaration: `
439
+ function(element,selector) {
440
+ console.log("filterLocators in progress");
441
+ console.log(element,selector);
442
+ return window.findElement(selector) === element;
443
+ }
444
+ `,
445
+ objectId: elObjectId,
446
+ arguments: [{ objectId: elObjectId }, { value: selector }],
447
+ returnByValue: true,
448
+ });
449
+
450
+ if (result.value) {
451
+ kept.push(selector);
452
+ }
453
+ }
454
+ return kept;
455
+ }
381
456
 
382
457
  async function getRecorderLocators(context, backendNodeId) {
383
458
  if (!cdp) {
@@ -388,7 +463,8 @@ async function getRecorderLocators(context, backendNodeId) {
388
463
  backendNodeId: backendNodeId,
389
464
  });
390
465
  const objectId = domNode.object.objectId;
391
- let res = await cdp.send("Runtime.callFunctionOn", {
466
+ let locators = [];
467
+ locators = await cdp.send("Runtime.callFunctionOn", {
392
468
  functionDeclaration:
393
469
  "function(element,options) { console.log(element,options); return window.getPWLocators(element,options); }",
394
470
  objectId: objectId,
@@ -405,8 +481,87 @@ async function getRecorderLocators(context, backendNodeId) {
405
481
  ],
406
482
  returnByValue: true,
407
483
  });
484
+ if (!locators || !locators.result || !locators.result.value) {
485
+ locators = [];
486
+ } else {
487
+ locators = locators.result?.value;
488
+ }
489
+ let cssLocators = [];
490
+ try {
491
+ let origenCss = await cdp.send("Runtime.callFunctionOn", {
492
+ functionDeclaration:
493
+ "function(element) { console.log(element); return window.generateUniqueCSSSelector(element); }",
494
+ objectId: objectId,
495
+ arguments: [
496
+ {
497
+ objectId: objectId,
498
+ },
499
+ ],
500
+ returnByValue: true,
501
+ });
502
+ origenCss = origenCss.result?.value;
503
+ if (origenCss) {
504
+ cssLocators.push(origenCss);
505
+ }
506
+ let noClasses = await cdp.send("Runtime.callFunctionOn", {
507
+ functionDeclaration:
508
+ "function(element,options) { console.log(element,options); return window.CssSelectorGenerator.getCssSelector(element,options); }",
509
+ objectId: objectId,
510
+ arguments: [
511
+ {
512
+ objectId: objectId,
513
+ },
514
+ {
515
+ value: {
516
+ blacklist: [/^(?!.*h\d).*?\d.*/, /\[style/, /\[data-input-id/, /\[blinq-container/],
517
+ combineWithinSelector: true,
518
+ combineBetweenSelectors: true,
519
+ selectors: ["id", "attribute", "tag", "nthchild", "nthoftype"],
520
+ maxCombinations: 100,
521
+ },
522
+ },
523
+ ],
524
+ returnByValue: true,
525
+ });
526
+ noClasses = noClasses.result?.value;
527
+ if (!cssLocators.includes(noClasses)) {
528
+ cssLocators.push(noClasses);
529
+ }
530
+ let withClasses = await cdp.send("Runtime.callFunctionOn", {
531
+ functionDeclaration:
532
+ "function(element,options) { console.log(element,options); return window.CssSelectorGenerator.getCssSelector(element,options); }",
533
+ objectId: objectId,
534
+ arguments: [
535
+ {
536
+ objectId: objectId,
537
+ },
538
+ {
539
+ value: {
540
+ blacklist: [/^(?!.*h\d).*?\d.*/, /\[style/, /\[data-input-id/, /\[blinq-container/],
541
+ combineWithinSelector: true,
542
+ combineBetweenSelectors: true,
543
+ selectors: ["id", "attribute", "tag", "nthchild", "nthoftype", "class"],
544
+ maxCombinations: 100,
545
+ },
546
+ },
547
+ ],
548
+ returnByValue: true,
549
+ });
550
+ withClasses = withClasses.result?.value;
551
+ if (!cssLocators.includes(withClasses)) {
552
+ cssLocators.push(withClasses);
553
+ }
554
+ cssLocators.sort((a, b) => a.length - b.length);
555
+
556
+ cssLocators = await filterLocators(cssLocators, objectId);
557
+ } catch (err) {
558
+ console.error("Error in generateUniqueCSSSelector", error);
559
+ }
560
+ locators?.push(...cssLocators.map((css) => ({ mode: "NO_TEXT", css })));
561
+
562
+ const result = classifyLocatorsByStrategy(locators);
408
563
 
409
- return res.result;
564
+ return { ...result, element_name: "" };
410
565
  } finally {
411
566
  await _closeCDP();
412
567
  }
@@ -4,6 +4,7 @@ import { CodePage } from "./code_gen/page_reflection.js";
4
4
  import logger from "../logger.js";
5
5
  import path from "path";
6
6
  import { fileURLToPath } from "url";
7
+ import { findFilesWithExtension } from "./cucumber/steps_definitions.js";
7
8
  const __filename = fileURLToPath(import.meta.url);
8
9
  const __dirname = path.dirname(__filename);
9
10
 
@@ -85,7 +86,10 @@ class Project {
85
86
  logger.error("Error loading utils_template");
86
87
  }
87
88
 
88
- let files = readdirSync(this.stepsFolder);
89
+ const files = findFilesWithExtension(this.stepsFolder, "mjs");
90
+
91
+ //let files = readdirSync(this.stepsFolder);
92
+
89
93
  for (let i = 0; i < files.length; i++) {
90
94
  let file = files[i];
91
95
  if (file.startsWith("_")) {
@@ -100,7 +104,7 @@ class Project {
100
104
  this.pages.push(page);
101
105
  }
102
106
  }
103
- return envFile;
107
+ return this.environment.name;
104
108
  }
105
109
  getPagesNames() {
106
110
  let result = [];