@dev-blinq/cucumber_client 1.0.1398-dev → 1.0.1398-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 (50) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +105 -105
  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 +63 -42
  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 +6 -2
  16. package/bin/assets/templates/utils_template.txt +1 -1
  17. package/bin/client/code_cleanup/find_step_definition_references.js +0 -1
  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 +63 -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 +27 -3
  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 +8 -2
  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 +3 -2
  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 -202
  33. package/bin/client/recorderv3/bvt_init.js +349 -0
  34. package/bin/client/recorderv3/bvt_recorder.js +1071 -107
  35. package/bin/client/recorderv3/implemented_steps.js +2 -0
  36. package/bin/client/recorderv3/index.js +4 -303
  37. package/bin/client/recorderv3/scriptTest.js +1 -1
  38. package/bin/client/recorderv3/services.js +814 -154
  39. package/bin/client/recorderv3/step_runner.js +315 -206
  40. package/bin/client/recorderv3/step_utils.js +473 -25
  41. package/bin/client/recorderv3/update_feature.js +9 -5
  42. package/bin/client/recorderv3/wbr_entry.js +61 -0
  43. package/bin/client/recording.js +1 -0
  44. package/bin/client/upload-service.js +3 -2
  45. package/bin/client/utils/socket_logger.js +132 -0
  46. package/bin/index.js +4 -1
  47. package/bin/logger.js +3 -2
  48. package/bin/min/consoleApi.min.cjs +2 -3
  49. package/bin/min/injectedScript.min.cjs +16 -16
  50. package/package.json +19 -9
@@ -390,6 +390,7 @@ class BVTRecorder {
390
390
  }
391
391
  case "input": {
392
392
  const target = this.eventUtils.getNearestInteractiveElement(this.eventUtils.deepEventTarget(event));
393
+
393
394
  if (target.nodeName === "INPUT" && target.type.toLowerCase() === "file") {
394
395
  return {
395
396
  details: {
@@ -434,6 +435,32 @@ class BVTRecorder {
434
435
  return;
435
436
  }
436
437
  case "keydown": {
438
+ // override event.preventDefault to capture the value
439
+
440
+ const oldPreventDefault = event.preventDefault.bind(event);
441
+ event.preventDefault = () => {
442
+ if (event.key.length >= 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
443
+ const target = this.eventUtils.getNearestInteractiveElement(this.eventUtils.deepEventTarget(event));
444
+ setTimeout((() => {
445
+ if (event.__bvt_recorded !== undefined) return;
446
+ const valueBefore = target.value;
447
+ let newValue = valueBefore;
448
+
449
+ this.recordEvent({
450
+ details: {
451
+ name: "fill",
452
+ text: newValue,
453
+ },
454
+ element: target,
455
+ }, target, "input", event);
456
+
457
+ event.__bvt_recorded = true;
458
+ }), 20)
459
+ }
460
+ oldPreventDefault();
461
+ };
462
+
463
+
437
464
  if (!this.eventUtils.shouldGenerateKeyPressFor(event)) return;
438
465
  // if (this._actionInProgress(event)) {
439
466
  // this._expectProgrammaticKeyUp = true;
@@ -676,7 +703,7 @@ class BVTRecorder {
676
703
 
677
704
  lastInputId = el.dataset.inputId;
678
705
 
679
- el.__locators = this.getLocatorsObject(el);
706
+ // el.__locators = this.getLocatorsObject(el, { maxLocators: 1 });
680
707
  }
681
708
  const role = this.PW.roleUtils.getAriaRole(el);
682
709
  const label =
@@ -684,13 +711,15 @@ class BVTRecorder {
684
711
  this.PW.roleUtils.getElementAccessibleName(el, true) ||
685
712
  "";
686
713
  const result = this.getElementProperties(el);
714
+ const elText = this.PW.selectorUtils.elementText(new Map(), el);
687
715
  return {
688
716
  role,
689
717
  label,
690
718
  inputID: el.dataset.inputId,
691
719
  tagName: el.tagName,
692
720
  type: el.type,
693
- text: this.PW.selectorUtils.elementText(new Map(), el).full.trim(),
721
+ text: elText.full.trim(),
722
+ textNormalized: elText.normalized.trim(),
694
723
  parent: `tagname: ${el.parentElement?.tagName}\ninnerText: ${el.parentElement?.innerText}`,
695
724
  attrs: {
696
725
  placeholder: el.getAttribute("placeholder"),
@@ -788,26 +817,15 @@ class BVTRecorder {
788
817
  });
789
818
  }
790
819
 
791
- getLocatorsObject(el) {
820
+ getLocatorsObject(el, options = {}) {
792
821
  if (this.contextElement) {
793
- const text = this.contextElement.innerText; // TODO: handle case where contextElement is not in dom/ children removed
794
- const contextEl = this.contextElement;
795
- // const { climb, commonParent } = window.getCommonParent(contextEl, el);
796
- const commonParent = this.locatorGenerator.dom_Parent.findLowestCommonAncestor([contextEl, el]);
797
- const climb = this.locatorGenerator.dom_Parent.getClimbCountToParent(contextEl, commonParent);
798
- const result = this.locatorGenerator.getElementLocators(el, {
799
- excludeText: true,
800
- root: commonParent,
801
- });
802
- result.locators.forEach((locator) => {
803
- locator.text = text;
804
- locator.climb = climb;
805
- });
822
+ const result = this.locatorGenerator.toContextLocators(el, this.contextElement, options);
806
823
  return result;
807
824
  }
808
825
  const isRecordingText = this.#mode === "recordingText";
809
826
  return this.locatorGenerator.getElementLocators(el, {
810
827
  excludeText: isRecordingText,
828
+ ...options,
811
829
  });
812
830
  }
813
831
  addListeners() {
@@ -844,31 +862,7 @@ class BVTRecorder {
844
862
  }
845
863
 
846
864
  performance.mark("command-send");
847
- const cmd = {
848
- mode: this.#mode,
849
- action: action.details,
850
- element: this.getElementDetails(actionElement, eventName),
851
- isPopupCloseClick: this.isPopupCloseEvent(e),
852
- // ...this.getLocatorsObject(actionElement),
853
- ...(actionElement.__locators ?? this.getLocatorsObject(actionElement)),
854
- frame: this.getFrameDetails(),
855
- statistics: {
856
- time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
857
- },
858
- };
859
- const snapshotDetails = {
860
- id: actionElement.getAttribute("data-blinq-id"),
861
- contextId: this.contextElement?.getAttribute("data-blinq-context-id"),
862
- doc: this.snapshotCapturer.createSnapshot({
863
- excludeSelectors: ["x-bvt-toolbar", "script", "style", "link[rel=stylesheet]"],
864
- }),
865
- };
866
- cmd.snapshotDetails = snapshotDetails;
867
- // eventQueue.enqueue(async () => {
868
- // await bvtRecorderBindings.validateLocators(snapshotDetails);
869
- // });
870
- // console.log(cmd);
871
- await bvtRecorderBindings.recordCommand(cmd);
865
+ this.recordEvent(action, actionElement, eventName, e);
872
866
  this.handleStateTransition(action.element);
873
867
  },
874
868
  { capture: true }
@@ -876,6 +870,33 @@ class BVTRecorder {
876
870
  });
877
871
  }
878
872
 
873
+ recordEvent(action, actionElement, eventName, e) {
874
+ const cmd = {
875
+ mode: this.#mode,
876
+ action: action.details,
877
+ element: this.getElementDetails(actionElement, eventName),
878
+ isPopupCloseClick: this.isPopupCloseEvent(e),
879
+ // ...(actionElement.__locators ?? this.getLocatorsObject(actionElement, { maxLocators: 1 })),
880
+ frame: this.getFrameDetails(),
881
+ statistics: {
882
+ time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
883
+ },
884
+ };
885
+ const snapshotDetails = {
886
+ id: actionElement.getAttribute("data-blinq-id"),
887
+ contextId: this.contextElement?.getAttribute("data-blinq-context-id"),
888
+ doc: this.snapshotCapturer.createSnapshot({
889
+ excludeSelectors: ["x-bvt-toolbar", "script", "style", "link[rel=stylesheet]"],
890
+ }),
891
+ };
892
+ cmd.snapshotDetails = snapshotDetails;
893
+ // eventQueue.enqueue(async () => {
894
+ // await bvtRecorderBindings.validateLocators(snapshotDetails);
895
+ // });
896
+ // console.log(cmd);
897
+ bvtRecorderBindings.recordCommand(cmd);
898
+ }
899
+
879
900
  // TODO: implement the corresponding logic for the below methods
880
901
  setPopupHandlers(_popopHandlers) {
881
902
  this.popupHandlers = _popopHandlers;
@@ -894,7 +915,7 @@ class BVTRecorder {
894
915
  this.interestedElements.clear();
895
916
  }
896
917
  processAriaSnapshot(snapshot) {
897
- const matchedElements = this.findMatchingElements(snapshot, this.snapshotElements);
918
+ const matchedElements = this.snapshotUtils.findMatchingElements(snapshot, this.snapshotElements);
898
919
  for (const el of matchedElements.values()) {
899
920
  const element = el;
900
921
  if (element) {
@@ -1,155 +1,155 @@
1
1
  class SnapshotCapturer {
2
- constructor(options = {}) {
3
- const {
4
- inlineImages = true,
5
- inlineStyles = true,
6
- excludeSelectors = []
7
- } = options;
8
- // this.options = {
9
- // inlineImages,
10
- // inlineStyles,
11
- // excludeSelectors
12
- // };
13
- this.inlineImages = inlineImages;
14
- this.inlineStyles = inlineStyles;
15
- this.excludeSelectors = excludeSelectors;
2
+ constructor(options = {}) {
3
+ const { inlineImages = true, inlineStyles = true, excludeSelectors = [] } = options;
4
+ // this.options = {
5
+ // inlineImages,
6
+ // inlineStyles,
7
+ // excludeSelectors
8
+ // };
9
+ this.inlineImages = inlineImages;
10
+ this.inlineStyles = inlineStyles;
11
+ this.excludeSelectors = excludeSelectors;
12
+ }
13
+ imageToDataURL(img, document) {
14
+ try {
15
+ // Create canvas to draw the image
16
+ const canvas = document.createElement("canvas");
17
+ canvas.width = img.naturalWidth || img.width;
18
+ canvas.height = img.naturalHeight || img.height;
19
+ const ctx = canvas.getContext("2d");
20
+
21
+ // Draw image to canvas and convert to data URL
22
+ ctx.drawImage(img, 0, 0);
23
+ return canvas.toDataURL("image/png");
24
+ } catch (error) {
25
+ console.warn(`Failed to inline image: ${img.src}`, error);
26
+ return img.src; // Fall back to original source
16
27
  }
17
- imageToDataURL(img, document) {
18
- try {
19
- // Create canvas to draw the image
20
- const canvas = document.createElement('canvas');
21
- canvas.width = img.naturalWidth || img.width;
22
- canvas.height = img.naturalHeight || img.height;
23
- const ctx = canvas.getContext('2d');
24
-
25
- // Draw image to canvas and convert to data URL
26
- ctx.drawImage(img, 0, 0);
27
- return canvas.toDataURL('image/png');
28
- } catch (error) {
29
- console.warn(`Failed to inline image: ${img.src}`, error);
30
- return img.src; // Fall back to original source
31
- }
32
- }
33
- processStyles(document) {
34
- if (!this.inlineStyles) return;
35
-
36
- const stylesheets = Array.from(document.styleSheets);
37
- for (const sheet of stylesheets) {
38
- try {
39
- if (!sheet.href) continue; // Skip inline styles
40
- const styleEl = document.createElement('style');
41
- let text = ""
42
- const cssRules = Array.from(sheet.cssRules || []);
43
- for (const rule of cssRules) {
44
- if (rule.cssText) {
45
- text += rule.cssText + '\n';
46
- }
47
- }
48
- styleEl.textContent = text;
49
-
50
- // Replace the link with our new style element
51
- if (sheet.ownerNode && sheet.ownerNode.parentNode) {
52
- sheet.ownerNode.parentNode.replaceChild(styleEl, sheet.ownerNode);
53
- } else if (sheet.ownerNode) {
54
- // If the owner node is not in the document, just append it
55
- document.head.appendChild(styleEl);
56
- }
57
-
58
- } catch (error) {
59
- console.warn(`Error processing stylesheet: ${sheet.href}`, error);
60
- }
28
+ }
29
+ processStyles(document) {
30
+ if (!this.inlineStyles) return;
31
+
32
+ const stylesheets = Array.from(window.document.styleSheets);
33
+ for (const sheet of stylesheets) {
34
+ try {
35
+ const el = sheet.ownerNode.cloneNode(true);
36
+ if (!el.href) {
37
+ document.head.appendChild(el);
38
+ } else {
39
+ const rules = Array.from(sheet.cssRules)
40
+ .map((rule) => rule.cssText)
41
+ .join("\n");
42
+ const styleEl = document.createElement("style");
43
+ styleEl.textContent = rules;
44
+ document.head.appendChild(styleEl);
61
45
  }
46
+ } catch (error) {
47
+ console.warn(`Error processing stylesheet: ${sheet.href}`, error);
48
+ }
62
49
  }
63
- processImages(document) {
64
- if (!this.inlineImages) return;
65
-
66
- const images = document.querySelectorAll('img');
67
- images.forEach(img => {
68
- // Skip SVGs and already data URLs
69
- if (img.src.startsWith('data:') || img.src.endsWith('.svg')) return;
70
-
71
- // Skip if the image is excluded
72
- if (this.excludeSelectors.some(selector => img.matches(selector))) return;
73
-
74
- // Only inline complete images
75
- if (img.complete && img.naturalWidth !== 0) {
76
- try {
77
- img.setAttribute('src', this.imageToDataURL(img, document));
78
- } catch (e) {
79
- console.warn(`Failed to process image: ${img.src}`, e);
80
- }
81
- }
82
- });
83
- }
84
- removeExcludedElements(document) {
85
- this.excludeSelectors.forEach(selector => {
86
- const elements = document.querySelectorAll(selector);
87
- elements.forEach(el => {
88
- el.parentNode?.removeChild(el);
89
- });
90
- });
91
- }
92
- processComputedStyles(document) {
93
- const elements = document.querySelectorAll('*');
94
-
95
- elements.forEach(el => {
96
- // Skip excluded elements
97
- if (this.excludeSelectors.some(selector => el.matches(selector))) return;
98
-
99
- // Get computed style
100
- const style = window.getComputedStyle(el);
101
-
102
- // Copy important styles to inline style
103
- const importantStyles = [
104
- 'display', 'position', 'width', 'height', 'margin', 'padding',
105
- 'color', 'background-color', 'font-family', 'font-size',
106
- 'text-align', 'line-height', 'border', 'box-shadow', 'opacity'
107
- // Add more styles as needed
108
- ];
109
-
110
- const inlineStyles = [];
111
-
112
- importantStyles.forEach(prop => {
113
- const value = style.getPropertyValue(prop);
114
- if (value) {
115
- inlineStyles.push(`${prop}: ${value}`);
116
- }
117
- });
118
-
119
- // Set inline style
120
- if (inlineStyles.length > 0) {
121
- const currentStyle = el.getAttribute('style') || '';
122
- el.setAttribute('style', currentStyle + inlineStyles.join('; ') + ';');
123
- }
124
- });
125
-
126
- }
127
- createSnapshot() {
128
- // Clone the document to avoid modifying the original
129
- const docClone = window.document.cloneNode(true);
50
+ }
51
+ processImages(document) {
52
+ if (!this.inlineImages) return;
130
53
 
131
- // Store the original document
132
- const originalDoc = window.document;
54
+ const images = document.querySelectorAll("img");
55
+ images.forEach((img) => {
56
+ // Skip SVGs and already data URLs
57
+ if (img.src.startsWith("data:") || img.src.endsWith(".svg")) return;
133
58
 
134
- // Temporarily "swap" the document for processing
135
- // (We're just using this as a convention - it doesn't actually replace the global document)
136
- const doc = docClone;
59
+ // Skip if the image is excluded
60
+ if (this.excludeSelectors.some((selector) => img.matches(selector))) return;
137
61
 
138
- // Process the clone
139
- this.processStyles(doc);
140
- this.processImages(doc);
141
- this.removeExcludedElements(doc);
142
- this.processComputedStyles(doc);
143
-
144
- // Generate HTML with doctype
145
- const doctype = originalDoc.doctype ?
146
- new XMLSerializer().serializeToString(originalDoc.doctype) : '<!DOCTYPE html>';
147
-
148
- // Get the HTML content
149
- const htmlContent = doc.documentElement.outerHTML;
150
-
151
- // Combine doctype and HTML content
152
- return `${doctype}${htmlContent}`;
153
- }
62
+ // Only inline complete images
63
+ if (img.complete && img.naturalWidth !== 0) {
64
+ try {
65
+ img.setAttribute("src", this.imageToDataURL(img, document));
66
+ } catch (e) {
67
+ console.warn(`Failed to process image: ${img.src}`, e);
68
+ }
69
+ }
70
+ });
71
+ }
72
+ removeExcludedElements(document) {
73
+ this.excludeSelectors.forEach((selector) => {
74
+ const elements = document.querySelectorAll(selector);
75
+ elements.forEach((el) => {
76
+ el.parentNode?.removeChild(el);
77
+ });
78
+ });
79
+ }
80
+ processComputedStyles(document) {
81
+ const elements = document.querySelectorAll("*");
82
+
83
+ elements.forEach((el) => {
84
+ // Skip excluded elements
85
+ if (this.excludeSelectors.some((selector) => el.matches(selector))) return;
86
+
87
+ // Get computed style
88
+ const style = window.getComputedStyle(el);
89
+
90
+ // Copy important styles to inline style
91
+ const importantStyles = [
92
+ "display",
93
+ "position",
94
+ "width",
95
+ "height",
96
+ "margin",
97
+ "padding",
98
+ "color",
99
+ "background-color",
100
+ "font-family",
101
+ "font-size",
102
+ "text-align",
103
+ "line-height",
104
+ "border",
105
+ "box-shadow",
106
+ "opacity",
107
+ // Add more styles as needed
108
+ ];
109
+
110
+ const inlineStyles = [];
111
+
112
+ importantStyles.forEach((prop) => {
113
+ const value = style.getPropertyValue(prop);
114
+ if (value) {
115
+ inlineStyles.push(`${prop}: ${value}`);
116
+ }
117
+ });
118
+
119
+ // Set inline style
120
+ if (inlineStyles.length > 0) {
121
+ const currentStyle = el.getAttribute("style") || "";
122
+ el.setAttribute("style", currentStyle + inlineStyles.join("; ") + ";");
123
+ }
124
+ });
125
+ }
126
+ createSnapshot() {
127
+ // Clone the document to avoid modifying the original
128
+ const docClone = window.document.cloneNode(true);
129
+
130
+ // Store the original document
131
+ const originalDoc = window.document;
132
+
133
+ // Temporarily "swap" the document for processing
134
+ // (We're just using this as a convention - it doesn't actually replace the global document)
135
+ const doc = docClone;
136
+
137
+ // Process the clone
138
+ this.processStyles(doc);
139
+ this.processImages(doc);
140
+ this.removeExcludedElements(doc);
141
+ this.processComputedStyles(doc);
142
+
143
+ // Generate HTML with doctype
144
+ const doctype = originalDoc.doctype
145
+ ? new XMLSerializer().serializeToString(originalDoc.doctype)
146
+ : "<!DOCTYPE html>";
147
+
148
+ // Get the HTML content
149
+ const htmlContent = doc.documentElement.outerHTML;
150
+
151
+ // Combine doctype and HTML content
152
+ return `${doctype}${htmlContent}`;
153
+ }
154
154
  }
155
- export default SnapshotCapturer;
155
+ export default SnapshotCapturer;