diff --git a/docs/src/api/class-response.md b/docs/src/api/class-response.md index 5ff51e6098..550ffe5240 100644 --- a/docs/src/api/class-response.md +++ b/docs/src/api/class-response.md @@ -22,6 +22,11 @@ Waits for this response to finish, returns always `null`. Returns the [Frame] that initiated this response. +## method: Response.fromServiceWorker +- returns: <[boolean]> + +Indicates whether this Response was fullfilled by a Service Worker's Fetch Handler (i.e. via [FetchEvent.respondWith](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith)). + ## method: Response.headers - returns: <[Object]<[string], [string]>> diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 429efd1e4a..a1b8a5c9ea 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -368,6 +368,10 @@ export class Response extends ChannelOwner implements return this._initializer.statusText; } + fromServiceWorker(): boolean { + return this._initializer.fromServiceWorker; + } + /** * @deprecated */ diff --git a/packages/playwright-core/src/protocol/channels.ts b/packages/playwright-core/src/protocol/channels.ts index e1cb131191..614a07d024 100644 --- a/packages/playwright-core/src/protocol/channels.ts +++ b/packages/playwright-core/src/protocol/channels.ts @@ -3183,6 +3183,7 @@ export type ResponseInitializer = { statusText: string, headers: NameValue[], timing: ResourceTiming, + fromServiceWorker: boolean, }; export interface ResponseEventTarget { } diff --git a/packages/playwright-core/src/protocol/protocol.yml b/packages/playwright-core/src/protocol/protocol.yml index 493e83676f..168d0ba785 100644 --- a/packages/playwright-core/src/protocol/protocol.yml +++ b/packages/playwright-core/src/protocol/protocol.yml @@ -2497,6 +2497,7 @@ Response: type: array items: NameValue timing: ResourceTiming + fromServiceWorker: boolean commands: diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index e517c0b89b..8279f323c0 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -299,7 +299,7 @@ export class CRNetworkManager { responseStart: -1, }; } - const response = new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), timing, getResponseBody, responsePayload.protocol); + const response = new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), timing, getResponseBody, !!responsePayload.fromServiceWorker, responsePayload.protocol); if (responsePayload?.remoteIPAddress && typeof responsePayload?.remotePort === 'number') { response._serverAddrFinished({ ipAddress: responsePayload.remoteIPAddress, diff --git a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts index 92def4f431..1a8ba62de0 100644 --- a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts +++ b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts @@ -80,7 +80,8 @@ export class ResponseDispatcher extends Dispatcher(); private _rawResponseHeadersPromise: ManualPromise | undefined; private _httpVersion: string | undefined; + private _fromServiceWorker: boolean; - constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray, timing: ResourceTiming, getResponseBodyCallback: GetResponseBodyCallback, httpVersion?: string) { + constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray, timing: ResourceTiming, getResponseBodyCallback: GetResponseBodyCallback, fromServiceWorker: boolean, httpVersion?: string) { super(request.frame(), 'response'); this._request = request; this._timing = timing; @@ -374,6 +375,7 @@ export class Response extends SdkObject { this._getResponseBodyCallback = getResponseBodyCallback; this._request._setResponse(this); this._httpVersion = httpVersion; + this._fromServiceWorker = fromServiceWorker; } _serverAddrFinished(addr?: RemoteAddr) { @@ -469,6 +471,10 @@ export class Response extends SdkObject { return this._httpVersion; } + fromServiceWorker(): boolean { + return this._fromServiceWorker; + } + private async _responseHeadersSize(): Promise { if (this._request.responseSize.responseHeadersSize) return this._request.responseSize.responseHeadersSize; diff --git a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts index 04cc64c648..9cd3db1f3d 100644 --- a/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts +++ b/packages/playwright-core/src/server/webkit/wkInterceptableRequest.ts @@ -88,7 +88,7 @@ export class WKInterceptableRequest { responseStart: timingPayload ? wkMillisToRoundishMillis(timingPayload.responseStart) : -1, }; const setCookieSeparator = process.platform === 'darwin' ? ',' : '\n'; - return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers, ',', setCookieSeparator), timing, getResponseBody); + return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers, ',', setCookieSeparator), timing, getResponseBody, responsePayload.source === 'service-worker'); } } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 8396ba264a..0af1a0dd02 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -14611,6 +14611,12 @@ export interface Response { */ frame(): Frame; + /** + * Indicates whether this Response was fullfilled by a Service Worker's Fetch Handler (i.e. via + * [FetchEvent.respondWith](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith)). + */ + fromServiceWorker(): boolean; + /** * **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use * [response.allHeaders()](https://playwright.dev/docs/api/class-response#response-all-headers) instead. diff --git a/tests/page/page-network-response.spec.ts b/tests/page/page-network-response.spec.ts index eca26785ae..25b1fe83a2 100644 --- a/tests/page/page-network-response.spec.ts +++ b/tests/page/page-network-response.spec.ts @@ -318,3 +318,18 @@ it('should return headers after route.fulfill', async ({ page, server }) => { 'content-language': 'en' }); }); + +it('should report if request was fromServiceWorker', async ({ page, server }) => { + { + const res = await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html'); + expect(res.fromServiceWorker()).toBe(false); + } + await page.evaluate(() => window['activationPromise']); + { + const [res] = await Promise.all([ + page.waitForResponse(/example\.txt/), + page.evaluate(() => fetch('/example.txt')), + ]); + expect(res.fromServiceWorker()).toBe(true); + } +});