@dev-blinq/cucumber_client 1.0.1198-dev → 1.0.1198-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/find_context.js +1 -1
- package/bin/assets/preload/recorderv3.js +75 -5
- 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 +841 -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 +59 -7
- package/bin/client/cli_helpers.js +11 -13
- package/bin/client/code_cleanup/utils.js +42 -14
- package/bin/client/code_gen/code_inversion.js +48 -11
- 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 +153 -25
- package/bin/client/cucumber/feature.js +92 -34
- package/bin/client/cucumber/steps_definitions.js +109 -83
- package/bin/client/local_agent.js +6 -2
- package/bin/client/project.js +6 -2
- package/bin/client/recorderv3/bvt_recorder.js +272 -76
- package/bin/client/recorderv3/implemented_steps.js +69 -14
- package/bin/client/recorderv3/index.js +49 -7
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +183 -13
- package/bin/client/recorderv3/step_utils.js +155 -8
- package/bin/client/recorderv3/update_feature.js +58 -30
- package/bin/client/recording.js +7 -0
- package/bin/client/run_cucumber.js +16 -2
- package/bin/client/scenario_report.js +18 -6
- package/bin/client/test_scenario.js +0 -1
- package/bin/index.js +1 -0
- package/package.json +15 -8
|
@@ -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
|
}
|
|
@@ -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();
|
|
@@ -928,9 +987,12 @@ class BVTRecorder {
|
|
|
928
987
|
// }
|
|
929
988
|
|
|
930
989
|
lastInputId = el.dataset.inputId;
|
|
990
|
+
|
|
991
|
+
el.__locators = this.getLocatorsObject(el);
|
|
931
992
|
}
|
|
932
993
|
const role = window.getAriaRole(el);
|
|
933
|
-
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);
|
|
934
996
|
return {
|
|
935
997
|
role,
|
|
936
998
|
label,
|
|
@@ -947,13 +1009,16 @@ class BVTRecorder {
|
|
|
947
1009
|
value: el.value,
|
|
948
1010
|
tagName: el.tagName,
|
|
949
1011
|
text: el.textContent,
|
|
950
|
-
type: el.getAttribute("type"),
|
|
1012
|
+
type: el.type, // el.getAttribute("type"),
|
|
951
1013
|
disabled: el.disabled,
|
|
952
1014
|
readOnly: el.readOnly,
|
|
953
1015
|
required: el.required,
|
|
954
1016
|
checked: el.checked,
|
|
955
1017
|
innerText: el.innerText,
|
|
956
1018
|
},
|
|
1019
|
+
attributes: result.attributes,
|
|
1020
|
+
properties: result.properties,
|
|
1021
|
+
dataset: result.dataset,
|
|
957
1022
|
};
|
|
958
1023
|
}
|
|
959
1024
|
handleEvent(e) {
|
|
@@ -1061,7 +1126,10 @@ class BVTRecorder {
|
|
|
1061
1126
|
mode: "IGNORE_DIGIT",
|
|
1062
1127
|
});
|
|
1063
1128
|
allStrategyLocators["ignore_digit"].push(locator);
|
|
1064
|
-
} else if (
|
|
1129
|
+
} else if (
|
|
1130
|
+
locator.css &&
|
|
1131
|
+
(locator.css.includes("text=") || locator.css.includes("[name=") || locator.css.includes("label="))
|
|
1132
|
+
) {
|
|
1065
1133
|
locs.push(locator);
|
|
1066
1134
|
allStrategyLocators["basic"].push(locator);
|
|
1067
1135
|
} else {
|
|
@@ -1095,6 +1163,7 @@ class BVTRecorder {
|
|
|
1095
1163
|
improviseLocators: false,
|
|
1096
1164
|
mustIncludeCSSChain: true,
|
|
1097
1165
|
root: commonParent,
|
|
1166
|
+
noCSSId: true,
|
|
1098
1167
|
});
|
|
1099
1168
|
locators.forEach((locator) => {
|
|
1100
1169
|
locator.text = text;
|
|
@@ -1121,7 +1190,7 @@ class BVTRecorder {
|
|
|
1121
1190
|
cssLocators.push(origenCss);
|
|
1122
1191
|
}
|
|
1123
1192
|
const noClasses = CssSelectorGenerator.getCssSelector(el, {
|
|
1124
|
-
blacklist: [/^(?!.*h\d).*?\d.*/, /\[style/, /\[data-input-id
|
|
1193
|
+
blacklist: [/^(?!.*h\d).*?\d.*/, /\[style/, /\[data-input-id/],
|
|
1125
1194
|
combineWithinSelector: true,
|
|
1126
1195
|
combineBetweenSelectors: true,
|
|
1127
1196
|
selectors: ["id", "attribute", "tag", "nthchild", "nthoftype"],
|
|
@@ -1215,7 +1284,8 @@ class BVTRecorder {
|
|
|
1215
1284
|
action: action.details,
|
|
1216
1285
|
element: this.getElementDetails(actionElement, eventName),
|
|
1217
1286
|
isPopupCloseClick: this.isPopupCloseEvent(e),
|
|
1218
|
-
...this.getLocatorsObject(actionElement),
|
|
1287
|
+
// ...this.getLocatorsObject(actionElement),
|
|
1288
|
+
...(actionElement.__locators ?? this.getLocatorsObject(actionElement)),
|
|
1219
1289
|
frame: this.getFrameDetails(),
|
|
1220
1290
|
statistics: {
|
|
1221
1291
|
time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
|
|
@@ -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
|