mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
Introduce polymorphic page.waitFor method
This patch: - introduces page.waitForSelector to wait for the selector to appear - introduces polymorphic page.waitFor method, which accepts either string (and in this case is a shortcut for page.waitForSelector) or number (and in this case it's a promisified timeout). References #91.
This commit is contained in:
parent
1891a49962
commit
dc032b42b9
38
docs/api.md
38
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();
|
||||
|
@ -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);
|
||||
|
15
lib/Page.js
15
lib/Page.js
@ -597,12 +597,21 @@ class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {string} target
|
||||
* @param {!Object=} options
|
||||
* @return {!Promise<undefined>}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
62
test/test.js
62
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 = '<h3><div></div></h3>');
|
||||
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(`<div style='display: none;visibility: hidden'></div>`);
|
||||
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');
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
[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()
|
Loading…
Reference in New Issue
Block a user