mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat(Page): introduce Page.waitForXPath (#1767)
This patch: - introduces `page.waitForXPath` method - introduces `frame.waitForXPath` method - amends `page.waitFor` to treat strings that start with `//` as xpath queries. Fixes #1757.
This commit is contained in:
committed by
Andrey Lushnikov
parent
62597bf897
commit
cb684ebbc4
@@ -566,8 +566,14 @@ class Frame {
|
||||
* @return {!Promise}
|
||||
*/
|
||||
waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
|
||||
if (helper.isString(selectorOrFunctionOrTimeout))
|
||||
return this.waitForSelector(/** @type {string} */(selectorOrFunctionOrTimeout), options);
|
||||
const xPathPattern = '//';
|
||||
|
||||
if (helper.isString(selectorOrFunctionOrTimeout)) {
|
||||
const string = /** @type {string} */ (selectorOrFunctionOrTimeout);
|
||||
if (string.startsWith(xPathPattern))
|
||||
return this.waitForXPath(string, options);
|
||||
return this.waitForSelector(string, options);
|
||||
}
|
||||
if (helper.isNumber(selectorOrFunctionOrTimeout))
|
||||
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout));
|
||||
if (typeof selectorOrFunctionOrTimeout === 'function')
|
||||
@@ -581,37 +587,16 @@ class Frame {
|
||||
* @return {!Promise}
|
||||
*/
|
||||
waitForSelector(selector, options = {}) {
|
||||
const timeout = options.timeout || 30000;
|
||||
const waitForVisible = !!options.visible;
|
||||
const waitForHidden = !!options.hidden;
|
||||
const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
|
||||
return this.waitForFunction(predicate, {timeout, polling}, selector, waitForVisible, waitForHidden);
|
||||
return this._waitForSelectorOrXPath(selector, false, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {boolean} waitForVisible
|
||||
* @param {boolean} waitForHidden
|
||||
* @return {?Node|boolean}
|
||||
*/
|
||||
function predicate(selector, waitForVisible, waitForHidden) {
|
||||
const node = document.querySelector(selector);
|
||||
if (!node)
|
||||
return waitForHidden;
|
||||
if (!waitForVisible && !waitForHidden)
|
||||
return node;
|
||||
const style = window.getComputedStyle(node);
|
||||
const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
|
||||
const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
|
||||
return success ? node : null;
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
function hasVisibleBoundingBox() {
|
||||
const rect = node.getBoundingClientRect();
|
||||
return !!(rect.top || rect.bottom || rect.width || rect.height);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} xpath
|
||||
* @param {!Object=} options
|
||||
* @return {!Promise}
|
||||
*/
|
||||
waitForXPath(xpath, options = {}) {
|
||||
return this._waitForSelectorOrXPath(xpath, true, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -632,6 +617,51 @@ class Frame {
|
||||
return this.evaluate(() => document.title);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selectorOrXPath
|
||||
* @param {boolean} isXPath
|
||||
* @param {!Object=} options
|
||||
* @return {!Promise}
|
||||
*/
|
||||
_waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
|
||||
const timeout = options.timeout || 30000;
|
||||
const waitForVisible = !!options.visible;
|
||||
const waitForHidden = !!options.hidden;
|
||||
const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
|
||||
return this.waitForFunction(predicate, {timeout, polling}, selectorOrXPath, isXPath, waitForVisible, waitForHidden);
|
||||
|
||||
/**
|
||||
* @param {string} selectorOrXPath
|
||||
* @param {boolean} isXPath
|
||||
* @param {boolean} waitForVisible
|
||||
* @param {boolean} waitForHidden
|
||||
* @return {?Node|boolean}
|
||||
*/
|
||||
function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
|
||||
const node = isXPath
|
||||
? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
|
||||
: document.querySelector(selectorOrXPath);
|
||||
if (!node)
|
||||
return waitForHidden;
|
||||
if (!waitForVisible && !waitForHidden)
|
||||
return node;
|
||||
const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
|
||||
|
||||
const style = window.getComputedStyle(element);
|
||||
const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
|
||||
const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
|
||||
return success ? node : null;
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
function hasVisibleBoundingBox() {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return !!(rect.top || rect.bottom || rect.width || rect.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Object} framePayload
|
||||
*/
|
||||
@@ -654,7 +684,7 @@ class Frame {
|
||||
|
||||
_detach() {
|
||||
for (const waitTask of this._waitTasks)
|
||||
waitTask.terminate(new Error('waitForSelector failed: frame got detached.'));
|
||||
waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
|
||||
this._detached = true;
|
||||
if (this._parentFrame)
|
||||
this._parentFrame._childFrames.delete(this);
|
||||
|
||||
Reference in New Issue
Block a user