feat(fetch): support query params (#8893)

This commit is contained in:
Yury Semikhatsky 2021-09-13 14:29:44 -07:00 committed by GitHub
parent 3e5645ecea
commit f8c0f0d637
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 98 additions and 6 deletions

View file

@ -16,6 +16,11 @@ context cookies from the response. The method will automatically follow redirect
Target URL or Request to get all fetch parameters from.
### option: FetchRequest.fetch.params
- `params` <[Object]<[string], [string]>>
Query parameters to be send with the URL.
### option: FetchRequest.fetch.method
- `method` <[string]>
@ -47,6 +52,11 @@ context cookies from the response. The method will automatically follow redirect
Target URL or Request to get all fetch parameters from.
### option: FetchRequest.get.params
- `params` <[Object]<[string], [string]>>
Query parameters to be send with the URL.
### option: FetchRequest.get.headers
- `headers` <[Object]<[string], [string]>>
@ -68,6 +78,11 @@ context cookies from the response. The method will automatically follow redirect
Target URL or Request to get all fetch parameters from.
### option: FetchRequest.post.params
- `params` <[Object]<[string], [string]>>
Query parameters to be send with the URL.
### option: FetchRequest.post.headers
- `headers` <[Object]<[string], [string]>>

View file

@ -17,13 +17,19 @@
import * as api from '../../types/types';
import { HeadersArray } from '../common/types';
import * as channels from '../protocol/channels';
import { assert, headersObjectToArray, isString } from '../utils/utils';
import { assert, headersObjectToArray, isString, objectToArray } from '../utils/utils';
import { BrowserContext } from './browserContext';
import * as network from './network';
import { RawHeaders } from './network';
import { Headers } from './types';
export type FetchOptions = { method?: string, headers?: Headers, data?: string | Buffer, timeout?: number };
export type FetchOptions = {
params?: { [key: string]: string; },
method?: string,
headers?: Headers,
data?: string | Buffer,
timeout?: number
};
export class FetchRequest implements api.FetchRequest {
private _context: BrowserContext;
@ -35,6 +41,7 @@ export class FetchRequest implements api.FetchRequest {
async get(
urlOrRequest: string | api.Request,
options?: {
params?: { [key: string]: string; };
headers?: { [key: string]: string; };
timeout?: number;
}): Promise<FetchResponse> {
@ -47,6 +54,7 @@ export class FetchRequest implements api.FetchRequest {
async post(
urlOrRequest: string | api.Request,
options?: {
params?: { [key: string]: string; };
headers?: { [key: string]: string; };
data?: string | Buffer;
timeout?: number;
@ -62,6 +70,7 @@ export class FetchRequest implements api.FetchRequest {
const request: network.Request | undefined = (urlOrRequest instanceof network.Request) ? urlOrRequest as network.Request : undefined;
assert(request || typeof urlOrRequest === 'string', 'First argument must be either URL string or Request');
const url = request ? request.url() : urlOrRequest as string;
const params = objectToArray(options.params);
const method = options.method || request?.method();
// Cannot call allHeaders() here as the request may be paused inside route handler.
const headersObj = options.headers || request?.headers() ;
@ -72,6 +81,7 @@ export class FetchRequest implements api.FetchRequest {
const postData = (postDataBuffer ? postDataBuffer.toString('base64') : undefined);
const result = await channel.fetch({
url,
params,
method,
headers,
postData,

View file

@ -28,7 +28,7 @@ import { CallMetadata } from '../server/instrumentation';
import { ArtifactDispatcher } from './artifactDispatcher';
import { Artifact } from '../server/artifact';
import { Request, Response } from '../server/network';
import { headersArrayToObject } from '../utils/utils';
import { arrayToObject, headersArrayToObject } from '../utils/utils';
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer, channels.BrowserContextEvents> implements channels.BrowserContextChannel {
private _context: BrowserContext;
@ -110,6 +110,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
async fetch(params: channels.BrowserContextFetchParams): Promise<channels.BrowserContextFetchResult> {
const { fetchResponse, error } = await playwrightFetch(this._context, {
url: params.url,
params: arrayToObject(params.params),
method: params.method,
headers: params.headers ? headersArrayToObject(params.headers, false) : undefined,
postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,

View file

@ -857,12 +857,14 @@ export type BrowserContextExposeBindingOptions = {
export type BrowserContextExposeBindingResult = void;
export type BrowserContextFetchParams = {
url: string,
params?: NameValue[],
method?: string,
headers?: NameValue[],
postData?: Binary,
timeout?: number,
};
export type BrowserContextFetchOptions = {
params?: NameValue[],
method?: string,
headers?: NameValue[],
postData?: Binary,

View file

@ -616,6 +616,9 @@ BrowserContext:
fetch:
parameters:
url: string
params:
type: array?
items: NameValue
method: string?
headers:
type: array?

View file

@ -394,6 +394,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
});
scheme.BrowserContextFetchParams = tObject({
url: tString,
params: tOptional(tArray(tType('NameValue'))),
method: tOptional(tString),
headers: tOptional(tArray(tType('NameValue'))),
postData: tOptional(tBinary),

View file

@ -66,7 +66,13 @@ export async function playwrightFetch(context: BrowserContext, params: types.Fet
if (context._options.ignoreHTTPSErrors)
options.rejectUnauthorized = false;
const fetchResponse = await sendRequest(context, new URL(params.url, context._options.baseURL), options, params.postData);
const requestUrl = new URL(params.url, context._options.baseURL);
if (params.params) {
for (const [name, value] of Object.entries(params.params))
requestUrl.searchParams.set(name, value);
}
const fetchResponse = await sendRequest(context, requestUrl, options, params.postData);
const fetchUid = context.storeFetchResponseBody(fetchResponse.body);
return { fetchResponse: { ...fetchResponse, fetchUid } };
} catch (e) {

View file

@ -374,6 +374,7 @@ export type SetStorageState = {
export type FetchOptions = {
url: string,
params?: { [name: string]: string },
method?: string,
headers?: { [name: string]: string },
postData?: Buffer,

View file

@ -24,6 +24,7 @@ import { spawn } from 'child_process';
import { getProxyForUrl } from 'proxy-from-env';
import * as URL from 'url';
import { getUbuntuVersionSync } from './ubuntuVersion';
import { NameValue } from '../protocol/channels';
// `https-proxy-agent` v5 is written in TypeScript and exposes generated types.
// However, as of June 2020, its types are generated with tsconfig that enables
@ -288,6 +289,24 @@ class HashStream extends stream.Writable {
}
}
export function objectToArray(map?: { [key: string]: string }): NameValue[] | undefined {
if (!map)
return undefined;
const result = [];
for (const [name, value] of Object.entries(map))
result.push({ name, value });
return result;
}
export function arrayToObject(array?: NameValue[]): { [key: string]: string } | undefined {
if (!array)
return undefined;
const result: { [key: string]: string } = {};
for (const {name, value} of array)
result[name] = value;
return result;
}
export async function calculateFileSha1(filename: string): Promise<string> {
const hashStream = new HashStream();
const stream = fs.createReadStream(filename);

View file

@ -40,7 +40,7 @@ it.afterAll(() => {
http.globalAgent = prevAgent;
});
it('should work', async ({context, server}) => {
it('get should work', async ({context, server}) => {
const response = await context.request.get(server.PREFIX + '/simple.json');
expect(response.url()).toBe(server.PREFIX + '/simple.json');
expect(response.status()).toBe(200);
@ -128,6 +128,25 @@ it('should add session cookies to request', async ({context, server}) => {
expect(req.headers.cookie).toEqual('username=John Doe');
});
it('should support queryParams', async ({context, server}) => {
let request;
server.setRoute('/empty.html', (req, res) => {
request = req;
server.serveFile(req, res);
});
for (const method of ['get', 'post', 'fetch']) {
await context.request[method](server.EMPTY_PAGE + '?p1=foo', {
params: {
'p1': 'v1',
'парам2': 'знач2',
}
});
const params = new URLSearchParams(request.url.substr(request.url.indexOf('?')));
expect(params.get('p1')).toEqual('v1');
expect(params.get('парам2')).toEqual('знач2');
}
});
it('should not add context cookie if cookie header passed as a parameter', async ({context, server}) => {
await context.addCookies([{
name: 'username',

15
types/types.d.ts vendored
View file

@ -12646,6 +12646,11 @@ export interface FetchRequest {
*/
method?: string;
/**
* Query parameters to be send with the URL.
*/
params?: { [key: string]: string; };
/**
* Request timeout in milliseconds.
*/
@ -12664,6 +12669,11 @@ export interface FetchRequest {
*/
headers?: { [key: string]: string; };
/**
* Query parameters to be send with the URL.
*/
params?: { [key: string]: string; };
/**
* Request timeout in milliseconds.
*/
@ -12687,6 +12697,11 @@ export interface FetchRequest {
*/
headers?: { [key: string]: string; };
/**
* Query parameters to be send with the URL.
*/
params?: { [key: string]: string; };
/**
* Request timeout in milliseconds.
*/

View file

@ -238,7 +238,7 @@ class TestServer {
request.on('data', chunk => body = Buffer.concat([body, chunk]));
request.on('end', () => resolve(body));
});
const pathName = url.parse(request.url).path;
const pathName = url.parse(request.url).pathname;
this.debugServer(`request ${request.method} ${pathName}`);
if (this._auths.has(pathName)) {
const auth = this._auths.get(pathName);