diff --git a/docs/api.md b/docs/api.md index 24ae5f2ff59..4343d0ec557 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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 diff --git a/lib/Page.js b/lib/Page.js index 87b04190410..4eee98b4f2a 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -632,6 +632,38 @@ class Page extends EventEmitter { return responses.get(this.mainFrame().url()) || null; } + /** + * @param {(string|Function)} urlOrPredicate + * @param {!Object=} options + * @return {!Promise} + */ + 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} + */ + 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} diff --git a/lib/helper.js b/lib/helper.js index 57bdc66454e..5770f056ead 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -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; + } } /** diff --git a/test/page.spec.js b/test/page.spec.js index c37f2aa9701..70087a3a5cb 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -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);