fix: make Request.frame throw when page is not ready yet (#26616)

Suggest to check `request.isNavigationRequest()` beforehand.

Fixes #24603.
This commit is contained in:
Dmitry Gozman 2023-08-22 14:06:21 -07:00 committed by GitHub
parent bb808ca964
commit 5646875e5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 114 additions and 8 deletions

View File

@ -68,6 +68,41 @@ page.RequestFailed += (_, request) =>
Returns the [Frame] that initiated this 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 ## method: Request.headers
* since: v1.8 * since: v1.8
- returns: <[Object]<[string], [string]>> - returns: <[Object]<[string], [string]>>
@ -103,6 +138,9 @@ Name of the header.
Whether this request is driving frame's navigation. 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 ## method: Request.method
* since: v1.8 * since: v1.8
- returns: <[string]> - returns: <[string]>
@ -252,12 +290,14 @@ Returns the matching [Response] object, or `null` if the response was not receiv
* langs: js * langs: js
- returns: <[null]|[Worker]> - 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. 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 ## async method: Request.sizes
* since: v1.15 * since: v1.15
- returns: <[Object]> - returns: <[Object]>

View File

@ -198,7 +198,15 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
assert(this.serviceWorker()); assert(this.serviceWorker());
throw new Error('Service Worker requests do not have an associated frame.'); 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 { serviceWorker(): Worker | null {
@ -267,7 +275,8 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
} }
_targetClosedScope(): LongStandingScope { _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();
} }
} }

View File

@ -18050,6 +18050,32 @@ export interface Request {
/** /**
* Returns the {@link Frame} that initiated this 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; frame(): Frame;
@ -18086,6 +18112,9 @@ export interface Request {
/** /**
* Whether this request is driving frame's navigation. * 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; isNavigationRequest(): boolean;
@ -18166,9 +18195,14 @@ export interface Request {
response(): Promise<null|Response>; response(): Promise<null|Response>;
/** /**
* **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. * 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; serviceWorker(): null|Worker;

View File

@ -425,3 +425,26 @@ it('should report all cookies in one header', async ({ page, server, isElectron,
const cookie = (await response.request().allHeaders())['cookie']; const cookie = (await response.request().allHeaders())['cookie'];
expect(cookie).toBe('myCookie=myValue; myOtherCookie=myOtherValue'); expect(cookie).toBe('myCookie=myValue; myOtherCookie=myOtherValue');
}); });
it('should not allow to access frame on popup main request', async ({ page, server }) => {
await page.setContent(`<a target=_blank href="${server.EMPTY_PAGE}">click me</a>`);
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;
});