From 4b3e5e5c175f8cace785b1dc689609d20a382532 Mon Sep 17 00:00:00 2001 From: Vignesh Shanmugam Date: Thu, 13 May 2021 10:29:14 -0700 Subject: [PATCH] feat(network): expose network events via browser context (#6370) - fix #6340 - Exposes all the network related events (request, response, requestfailed, requestfinished) through the browser context to allow for managing network activity even if the is any navigations through popups or to new tabs which could result in creation of multiple page objects. --- docs/src/api/class-browsercontext.md | 35 +++ src/client/browserContext.ts | 33 +++ src/client/events.ts | 4 + src/client/page.ts | 17 -- src/dispatchers/browserContextDispatcher.ts | 24 ++- src/dispatchers/pageDispatcher.ts | 14 +- src/protocol/channels.ts | 42 ++-- src/protocol/protocol.yml | 42 ++-- src/server/browserContext.ts | 4 + src/server/chromium/crNetworkManager.ts | 4 +- src/server/frames.ts | 14 +- src/server/page.ts | 6 + tests/browsercontext-network-event.spec.ts | 109 ++++++++++ tests/channels.spec.ts | 7 +- types/types.d.ts | 222 ++++++++++++++++++++ 15 files changed, 498 insertions(+), 79 deletions(-) create mode 100644 tests/browsercontext-network-event.spec.ts diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index c478049fc9..1a04c554df 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -143,6 +143,41 @@ Use [`method: Page.waitForLoadState`] to wait until the page gets to a particula cases). ::: +## event: BrowserContext.request +- argument: <[Request]> + +Emitted when a request is issued from any pages created through this context. +The [request] object is read-only. To only listen for requests from a particular +page, use [`event: Page.request`]. + +In order to intercept and mutate requests, see [`method: BrowserContext.route`] +or [`method: Page.route`]. + +## event: BrowserContext.requestFailed +- argument: <[Request]> + +Emitted when a request fails, for example by timing out. To only listen for +failed requests from a particular page, use [`event: Page.requestFailed`]. + +:::note +HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete +with [`event: BrowserContext.requestFinished`] event and not with [`event: BrowserContext.requestFailed`]. +::: + +## event: BrowserContext.requestFinished +- argument: <[Request]> + +Emitted when a request finishes successfully after downloading the response body. For a successful response, the +sequence of events is `request`, `response` and `requestfinished`. To listen for +successful requests from a particular page, use [`event: Page.requestFinished`]. + +## event: BrowserContext.response +- argument: <[Response]> + +Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events +is `request`, `response` and `requestfinished`. To listen for response events +from a particular page, use [`event: Page.response`]. + ## event: BrowserContext.serviceWorker * langs: js, python - argument: <[Worker]> diff --git a/src/client/browserContext.ts b/src/client/browserContext.ts index 0df343192b..f3ac2d227e 100644 --- a/src/client/browserContext.ts +++ b/src/client/browserContext.ts @@ -86,6 +86,10 @@ export class BrowserContext extends ChannelOwner this._onRequest(network.Request.from(request), Page.fromNullable(page))); + this._channel.on('requestFailed', ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, Page.fromNullable(page))); + this._channel.on('requestFinished', ({ request, responseEndTiming, page }) => this._onRequestFinished(network.Request.from(request), responseEndTiming, Page.fromNullable(page))); + this._channel.on('response', ({ response, page }) => this._onResponse(network.Response.from(response), Page.fromNullable(page))); this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f)); } @@ -96,6 +100,35 @@ export class BrowserContext extends ChannelOwner this._onFrameDetached(Frame.from(frame))); this._channel.on('load', () => this.emit(Events.Page.Load, this)); this._channel.on('pageError', ({ error }) => this.emit(Events.Page.PageError, parseError(error))); - this._channel.on('request', ({ request }) => this.emit(Events.Page.Request, Request.from(request))); - this._channel.on('requestFailed', ({ request, failureText, responseEndTiming }) => this._onRequestFailed(Request.from(request), responseEndTiming, failureText)); - this._channel.on('requestFinished', ({ request, responseEndTiming }) => this._onRequestFinished(Request.from(request), responseEndTiming)); - this._channel.on('response', ({ response }) => this.emit(Events.Page.Response, Response.from(response))); this._channel.on('route', ({ route, request }) => this._onRoute(Route.from(route), Request.from(request))); this._channel.on('video', ({ artifact }) => { const artifactObject = Artifact.from(artifact); @@ -152,19 +148,6 @@ export class Page extends ChannelOwner implements channels.BrowserContextChannel { private _context: BrowserContext; @@ -63,6 +64,27 @@ export class BrowserContextDispatcher extends Dispatcher this._dispatchEvent('serviceWorker', { worker: new WorkerDispatcher(this._scope, serviceWorker) })); } + context.on(BrowserContext.Events.Request, (request: Request) => { + return this._dispatchEvent('request', { + request: RequestDispatcher.from(this._scope, request), + page: PageDispatcher.fromNullable(this._scope, request.frame()._page.initializedOrUndefined()) + }); + }); + context.on(BrowserContext.Events.Response, (response: Response) => this._dispatchEvent('response', { + response: ResponseDispatcher.from(this._scope, response), + page: PageDispatcher.fromNullable(this._scope, response.frame()._page.initializedOrUndefined()) + })); + context.on(BrowserContext.Events.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', { + request: RequestDispatcher.from(this._scope, request), + failureText: request._failureText, + responseEndTiming: request._responseEndTiming, + page: PageDispatcher.fromNullable(this._scope, request.frame()._page.initializedOrUndefined()) + })); + context.on(BrowserContext.Events.RequestFinished, (request: Request) => this._dispatchEvent('requestFinished', { + request: RequestDispatcher.from(scope, request), + responseEndTiming: request._responseEndTiming, + page: PageDispatcher.fromNullable(this._scope, request.frame()._page.initializedOrUndefined()) + })); } async setDefaultNavigationTimeoutNoReply(params: channels.BrowserContextSetDefaultNavigationTimeoutNoReplyParams) { diff --git a/src/dispatchers/pageDispatcher.ts b/src/dispatchers/pageDispatcher.ts index bdc98e4e05..c14bb6015f 100644 --- a/src/dispatchers/pageDispatcher.ts +++ b/src/dispatchers/pageDispatcher.ts @@ -16,7 +16,6 @@ import { BrowserContext } from '../server/browserContext'; import { Frame } from '../server/frames'; -import { Request } from '../server/network'; import { Page, Worker } from '../server/page'; import * as channels from '../protocol/channels'; import { Dispatcher, DispatcherScope, existingDispatcher, lookupDispatcher, lookupNullableDispatcher } from './dispatcher'; @@ -39,7 +38,7 @@ import { createGuid } from '../utils/utils'; export class PageDispatcher extends Dispatcher implements channels.PageChannel { private _page: Page; - private static fromNullable(scope: DispatcherScope, page: Page | undefined): PageDispatcher | undefined { + static fromNullable(scope: DispatcherScope, page: Page | undefined): PageDispatcher | undefined { if (!page) return undefined; const result = existingDispatcher(page); @@ -75,17 +74,6 @@ export class PageDispatcher extends Dispatcher i page.on(Page.Events.FrameDetached, frame => this._onFrameDetached(frame)); page.on(Page.Events.Load, () => this._dispatchEvent('load')); page.on(Page.Events.PageError, error => this._dispatchEvent('pageError', { error: serializeError(error) })); - page.on(Page.Events.Request, request => this._dispatchEvent('request', { request: RequestDispatcher.from(this._scope, request) })); - page.on(Page.Events.RequestFailed, (request: Request) => this._dispatchEvent('requestFailed', { - request: RequestDispatcher.from(this._scope, request), - failureText: request._failureText, - responseEndTiming: request._responseEndTiming - })); - page.on(Page.Events.RequestFinished, (request: Request) => this._dispatchEvent('requestFinished', { - request: RequestDispatcher.from(scope, request), - responseEndTiming: request._responseEndTiming - })); - page.on(Page.Events.Response, response => this._dispatchEvent('response', { response: ResponseDispatcher.from(this._scope, response) })); page.on(Page.Events.WebSocket, webSocket => this._dispatchEvent('webSocket', { webSocket: new WebSocketDispatcher(this._scope, webSocket) })); page.on(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) })); page.on(Page.Events.Video, (artifact: Artifact) => this._dispatchEvent('video', { artifact: existingDispatcher(artifact) })); diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index fa1c2edf28..7ea19290f5 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -590,6 +590,10 @@ export interface BrowserContextChannel extends Channel { on(event: 'video', callback: (params: BrowserContextVideoEvent) => void): this; on(event: 'backgroundPage', callback: (params: BrowserContextBackgroundPageEvent) => void): this; on(event: 'serviceWorker', callback: (params: BrowserContextServiceWorkerEvent) => void): this; + on(event: 'request', callback: (params: BrowserContextRequestEvent) => void): this; + on(event: 'requestFailed', callback: (params: BrowserContextRequestFailedEvent) => void): this; + on(event: 'requestFinished', callback: (params: BrowserContextRequestFinishedEvent) => void): this; + on(event: 'response', callback: (params: BrowserContextResponseEvent) => void): this; addCookies(params: BrowserContextAddCookiesParams, metadata?: Metadata): Promise; addInitScript(params: BrowserContextAddInitScriptParams, metadata?: Metadata): Promise; clearCookies(params?: BrowserContextClearCookiesParams, metadata?: Metadata): Promise; @@ -634,6 +638,25 @@ export type BrowserContextBackgroundPageEvent = { export type BrowserContextServiceWorkerEvent = { worker: WorkerChannel, }; +export type BrowserContextRequestEvent = { + request: RequestChannel, + page?: PageChannel, +}; +export type BrowserContextRequestFailedEvent = { + request: RequestChannel, + failureText?: string, + responseEndTiming: number, + page?: PageChannel, +}; +export type BrowserContextRequestFinishedEvent = { + request: RequestChannel, + responseEndTiming: number, + page?: PageChannel, +}; +export type BrowserContextResponseEvent = { + response: ResponseChannel, + page?: PageChannel, +}; export type BrowserContextAddCookiesParams = { cookies: SetNetworkCookie[], }; @@ -832,10 +855,6 @@ export interface PageChannel extends Channel { on(event: 'frameDetached', callback: (params: PageFrameDetachedEvent) => void): this; on(event: 'load', callback: (params: PageLoadEvent) => void): this; on(event: 'pageError', callback: (params: PagePageErrorEvent) => void): this; - on(event: 'request', callback: (params: PageRequestEvent) => void): this; - on(event: 'requestFailed', callback: (params: PageRequestFailedEvent) => void): this; - on(event: 'requestFinished', callback: (params: PageRequestFinishedEvent) => void): this; - on(event: 'response', callback: (params: PageResponseEvent) => void): this; on(event: 'route', callback: (params: PageRouteEvent) => void): this; on(event: 'video', callback: (params: PageVideoEvent) => void): this; on(event: 'webSocket', callback: (params: PageWebSocketEvent) => void): this; @@ -903,21 +922,6 @@ export type PageLoadEvent = {}; export type PagePageErrorEvent = { error: SerializedError, }; -export type PageRequestEvent = { - request: RequestChannel, -}; -export type PageRequestFailedEvent = { - request: RequestChannel, - failureText?: string, - responseEndTiming: number, -}; -export type PageRequestFinishedEvent = { - request: RequestChannel, - responseEndTiming: number, -}; -export type PageResponseEvent = { - response: ResponseChannel, -}; export type PageRouteEvent = { route: RouteChannel, request: RequestChannel, diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index 347d23d694..ca19786919 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -642,6 +642,29 @@ BrowserContext: parameters: worker: Worker + request: + parameters: + request: Request + page: Page? + + requestFailed: + parameters: + request: Request + failureText: string? + responseEndTiming: number + page: Page? + + requestFinished: + parameters: + request: Request + responseEndTiming: number + page: Page? + + response: + parameters: + response: Response + page: Page? + Page: type: interface @@ -964,25 +987,6 @@ Page: parameters: error: SerializedError - request: - parameters: - request: Request - - requestFailed: - parameters: - request: Request - failureText: string? - responseEndTiming: number - - requestFinished: - parameters: - request: Request - responseEndTiming: number - - response: - parameters: - response: Response - route: parameters: route: Route diff --git a/src/server/browserContext.ts b/src/server/browserContext.ts index 6c7a8bb8b3..d8b40ff055 100644 --- a/src/server/browserContext.ts +++ b/src/server/browserContext.ts @@ -39,6 +39,10 @@ export abstract class BrowserContext extends SdkObject { static Events = { Close: 'close', Page: 'page', + Request: 'request', + Response: 'response', + RequestFailed: 'requestfailed', + RequestFinished: 'requestfinished', BeforeClose: 'beforeclose', VideoStarted: 'videostarted', }; diff --git a/src/server/chromium/crNetworkManager.ts b/src/server/chromium/crNetworkManager.ts index 303b92c8ef..7c008a22cd 100644 --- a/src/server/chromium/crNetworkManager.ts +++ b/src/server/chromium/crNetworkManager.ts @@ -207,10 +207,10 @@ export class CRNetworkManager { frame = this._page._frameManager.frame(requestPausedEvent.frameId); // Check if it's main resource request interception (targetId === main frame id). - if (!frame && requestPausedEvent && requestWillBeSentEvent.frameId === (this._page._delegate as CRPage)._targetId) { + if (!frame && requestWillBeSentEvent.frameId === (this._page._delegate as CRPage)._targetId) { // Main resource request for the page is being intercepted so the Frame is not created // yet. Precreate it here for the purposes of request interception. It will be updated - // later as soon as the request contnues and we receive frame tree from the page. + // later as soon as the request continues and we receive frame tree from the page. frame = this._page._frameManager.frameAttached(requestWillBeSentEvent.frameId, null); } diff --git a/src/server/frames.ts b/src/server/frames.ts index 00b19d69dc..5e91dfd96b 100644 --- a/src/server/frames.ts +++ b/src/server/frames.ts @@ -263,6 +263,7 @@ export class FrameManager { route.continue(); return; } + this._page._browserContext.emit(BrowserContext.Events.Request, request); this._page._requestStarted(request); } @@ -271,12 +272,15 @@ export class FrameManager { return; this._responses.push(response); this._page.emit(Page.Events.Response, response); + this._page._browserContext.emit(BrowserContext.Events.Response, response); } requestFinished(request: network.Request) { this._inflightRequestFinished(request); - if (!request._isFavicon) - this._page.emit(Page.Events.RequestFinished, request); + if (request._isFavicon) + return; + this._page._browserContext.emit(BrowserContext.Events.RequestFinished, request); + this._page.emit(Page.Events.RequestFinished, request); } requestFailed(request: network.Request, canceled: boolean) { @@ -288,8 +292,10 @@ export class FrameManager { errorText += '; maybe frame was detached?'; this.frameAbortedNavigation(frame._id, errorText, frame.pendingDocument()!.documentId); } - if (!request._isFavicon) - this._page.emit(Page.Events.RequestFailed, request); + if (request._isFavicon) + return; + this._page._browserContext.emit(BrowserContext.Events.RequestFailed, request); + this._page.emit(Page.Events.RequestFailed, request); } removeChildFramesRecursively(frame: Frame) { diff --git a/src/server/page.ts b/src/server/page.ts index 57dc55306a..8095ed7d42 100644 --- a/src/server/page.ts +++ b/src/server/page.ts @@ -122,6 +122,7 @@ export class Page extends SdkObject { private _closedCallback: () => void; private _closedPromise: Promise; private _disconnected = false; + private _initialized = false; private _disconnectedCallback: (e: Error) => void; readonly _disconnectedPromise: Promise; private _crashedCallback: (e: Error) => void; @@ -195,6 +196,7 @@ export class Page extends SdkObject { return; this._setIsError(error); } + this._initialized = true; this._browserContext.emit(BrowserContext.Events.Page, this); // I may happen that page iniatialization finishes after Close event has already been sent, // in that case we fire another Close event to ensure that each reported Page will have @@ -203,6 +205,10 @@ export class Page extends SdkObject { this.emit(Page.Events.Close); } + initializedOrUndefined() { + return this._initialized ? this : undefined; + } + async _doSlowMo() { const slowMo = this._browserContext._browser.options.slowMo; if (!slowMo) diff --git a/tests/browsercontext-network-event.spec.ts b/tests/browsercontext-network-event.spec.ts new file mode 100644 index 0000000000..770f1d17d8 --- /dev/null +++ b/tests/browsercontext-network-event.spec.ts @@ -0,0 +1,109 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * Modifications copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { browserTest as it, expect } from './config/browserTest'; + +it('BrowserContext.Events.Request', async ({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const requests = []; + context.on('request', request => requests.push(request)); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [page1] = await Promise.all([ + context.waitForEvent('page'), + page.click('a'), + ]); + await page1.waitForLoadState(); + const urls = requests.map(r => r.url()); + expect(urls).toEqual([ + server.EMPTY_PAGE, + `${server.PREFIX}/one-style.html`, + `${server.PREFIX}/one-style.css` + ]); +}); + +it('BrowserContext.Events.Response', async ({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const responses = []; + context.on('response', response => responses.push(response)); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [page1] = await Promise.all([ + context.waitForEvent('page'), + page.click('a'), + ]); + await page1.waitForLoadState(); + const urls = responses.map(r => r.url()); + expect(urls).toEqual([ + server.EMPTY_PAGE, + `${server.PREFIX}/one-style.html`, + `${server.PREFIX}/one-style.css` + ]); +}); + +it('BrowserContext.Events.RequestFailed', async ({browser, server}) => { + server.setRoute('/one-style.css', (_, res) => { + res.setHeader('Content-Type', 'text/css'); + res.connection.destroy(); + }); + const context = await browser.newContext(); + const page = await context.newPage(); + const failedRequests = []; + context.on('requestfailed', request => failedRequests.push(request)); + await page.goto(server.PREFIX + '/one-style.html'); + expect(failedRequests.length).toBe(1); + expect(failedRequests[0].url()).toContain('one-style.css'); + expect(await failedRequests[0].response()).toBe(null); + expect(failedRequests[0].resourceType()).toBe('stylesheet'); + expect(failedRequests[0].frame()).toBeTruthy(); +}); + + +it('BrowserContext.Events.RequestFinished', async ({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [response] = await Promise.all([ + page.goto(server.EMPTY_PAGE), + context.waitForEvent('requestfinished') + ]); + const request = response.request(); + expect(request.url()).toBe(server.EMPTY_PAGE); + expect(await request.response()).toBeTruthy(); + expect(request.frame() === page.mainFrame()).toBe(true); + expect(request.frame().url()).toBe(server.EMPTY_PAGE); + expect(request.failure()).toBe(null); +}); + +it('should fire events in proper order', async ({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const events = []; + context.on('request', () => events.push('request')); + context.on('response', () => events.push('response')); + context.on('requestfinished', () => events.push('requestfinished')); + await Promise.all([ + page.goto(server.EMPTY_PAGE), + context.waitForEvent('requestfinished') + ]); + expect(events).toEqual([ + 'request', + 'response', + 'requestfinished', + ]); +}); diff --git a/tests/channels.spec.ts b/tests/channels.spec.ts index ba4e3d3fec..0746850d46 100644 --- a/tests/channels.spec.ts +++ b/tests/channels.spec.ts @@ -49,10 +49,9 @@ it('should scope context handles', async ({browserType, browserOptions, server}) { _guid: 'browser', objects: [ { _guid: 'browser-context', objects: [ { _guid: 'frame', objects: [] }, - { _guid: 'page', objects: [ - { _guid: 'request', objects: [] }, - { _guid: 'response', objects: [] }, - ]}, + { _guid: 'page', objects: []}, + { _guid: 'request', objects: [] }, + { _guid: 'response', objects: [] }, ]}, ] }, ] }, diff --git a/types/types.d.ts b/types/types.d.ts index c274a9e4cd..c2bb2bd366 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -4838,6 +4838,43 @@ export interface BrowserContext { */ on(event: 'page', listener: (page: Page) => void): this; + /** + * Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only + * listen for requests from a particular page, use + * [page.on('request')](https://playwright.dev/docs/api/class-page#pageonrequest). + * + * In order to intercept and mutate requests, see + * [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browsercontextrouteurl-handler) + * or [page.route(url, handler)](https://playwright.dev/docs/api/class-page#pagerouteurl-handler). + */ + on(event: 'request', listener: (request: Request) => void): this; + + /** + * Emitted when a request fails, for example by timing out. To only listen for failed requests from a particular page, use + * [page.on('requestfailed')](https://playwright.dev/docs/api/class-page#pageonrequestfailed). + * + * > NOTE: HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will + * complete with + * [browserContext.on('requestfinished')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfinished) + * event and not with + * [browserContext.on('requestfailed')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfailed). + */ + on(event: 'requestfailed', listener: (request: Request) => void): this; + + /** + * Emitted when a request finishes successfully after downloading the response body. For a successful response, the + * sequence of events is `request`, `response` and `requestfinished`. To listen for successful requests from a particular + * page, use [page.on('requestfinished')](https://playwright.dev/docs/api/class-page#pageonrequestfinished). + */ + on(event: 'requestfinished', listener: (request: Request) => void): this; + + /** + * Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events + * is `request`, `response` and `requestfinished`. To listen for response events from a particular page, use + * [page.on('response')](https://playwright.dev/docs/api/class-page#pageonresponse). + */ + on(event: 'response', listener: (response: Response) => void): this; + /** * > NOTE: Service workers are only supported on Chromium-based browsers. * @@ -4888,6 +4925,43 @@ export interface BrowserContext { */ once(event: 'page', listener: (page: Page) => void): this; + /** + * Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only + * listen for requests from a particular page, use + * [page.on('request')](https://playwright.dev/docs/api/class-page#pageonrequest). + * + * In order to intercept and mutate requests, see + * [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browsercontextrouteurl-handler) + * or [page.route(url, handler)](https://playwright.dev/docs/api/class-page#pagerouteurl-handler). + */ + once(event: 'request', listener: (request: Request) => void): this; + + /** + * Emitted when a request fails, for example by timing out. To only listen for failed requests from a particular page, use + * [page.on('requestfailed')](https://playwright.dev/docs/api/class-page#pageonrequestfailed). + * + * > NOTE: HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will + * complete with + * [browserContext.on('requestfinished')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfinished) + * event and not with + * [browserContext.on('requestfailed')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfailed). + */ + once(event: 'requestfailed', listener: (request: Request) => void): this; + + /** + * Emitted when a request finishes successfully after downloading the response body. For a successful response, the + * sequence of events is `request`, `response` and `requestfinished`. To listen for successful requests from a particular + * page, use [page.on('requestfinished')](https://playwright.dev/docs/api/class-page#pageonrequestfinished). + */ + once(event: 'requestfinished', listener: (request: Request) => void): this; + + /** + * Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events + * is `request`, `response` and `requestfinished`. To listen for response events from a particular page, use + * [page.on('response')](https://playwright.dev/docs/api/class-page#pageonresponse). + */ + once(event: 'response', listener: (response: Response) => void): this; + /** * > NOTE: Service workers are only supported on Chromium-based browsers. * @@ -4938,6 +5012,43 @@ export interface BrowserContext { */ addListener(event: 'page', listener: (page: Page) => void): this; + /** + * Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only + * listen for requests from a particular page, use + * [page.on('request')](https://playwright.dev/docs/api/class-page#pageonrequest). + * + * In order to intercept and mutate requests, see + * [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browsercontextrouteurl-handler) + * or [page.route(url, handler)](https://playwright.dev/docs/api/class-page#pagerouteurl-handler). + */ + addListener(event: 'request', listener: (request: Request) => void): this; + + /** + * Emitted when a request fails, for example by timing out. To only listen for failed requests from a particular page, use + * [page.on('requestfailed')](https://playwright.dev/docs/api/class-page#pageonrequestfailed). + * + * > NOTE: HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will + * complete with + * [browserContext.on('requestfinished')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfinished) + * event and not with + * [browserContext.on('requestfailed')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfailed). + */ + addListener(event: 'requestfailed', listener: (request: Request) => void): this; + + /** + * Emitted when a request finishes successfully after downloading the response body. For a successful response, the + * sequence of events is `request`, `response` and `requestfinished`. To listen for successful requests from a particular + * page, use [page.on('requestfinished')](https://playwright.dev/docs/api/class-page#pageonrequestfinished). + */ + addListener(event: 'requestfinished', listener: (request: Request) => void): this; + + /** + * Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events + * is `request`, `response` and `requestfinished`. To listen for response events from a particular page, use + * [page.on('response')](https://playwright.dev/docs/api/class-page#pageonresponse). + */ + addListener(event: 'response', listener: (response: Response) => void): this; + /** * > NOTE: Service workers are only supported on Chromium-based browsers. * @@ -4988,6 +5099,43 @@ export interface BrowserContext { */ removeListener(event: 'page', listener: (page: Page) => void): this; + /** + * Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only + * listen for requests from a particular page, use + * [page.on('request')](https://playwright.dev/docs/api/class-page#pageonrequest). + * + * In order to intercept and mutate requests, see + * [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browsercontextrouteurl-handler) + * or [page.route(url, handler)](https://playwright.dev/docs/api/class-page#pagerouteurl-handler). + */ + removeListener(event: 'request', listener: (request: Request) => void): this; + + /** + * Emitted when a request fails, for example by timing out. To only listen for failed requests from a particular page, use + * [page.on('requestfailed')](https://playwright.dev/docs/api/class-page#pageonrequestfailed). + * + * > NOTE: HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will + * complete with + * [browserContext.on('requestfinished')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfinished) + * event and not with + * [browserContext.on('requestfailed')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfailed). + */ + removeListener(event: 'requestfailed', listener: (request: Request) => void): this; + + /** + * Emitted when a request finishes successfully after downloading the response body. For a successful response, the + * sequence of events is `request`, `response` and `requestfinished`. To listen for successful requests from a particular + * page, use [page.on('requestfinished')](https://playwright.dev/docs/api/class-page#pageonrequestfinished). + */ + removeListener(event: 'requestfinished', listener: (request: Request) => void): this; + + /** + * Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events + * is `request`, `response` and `requestfinished`. To listen for response events from a particular page, use + * [page.on('response')](https://playwright.dev/docs/api/class-page#pageonresponse). + */ + removeListener(event: 'response', listener: (response: Response) => void): this; + /** * > NOTE: Service workers are only supported on Chromium-based browsers. * @@ -5038,6 +5186,43 @@ export interface BrowserContext { */ off(event: 'page', listener: (page: Page) => void): this; + /** + * Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only + * listen for requests from a particular page, use + * [page.on('request')](https://playwright.dev/docs/api/class-page#pageonrequest). + * + * In order to intercept and mutate requests, see + * [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browsercontextrouteurl-handler) + * or [page.route(url, handler)](https://playwright.dev/docs/api/class-page#pagerouteurl-handler). + */ + off(event: 'request', listener: (request: Request) => void): this; + + /** + * Emitted when a request fails, for example by timing out. To only listen for failed requests from a particular page, use + * [page.on('requestfailed')](https://playwright.dev/docs/api/class-page#pageonrequestfailed). + * + * > NOTE: HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will + * complete with + * [browserContext.on('requestfinished')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfinished) + * event and not with + * [browserContext.on('requestfailed')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfailed). + */ + off(event: 'requestfailed', listener: (request: Request) => void): this; + + /** + * Emitted when a request finishes successfully after downloading the response body. For a successful response, the + * sequence of events is `request`, `response` and `requestfinished`. To listen for successful requests from a particular + * page, use [page.on('requestfinished')](https://playwright.dev/docs/api/class-page#pageonrequestfinished). + */ + off(event: 'requestfinished', listener: (request: Request) => void): this; + + /** + * Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events + * is `request`, `response` and `requestfinished`. To listen for response events from a particular page, use + * [page.on('response')](https://playwright.dev/docs/api/class-page#pageonresponse). + */ + off(event: 'response', listener: (response: Response) => void): this; + /** * > NOTE: Service workers are only supported on Chromium-based browsers. * @@ -5509,6 +5694,43 @@ export interface BrowserContext { */ waitForEvent(event: 'page', optionsOrPredicate?: { predicate?: (page: Page) => boolean | Promise, timeout?: number } | ((page: Page) => boolean | Promise)): Promise; + /** + * Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only + * listen for requests from a particular page, use + * [page.on('request')](https://playwright.dev/docs/api/class-page#pageonrequest). + * + * In order to intercept and mutate requests, see + * [browserContext.route(url, handler)](https://playwright.dev/docs/api/class-browsercontext#browsercontextrouteurl-handler) + * or [page.route(url, handler)](https://playwright.dev/docs/api/class-page#pagerouteurl-handler). + */ + waitForEvent(event: 'request', optionsOrPredicate?: { predicate?: (request: Request) => boolean | Promise, timeout?: number } | ((request: Request) => boolean | Promise)): Promise; + + /** + * Emitted when a request fails, for example by timing out. To only listen for failed requests from a particular page, use + * [page.on('requestfailed')](https://playwright.dev/docs/api/class-page#pageonrequestfailed). + * + * > NOTE: HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will + * complete with + * [browserContext.on('requestfinished')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfinished) + * event and not with + * [browserContext.on('requestfailed')](https://playwright.dev/docs/api/class-browsercontext#browsercontextonrequestfailed). + */ + waitForEvent(event: 'requestfailed', optionsOrPredicate?: { predicate?: (request: Request) => boolean | Promise, timeout?: number } | ((request: Request) => boolean | Promise)): Promise; + + /** + * Emitted when a request finishes successfully after downloading the response body. For a successful response, the + * sequence of events is `request`, `response` and `requestfinished`. To listen for successful requests from a particular + * page, use [page.on('requestfinished')](https://playwright.dev/docs/api/class-page#pageonrequestfinished). + */ + waitForEvent(event: 'requestfinished', optionsOrPredicate?: { predicate?: (request: Request) => boolean | Promise, timeout?: number } | ((request: Request) => boolean | Promise)): Promise; + + /** + * Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events + * is `request`, `response` and `requestfinished`. To listen for response events from a particular page, use + * [page.on('response')](https://playwright.dev/docs/api/class-page#pageonresponse). + */ + waitForEvent(event: 'response', optionsOrPredicate?: { predicate?: (response: Response) => boolean | Promise, timeout?: number } | ((response: Response) => boolean | Promise)): Promise; + /** * > NOTE: Service workers are only supported on Chromium-based browsers. *