mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	feat(fetch): fulfill without passing fetch response body client<->server (#8789)
This commit is contained in:
		
							parent
							
								
									5a305a9c2e
								
							
						
					
					
						commit
						b11b274b0d
					
				@ -216,7 +216,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _fetch(url: string, options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer, timeout?: number } = {}): Promise<network.FetchResponse> {
 | 
			
		||||
  async _fetch(url: string, options: FetchOptions = {}): Promise<network.FetchResponse> {
 | 
			
		||||
    return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
 | 
			
		||||
      const postDataBuffer = isString(options.postData) ? Buffer.from(options.postData, 'utf8') : options.postData;
 | 
			
		||||
      const result = await channel.fetch({
 | 
			
		||||
@ -383,6 +383,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type FetchOptions = { url?: string, method?: string, headers?: Headers, postData?: string | Buffer, timeout?: number };
 | 
			
		||||
 | 
			
		||||
export async function prepareBrowserContextParams(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
 | 
			
		||||
  if (options.videoSize && !options.videosPath)
 | 
			
		||||
    throw new Error(`"videoSize" option requires "videosPath" to be specified`);
 | 
			
		||||
 | 
			
		||||
@ -310,15 +310,18 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async fulfill(options: { response?: Response, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) {
 | 
			
		||||
  async fulfill(options: { response?: Response|FetchResponse, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) {
 | 
			
		||||
    return this._wrapApiCall(async (channel: channels.RouteChannel) => {
 | 
			
		||||
      let useInterceptedResponseBody;
 | 
			
		||||
      let fetchResponseUid;
 | 
			
		||||
      let { status: statusOption, headers: headersOption, body: bodyOption } = options;
 | 
			
		||||
      if (options.response) {
 | 
			
		||||
        statusOption ||= options.response.status();
 | 
			
		||||
        headersOption ||= options.response.headers();
 | 
			
		||||
        if (options.body === undefined && options.path === undefined) {
 | 
			
		||||
          if (options.response === this._interceptedResponse)
 | 
			
		||||
          if (options.response instanceof FetchResponse)
 | 
			
		||||
            fetchResponseUid = (options.response as FetchResponse)._fetchUid();
 | 
			
		||||
          else if (options.response === this._interceptedResponse)
 | 
			
		||||
            useInterceptedResponseBody = true;
 | 
			
		||||
          else
 | 
			
		||||
            bodyOption = await options.response.body();
 | 
			
		||||
@ -358,7 +361,8 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
 | 
			
		||||
        headers: headersObjectToArray(headers),
 | 
			
		||||
        body,
 | 
			
		||||
        isBase64,
 | 
			
		||||
        useInterceptedResponseBody
 | 
			
		||||
        useInterceptedResponseBody,
 | 
			
		||||
        fetchResponseUid
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@ -553,7 +557,7 @@ export class FetchResponse {
 | 
			
		||||
 | 
			
		||||
  async body(): Promise<Buffer> {
 | 
			
		||||
    return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
 | 
			
		||||
      const result = await channel.fetchResponseBody({ fetchUid: this._initializer.fetchUid });
 | 
			
		||||
      const result = await channel.fetchResponseBody({ fetchUid: this._fetchUid() });
 | 
			
		||||
      if (!result.binary)
 | 
			
		||||
        throw new Error('Response has been disposed');
 | 
			
		||||
      return Buffer.from(result.binary!, 'base64');
 | 
			
		||||
@ -572,9 +576,13 @@ export class FetchResponse {
 | 
			
		||||
 | 
			
		||||
  async dispose(): Promise<void> {
 | 
			
		||||
    return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
 | 
			
		||||
      await channel.disposeFetchResponse({ fetchUid: this._initializer.fetchUid });
 | 
			
		||||
      await channel.disposeFetchResponse({ fetchUid: this._fetchUid() });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _fetchUid(): string {
 | 
			
		||||
    return this._initializer.fetchUid;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class WebSocket extends ChannelOwner<channels.WebSocketChannel, channels.WebSocketInitializer> implements api.WebSocket {
 | 
			
		||||
 | 
			
		||||
@ -19,9 +19,10 @@ import { Events } from './events';
 | 
			
		||||
import { assert } from '../utils/utils';
 | 
			
		||||
import { TimeoutSettings } from '../utils/timeoutSettings';
 | 
			
		||||
import * as channels from '../protocol/channels';
 | 
			
		||||
import * as network from './network';
 | 
			
		||||
import { parseError, serializeError } from '../protocol/serializers';
 | 
			
		||||
import { Accessibility } from './accessibility';
 | 
			
		||||
import { BrowserContext } from './browserContext';
 | 
			
		||||
import { BrowserContext, FetchOptions } from './browserContext';
 | 
			
		||||
import { ChannelOwner } from './channelOwner';
 | 
			
		||||
import { ConsoleMessage } from './consoleMessage';
 | 
			
		||||
import { Dialog } from './dialog';
 | 
			
		||||
@ -437,6 +438,10 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
 | 
			
		||||
    return this._mainFrame.evaluate(pageFunction, arg);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _fetch(url: string, options: FetchOptions = {}): Promise<network.FetchResponse> {
 | 
			
		||||
    return await this._browserContext._fetch(url, options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) {
 | 
			
		||||
    return this._wrapApiCall(async (channel: channels.PageChannel) => {
 | 
			
		||||
      const source = await evaluationScript(script, arg);
 | 
			
		||||
 | 
			
		||||
@ -2680,6 +2680,7 @@ export type RouteFulfillParams = {
 | 
			
		||||
  body?: string,
 | 
			
		||||
  isBase64?: boolean,
 | 
			
		||||
  useInterceptedResponseBody?: boolean,
 | 
			
		||||
  fetchResponseUid?: string,
 | 
			
		||||
};
 | 
			
		||||
export type RouteFulfillOptions = {
 | 
			
		||||
  status?: number,
 | 
			
		||||
@ -2687,6 +2688,7 @@ export type RouteFulfillOptions = {
 | 
			
		||||
  body?: string,
 | 
			
		||||
  isBase64?: boolean,
 | 
			
		||||
  useInterceptedResponseBody?: boolean,
 | 
			
		||||
  fetchResponseUid?: string,
 | 
			
		||||
};
 | 
			
		||||
export type RouteFulfillResult = void;
 | 
			
		||||
export type RouteResponseBodyParams = {};
 | 
			
		||||
 | 
			
		||||
@ -2191,6 +2191,7 @@ Route:
 | 
			
		||||
        body: string?
 | 
			
		||||
        isBase64: boolean?
 | 
			
		||||
        useInterceptedResponseBody: boolean?
 | 
			
		||||
        fetchResponseUid: string?
 | 
			
		||||
 | 
			
		||||
    responseBody:
 | 
			
		||||
      returns:
 | 
			
		||||
 | 
			
		||||
@ -1049,6 +1049,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
 | 
			
		||||
    body: tOptional(tString),
 | 
			
		||||
    isBase64: tOptional(tBoolean),
 | 
			
		||||
    useInterceptedResponseBody: tOptional(tBoolean),
 | 
			
		||||
    fetchResponseUid: tOptional(tString),
 | 
			
		||||
  });
 | 
			
		||||
  scheme.RouteResponseBodyParams = tOptional(tObject({}));
 | 
			
		||||
  scheme.ResourceTiming = tObject({
 | 
			
		||||
 | 
			
		||||
@ -219,13 +219,19 @@ export class Route extends SdkObject {
 | 
			
		||||
    await this._delegate.abort(errorCode);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async fulfill(overrides: { status?: number, headers?: types.HeadersArray, body?: string, isBase64?: boolean, useInterceptedResponseBody?: boolean }) {
 | 
			
		||||
  async fulfill(overrides: { status?: number, headers?: types.HeadersArray, body?: string, isBase64?: boolean, useInterceptedResponseBody?: boolean, fetchResponseUid?: string }) {
 | 
			
		||||
    assert(!this._handled, 'Route is already handled!');
 | 
			
		||||
    this._handled = true;
 | 
			
		||||
    let body = overrides.body;
 | 
			
		||||
    let isBase64 = overrides.isBase64 || false;
 | 
			
		||||
    if (body === undefined) {
 | 
			
		||||
      if (this._response && overrides.useInterceptedResponseBody) {
 | 
			
		||||
      if (overrides.fetchResponseUid) {
 | 
			
		||||
        const context = this._request.frame()._page._browserContext;
 | 
			
		||||
        const buffer = context.fetchResponses.get(overrides.fetchResponseUid);
 | 
			
		||||
        assert(buffer, 'Fetch response has been disposed');
 | 
			
		||||
        body = buffer.toString('utf8');
 | 
			
		||||
        isBase64 = false;
 | 
			
		||||
      } else if (this._response && overrides.useInterceptedResponseBody) {
 | 
			
		||||
        body = (await this._delegate.responseBody()).toString('utf8');
 | 
			
		||||
        isBase64 = false;
 | 
			
		||||
      } else {
 | 
			
		||||
 | 
			
		||||
@ -193,3 +193,34 @@ it('should include the origin header', async ({page, server, isAndroid}) => {
 | 
			
		||||
  expect(text).toBe('done');
 | 
			
		||||
  expect(interceptedRequest.headers()['origin']).toEqual(server.PREFIX);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
it('should fulfill with fetch result', async ({page, server}) => {
 | 
			
		||||
  await page.route('**/*', async route => {
 | 
			
		||||
    // @ts-expect-error
 | 
			
		||||
    const response = await page._fetch(server.PREFIX + '/simple.json');
 | 
			
		||||
    // @ts-expect-error
 | 
			
		||||
    route.fulfill({ response });
 | 
			
		||||
  });
 | 
			
		||||
  const response = await page.goto(server.EMPTY_PAGE);
 | 
			
		||||
  expect(response.status()).toBe(200);
 | 
			
		||||
  expect(await response.json()).toEqual({'foo': 'bar'});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
it('should fulfill with fetch result and overrides', async ({page, server}) => {
 | 
			
		||||
  await page.route('**/*', async route => {
 | 
			
		||||
    // @ts-expect-error
 | 
			
		||||
    const response = await page._fetch(server.PREFIX + '/simple.json');
 | 
			
		||||
    route.fulfill({
 | 
			
		||||
      // @ts-expect-error
 | 
			
		||||
      response,
 | 
			
		||||
      status: 201,
 | 
			
		||||
      headers: {
 | 
			
		||||
        'foo': 'bar'
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  const response = await page.goto(server.EMPTY_PAGE);
 | 
			
		||||
  expect(response.status()).toBe(201);
 | 
			
		||||
  expect((await response.allHeaders()).foo).toEqual('bar');
 | 
			
		||||
  expect(await response.json()).toEqual({'foo': 'bar'});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user