Implement timeout option for page.waitFor

This patch implements timeout option for page.waitFor. The function
will throw if the selector doesn't appear during timeout milliseconds
of waittime.

References #89, #91.
This commit is contained in:
Andrey Lushnikov 2017-07-21 11:46:57 -07:00
parent aba61de905
commit 1f954fa7ed
3 changed files with 24 additions and 7 deletions

View File

@ -804,12 +804,13 @@ Returns frame's url.
#### frame.waitFor(selector[, options]) #### frame.waitFor(selector[, options])
- `selector` <[string]> CSS selector of awaited element, - `selector` <[string]> CSS selector of awaited element,
- `options` <[Object]> Optional waiting parameters - `options` <[Object]> Optional waiting parameters
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. - `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds).
- returns: <[Promise]> Promise which resolves when element specified by selector string is added to DOM. - returns: <[Promise]> Promise which resolves when element specified by selector string is added to DOM.
Wait for the `selector` to appear in page. If at the moment of calling Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return the method the `selector` already exists, the method will return
immediately. immediately. If the selector doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
This method works across navigations: This method works across navigations:
```js ```js

View File

@ -174,16 +174,17 @@ class FrameManager extends EventEmitter {
* @param {!Frame} frame * @param {!Frame} frame
* @param {string} selector * @param {string} selector
* @param {boolean} waitForVisible * @param {boolean} waitForVisible
* @param {number} timeout
* @return {!Promise<undefined>} * @return {!Promise<undefined>}
*/ */
async _waitForSelector(frame, selector, waitForVisible) { async _waitForSelector(frame, selector, waitForVisible, timeout) {
let contextId = undefined; let contextId = undefined;
if (!frame.isMainFrame()) { if (!frame.isMainFrame()) {
contextId = this._frameIdToExecutionContextId.get(frame._id); contextId = this._frameIdToExecutionContextId.get(frame._id);
console.assert(contextId, 'Frame does not have default context to evaluate in!'); console.assert(contextId, 'Frame does not have default context to evaluate in!');
} }
let { exceptionDetails } = await this._client.send('Runtime.evaluate', { let { exceptionDetails } = await this._client.send('Runtime.evaluate', {
expression: helper.evaluationString(inPageWatchdog, selector, waitForVisible), expression: helper.evaluationString(inPageWatchdog, selector, waitForVisible, timeout),
contextId, contextId,
awaitPromise: true, awaitPromise: true,
returnByValue: false, returnByValue: false,
@ -194,10 +195,15 @@ class FrameManager extends EventEmitter {
/** /**
* @param {string} selector * @param {string} selector
* @param {boolean} waitForVisible * @param {boolean} waitForVisible
* @param {number} timeout
* @return {!Promise} * @return {!Promise}
*/ */
function inPageWatchdog(selector, visible) { async function inPageWatchdog(selector, visible, timeout) {
return visible ? waitForVisible(selector) : waitInDOM(selector); const resultPromise = visible ? waitForVisible(selector) : waitInDOM(selector);
const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(reject.bind(null, new Error(`waitFor failed: timeout ${timeout}ms exceeded.`)), timeout);
});
await Promise.race([resultPromise, timeoutPromise]);
/** /**
* @param {string} selector * @param {string} selector
@ -342,7 +348,11 @@ class Frame {
* @return {!Promise} * @return {!Promise}
*/ */
async waitFor(selector, options = {}) { async waitFor(selector, options = {}) {
const awaitedElement = new AwaitedElement(() => this._frameManager._waitForSelector(this, selector, !!options.visible)); const timeout = options.timeout || 30000;
const awaitedElement = new AwaitedElement(() => this._frameManager._waitForSelector(this, selector, !!options.visible, timeout));
// Since navigation will re-install page watchdogs, we should timeout on our
// end as well.
setTimeout(() => awaitedElement.terminate(new Error(`waitFor failed: timeout ${timeout}ms exceeded`)), timeout);
this._awaitedElements.add(awaitedElement); this._awaitedElements.add(awaitedElement);
let cleanup = () => this._awaitedElements.delete(awaitedElement); let cleanup = () => this._awaitedElements.delete(awaitedElement);

View File

@ -274,6 +274,12 @@ describe('Puppeteer', function() {
await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility')); await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
expect(await waitFor).toBe(true); expect(await waitFor).toBe(true);
})); }));
it('should respect timeout', SX(async function() {
let error = null;
await page.waitFor('div', {timeout: 10}).catch(e => error = e);
expect(error).toBeTruthy();
expect(error.message).toContain('waitFor failed: timeout');
}));
}); });
describe('Page.Events.Console', function() { describe('Page.Events.Console', function() {