diff --git a/docs/api.md b/docs/api.md index b1afa8a6756..f6ea2038616 100644 --- a/docs/api.md +++ b/docs/api.md @@ -59,8 +59,9 @@ + [page.url()](#pageurl) + [page.userAgent()](#pageuseragent) + [page.viewport()](#pageviewport) - + [page.waitFor(selector[, options])](#pagewaitforselector-options) + + [page.waitFor(target[, options])](#pagewaitfortarget-options) + [page.waitForNavigation(options)](#pagewaitfornavigationoptions) + + [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) * [class: Keyboard](#class-keyboard) + [keyboard.down(key[, options])](#keyboarddownkey-options) + [keyboard.modifiers()](#keyboardmodifiers) @@ -83,7 +84,8 @@ + [frame.name()](#framename) + [frame.parentFrame()](#frameparentframe) + [frame.url()](#frameurl) - + [frame.waitFor(selector[, options])](#framewaitforselector-options) + + [frame.waitFor(target[, options])](#framewaitfortarget-options) + + [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options) * [class: Request](#class-request) + [request.headers](#requestheaders) + [request.method](#requestmethod) @@ -592,18 +594,24 @@ This is a shortcut for [page.mainFrame().url()](#frameurl) #### page.viewport() - returns: <[Object]> An object with the save fields as described in [page.setViewport](#pagesetviewportviewport) +#### page.waitFor(target[, options]) +- `target` <[string]|[number]> A target to wait for. +- `options` <[Object]> Optional waiting parameters. +- returns: <[Promise]> -#### page.waitFor(selector[, options]) -- `selector` <[string]> A query selector to wait for on the page. -- `options` <[Object]> Optional waiting parameters. Same as options for the [frame.waitFor](#framewaitforselector) -- returns: <[Promise]> Promise which resolves when the element matching `selector` appears in the page. - -Shortcut for [page.mainFrame().waitFor(selector)](#framewaitforselector). +Shortcut for [page.mainFrame().waitFor()](#framewaitfortargetoptions). #### page.waitForNavigation(options) - `options` <[Object]> Navigation parameters, same as in [page.navigate](#pagenavigateurl-options). - returns: <[Promise]<[Response]>> Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. +#### page.waitForSelector(selector[, options]) +- `selector` <[string]> A query selector to wait for on the page. +- `options` <[Object]> Optional waiting parameters. Same as options for the [frame.waitFor](#framewaitforselector) +- returns: <[Promise]> Promise which resolves when the element matching `selector` appears in the page. + +Shortcut for [page.mainFrame().waitForSelector()](#framewaitforselectorselectoroptions). + ### class: Keyboard Keyboard provides an api for managing a virtual keyboard. The high level api is [`keyboard.type`](#keyboardtypetext), which takes raw characters and generates proper keydown, keypress/input, and keyup events on your page. @@ -800,7 +808,17 @@ Returns frame's name as specified in the tag. Returns frame's url. -#### frame.waitFor(selector[, options]) +#### frame.waitFor(target[, options]) +- `target` <[string]|[number]> A target to wait for +- `options` <[Object]> Optional waiting parameters +- returns: <[Promise]> + +This method behaves differently wrt the type of the first parameter: +- if `target` is a `string`, than target is treated as a selector to wait for and the method is a shortcut for [frame.waitForSelector](#framewaitforselectorselectoroptions) +- if `target` is a `number`, than target is treated as timeout in milliseconds and the method returns a promise which resolves after the timeout +- otherwise, an exception is thrown + +#### frame.waitForSelector(selector[, options]) - `selector` <[string]> CSS selector of awaited element, - `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. Defaults to `false`. @@ -818,7 +836,7 @@ const browser = new Browser(); browser.newPage().then(async page => { let currentURL; - page.waitFor('img').then(() => console.log('First URL with image: ' + currentURL)); + page.waitForSelector('img').then(() => console.log('First URL with image: ' + currentURL)); for (currentURL of ['https://example.com', 'https://google.com', 'https://bbc.com']) await page.navigate(currentURL); browser.close(); diff --git a/lib/FrameManager.js b/lib/FrameManager.js index aa3afc7f974..7e8ba3743cb 100644 --- a/lib/FrameManager.js +++ b/lib/FrameManager.js @@ -201,7 +201,7 @@ class FrameManager extends EventEmitter { async function inPageWatchdog(selector, visible, timeout) { 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); + setTimeout(reject.bind(null, new Error(`waitForSelector failed: timeout ${timeout}ms exceeded.`)), timeout); }); await Promise.race([resultPromise, timeoutPromise]); @@ -342,17 +342,30 @@ class Frame { return this._detached; } + /** + * @param {(string|number)} target + * @param {!Object=} options + * @return {!Promise} + */ + waitFor(target, options = {}) { + if (typeof target === 'string' || target instanceof String) + return this.waitForSelector(target, options); + if (typeof target === 'number' || target instanceof Number) + return new Promise(fulfill => setTimeout(fulfill, target)); + return Promise.reject(new Error('Unsupported target type: ' + (typeof target))); + } + /** * @param {string} selector * @param {!Object=} options * @return {!Promise} */ - async waitFor(selector, options = {}) { + waitForSelector(selector, options = {}) { 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); + setTimeout(() => awaitedElement.terminate(new Error(`waitForSelector failed: timeout ${timeout}ms exceeded`)), timeout); this._awaitedElements.add(awaitedElement); let cleanup = () => this._awaitedElements.delete(awaitedElement); diff --git a/lib/Page.js b/lib/Page.js index 9f107932b52..dde60b47c8f 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -597,12 +597,21 @@ class Page extends EventEmitter { } /** - * @param {string} selector + * @param {string} target * @param {!Object=} options * @return {!Promise} */ - waitFor(selector, options) { - return this.mainFrame().waitFor(selector, options); + waitFor(target, options) { + return this.mainFrame().waitFor(target, options); + } + + /** + * @param {string} selector + * @param {!Object=} options + * @return {!Promise} + */ + waitForSelector(selector, options = {}) { + return this.mainFrame().waitForSelector(selector, options); } /** diff --git a/test/test.js b/test/test.js index 2cb62003cbb..088623fc1d1 100644 --- a/test/test.js +++ b/test/test.js @@ -162,7 +162,7 @@ describe('Puppeteer', function() { })); }); - describe('Frame.waitFor', function() { + describe('Frame.waitForSelector', function() { let FrameUtils = require('./frame-utils'); let addElement = tag => document.body.appendChild(document.createElement(tag)); @@ -170,12 +170,12 @@ describe('Puppeteer', function() { await page.navigate(EMPTY_PAGE); let frame = page.mainFrame(); let added = false; - await frame.waitFor('*').then(() => added = true); + await frame.waitForSelector('*').then(() => added = true); expect(added).toBe(true); added = false; await frame.evaluate(addElement, 'div'); - await frame.waitFor('div').then(() => added = true); + await frame.waitForSelector('div').then(() => added = true); expect(added).toBe(true); })); @@ -183,10 +183,10 @@ describe('Puppeteer', function() { await page.navigate(EMPTY_PAGE); let frame = page.mainFrame(); let added = false; - frame.waitFor('div').then(() => added = true); + frame.waitForSelector('div').then(() => added = true); // run nop function.. await frame.evaluate(() => 42); - // .. to be sure that waitFor promise is not resolved yet. + // .. to be sure that waitForSelector promise is not resolved yet. expect(added).toBe(false); await frame.evaluate(addElement, 'br'); expect(added).toBe(false); @@ -198,19 +198,19 @@ describe('Puppeteer', function() { await page.navigate(EMPTY_PAGE); let frame = page.mainFrame(); let added = false; - frame.waitFor('h3 div').then(() => added = true); + frame.waitForSelector('h3 div').then(() => added = true); expect(added).toBe(false); await frame.evaluate(addElement, 'span'); await page.$('span', span => span.innerHTML = '

'); expect(added).toBe(true); })); - it('Page.waitFor is shortcut for main frame', SX(async function() { + it('Page.waitForSelector 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); + page.waitForSelector('div').then(() => added = true); await otherFrame.evaluate(addElement, 'div'); expect(added).toBe(false); await page.evaluate(addElement, 'div'); @@ -223,7 +223,7 @@ describe('Puppeteer', function() { let frame1 = page.frames()[1]; let frame2 = page.frames()[2]; let added = false; - frame2.waitFor('div').then(() => added = true); + frame2.waitForSelector('div').then(() => added = true); expect(added).toBe(false); await frame1.evaluate(addElement, 'div'); expect(added).toBe(false); @@ -237,8 +237,8 @@ describe('Puppeteer', function() { }); await page.navigate(EMPTY_PAGE); try { - await page.waitFor('*'); - fail('Failed waitFor did not throw.'); + await page.waitForSelector('*'); + fail('Failed waitForSelector did not throw.'); } catch (e) { expect(e.message).toContain('document.querySelector is not a function'); } @@ -247,7 +247,7 @@ describe('Puppeteer', function() { await FrameUtils.attachFrame(page, 'frame1', EMPTY_PAGE); let frame = page.frames()[1]; let waitError = null; - let waitPromise = frame.waitFor('.box').catch(e => waitError = e); + let waitPromise = frame.waitForSelector('.box').catch(e => waitError = e); await FrameUtils.detachFrame(page, 'frame1'); await waitPromise; expect(waitError).toBeTruthy(); @@ -255,30 +255,56 @@ describe('Puppeteer', function() { })); it('should survive navigation', SX(async function() { let boxFound = false; - let waitFor = page.waitFor('.box').then(() => boxFound = true); + let waitForSelector = page.waitForSelector('.box').then(() => boxFound = true); await page.navigate(EMPTY_PAGE); expect(boxFound).toBe(false); await page.reload(); expect(boxFound).toBe(false); await page.navigate(PREFIX + '/grid.html'); - await waitFor; + await waitForSelector; expect(boxFound).toBe(true); })); it('should wait for visible', SX(async function() { let divFound = false; - let waitFor = page.waitFor('div', {visible: true}).then(() => divFound = true); + let waitForSelector = page.waitForSelector('div', {visible: true}).then(() => divFound = true); await page.setContent(`
`); expect(divFound).toBe(false); await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); expect(divFound).toBe(false); await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility')); - expect(await waitFor).toBe(true); + expect(await waitForSelector).toBe(true); })); it('should respect timeout', SX(async function() { let error = null; - await page.waitFor('div', {timeout: 10}).catch(e => error = e); + await page.waitForSelector('div', {timeout: 10}).catch(e => error = e); expect(error).toBeTruthy(); - expect(error.message).toContain('waitFor failed: timeout'); + expect(error.message).toContain('waitForSelector failed: timeout'); + })); + }); + + describe('Page.waitFor', function() { + it('should wait for selector', SX(async function() { + let found = false; + let waitFor = page.waitFor('div').then(() => found = true); + await page.navigate(EMPTY_PAGE); + expect(found).toBe(false); + await page.navigate(PREFIX + '/grid.html'); + await waitFor; + expect(found).toBe(true); + })); + it('should timeout', SX(async function() { + startTime = Date.now(); + const timeout = 42; + await page.waitFor(timeout); + expect(Date.now() - startTime).not.toBeLessThan(timeout); + })); + it('should throw when unknown type', SX(async function() { + try { + await page.waitFor({foo: 'bar'}); + fail('Failed to throw exception'); + } catch (e) { + expect(e.message).toContain('Unsupported target type'); + } })); }); diff --git a/utils/doclint/lint.js b/utils/doclint/lint.js index 8b06500ff74..6d3e7783711 100644 --- a/utils/doclint/lint.js +++ b/utils/doclint/lint.js @@ -94,10 +94,13 @@ function lintMarkdown(doc) { if (member1.type === 'method' && member1.name === 'constructor') continue; if (member1.name > member2.name) { - let memberName = `${cls.name}.${member1.name}`; + let memberName1 = `${cls.name}.${member1.name}`; if (member1.type === 'method') - memberName += '()'; - errors.push(`${memberName} breaks alphabetic ordering of class members.`); + memberName1 += '()'; + let memberName2 = `${cls.name}.${member2.name}`; + if (member2.type === 'method') + memberName2 += '()'; + errors.push(`Bad alphabetic ordering of ${cls.name} members: ${memberName1} should go after ${memberName2}`); } } } diff --git a/utils/doclint/test/golden/07-sorting.txt b/utils/doclint/test/golden/07-sorting.txt index 339ef8ae343..39af4c61e75 100644 --- a/utils/doclint/test/golden/07-sorting.txt +++ b/utils/doclint/test/golden/07-sorting.txt @@ -1,5 +1,5 @@ [MarkDown] Events should go first. Event 'b' in class Foo breaks order [MarkDown] Constructor of Foo should go before other methods [MarkDown] Event 'c' in class Foo breaks alphabetic ordering of events -[MarkDown] Foo.ddd breaks alphabetic ordering of class members. -[MarkDown] Foo.ccc() breaks alphabetic ordering of class members. \ No newline at end of file +[MarkDown] Bad alphabetic ordering of Foo members: Foo.ddd should go after Foo.constructor() +[MarkDown] Bad alphabetic ordering of Foo members: Foo.ccc() should go after Foo.bbb() \ No newline at end of file