@dev-blinq/cucumber_client 1.0.1428-dev → 1.0.1428-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 +73 -73
  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 +70 -45
  12. package/bin/assets/scripts/snapshot_capturer.js +147 -147
  13. package/bin/assets/scripts/unique_locators.js +170 -49
  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 +16 -16
  17. package/bin/client/code_cleanup/find_step_definition_references.js +0 -1
  18. package/bin/client/code_gen/api_codegen.js +2 -2
  19. package/bin/client/code_gen/code_inversion.js +63 -2
  20. package/bin/client/code_gen/function_signature.js +4 -0
  21. package/bin/client/code_gen/page_reflection.js +52 -11
  22. package/bin/client/code_gen/playwright_codeget.js +25 -3
  23. package/bin/client/cucumber/feature_data.js +2 -2
  24. package/bin/client/cucumber/project_to_document.js +8 -2
  25. package/bin/client/cucumber/steps_definitions.js +19 -3
  26. package/bin/client/local_agent.js +3 -2
  27. package/bin/client/parse_feature_file.js +23 -26
  28. package/bin/client/playground/projects/env.json +2 -2
  29. package/bin/client/recorderv3/bvt_init.js +363 -0
  30. package/bin/client/recorderv3/bvt_recorder.js +1009 -47
  31. package/bin/client/recorderv3/implemented_steps.js +2 -0
  32. package/bin/client/recorderv3/index.js +3 -283
  33. package/bin/client/recorderv3/scriptTest.js +1 -1
  34. package/bin/client/recorderv3/services.js +818 -142
  35. package/bin/client/recorderv3/step_runner.js +28 -8
  36. package/bin/client/recorderv3/step_utils.js +511 -39
  37. package/bin/client/recorderv3/update_feature.js +32 -13
  38. package/bin/client/recorderv3/wbr_entry.js +61 -0
  39. package/bin/client/recording.js +1 -0
  40. package/bin/client/upload-service.js +4 -2
  41. package/bin/client/utils/socket_logger.js +1 -1
  42. package/bin/index.js +4 -1
  43. package/package.json +6 -4
@@ -144,7 +144,7 @@ class BVTRecorder {
144
144
  getAction: (e) => {
145
145
  this.eventUtils.consumeEvent(e);
146
146
  },
147
- getInterestedElement: () => { },
147
+ getInterestedElement: () => {},
148
148
  hoverOutlineStyle: "",
149
149
  });
150
150
 
@@ -159,7 +159,7 @@ class BVTRecorder {
159
159
  };
160
160
  },
161
161
  getAction: () => null,
162
- getInterestedElement: () => { },
162
+ getInterestedElement: () => {},
163
163
  hoverOutlineStyle: "",
164
164
  });
165
165
 
@@ -174,7 +174,7 @@ class BVTRecorder {
174
174
  };
175
175
  },
176
176
  getAction: () => null,
177
- getInterestedElement: () => { },
177
+ getInterestedElement: () => {},
178
178
  hoverOutlineStyle: "",
179
179
  });
180
180
 
@@ -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,36 @@ 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
+ {
451
+ details: {
452
+ name: "fill",
453
+ text: newValue,
454
+ },
455
+ element: target,
456
+ },
457
+ target,
458
+ "input",
459
+ event
460
+ );
461
+
462
+ event.__bvt_recorded = true;
463
+ }, 20);
464
+ }
465
+ oldPreventDefault();
466
+ };
467
+
437
468
  if (!this.eventUtils.shouldGenerateKeyPressFor(event)) return;
438
469
  // if (this._actionInProgress(event)) {
439
470
  // this._expectProgrammaticKeyUp = true;
@@ -676,7 +707,7 @@ class BVTRecorder {
676
707
 
677
708
  lastInputId = el.dataset.inputId;
678
709
 
679
- el.__locators = this.getLocatorsObject(el);
710
+ // el.__locators = this.getLocatorsObject(el, { maxLocators: 1 });
680
711
  }
681
712
  const role = this.PW.roleUtils.getAriaRole(el);
682
713
  const label =
@@ -684,13 +715,15 @@ class BVTRecorder {
684
715
  this.PW.roleUtils.getElementAccessibleName(el, true) ||
685
716
  "";
686
717
  const result = this.getElementProperties(el);
718
+ const elText = this.PW.selectorUtils.elementText(new Map(), el);
687
719
  return {
688
720
  role,
689
721
  label,
690
722
  inputID: el.dataset.inputId,
691
723
  tagName: el.tagName,
692
724
  type: el.type,
693
- text: this.PW.selectorUtils.elementText(new Map(), el).full.trim(),
725
+ text: elText.full.trim(),
726
+ textNormalized: elText.normalized.trim(),
694
727
  parent: `tagname: ${el.parentElement?.tagName}\ninnerText: ${el.parentElement?.innerText}`,
695
728
  attrs: {
696
729
  placeholder: el.getAttribute("placeholder"),
@@ -788,26 +821,15 @@ class BVTRecorder {
788
821
  });
789
822
  }
790
823
 
791
- getLocatorsObject(el) {
824
+ getLocatorsObject(el, options = {}) {
792
825
  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
- });
826
+ const result = this.locatorGenerator.toContextLocators(el, this.contextElement, options);
806
827
  return result;
807
828
  }
808
829
  const isRecordingText = this.#mode === "recordingText";
809
830
  return this.locatorGenerator.getElementLocators(el, {
810
831
  excludeText: isRecordingText,
832
+ ...options,
811
833
  });
812
834
  }
813
835
  addListeners() {
@@ -844,31 +866,7 @@ class BVTRecorder {
844
866
  }
845
867
 
846
868
  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);
869
+ this.recordEvent(action, actionElement, eventName, e);
872
870
  this.handleStateTransition(action.element);
873
871
  },
874
872
  { capture: true }
@@ -876,6 +874,33 @@ class BVTRecorder {
876
874
  });
877
875
  }
878
876
 
877
+ recordEvent(action, actionElement, eventName, e) {
878
+ const cmd = {
879
+ mode: this.#mode,
880
+ action: action.details,
881
+ element: this.getElementDetails(actionElement, eventName),
882
+ isPopupCloseClick: this.isPopupCloseEvent(e),
883
+ // ...(actionElement.__locators ?? this.getLocatorsObject(actionElement, { maxLocators: 1 })),
884
+ frame: this.getFrameDetails(),
885
+ statistics: {
886
+ time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
887
+ },
888
+ };
889
+ const snapshotDetails = {
890
+ id: actionElement.getAttribute("data-blinq-id"),
891
+ contextId: this.contextElement?.getAttribute("data-blinq-context-id"),
892
+ doc: this.snapshotCapturer.createSnapshot({
893
+ excludeSelectors: ["x-bvt-toolbar", "script", "style", "link[rel=stylesheet]"],
894
+ }),
895
+ };
896
+ cmd.snapshotDetails = snapshotDetails;
897
+ // eventQueue.enqueue(async () => {
898
+ // await bvtRecorderBindings.validateLocators(snapshotDetails);
899
+ // });
900
+ // console.log(cmd);
901
+ bvtRecorderBindings.recordCommand(cmd);
902
+ }
903
+
879
904
  // TODO: implement the corresponding logic for the below methods
880
905
  setPopupHandlers(_popopHandlers) {
881
906
  this.popupHandlers = _popopHandlers;
@@ -894,7 +919,7 @@ class BVTRecorder {
894
919
  this.interestedElements.clear();
895
920
  }
896
921
  processAriaSnapshot(snapshot) {
897
- const matchedElements = this.findMatchingElements(snapshot, this.snapshotElements);
922
+ const matchedElements = this.snapshotUtils.findMatchingElements(snapshot, this.snapshotElements);
898
923
  for (const el of matchedElements.values()) {
899
924
  const element = el;
900
925
  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;