@dev-blinq/cucumber_client 1.0.1223-dev → 1.0.1223-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 (40) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +220 -0
  2. package/bin/assets/preload/recorderv3.js +69 -5
  3. package/bin/assets/preload/unique_locators.js +3 -2
  4. package/bin/assets/scripts/aria_snapshot.js +235 -0
  5. package/bin/assets/scripts/dom_attr.js +372 -0
  6. package/bin/assets/scripts/dom_element.js +0 -0
  7. package/bin/assets/scripts/dom_parent.js +185 -0
  8. package/bin/assets/scripts/event_utils.js +105 -0
  9. package/bin/assets/scripts/pw.js +7886 -0
  10. package/bin/assets/scripts/recorder.js +1147 -0
  11. package/bin/assets/scripts/snapshot_capturer.js +155 -0
  12. package/bin/assets/scripts/unique_locators.js +841 -0
  13. package/bin/assets/scripts/yaml.js +4770 -0
  14. package/bin/assets/templates/_hooks_template.txt +37 -0
  15. package/bin/assets/templates/page_template.txt +2 -16
  16. package/bin/assets/templates/utils_template.txt +48 -63
  17. package/bin/client/apiTest/apiTest.js +6 -0
  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 +68 -10
  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 +170 -33
  24. package/bin/client/cucumber/feature.js +85 -27
  25. package/bin/client/cucumber/steps_definitions.js +84 -76
  26. package/bin/client/local_agent.js +7 -3
  27. package/bin/client/project.js +7 -1
  28. package/bin/client/recorderv3/bvt_recorder.js +267 -80
  29. package/bin/client/recorderv3/implemented_steps.js +74 -12
  30. package/bin/client/recorderv3/index.js +58 -8
  31. package/bin/client/recorderv3/network.js +299 -0
  32. package/bin/client/recorderv3/step_runner.js +319 -67
  33. package/bin/client/recorderv3/step_utils.js +157 -6
  34. package/bin/client/recorderv3/update_feature.js +58 -30
  35. package/bin/client/recording.js +7 -0
  36. package/bin/client/run_cucumber.js +15 -2
  37. package/bin/client/scenario_report.js +4 -8
  38. package/bin/client/test_scenario.js +0 -1
  39. package/bin/index.js +1 -0
  40. package/package.json +15 -8
@@ -459,7 +459,7 @@ class BVTRecorder {
459
459
  getAction: (e) => {
460
460
  consumeEvent(e);
461
461
  },
462
- getInterestedElement: () => { },
462
+ getInterestedElement: () => {},
463
463
  hoverOutlineStyle: "",
464
464
  });
465
465
 
@@ -474,7 +474,7 @@ class BVTRecorder {
474
474
  };
475
475
  },
476
476
  getAction: () => null,
477
- getInterestedElement: () => { },
477
+ getInterestedElement: () => {},
478
478
  hoverOutlineStyle: "",
479
479
  });
480
480
 
@@ -489,7 +489,7 @@ class BVTRecorder {
489
489
  };
490
490
  },
491
491
  getAction: () => null,
492
- getInterestedElement: () => { },
492
+ getInterestedElement: () => {},
493
493
  hoverOutlineStyle: "",
494
494
  });
495
495
 
@@ -919,6 +919,65 @@ class BVTRecorder {
919
919
  }
920
920
  this.contextElement = null;
921
921
  }
922
+ getElementProperties(element) {
923
+ if (!element || !(element instanceof HTMLElement || element instanceof SVGElement)) {
924
+ throw new Error("Please provide a valid HTML element");
925
+ }
926
+
927
+ const result = {
928
+ properties: {},
929
+ attributes: {},
930
+ dataset: {},
931
+ };
932
+
933
+ const unsortedProperties = {};
934
+ const unsortedAttributes = {};
935
+ const unsortedDataset = {};
936
+
937
+ // Get enumerable properties
938
+ for (const prop in element) {
939
+ try {
940
+ const value = element[prop];
941
+ if (
942
+ typeof value !== "function" &&
943
+ typeof value !== "object" &&
944
+ value !== null &&
945
+ value !== undefined &&
946
+ !prop.includes("_")
947
+ ) {
948
+ unsortedProperties[prop] = value;
949
+ }
950
+ } catch (error) {
951
+ unsortedProperties[prop] = "[Error accessing property]";
952
+ }
953
+ }
954
+
955
+ // Get all attributes
956
+ if (element.attributes) {
957
+ for (const attr of element.attributes) {
958
+ if (attr.name === "data-blinq-id" || attr.name === "data-input-id") continue;
959
+ unsortedAttributes[attr.name] = attr.value;
960
+ }
961
+ }
962
+
963
+ // Get dataset properties (data-* attributes)
964
+ if (element.dataset) {
965
+ for (const [key, value] of Object.entries(element.dataset)) {
966
+ if (key === "blinqId" || key === "inputId") continue;
967
+ unsortedDataset[key] = value;
968
+ }
969
+ }
970
+
971
+ // Sort each object by key
972
+ const sortByKey = (obj) => Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
973
+
974
+ result.properties = sortByKey(unsortedProperties);
975
+ result.attributes = sortByKey(unsortedAttributes);
976
+ result.dataset = sortByKey(unsortedDataset);
977
+
978
+ return result;
979
+ }
980
+
922
981
  getElementDetails(el, type) {
923
982
  if (lastInputId !== el.dataset.inputId || type !== "input") {
924
983
  el.dataset.inputId = getNextInputId();
@@ -932,7 +991,8 @@ class BVTRecorder {
932
991
  el.__locators = this.getLocatorsObject(el);
933
992
  }
934
993
  const role = window.getAriaRole(el);
935
- const label = window.getElementAccessibleName(el, false) || window.getElementAccessibleName(el, true) || role || "";
994
+ const label = window.getElementAccessibleName(el, false) || window.getElementAccessibleName(el, true) || "";
995
+ const result = this.getElementProperties(el);
936
996
  return {
937
997
  role,
938
998
  label,
@@ -956,6 +1016,9 @@ class BVTRecorder {
956
1016
  checked: el.checked,
957
1017
  innerText: el.innerText,
958
1018
  },
1019
+ attributes: result.attributes,
1020
+ properties: result.properties,
1021
+ dataset: result.dataset,
959
1022
  };
960
1023
  }
961
1024
  handleEvent(e) {
@@ -1100,6 +1163,7 @@ class BVTRecorder {
1100
1163
  improviseLocators: false,
1101
1164
  mustIncludeCSSChain: true,
1102
1165
  root: commonParent,
1166
+ noCSSId: true,
1103
1167
  });
1104
1168
  locators.forEach((locator) => {
1105
1169
  locator.text = text;
@@ -1126,7 +1190,7 @@ class BVTRecorder {
1126
1190
  cssLocators.push(origenCss);
1127
1191
  }
1128
1192
  const noClasses = CssSelectorGenerator.getCssSelector(el, {
1129
- blacklist: [/^(?!.*h\d).*?\d.*/, /\[style/, /\[data-input-id/, /\[blinq-container/],
1193
+ blacklist: [/^(?!.*h\d).*?\d.*/, /\[style/, /\[data-input-id/],
1130
1194
  combineWithinSelector: true,
1131
1195
  combineBetweenSelectors: true,
1132
1196
  selectors: ["id", "attribute", "tag", "nthchild", "nthoftype"],
@@ -24,7 +24,7 @@ const getMatchingElements = (selector, options = {}) => {
24
24
  const root = options?.root || window.document;
25
25
  const prefix = options?.prefix;
26
26
  if (prefix) {
27
- selector = `${prefix} >> ${selector}`;
27
+ selector = `${prefix} >> ${selector} >> visible=true`;
28
28
  }
29
29
  return window.__injectedScript.querySelectorAll(window.__injectedScript.parseSelector(selector), root);
30
30
  };
@@ -357,6 +357,7 @@ function generateUniqueCSSSelector(element, options) {
357
357
  const root = options?.root || window.document;
358
358
  const separator = options?.separator || " > ";
359
359
  const isUnique = options?.isunique || ((selector) => getMatchingElements(selector, options).length === 1);
360
+ const noCSSId = options?.noCSSId || false;
360
361
 
361
362
  if (!(element instanceof Element)) return null;
362
363
 
@@ -364,7 +365,7 @@ function generateUniqueCSSSelector(element, options) {
364
365
 
365
366
  let selector = "";
366
367
  const id = element.getAttribute("id");
367
- if (id && !/\d/.test(id)) {
368
+ if (id && !/\d/.test(id) && (!noCSSId)) {
368
369
  selector = "#" + cssEscape(id);
369
370
  if (isUnique(selector)) return selector;
370
371
  }
@@ -0,0 +1,235 @@
1
+ class AriaSnapshotUtils {
2
+ isLeafNode(node) {
3
+ if (node.kind === "text") {
4
+ return true;
5
+ } else {
6
+ return !node.children || node.children.length === 0;
7
+ }
8
+ }
9
+
10
+ deepClone(node) {
11
+ if (node.kind === "text") {
12
+ return node.text;
13
+ } else {
14
+ const result = {
15
+ kind: "role",
16
+ role: node.role,
17
+ };
18
+ if ("checked" in node) result.checked = node.checked;
19
+ if ("disabled" in node) result.disabled = node.disabled;
20
+ if ("expanded" in node) result.expanded = node.expanded;
21
+ if ("level" in node) result.level = node.level;
22
+ if ("pressed" in node) result.pressed = node.pressed;
23
+ if ("selected" in node) result.selected = node.selected;
24
+ if (node.name !== undefined) {
25
+ result.name = node.name;
26
+ }
27
+ if (node.props) {
28
+ result.props = Object.assign({}, node.props);
29
+ }
30
+ if (node.containerMode) {
31
+ result.containerMode = node.containerMode;
32
+ }
33
+ if (node.children) {
34
+ result.children = node.children.map(child => this.deepClone(child));
35
+ }
36
+ return result;
37
+ }
38
+ }
39
+
40
+ yamlEscapeKeyIfNeeded(str) {
41
+ if (!this.yamlStringNeedsQuotes(str)) return str;
42
+ return `'` + str.replace(/'/g, `''`) + `'`;
43
+ }
44
+
45
+ yamlEscapeValueIfNeeded(str) {
46
+ if (!this.yamlStringNeedsQuotes(str)) return str;
47
+ return (
48
+ '"' +
49
+ str.replace(/[\\"\x00-\x1f\x7f-\x9f]/g, (c) => {
50
+ switch (c) {
51
+ case "\\":
52
+ return "\\\\";
53
+ case '"':
54
+ return '\\"';
55
+ case "\b":
56
+ return "\\b";
57
+ case "\f":
58
+ return "\\f";
59
+ case "\n":
60
+ return "\\n";
61
+ case "\r":
62
+ return "\\r";
63
+ case "\t":
64
+ return "\\t";
65
+ default:
66
+ const code = c.charCodeAt(0);
67
+ return "\\x" + code.toString(16).padStart(2, "0");
68
+ }
69
+ }) +
70
+ '"'
71
+ );
72
+ }
73
+
74
+ yamlStringNeedsQuotes(str) {
75
+ if (!str) return false;
76
+ if (str.length === 0) return true;
77
+ if (/^\s|\s$/.test(str)) return true;
78
+ if (/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/.test(str)) return true;
79
+ if (/^-/.test(str)) return true;
80
+ if (/[\n:](\s|$)/.test(str)) return true;
81
+ if (/\s#/.test(str)) return true;
82
+ if (/[\n\r]/.test(str)) return true;
83
+ if (/^[&*\],?!>|@"'#%]/.test(str)) return true;
84
+ if (/[{}`]/.test(str)) return true;
85
+ if (/^\[/.test(str)) return true;
86
+ if (!isNaN(Number(str)) || ["y", "n", "yes", "no", "true", "false", "on", "off", "null"].includes(str.toLowerCase()))
87
+ return true;
88
+ return false;
89
+ }
90
+
91
+ filterParentToChildren(parent, targetChildren) {
92
+ const isDirectMatch = targetChildren.some((child) => JSON.stringify(child) === JSON.stringify(parent));
93
+ if (isDirectMatch || this.isLeafNode(parent)) {
94
+ return isDirectMatch ? this.deepClone(parent) : null;
95
+ }
96
+ if (parent.kind === "role" && parent.children && parent.children.length > 0) {
97
+ const filteredChildren = parent.children
98
+ .map((child) => this.filterParentToChildren(child, targetChildren))
99
+ .filter((child) => child !== null);
100
+ if (filteredChildren.length > 0) {
101
+ const result = this.deepClone(parent);
102
+ result.children = filteredChildren;
103
+ return result;
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+
109
+ serializeAriaSnapshot(ariaSnapshot, indent = 0) {
110
+ const lines = [];
111
+ const visit = (node, parentNode, indent) => {
112
+ if (typeof node === "string") {
113
+ const text = this.yamlEscapeValueIfNeeded(node);
114
+ if (text) lines.push(indent + "- text: " + text);
115
+ return;
116
+ }
117
+ let key = node.role;
118
+
119
+ if (node.name && node.name.length <= 900) {
120
+ const name = node.name;
121
+ if (name) {
122
+ const stringifiedName = name.startsWith("/") && name.endsWith("/") ? name : JSON.stringify(name);
123
+ key += " " + stringifiedName;
124
+ }
125
+ }
126
+ if (node.checked === "mixed") key += ` [checked=mixed]`;
127
+ if (node.checked === true) key += ` [checked]`;
128
+ if (node.disabled) key += ` [disabled]`;
129
+ if (node.expanded) key += ` [expanded]`;
130
+ if (node.level) key += ` [level=${node.level}]`;
131
+ if (node.pressed === "mixed") key += ` [pressed=mixed]`;
132
+ if (node.pressed === true) key += ` [pressed]`;
133
+ if (node.selected === true) key += ` [selected]`;
134
+
135
+ const escapedKey = indent + "- " + this.yamlEscapeKeyIfNeeded(key);
136
+ if (node.props === undefined) node.props = {};
137
+ if (node.children === undefined) node.children = [];
138
+ const hasProps = !!Object.keys(node.props).length;
139
+ if (!node.children.length && !hasProps) {
140
+ lines.push(escapedKey);
141
+ } else if (node.children.length === 1 && typeof node.children[0] === "string" && !hasProps) {
142
+ const text = node.children[0];
143
+ if (text) lines.push(escapedKey + ": " + this.yamlEscapeValueIfNeeded(text));
144
+ else lines.push(escapedKey);
145
+ } else {
146
+ lines.push(escapedKey + ":");
147
+ for (const [name, value] of Object.entries(node.props) || []) {
148
+ lines.push(indent + " - /" + name + ": " + this.yamlEscapeValueIfNeeded(value));
149
+ }
150
+ for (const child of node.children || []) {
151
+ visit(child, node, indent + " ");
152
+ }
153
+ }
154
+ };
155
+
156
+ const node = ariaSnapshot;
157
+ if (node.role === "fragment") {
158
+ for (const child of node.children || []) visit(child, node, "");
159
+ } else {
160
+ visit(node, null, "");
161
+ }
162
+ return lines.join("\n");
163
+ }
164
+
165
+ isSnapshotAvailable(element, elementSet) {
166
+ if (elementSet.has(element)) {
167
+ return element;
168
+ } else {
169
+ return undefined;
170
+ }
171
+ }
172
+
173
+ findMatchingElements(inputSnapshot, objectSet) {
174
+ const matches = new Map();
175
+ const lines = inputSnapshot.trim().split("\n");
176
+ const subtrees = this.extractSubtrees(lines);
177
+
178
+ subtrees.forEach((subtree) => {
179
+ const subtreeText = subtree.map((t) => t.trim()).join("\n");
180
+ for (const obj of objectSet) {
181
+ const normalizedSubtree = subtreeText.trim();
182
+ const normalizedSnapshot = obj.snapshot
183
+ .split("\n")
184
+ .map((s) => s.trim())
185
+ .join("\n");
186
+ if (normalizedSnapshot === normalizedSubtree) {
187
+ matches.set(subtreeText, obj.element);
188
+ break;
189
+ }
190
+ }
191
+ });
192
+
193
+ return matches;
194
+ }
195
+
196
+ extractSubtrees(lines) {
197
+ const subtrees = [];
198
+ const indentSize = this.getIndentSize(lines);
199
+
200
+ for (let i = 0; i < lines.length; i++) {
201
+ const currentLine = lines[i];
202
+ const currentIndent = this.getIndentLevel(currentLine);
203
+ const subtree = [currentLine];
204
+ for (let j = i + 1; j < lines.length; j++) {
205
+ const nextLine = lines[j];
206
+ const nextIndent = this.getIndentLevel(nextLine);
207
+ if (nextIndent > currentIndent) {
208
+ subtree.push(nextLine);
209
+ } else {
210
+ break;
211
+ }
212
+ }
213
+ subtrees.push(subtree);
214
+ }
215
+ subtrees.sort((a, b) => a.length - b.length);
216
+ return subtrees;
217
+ }
218
+
219
+ getIndentLevel(line) {
220
+ const match = line.match(/^(\s*)/);
221
+ return match ? match[1].length : 0;
222
+ }
223
+
224
+ getIndentSize(lines) {
225
+ for (let i = 1; i < lines.length; i++) {
226
+ const indentLevel = this.getIndentLevel(lines[i]);
227
+ if (indentLevel > 0) {
228
+ return indentLevel - this.getIndentLevel(lines[i - 1]);
229
+ }
230
+ }
231
+ return 2;
232
+ }
233
+ }
234
+
235
+ export default AriaSnapshotUtils