diff --git a/docs/src/api/class-request.md b/docs/src/api/class-request.md index 15b566bd08..fcf231f754 100644 --- a/docs/src/api/class-request.md +++ b/docs/src/api/class-request.md @@ -68,6 +68,41 @@ page.RequestFailed += (_, request) => Returns the [Frame] that initiated this request. +**Usage** + +```js +const frameUrl = request.frame().url(); +``` + +```java +String frameUrl = request.frame().url(); +``` + +```py +frame_url = request.frame.url +``` + +```csharp +var frameUrl = request.Frame.Url; +``` + +**Details** + +Note that in some cases the frame is not available, and this method will throw. +* When request originates in the Service Worker. You can use `request.serviceWorker()` to check that. +* When navigation request is issued before the corresponding frame is created. You can use [`method: Request.isNavigationRequest`] to check that. + +Here is an example that handles all the cases: + +```js +if (request.serviceWorker()) + console.log(`request ${request.url()} from a service worker`); +else if (request.isNavigationRequest()) + console.log(`request ${request.url()} is a navigation request`); +else + console.log(`request ${request.url()} from a frame ${request.frame().url()}`); +``` + ## method: Request.headers * since: v1.8 - returns: <[Object]<[string], [string]>> @@ -103,6 +138,9 @@ Name of the header. Whether this request is driving frame's navigation. +Some navigation requests are issued before the corresponding frame is created, and therefore +do not have [`method: Request.frame`] available. + ## method: Request.method * since: v1.8 - returns: <[string]> @@ -252,12 +290,14 @@ Returns the matching [Response] object, or `null` if the response was not receiv * langs: js - returns: <[null]|[Worker]> -:::note -This field is Chromium only. It's safe to call when using other browsers, but it will always be `null`. -::: - The Service [Worker] that is performing the request. +**Details** + +This method is Chromium only. It's safe to call when using other browsers, but it will always be `null`. + +Requests originated in a Service Worker do not have a [`method: Request.frame`] available. + ## async method: Request.sizes * since: v1.15 - returns: <[Object]> diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 83e87f4cb3..681777166b 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -198,7 +198,15 @@ export class Request extends ChannelOwner implements ap assert(this.serviceWorker()); throw new Error('Service Worker requests do not have an associated frame.'); } - return Frame.from(this._initializer.frame); + const frame = Frame.from(this._initializer.frame); + if (!frame._page) { + throw new Error([ + 'Frame for this navigation request is not available, because the request', + 'was issued before the frame is created. You can check whether the request', + 'is a navigation request by calling isNavigationRequest() method.', + ].join('\n')); + } + return frame; } serviceWorker(): Worker | null { @@ -267,7 +275,8 @@ export class Request extends ChannelOwner implements ap } _targetClosedScope(): LongStandingScope { - return this.serviceWorker()?._closedScope || this.frame()._page?._closedOrCrashedScope || new LongStandingScope(); + const frame = Frame.fromNullable(this._initializer.frame); + return this.serviceWorker()?._closedScope || frame?._page?._closedOrCrashedScope || new LongStandingScope(); } } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 67d5e562d3..3efc2b322b 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -18050,6 +18050,32 @@ export interface Request { /** * Returns the {@link Frame} that initiated this request. + * + * **Usage** + * + * ```js + * const frameUrl = request.frame().url(); + * ``` + * + * **Details** + * + * Note that in some cases the frame is not available, and this method will throw. + * - When request originates in the Service Worker. You can use `request.serviceWorker()` to check that. + * - When navigation request is issued before the corresponding frame is created. You can use + * [request.isNavigationRequest()](https://playwright.dev/docs/api/class-request#request-is-navigation-request) to + * check that. + * + * Here is an example that handles all the cases: + * + * ```js + * if (request.serviceWorker()) + * console.log(`request ${request.url()} from a service worker`); + * else if (request.isNavigationRequest()) + * console.log(`request ${request.url()} is a navigation request`); + * else + * console.log(`request ${request.url()} from a frame ${request.frame().url()}`); + * ``` + * */ frame(): Frame; @@ -18086,6 +18112,9 @@ export interface Request { /** * Whether this request is driving frame's navigation. + * + * Some navigation requests are issued before the corresponding frame is created, and therefore do not have + * [request.frame()](https://playwright.dev/docs/api/class-request#request-frame) available. */ isNavigationRequest(): boolean; @@ -18166,9 +18195,14 @@ export interface Request { response(): Promise; /** - * **NOTE** This field is Chromium only. It's safe to call when using other browsers, but it will always be `null`. - * * The Service {@link Worker} that is performing the request. + * + * **Details** + * + * This method is Chromium only. It's safe to call when using other browsers, but it will always be `null`. + * + * Requests originated in a Service Worker do not have a + * [request.frame()](https://playwright.dev/docs/api/class-request#request-frame) available. */ serviceWorker(): null|Worker; diff --git a/tests/page/page-network-request.spec.ts b/tests/page/page-network-request.spec.ts index e9bcec5ded..0636370923 100644 --- a/tests/page/page-network-request.spec.ts +++ b/tests/page/page-network-request.spec.ts @@ -425,3 +425,26 @@ it('should report all cookies in one header', async ({ page, server, isElectron, const cookie = (await response.request().allHeaders())['cookie']; expect(cookie).toBe('myCookie=myValue; myOtherCookie=myOtherValue'); }); + +it('should not allow to access frame on popup main request', async ({ page, server }) => { + await page.setContent(`click me`); + const requestPromise = page.context().waitForEvent('request'); + const popupPromise = page.context().waitForEvent('page'); + const clicked = page.getByText('click me').click(); + const request = await requestPromise; + + expect(request.isNavigationRequest()).toBe(true); + + let error; + try { + request.frame(); + } catch (e) { + error = e; + } + expect(error.message).toContain('Frame for this navigation request is not available'); + + const response = await request.response(); + await response.finished(); + await popupPromise; + await clicked; +});