diff --git a/docs/api.md b/docs/api.md index 71d6a7f2..4693614d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -36,6 +36,7 @@ * [page.url()](#pageurl) * [page.userAgent()](#pageuseragent) * [page.viewportSize()](#pageviewportsize) + * [page.waitFor(selector)](#pagewaitforselector) - [class: Dialog](#class-dialog) * [dialog.accept()](#dialogaccept) * [dialog.dismiss()](#dialogdismiss) @@ -49,6 +50,7 @@ * [frame.parentFrame()](#frameparentframe) * [frame.securityOrigin()](#framesecurityorigin) * [frame.url()](#frameurl) + * [frame.waitFor(selector)](#framewaitforselector) - [class: Request](#class-request) * [request.response()](#requestresponse) - [class: Response](#class-response) @@ -248,6 +250,9 @@ Pages could be closed by `page.close()` method. - `width` <[number]> Page's width in pixels. - `height` <[number]> Page's height in pixels. +#### page.waitFor(selector) + +Shortcut for [page.mainFrame().waitFor(selector)](#framewaitforselector). ### class: Dialog #### dialog.accept() @@ -268,6 +273,11 @@ Pages could be closed by `page.close()` method. #### frame.parentFrame() #### frame.securityOrigin() #### frame.url() +#### frame.waitFor(selector) + +- `selector` <[string]> CSS selector of awaited element, +- returns: <[Promise]> Promise which resolves when element specified by selector string is added to DOM. + ### class: Request #### request.response() diff --git a/lib/FrameManager.js b/lib/FrameManager.js index 41ad7fb1..28662e17 100644 --- a/lib/FrameManager.js +++ b/lib/FrameManager.js @@ -197,6 +197,39 @@ class FrameManager extends EventEmitter { }); return response.result.value; } + + /** + * @param {string} selector + * @param {!Frame} frame + * @return {!Promise} + */ + async _waitForInFrame(selector, frame) { + let code = selector => new Promise((fulfill, reject) => { + if (document.querySelector(selector)) { + fulfill(); + return; + } + new MutationObserver((mutations, observer) => { + for (let mutation of mutations) { + for (let node of mutation.addedNodes) { + if (node.matches(selector)) { + observer.disconnect(); + fulfill(); + return; + } + } + } + }).observe(document.documentElement, { + childList: true, + subtree: true + }); + }); + await this._client.send('Runtime.evaluate', { + expression: helper.evaluationString(code, selector), + awaitPromise: true, + returnByValue: true, + }); + } } /** @enum {string} */ @@ -287,6 +320,14 @@ class Frame { return this._detached; } + /** + * @param {string} selector + * @return {!Promise} + */ + async waitFor(selector) { + await this._frameManager._waitForInFrame(selector, this); + } + /** * @param {?Object} framePayload */ diff --git a/lib/Page.js b/lib/Page.js index ae4877e8..81b602f9 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -561,6 +561,14 @@ class Page extends EventEmitter { }); } } + + /** + * @param {string} selector + * @return {!Promise} + */ + waitFor(selector) { + return this.mainFrame().waitFor(selector); + } } /** @enum {string} */ diff --git a/test/test.js b/test/test.js index 37301e43..1ac78349 100644 --- a/test/test.js +++ b/test/test.js @@ -131,6 +131,51 @@ describe('Puppeteer', function() { })); }); + describe('Frame.waitFor', function() { + let FrameUtils = require('./frame-utils'); + let addElement = tag => document.body.appendChild(document.createElement(tag)); + + it('should immediately resolve promise if node exists', SX(async function() { + await page.navigate(EMPTY_PAGE); + let frame = page.mainFrame(); + let added = false; + await frame.waitFor('*').then(() => added = true); + expect(added).toBe(true); + + added = false; + await frame.evaluate(addElement, 'div'); + await frame.waitFor('div').then(() => added = true); + expect(added).toBe(true); + })); + + it('should resolve promise when node is added', SX(async function() { + await page.navigate(EMPTY_PAGE); + let frame = page.mainFrame(); + let added = false; + frame.waitFor('div').then(() => added = true); + // run nop function.. + await frame.evaluate(() => 42); + // .. to be sure that waitFor promise is not resolved yet. + expect(added).toBe(false); + await frame.evaluate(addElement, 'br'); + expect(added).toBe(false); + await frame.evaluate(addElement, 'div'); + expect(added).toBe(true); + })); + + it('Page.waitFor is shortcut for main frame', SX(async function() { + await page.navigate(EMPTY_PAGE); + await FrameUtils.attachFrame(page, 'frame1', EMPTY_PAGE); + let otherFrame = page.frames()[1]; + let added = false; + page.waitFor('div').then(() => added = true); + await otherFrame.evaluate(addElement, 'div'); + expect(added).toBe(false); + await page.evaluate(addElement, 'div'); + expect(added).toBe(true); + })); + }); + it('Page Events: ConsoleMessage', SX(async function() { let msgs = []; page.on('consolemessage', msg => msgs.push(msg));