Implement page.waitForFunction method

The page.waitForFunction method allows to wait for a general predicate.
The predicate will be continiously polled for in page, until
it either returns true or the timeout happens.

The polling parameter could be one of the following:
- 'raf' - to poll on every animation frame
- 'mutation' - to poll on every dom mutation
- <number> - to poll every X milliseconds

References #91
This commit is contained in:
Andrey Lushnikov
2017-07-27 15:17:43 -07:00
parent 47a0366b16
commit ff5ed1c738
5 changed files with 197 additions and 31 deletions

View File

@@ -275,9 +275,9 @@ class Frame {
* @return {!Promise}
*/
waitFor(selectorOrTimeout, options = {}) {
if (typeof selectorOrTimeout === 'string' || selectorOrTimeout instanceof String)
if (helper.isString(selectorOrTimeout))
return this.waitForSelector(selectorOrTimeout, options);
if (typeof selectorOrTimeout === 'number' || selectorOrTimeout instanceof Number)
if (helper.isNumber(selectorOrTimeout))
return new Promise(fulfill => setTimeout(fulfill, selectorOrTimeout));
return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrTimeout)));
}
@@ -290,8 +290,35 @@ class Frame {
waitForSelector(selector, options = {}) {
const timeout = options.timeout || 30000;
const waitForVisible = !!options.visible;
const pageScript = helper.evaluationString(waitForSelectorPageFunction, selector, waitForVisible, timeout);
return new WaitTask(this, pageScript, timeout).promise;
const polling = waitForVisible ? 'raf' : 'mutation';
return this.waitForFunction(predicate, {timeout, polling}, selector, waitForVisible);
/**
* @param {string} selector
* @param {boolean} waitForVisible
* @return {boolean}
*/
function predicate(selector, waitForVisible) {
const node = document.querySelector(selector);
if (!node)
return false;
if (!waitForVisible)
return true;
const style = window.getComputedStyle(node);
return style && style.display !== 'none' && style.visibility !== 'hidden';
}
}
/**
* @param {function()} pageFunction
* @param {!Object=} options
* @return {!Promise}
*/
waitForFunction(pageFunction, options = {}, ...args) {
const timeout = options.timeout || 30000;
const polling = options.polling || 'raf';
const predicateCode = 'return ' + helper.evaluationString(pageFunction, ...args);
return new WaitTask(this, predicateCode, polling, timeout).promise;
}
/**
@@ -415,12 +442,20 @@ helper.tracePublicAPI(Frame);
class WaitTask {
/**
* @param {!Frame} frame
* @param {string} pageScript
* @param {string} predicateBody
* @param {string} polling
* @param {number} timeout
*/
constructor(frame, pageScript, timeout) {
constructor(frame, predicateBody, polling, timeout) {
if (helper.isString(polling))
console.assert(polling === 'raf' || polling === 'mutation', 'Unknown polling option: ' + polling);
else if (helper.isNumber(polling))
console.assert(polling > 0, 'Cannot poll with non-positive interval: ' + polling);
else
throw new Error('Unknown polling options: ' + polling);
this._frame = frame;
this._pageScript = pageScript;
this._pageScript = helper.evaluationString(waitForPredicatePageFunction, predicateBody, polling, timeout);
this._runCount = 0;
frame._waitTasks.add(this);
this.promise = new Promise((resolve, reject) => {
@@ -475,31 +510,34 @@ class WaitTask {
}
/**
* @param {string} selector
* @param {boolean} waitForVisible
* @param {string} predicateBody
* @param {string} polling
* @param {number} timeout
* @return {!Promise<boolean>}
*/
async function waitForSelectorPageFunction(selector, visible, timeout) {
async function waitForPredicatePageFunction(predicateBody, polling, timeout) {
const predicate = new Function(predicateBody);
let timedOut = false;
setTimeout(() => timedOut = true, timeout);
await waitForDOM();
await waitForVisible();
if (polling === 'raf')
await pollRaf();
else if (polling === 'mutation')
await pollMutation();
else if (typeof polling === 'number')
await pollInterval(polling);
return !timedOut;
/**
* @return {!Promise<!Element>}
*/
function waitForDOM() {
let node = document.querySelector(selector);
if (node)
function pollMutation() {
if (predicate())
return Promise.resolve();
let fulfill;
const result = new Promise(x => fulfill = x);
const observer = new MutationObserver(mutations => {
const node = document.querySelector(selector);
if (node || timedOut) {
if (timedOut || predicate()) {
observer.disconnect();
fulfill();
}
@@ -512,26 +550,37 @@ async function waitForSelectorPageFunction(selector, visible, timeout) {
}
/**
* @return {!Promise<!Element>}
* @return {!Promise}
*/
function waitForVisible() {
function pollRaf() {
let fulfill;
const result = new Promise(x => fulfill = x);
onRaf();
return result;
function onRaf() {
if (timedOut) {
if (timedOut || predicate())
fulfill();
return;
}
const node = document.querySelector(selector);
const style = node ? window.getComputedStyle(node) : null;
if (!style || style.display === 'none' || style.visibility === 'hidden') {
else
requestAnimationFrame(onRaf);
return;
}
fulfill();
}
}
/**
* @param {number} pollInterval
* @return {!Promise}
*/
function pollInterval(pollInterval) {
let fulfill;
const result = new Promise(x => fulfill = x);
onTimeout();
return result;
function onTimeout() {
if (timedOut || predicate())
fulfill();
else
setTimeout(onTimeout, pollInterval);
}
}
}