@wdio/nightwatch-devtools 1.5.0 → 1.5.1
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/dist/index.js +60 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -554,10 +554,45 @@ function isSameElement(node1, node2) {
|
|
|
554
554
|
}
|
|
555
555
|
return false;
|
|
556
556
|
}
|
|
557
|
+
function sanitizePageSource(sourceXML) {
|
|
558
|
+
let sanitized = sourceXML.replace(
|
|
559
|
+
/&(?!(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);)/g,
|
|
560
|
+
"&"
|
|
561
|
+
);
|
|
562
|
+
const VOID_ELEMENTS = [
|
|
563
|
+
"link",
|
|
564
|
+
"meta",
|
|
565
|
+
"br",
|
|
566
|
+
"hr",
|
|
567
|
+
"img",
|
|
568
|
+
"input",
|
|
569
|
+
"source",
|
|
570
|
+
"area",
|
|
571
|
+
"base",
|
|
572
|
+
"col",
|
|
573
|
+
"embed",
|
|
574
|
+
"track",
|
|
575
|
+
"wbr"
|
|
576
|
+
];
|
|
577
|
+
for (const tag of VOID_ELEMENTS) {
|
|
578
|
+
sanitized = sanitized.replace(
|
|
579
|
+
// eslint-disable-next-line security/detect-non-literal-regexp -- tag from hardcoded list
|
|
580
|
+
new RegExp(`<${tag}(\\s[^>]*?[^/])>`, "gi"),
|
|
581
|
+
`<${tag}$1/>`
|
|
582
|
+
);
|
|
583
|
+
sanitized = sanitized.replace(
|
|
584
|
+
// eslint-disable-next-line security/detect-non-literal-regexp -- tag from hardcoded list
|
|
585
|
+
new RegExp(`<${tag}>`, "gi"),
|
|
586
|
+
`<${tag}/>`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
return sanitized;
|
|
590
|
+
}
|
|
557
591
|
function xmlToJSON(sourceXML) {
|
|
558
592
|
try {
|
|
593
|
+
const sanitized = sanitizePageSource(sourceXML);
|
|
559
594
|
const parser = new DOMParser();
|
|
560
|
-
const sourceDoc = parser.parseFromString(
|
|
595
|
+
const sourceDoc = parser.parseFromString(sanitized, "text/xml");
|
|
561
596
|
const parseErrors = sourceDoc.getElementsByTagName("parsererror");
|
|
562
597
|
if (parseErrors.length > 0) {
|
|
563
598
|
console.error(
|
|
@@ -576,8 +611,9 @@ function xmlToJSON(sourceXML) {
|
|
|
576
611
|
}
|
|
577
612
|
function xmlToDOM(sourceXML) {
|
|
578
613
|
try {
|
|
614
|
+
const sanitized = sanitizePageSource(sourceXML);
|
|
579
615
|
const parser = new DOMParser();
|
|
580
|
-
const doc = parser.parseFromString(
|
|
616
|
+
const doc = parser.parseFromString(sanitized, "text/xml");
|
|
581
617
|
const parseErrors = doc.getElementsByTagName("parsererror");
|
|
582
618
|
if (parseErrors.length > 0) {
|
|
583
619
|
console.error("[xmlToDOM] XML parsing error:", parseErrors[0].textContent);
|
|
@@ -834,7 +870,7 @@ function isValidValue(value) {
|
|
|
834
870
|
return value !== void 0 && value !== null && value !== "null" && value.trim() !== "";
|
|
835
871
|
}
|
|
836
872
|
function escapeText(text) {
|
|
837
|
-
return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
873
|
+
return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
838
874
|
}
|
|
839
875
|
function escapeXPathValue(value) {
|
|
840
876
|
if (!value.includes("'")) {
|
|
@@ -1395,6 +1431,20 @@ function classifyMobileRole(tagName, platform) {
|
|
|
1395
1431
|
}
|
|
1396
1432
|
return IOS_ROLE_MAP[tagName] || simplifyTag(tagName);
|
|
1397
1433
|
}
|
|
1434
|
+
function getFirstChildText(element) {
|
|
1435
|
+
const queue = [...element.children || []];
|
|
1436
|
+
while (queue.length > 0) {
|
|
1437
|
+
const el = queue.shift();
|
|
1438
|
+
const text = el.attributes?.text?.trim();
|
|
1439
|
+
if (text) {
|
|
1440
|
+
return text;
|
|
1441
|
+
}
|
|
1442
|
+
if (el.children) {
|
|
1443
|
+
queue.push(...el.children);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return void 0;
|
|
1447
|
+
}
|
|
1398
1448
|
function getBestAndroidLocator(attrs) {
|
|
1399
1449
|
if (attrs._selector) {
|
|
1400
1450
|
return attrs._selector;
|
|
@@ -1480,7 +1530,7 @@ function isMobileInViewport(element, platform, viewport) {
|
|
|
1480
1530
|
function collectMobileNodes(element, platform, depth, nodes, walkOpts) {
|
|
1481
1531
|
const attrs = element.attributes;
|
|
1482
1532
|
const role = classifyMobileRole(element.tagName, platform);
|
|
1483
|
-
|
|
1533
|
+
let name = getMobileNodeIdentity(attrs, platform);
|
|
1484
1534
|
const explicit = isExplicitlyInteractive(attrs, platform);
|
|
1485
1535
|
const interactive = isMobileInteractive(element, platform);
|
|
1486
1536
|
const inViewport = isMobileInViewport(element, platform, walkOpts.viewport);
|
|
@@ -1525,6 +1575,12 @@ function collectMobileNodes(element, platform, depth, nodes, walkOpts) {
|
|
|
1525
1575
|
if (!locator) {
|
|
1526
1576
|
locator = (platform === "android" ? getBestAndroidLocator(attrs) : getBestIOSLocator(attrs)) ?? "";
|
|
1527
1577
|
}
|
|
1578
|
+
if (!name && platform === "android" && (locator.startsWith("class:") || locator.startsWith('android=new UiSelector().className("'))) {
|
|
1579
|
+
const childText = getFirstChildText(element);
|
|
1580
|
+
if (childText && childText.length < 100) {
|
|
1581
|
+
name = childText;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1528
1584
|
}
|
|
1529
1585
|
nodes.push({
|
|
1530
1586
|
role,
|