feat(page): introduce waitForRequest and waitForResponse methods (#2776)

This patch introduces `page.waitForRequest` and `page.waitForResponse` helper methods.

Fixes #2362
This commit is contained in:
Matthew Shirley 2018-07-12 14:36:31 -07:00 committed by Andrey Lushnikov
parent 3ebbf125ff
commit 726c8dc046
4 changed files with 172 additions and 0 deletions

View File

@ -128,6 +128,8 @@
* [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args)
* [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
* [page.waitForNavigation(options)](#pagewaitfornavigationoptions)
* [page.waitForRequest(urlOrPredicate, options)](#pagewaitforrequesturlorpredicate-options)
* [page.waitForResponse(urlOrPredicate, options)](#pagewaitforresponseurlorpredicate-options)
* [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options)
* [page.waitForXPath(xpath[, options])](#pagewaitforxpathxpath-options)
* [page.workers()](#pageworkers)
@ -1572,6 +1574,30 @@ await navigationPromise; // The navigationPromise resolves after navigation has
**NOTE** Usage of the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to change the URL is considered a navigation.
#### page.waitForRequest(urlOrPredicate, options)
- `urlOrPredicate` <[string]|[Function]> A URL or predicate to wait for.
- `options` <[Object]> Optional waiting parameters
- `timeout` <[number]> Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout.
- returns: <[Promise]<[Request]>> Promise which resolves to the matched request.
```js
const firstRequest = await page.waitForRequest('http://example.com/resource');
const finalRequest = await page.waitForRequest(request => request.url() === 'http://example.com' && request.method() === 'GET');
return firstRequest.url();
```
#### page.waitForResponse(urlOrPredicate, options)
- `urlOrPredicate` <[string]|[Function]> A URL or predicate to wait for.
- `options` <[Object]> Optional waiting parameters
- `timeout` <[number]> Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout.
- returns: <[Promise]<[Response]>> Promise which resolves to the matched response.
```js
const firstResponse = await page.waitForResponse('https://example.com/resource');
const finalResponse = await page.waitForResponse(response => response.url() === 'https://example.com' && response.status() === 200);
return finalResponse.ok();
```
#### page.waitForSelector(selector[, options])
- `selector` <[string]> A [selector] of an element to wait for
- `options` <[Object]> Optional waiting parameters

View File

@ -632,6 +632,38 @@ class Page extends EventEmitter {
return responses.get(this.mainFrame().url()) || null;
}
/**
* @param {(string|Function)} urlOrPredicate
* @param {!Object=} options
* @return {!Promise<!Puppeteer.Request>}
*/
async waitForRequest(urlOrPredicate, options = {}) {
const timeout = typeof options.timeout === 'number' ? options.timeout : 30000;
return helper.waitForEvent(this._networkManager, NetworkManager.Events.Request, request => {
if (helper.isString(urlOrPredicate))
return (urlOrPredicate === request.url());
if (typeof urlOrPredicate === 'function')
return !!(urlOrPredicate(request));
return false;
}, timeout);
}
/**
* @param {(string|Function)} urlOrPredicate
* @param {!Object=} options
* @return {!Promise<!Puppeteer.Response>}
*/
async waitForResponse(urlOrPredicate, options = {}) {
const timeout = typeof options.timeout === 'number' ? options.timeout : 30000;
return helper.waitForEvent(this._networkManager, NetworkManager.Events.Response, response => {
if (helper.isString(urlOrPredicate))
return (urlOrPredicate === response.url());
if (typeof urlOrPredicate === 'function')
return !!(urlOrPredicate(response));
return false;
}, timeout);
}
/**
* @param {!Object=} options
* @return {!Promise<?Puppeteer.Response>}

View File

@ -240,6 +240,37 @@ class Helper {
}
return promisified;
}
/**
* @param {!NodeJS.EventEmitter} emitter
* @param {string} eventName
* @param {function} predicate
* @return {!Promise}
*/
static waitForEvent(emitter, eventName, predicate, timeout) {
let eventTimeout, resolveCallback, rejectCallback;
const promise = new Promise((resolve, reject) => {
resolveCallback = resolve;
rejectCallback = reject;
});
const listener = Helper.addEventListener(emitter, eventName, event => {
if (!predicate(event))
return;
cleanup();
resolveCallback(event);
});
if (timeout) {
eventTimeout = setTimeout(() => {
cleanup();
rejectCallback(new Error('Timeout exceeded while waiting for event'));
}, timeout);
}
function cleanup() {
Helper.removeEventListeners([listener]);
clearTimeout(eventTimeout);
}
return promise;
}
}
/**

View File

@ -786,6 +786,89 @@ module.exports.addTests = function({testRunner, expect, puppeteer, DeviceDescrip
});
});
describe('Page.waitForRequest', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([
page.waitForRequest(server.PREFIX + '/404'),
page.evaluate(() => fetch('/404', {method: 'GET'}))
]);
expect(request.method()).toBe('GET');
expect(request.url()).toBe(server.PREFIX + '/404');
});
it('should work with regex pattern', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([
page.waitForRequest(request => new RegExp(`${server.PREFIX}/(200|404)`).test(request.url())),
page.evaluate(() => fetch('/404', {method: 'GET'}))
]);
expect(request.method()).toBe('GET');
expect(request.url()).toBe(server.PREFIX + '/404');
});
it('should work with predicate', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([
page.waitForRequest(request => request.url() === server.PREFIX + '/404' && request.method() === 'PATCH'),
page.evaluate(() => fetch('/404', {method: 'PATCH'}))
]);
expect(request.method()).toBe('PATCH');
expect(request.url()).toBe(server.PREFIX + '/404');
});
it('should work with no timeout', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [request] = await Promise.all([
page.waitForRequest(server.PREFIX + '/404', { timeout: 0}),
page.waitFor(50),
page.evaluate(() => fetch('/404', {method: 'GET'}))
]);
expect(request.method()).toBe('GET');
expect(request.url()).toBe(server.PREFIX + '/404');
});
});
describe('Page.waitForResponse', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [response] = await Promise.all([
page.waitForResponse(server.PREFIX + '/grid.html'),
page.waitFor(100),
page.evaluate(() => fetch('/grid.html', {method: 'GET'}))
]);
expect(response.ok()).toBe(true);
expect(response.url()).toBe(server.PREFIX + '/grid.html');
});
it('should work with regex', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [response] = await Promise.all([
page.waitForResponse(response => new RegExp(`${server.PREFIX}/(get|grid.html)`).test(response.url())),
page.waitFor(100),
page.evaluate(() => fetch('/grid.html', {method: 'GET'}))
]);
expect(response.ok()).toBe(true);
expect(response.url()).toBe(server.PREFIX + '/grid.html');
});
it('should work with predicate', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [response] = await Promise.all([
page.waitForResponse(response => response.url() === server.PREFIX + '/grid.html' && response.status() === 200),
page.waitFor(100),
page.evaluate(() => fetch('/grid.html', {method: 'PATCH'}))
]);
expect(response.ok()).toBe(true);
expect(response.url()).toBe(server.PREFIX + '/grid.html');
});
it('should work with no timeout', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const [response] = await Promise.all([
page.waitForResponse(server.PREFIX + '/grid.html', { timeout: 0}),
page.waitFor(50),
page.evaluate(() => fetch('/grid.html', {method: 'GET'}))
]);
expect(response.ok()).toBe(true);
expect(response.url()).toBe(server.PREFIX + '/grid.html');
});
});
describe('Page.goBack', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);