/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
build-system/tasks/e2e/driver/query-xpath.js
164 строки
5 KB
Kristofer Baxter
♻️ Remove Notice from JS and Markdown (#35717)
19 авг 2021, 21:21
Не верифицирован
19 авг 2021, 21:21
f564b7e
Код
Авторство
О чём код?
const TEST_ID_PROPERTY = 'iAmphtmlXpathTestId'; const TEST_ID_ATTRIBUTE = 'data-i-amphtml-xpath-test-id'; /** * We use this function to query for elements with XPath because the standard * XPath implementation that ships with the browser is unable to do * the following: * 1. Query elements beyond a ShadowDOM boundary * 2. Query elements inside a document that is not the main document * To pass the ShadowDOM boundary, we clone the ShadowDOM tree into a detached * document and run the XPath query against this document. Before cloning the * original tree, every node is assigned a unique ID which is used to match * the results of the XPath query in the cloned tree to nodes in the original * document's tree. * * This function runs in the document under test, and has no access to * variables except what is passed in and what is on the global object * in the test page. * * This returns null instead of an empty list when there are not yet any * results because Selenium uses the null value to determine that it should * continue waiting. * * Precondition: wgxpath has been installed on the window object * @param {string} xpathString * @param {!HTMLElement} context * @return {?Array<!HTMLElement>} */ function queryXpath(xpathString, context) { // Install https://github.com/google/wicked-good-xpath // Its implementation is capable of evaluating xpath from detached // documents. const {wgxpath} = window; const fakeWindow = {document: {}}; wgxpath.install(fakeWindow); const {XPathResult} = fakeWindow; const {evaluate} = fakeWindow.document; // Add an ID to every element in the tree so we can reference them later. // This allows us to correlate nodes from the cloned tree to the // original tree. for (const {index, item} of createIndexedInterator( createElementIterator(context) )) { setData(item, index); } const fakeDocument = document.implementation.createDocument( 'http://www.w3.org/1999/xhtml', 'html', null ); try { fakeDocument.documentElement.appendChild( context.cloneNode(/* deep */ true) ); } catch (e) { // Appending the AMP `CustomElement`s to the new document throws errors // because the implementations expect the AmpDoc to be present and it // is not. // It's ok to ignore this error since we just need the DOM structure. } // Map the xpath results from the fake tree to the corresponding nodes in // the test document tree. const xpathIterator = createXpathIterator( evaluate(xpathString, fakeDocument, null, XPathResult.ANY_TYPE) ); const elements = [...xpathIterator].map((node) => { const testId = getData(node); const selector = `[${TEST_ID_ATTRIBUTE}="${testId}"]`; return context./*OK*/ querySelector(selector); }); // Restore the DOM to the original state without the ID in the dataset. for (const node of createElementIterator(context)) { removeData(node); } return elements.length ? /** @type {Array<HTMLElement>} */ (elements) : null; } /** * Get the xpath test id data param * @param {!HTMLElement} node * @return {string|undefined} */ function getData(node) { return node.dataset[TEST_ID_PROPERTY]; } /** * Set the xpath test id data param * @param {!HTMLElement} node * @param {number} value */ function setData(node, value) { node.dataset[TEST_ID_PROPERTY] = value.toString(); } /** * Remove the xpath test id data param. * @param {!HTMLElement} node */ function removeData(node) { delete node.dataset[TEST_ID_PROPERTY]; } /** * Create an iterator over the subtree of the given element. * @param {!HTMLElement} element * @return {!Iterable<!HTMLElement>} */ function createElementIterator(element) { const document = element.ownerDocument; return createCommonIterator( document.createNodeIterator(element, NodeFilter.SHOW_ELEMENT), 'nextNode' ); } /** * Create an iterator from an XPathResult instance. * @param {!XPathResult} xpathResult * @return {!Iterable<!HTMLElement>} */ function createXpathIterator(xpathResult) { return createCommonIterator(xpathResult, 'iterateNext'); } /** * Create an iterator from an object with iterator-like behavior but a * different API name for the iteration method. * @template T * @param {!NodeIterator|!XPathResult} iterator * @param {string} nextProperty * @return {!Iterable<T>} */ function* createCommonIterator(iterator, nextProperty) { let item; while (Boolean((item = iterator[nextProperty]()))) { yield item; } } /** * Create an iterator from an existing iterator, which nests the original * value under the `item` property in the iteration object value and adds an * `index` property. * @template T * @param {!Iterable<T>} iter * @return {!Iterable<{item: T, index: number}>} */ function* createIndexedInterator(iter) { let index = 0; for (const item of iter) { yield {item, index}; index++; } } window.queryXpath = queryXpath;