This article describes how to find nodes in the Document Object Model (DOM), where the native (standard) JavaScript methods fail.
On this page:
- XPath in Equal Access results unusable when shadowDOM is involved
- Find a node inside a shadowDome
- Example
How to handle shadowDOM when specified within XPath
When scanning a page with the IBM Equal Access engine, it provides the location of flagged elements using XPath.
XPath works by traversing nodes in a page structure, similar to how you’d move through an XML tree. Each step in the XPath represents a move from one node to another, starting from the root and going down to the target element.
ShadowDOM does not present itself in the lightDOM in a standard way. By definition, it obscures a subsection of the tree.
#document-fragment is a convention that IBM created to support shadowDOM in XPath. XPath does not natively support specifying shadowDOM locations.
However, it might be placed in the XPath to indicate that we’ve crossed a shadowDOM boundary.
Consequently, the full XPath to an img flagged inside a shadowDOM might look like this:
/html[1]/body[1]/div[5]/#document-fragment[1]/div[1]/img[1]
While this solution is recognized by some automated test harness tools, it is non-standard.
document.evaluate is a standard JavaScript method to find nodes using XPath.
If you use document.evaluate to find a node with XPath and stop just before the #document-fragment, it will correctly point to where the shadow DOM is attached. But if you try to go a step further, it gives an error because XPath can’t access elements inside the shadowDOM directly.
Uncaught NotSupportedError: Failed to execute 'evaluate' on 'Document': The node provided is '#document-fragment', which is not a valid context node type.
How to find a node inside a shadowDOM specified by XPath
To find a node a node inside a shadowDOM, copy and paste this snippet into your browser’s console to split the XPath:
function getNodeBySimpleXPath(xpath, root = document) {
const segments = xpath.split("/").filter(Boolean);
let node = root;
// If root is a Document, start at <html>
if (node.nodeType === Node.DOCUMENT_NODE) {
node = node.documentElement;
}
for (let i = 0; i < segments.length; i++) {
let seg = segments[i];
if (seg === "#document-fragment[1]") {
if (!node.shadowRoot) {
return null;
}
node = node.shadowRoot;
continue;
}
let match = seg.match(/^([a-zA-Z0-9_\-*]+)\[(\d+)\]$/);
if (!match) {
throw new Error("Unsupported segment: " + seg);
}
let tag = match[1].toLowerCase();
let index = parseInt(match[2], 10);
console.debug('tag: ' + tag + ', index: ' + index);
// Special case: if we’re at <html> and seg is html[1], don’t move
if (i === 0 && node.tagName.toLowerCase() === tag && index === 1) {
continue;
}
let candidates = [];
// taking care of shadow-dom slots
let children = (node.tagName && node.tagName.toLowerCase() === 'slot')
? node.assignedNodes({flatten: true})
: node.childNodes;
for (let child of children) {
if (child.nodeType === Node.ELEMENT_NODE &&
(tag === '*' || child.tagName.toLowerCase() === tag)) {
candidates.push(child);
}
}
node = candidates[index - 1]; // XPath is 1-based
if (!node) {
console.debug('node not found - returning');
return null;
}
}
return node;
}This implements a new getNodeBySimpleXPath function. The function will return the node specified by the XPath, from the shadowDOM.
Example
Here is an example of how to get an element in the shadowDOM where the native (standard) JavaScript methods fail.
Run a scan of https://not.webaccessibility.com/shadowdom.html in the platform, and view the Equal Access results. A set of
imgelements will be flagged inside shadowDOM.
One of the images has an XPath like this:
/html[1]/body[1]/div[5]/#document-fragment[1]/div[1]/img[1]
Due to the
#document-fragment, it breaks if you try to find that node using the standarddocument.evaluatemethod. Instead, pass it in to the new helper function:
getNodeBySimpleXPath(“/html[1]/body[1]/div[5]/#document-fragment[1]/div[1]/img[1]”)
The following result is returned:
<img id="fail2" src="images/goat-4.jpg" width="200">
Hover over this img element in the console, to highlight it in the browser.
Right-click the img element, and select Scroll Into View to see where the img element appears on the page.
By using the helper function we can make use of the IBM shadowDOM XPath convention to find nodes in the shadowDOM where the native (standard) JavaScript methods fail.
Comments
0 comments
Article is closed for comments.