@dev-blinq/cucumber_client 1.0.1173-dev → 1.0.1173-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.
- package/bin/assets/bundled_scripts/recorder.js +220 -0
- package/bin/assets/preload/accessibility.js +1 -1
- package/bin/assets/preload/find_context.js +1 -1
- package/bin/assets/preload/generateSelector.js +24 -0
- package/bin/assets/preload/locators.js +18 -0
- package/bin/assets/preload/recorderv3.js +85 -11
- package/bin/assets/preload/unique_locators.js +24 -3
- package/bin/assets/scripts/aria_snapshot.js +235 -0
- package/bin/assets/scripts/dom_attr.js +372 -0
- package/bin/assets/scripts/dom_element.js +0 -0
- package/bin/assets/scripts/dom_parent.js +185 -0
- package/bin/assets/scripts/event_utils.js +105 -0
- package/bin/assets/scripts/pw.js +7886 -0
- package/bin/assets/scripts/recorder.js +1147 -0
- package/bin/assets/scripts/snapshot_capturer.js +155 -0
- package/bin/assets/scripts/unique_locators.js +844 -0
- package/bin/assets/scripts/yaml.js +4770 -0
- package/bin/assets/templates/page_template.txt +2 -16
- package/bin/assets/templates/utils_template.txt +65 -12
- package/bin/client/cli_helpers.js +0 -1
- package/bin/client/code_cleanup/utils.js +43 -14
- package/bin/client/code_gen/code_inversion.js +112 -18
- package/bin/client/code_gen/index.js +3 -0
- package/bin/client/code_gen/page_reflection.js +37 -20
- package/bin/client/code_gen/playwright_codeget.js +152 -48
- package/bin/client/cucumber/feature.js +96 -42
- package/bin/client/cucumber/project_to_document.js +8 -7
- package/bin/client/cucumber/steps_definitions.js +59 -16
- package/bin/client/local_agent.js +9 -7
- package/bin/client/operations/dump_tree.js +159 -8
- package/bin/client/playground/playground.js +1 -1
- package/bin/client/project.js +6 -2
- package/bin/client/recorderv3/bvt_recorder.js +236 -79
- package/bin/client/recorderv3/cli.js +1 -0
- package/bin/client/recorderv3/implemented_steps.js +111 -11
- package/bin/client/recorderv3/index.js +45 -4
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +179 -13
- package/bin/client/recorderv3/step_utils.js +159 -14
- package/bin/client/recorderv3/update_feature.js +55 -30
- package/bin/client/recording.js +8 -0
- package/bin/client/run_cucumber.js +116 -4
- package/bin/client/scenario_report.js +112 -50
- package/bin/client/test_scenario.js +0 -1
- package/bin/index.js +1 -0
- package/package.json +15 -8
- package/bin/client/code_gen/get_implemented_steps.js +0 -27
|
@@ -2,7 +2,7 @@ function getElementAccessibleDescription(element, includeHidden) {
|
|
|
2
2
|
let accessibleDescription = null;
|
|
3
3
|
|
|
4
4
|
// https://w3c.github.io/accname/#mapping_additional_nd_description
|
|
5
|
-
//
|
|
5
|
+
// https://www.w3.org/TR/html-aam-1.0/#accdesc-computation
|
|
6
6
|
accessibleDescription = "";
|
|
7
7
|
|
|
8
8
|
if (element.hasAttribute("aria-describedby")) {
|
|
@@ -511,7 +511,7 @@ function findContext(element) {
|
|
|
511
511
|
const textsInnerText = [];
|
|
512
512
|
for (let i = 0; i < texts.length; i++) {
|
|
513
513
|
const text = texts[i];
|
|
514
|
-
textsInnerText.push(text.textContent);
|
|
514
|
+
textsInnerText.push(text.parentElement.textContent.trim());
|
|
515
515
|
// set attribute to the text: blinq-text-${key}-${i}
|
|
516
516
|
text.parentElement.setAttribute(`blinq-text-${key}-${i}`, "");
|
|
517
517
|
}
|
|
@@ -196,6 +196,30 @@ function escapeRegExp(s) {
|
|
|
196
196
|
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
|
197
197
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
|
|
198
198
|
}
|
|
199
|
+
function enclosingShadowRootOrDocument(element) {
|
|
200
|
+
let node = element;
|
|
201
|
+
while (node.parentNode) node = node.parentNode;
|
|
202
|
+
if (node.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */ || node.nodeType === 9 /* Node.DOCUMENT_NODE */)
|
|
203
|
+
return node;
|
|
204
|
+
}
|
|
205
|
+
function getIdRefs(element, ref) {
|
|
206
|
+
if (!ref) return [];
|
|
207
|
+
const root = enclosingShadowRootOrDocument(element);
|
|
208
|
+
if (!root) return [];
|
|
209
|
+
try {
|
|
210
|
+
const ids = ref.split(" ").filter((id) => !!id);
|
|
211
|
+
const set = new Set();
|
|
212
|
+
for (const id of ids) {
|
|
213
|
+
// https://www.w3.org/TR/wai-aria-1.2/#mapping_additional_relations_error_processing
|
|
214
|
+
// "If more than one element has the same ID, the user agent SHOULD use the first element found with the given ID"
|
|
215
|
+
const firstElement = root.querySelector("#" + CSS.escape(id));
|
|
216
|
+
if (firstElement) set.add(firstElement);
|
|
217
|
+
}
|
|
218
|
+
return [...set];
|
|
219
|
+
} catch (e) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
199
223
|
function getAriaLabelledByElements(element) {
|
|
200
224
|
const ref = element.getAttribute("aria-labelledby");
|
|
201
225
|
if (ref === null) return null;
|
|
@@ -697,6 +697,24 @@ function querySelector_shadow_aware(element, selector) {
|
|
|
697
697
|
}
|
|
698
698
|
window.querySelector_shadow_aware = querySelector_shadow_aware;
|
|
699
699
|
|
|
700
|
+
function findElement(css, scope = document) {
|
|
701
|
+
// return null if no css provided
|
|
702
|
+
if (!css) return null;
|
|
703
|
+
const selector = window.__injectedScript.parseSelector(css);
|
|
704
|
+
try {
|
|
705
|
+
const elements = window.__injectedScript.querySelectorAll(selector, scope, true);
|
|
706
|
+
if (!elements) return null;
|
|
707
|
+
if (elements.length === 1) {
|
|
708
|
+
return elements[0];
|
|
709
|
+
} else {
|
|
710
|
+
return elements;
|
|
711
|
+
}
|
|
712
|
+
} catch (error) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
window.findElement = findElement;
|
|
717
|
+
|
|
700
718
|
// export {
|
|
701
719
|
// getUniqueLocators,
|
|
702
720
|
// getBasicInfo,
|
|
@@ -304,13 +304,16 @@ function findMatchingElements(inputSnapshot, objectSet) {
|
|
|
304
304
|
|
|
305
305
|
// For each subtree, check for matches in the object set
|
|
306
306
|
subtrees.forEach((subtree) => {
|
|
307
|
-
const subtreeText = subtree.join("\n");
|
|
307
|
+
const subtreeText = subtree.map((t) => t.trim()).join("\n");
|
|
308
308
|
|
|
309
309
|
// Look for matching snapshots in the object set
|
|
310
310
|
for (const obj of objectSet) {
|
|
311
311
|
// Normalize snapshots for comparison (trim whitespace, etc.)
|
|
312
312
|
const normalizedSubtree = subtreeText.trim();
|
|
313
|
-
const normalizedSnapshot = obj.snapshot
|
|
313
|
+
const normalizedSnapshot = obj.snapshot
|
|
314
|
+
.split("\n")
|
|
315
|
+
.map((s) => s.trim())
|
|
316
|
+
.join("\n");
|
|
314
317
|
|
|
315
318
|
// Check if the current object's snapshot matches our subtree
|
|
316
319
|
if (normalizedSnapshot === normalizedSubtree) {
|
|
@@ -456,7 +459,7 @@ class BVTRecorder {
|
|
|
456
459
|
getAction: (e) => {
|
|
457
460
|
consumeEvent(e);
|
|
458
461
|
},
|
|
459
|
-
getInterestedElement: () => {},
|
|
462
|
+
getInterestedElement: () => { },
|
|
460
463
|
hoverOutlineStyle: "",
|
|
461
464
|
});
|
|
462
465
|
|
|
@@ -471,7 +474,7 @@ class BVTRecorder {
|
|
|
471
474
|
};
|
|
472
475
|
},
|
|
473
476
|
getAction: () => null,
|
|
474
|
-
getInterestedElement: () => {},
|
|
477
|
+
getInterestedElement: () => { },
|
|
475
478
|
hoverOutlineStyle: "",
|
|
476
479
|
});
|
|
477
480
|
|
|
@@ -486,7 +489,7 @@ class BVTRecorder {
|
|
|
486
489
|
};
|
|
487
490
|
},
|
|
488
491
|
getAction: () => null,
|
|
489
|
-
getInterestedElement: () => {},
|
|
492
|
+
getInterestedElement: () => { },
|
|
490
493
|
hoverOutlineStyle: "",
|
|
491
494
|
});
|
|
492
495
|
|
|
@@ -916,6 +919,65 @@ class BVTRecorder {
|
|
|
916
919
|
}
|
|
917
920
|
this.contextElement = null;
|
|
918
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
|
+
|
|
919
981
|
getElementDetails(el, type) {
|
|
920
982
|
if (lastInputId !== el.dataset.inputId || type !== "input") {
|
|
921
983
|
el.dataset.inputId = getNextInputId();
|
|
@@ -925,9 +987,12 @@ class BVTRecorder {
|
|
|
925
987
|
// }
|
|
926
988
|
|
|
927
989
|
lastInputId = el.dataset.inputId;
|
|
990
|
+
|
|
991
|
+
el.__locators = this.getLocatorsObject(el);
|
|
928
992
|
}
|
|
929
993
|
const role = window.getAriaRole(el);
|
|
930
|
-
const label = window.getElementAccessibleName(el, false) || window.getElementAccessibleName(el, true) ||
|
|
994
|
+
const label = window.getElementAccessibleName(el, false) || window.getElementAccessibleName(el, true) || "";
|
|
995
|
+
const result = this.getElementProperties(el);
|
|
931
996
|
return {
|
|
932
997
|
role,
|
|
933
998
|
label,
|
|
@@ -944,13 +1009,16 @@ class BVTRecorder {
|
|
|
944
1009
|
value: el.value,
|
|
945
1010
|
tagName: el.tagName,
|
|
946
1011
|
text: el.textContent,
|
|
947
|
-
type: el.getAttribute("type"),
|
|
1012
|
+
type: el.type, // el.getAttribute("type"),
|
|
948
1013
|
disabled: el.disabled,
|
|
949
1014
|
readOnly: el.readOnly,
|
|
950
1015
|
required: el.required,
|
|
951
1016
|
checked: el.checked,
|
|
952
1017
|
innerText: el.innerText,
|
|
953
1018
|
},
|
|
1019
|
+
attributes: result.attributes,
|
|
1020
|
+
properties: result.properties,
|
|
1021
|
+
dataset: result.dataset,
|
|
954
1022
|
};
|
|
955
1023
|
}
|
|
956
1024
|
handleEvent(e) {
|
|
@@ -1058,7 +1126,10 @@ class BVTRecorder {
|
|
|
1058
1126
|
mode: "IGNORE_DIGIT",
|
|
1059
1127
|
});
|
|
1060
1128
|
allStrategyLocators["ignore_digit"].push(locator);
|
|
1061
|
-
} else if (
|
|
1129
|
+
} else if (
|
|
1130
|
+
locator.css &&
|
|
1131
|
+
(locator.css.includes("text=") || locator.css.includes("[name=") || locator.css.includes("label="))
|
|
1132
|
+
) {
|
|
1062
1133
|
locs.push(locator);
|
|
1063
1134
|
allStrategyLocators["basic"].push(locator);
|
|
1064
1135
|
} else {
|
|
@@ -1092,6 +1163,7 @@ class BVTRecorder {
|
|
|
1092
1163
|
improviseLocators: false,
|
|
1093
1164
|
mustIncludeCSSChain: true,
|
|
1094
1165
|
root: commonParent,
|
|
1166
|
+
noCSSId: true,
|
|
1095
1167
|
});
|
|
1096
1168
|
locators.forEach((locator) => {
|
|
1097
1169
|
locator.text = text;
|
|
@@ -1118,7 +1190,7 @@ class BVTRecorder {
|
|
|
1118
1190
|
cssLocators.push(origenCss);
|
|
1119
1191
|
}
|
|
1120
1192
|
const noClasses = CssSelectorGenerator.getCssSelector(el, {
|
|
1121
|
-
blacklist: [/^(?!.*h\d).*?\d.*/, /\[style/, /\[data-input-id
|
|
1193
|
+
blacklist: [/^(?!.*h\d).*?\d.*/, /\[style/, /\[data-input-id/],
|
|
1122
1194
|
combineWithinSelector: true,
|
|
1123
1195
|
combineBetweenSelectors: true,
|
|
1124
1196
|
selectors: ["id", "attribute", "tag", "nthchild", "nthoftype"],
|
|
@@ -1212,7 +1284,8 @@ class BVTRecorder {
|
|
|
1212
1284
|
action: action.details,
|
|
1213
1285
|
element: this.getElementDetails(actionElement, eventName),
|
|
1214
1286
|
isPopupCloseClick: this.isPopupCloseEvent(e),
|
|
1215
|
-
...this.getLocatorsObject(actionElement),
|
|
1287
|
+
// ...this.getLocatorsObject(actionElement),
|
|
1288
|
+
...(actionElement.__locators ?? this.getLocatorsObject(actionElement)),
|
|
1216
1289
|
frame: this.getFrameDetails(),
|
|
1217
1290
|
statistics: {
|
|
1218
1291
|
time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
|
|
@@ -1352,7 +1425,8 @@ function countAccurances(css, scope = document) {
|
|
|
1352
1425
|
}
|
|
1353
1426
|
window.countAccurances = countAccurances;
|
|
1354
1427
|
function findElement(css, scope = document) {
|
|
1355
|
-
//
|
|
1428
|
+
// Note: If you change anything iin this function
|
|
1429
|
+
// please change it in locators.js
|
|
1356
1430
|
if (!css) return null;
|
|
1357
1431
|
const selector = window.__injectedScript.parseSelector(css);
|
|
1358
1432
|
try {
|
|
@@ -24,11 +24,13 @@ 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
|
};
|
|
31
31
|
|
|
32
|
+
window.getMatchingElements = getMatchingElements;
|
|
33
|
+
|
|
32
34
|
const getPWSelectors = (element, options = {}) => {
|
|
33
35
|
const selectors = [];
|
|
34
36
|
const exludeText = options?.excludeText || false;
|
|
@@ -147,7 +149,25 @@ const getElementLocators = (element, options) => {
|
|
|
147
149
|
.map((item) => {
|
|
148
150
|
return item;
|
|
149
151
|
});
|
|
150
|
-
|
|
152
|
+
if (result.nonUnique.length === 0) {
|
|
153
|
+
// find slector with min score
|
|
154
|
+
let minScore = Infinity;
|
|
155
|
+
let minSelector = null;
|
|
156
|
+
for (const locator of pw_locators) {
|
|
157
|
+
if (locator.score < minScore) {
|
|
158
|
+
minScore = locator.score;
|
|
159
|
+
minSelector = locator.selector;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (minSelector) {
|
|
163
|
+
result.nonUnique.push({
|
|
164
|
+
css: minSelector,
|
|
165
|
+
priority: 2,
|
|
166
|
+
elements: getMatchingElements(minSelector, options),
|
|
167
|
+
score: minScore,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
151
171
|
return result;
|
|
152
172
|
};
|
|
153
173
|
|
|
@@ -337,6 +357,7 @@ function generateUniqueCSSSelector(element, options) {
|
|
|
337
357
|
const root = options?.root || window.document;
|
|
338
358
|
const separator = options?.separator || " > ";
|
|
339
359
|
const isUnique = options?.isunique || ((selector) => getMatchingElements(selector, options).length === 1);
|
|
360
|
+
const noCSSId = options?.noCSSId || false;
|
|
340
361
|
|
|
341
362
|
if (!(element instanceof Element)) return null;
|
|
342
363
|
|
|
@@ -344,7 +365,7 @@ function generateUniqueCSSSelector(element, options) {
|
|
|
344
365
|
|
|
345
366
|
let selector = "";
|
|
346
367
|
const id = element.getAttribute("id");
|
|
347
|
-
if (id && !/\d/.test(id)) {
|
|
368
|
+
if (id && !/\d/.test(id) && (!noCSSId)) {
|
|
348
369
|
selector = "#" + cssEscape(id);
|
|
349
370
|
if (isUnique(selector)) return selector;
|
|
350
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
|