feat(fetch): support query params (#8893)
This commit is contained in:
parent
3e5645ecea
commit
f8c0f0d637
|
|
@ -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.
|
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
|
### option: FetchRequest.fetch.method
|
||||||
- `method` <[string]>
|
- `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.
|
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
|
### option: FetchRequest.get.headers
|
||||||
- `headers` <[Object]<[string], [string]>>
|
- `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.
|
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
|
### option: FetchRequest.post.headers
|
||||||
- `headers` <[Object]<[string], [string]>>
|
- `headers` <[Object]<[string], [string]>>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,19 @@
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import { HeadersArray } from '../common/types';
|
import { HeadersArray } from '../common/types';
|
||||||
import * as channels from '../protocol/channels';
|
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 { BrowserContext } from './browserContext';
|
||||||
import * as network from './network';
|
import * as network from './network';
|
||||||
import { RawHeaders } from './network';
|
import { RawHeaders } from './network';
|
||||||
import { Headers } from './types';
|
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 {
|
export class FetchRequest implements api.FetchRequest {
|
||||||
private _context: BrowserContext;
|
private _context: BrowserContext;
|
||||||
|
|
@ -35,6 +41,7 @@ export class FetchRequest implements api.FetchRequest {
|
||||||
async get(
|
async get(
|
||||||
urlOrRequest: string | api.Request,
|
urlOrRequest: string | api.Request,
|
||||||
options?: {
|
options?: {
|
||||||
|
params?: { [key: string]: string; };
|
||||||
headers?: { [key: string]: string; };
|
headers?: { [key: string]: string; };
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
}): Promise<FetchResponse> {
|
}): Promise<FetchResponse> {
|
||||||
|
|
@ -47,6 +54,7 @@ export class FetchRequest implements api.FetchRequest {
|
||||||
async post(
|
async post(
|
||||||
urlOrRequest: string | api.Request,
|
urlOrRequest: string | api.Request,
|
||||||
options?: {
|
options?: {
|
||||||
|
params?: { [key: string]: string; };
|
||||||
headers?: { [key: string]: string; };
|
headers?: { [key: string]: string; };
|
||||||
data?: string | Buffer;
|
data?: string | Buffer;
|
||||||
timeout?: number;
|
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;
|
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');
|
assert(request || typeof urlOrRequest === 'string', 'First argument must be either URL string or Request');
|
||||||
const url = request ? request.url() : urlOrRequest as string;
|
const url = request ? request.url() : urlOrRequest as string;
|
||||||
|
const params = objectToArray(options.params);
|
||||||
const method = options.method || request?.method();
|
const method = options.method || request?.method();
|
||||||
// Cannot call allHeaders() here as the request may be paused inside route handler.
|
// Cannot call allHeaders() here as the request may be paused inside route handler.
|
||||||
const headersObj = options.headers || request?.headers() ;
|
const headersObj = options.headers || request?.headers() ;
|
||||||
|
|
@ -72,6 +81,7 @@ export class FetchRequest implements api.FetchRequest {
|
||||||
const postData = (postDataBuffer ? postDataBuffer.toString('base64') : undefined);
|
const postData = (postDataBuffer ? postDataBuffer.toString('base64') : undefined);
|
||||||
const result = await channel.fetch({
|
const result = await channel.fetch({
|
||||||
url,
|
url,
|
||||||
|
params,
|
||||||
method,
|
method,
|
||||||
headers,
|
headers,
|
||||||
postData,
|
postData,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { CallMetadata } from '../server/instrumentation';
|
||||||
import { ArtifactDispatcher } from './artifactDispatcher';
|
import { ArtifactDispatcher } from './artifactDispatcher';
|
||||||
import { Artifact } from '../server/artifact';
|
import { Artifact } from '../server/artifact';
|
||||||
import { Request, Response } from '../server/network';
|
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 {
|
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer, channels.BrowserContextEvents> implements channels.BrowserContextChannel {
|
||||||
private _context: BrowserContext;
|
private _context: BrowserContext;
|
||||||
|
|
@ -110,6 +110,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
async fetch(params: channels.BrowserContextFetchParams): Promise<channels.BrowserContextFetchResult> {
|
async fetch(params: channels.BrowserContextFetchParams): Promise<channels.BrowserContextFetchResult> {
|
||||||
const { fetchResponse, error } = await playwrightFetch(this._context, {
|
const { fetchResponse, error } = await playwrightFetch(this._context, {
|
||||||
url: params.url,
|
url: params.url,
|
||||||
|
params: arrayToObject(params.params),
|
||||||
method: params.method,
|
method: params.method,
|
||||||
headers: params.headers ? headersArrayToObject(params.headers, false) : undefined,
|
headers: params.headers ? headersArrayToObject(params.headers, false) : undefined,
|
||||||
postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,
|
postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,
|
||||||
|
|
|
||||||
|
|
@ -857,12 +857,14 @@ export type BrowserContextExposeBindingOptions = {
|
||||||
export type BrowserContextExposeBindingResult = void;
|
export type BrowserContextExposeBindingResult = void;
|
||||||
export type BrowserContextFetchParams = {
|
export type BrowserContextFetchParams = {
|
||||||
url: string,
|
url: string,
|
||||||
|
params?: NameValue[],
|
||||||
method?: string,
|
method?: string,
|
||||||
headers?: NameValue[],
|
headers?: NameValue[],
|
||||||
postData?: Binary,
|
postData?: Binary,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
};
|
};
|
||||||
export type BrowserContextFetchOptions = {
|
export type BrowserContextFetchOptions = {
|
||||||
|
params?: NameValue[],
|
||||||
method?: string,
|
method?: string,
|
||||||
headers?: NameValue[],
|
headers?: NameValue[],
|
||||||
postData?: Binary,
|
postData?: Binary,
|
||||||
|
|
|
||||||
|
|
@ -616,6 +616,9 @@ BrowserContext:
|
||||||
fetch:
|
fetch:
|
||||||
parameters:
|
parameters:
|
||||||
url: string
|
url: string
|
||||||
|
params:
|
||||||
|
type: array?
|
||||||
|
items: NameValue
|
||||||
method: string?
|
method: string?
|
||||||
headers:
|
headers:
|
||||||
type: array?
|
type: array?
|
||||||
|
|
|
||||||
|
|
@ -394,6 +394,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
});
|
});
|
||||||
scheme.BrowserContextFetchParams = tObject({
|
scheme.BrowserContextFetchParams = tObject({
|
||||||
url: tString,
|
url: tString,
|
||||||
|
params: tOptional(tArray(tType('NameValue'))),
|
||||||
method: tOptional(tString),
|
method: tOptional(tString),
|
||||||
headers: tOptional(tArray(tType('NameValue'))),
|
headers: tOptional(tArray(tType('NameValue'))),
|
||||||
postData: tOptional(tBinary),
|
postData: tOptional(tBinary),
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,13 @@ export async function playwrightFetch(context: BrowserContext, params: types.Fet
|
||||||
if (context._options.ignoreHTTPSErrors)
|
if (context._options.ignoreHTTPSErrors)
|
||||||
options.rejectUnauthorized = false;
|
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);
|
const fetchUid = context.storeFetchResponseBody(fetchResponse.body);
|
||||||
return { fetchResponse: { ...fetchResponse, fetchUid } };
|
return { fetchResponse: { ...fetchResponse, fetchUid } };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,7 @@ export type SetStorageState = {
|
||||||
|
|
||||||
export type FetchOptions = {
|
export type FetchOptions = {
|
||||||
url: string,
|
url: string,
|
||||||
|
params?: { [name: string]: string },
|
||||||
method?: string,
|
method?: string,
|
||||||
headers?: { [name: string]: string },
|
headers?: { [name: string]: string },
|
||||||
postData?: Buffer,
|
postData?: Buffer,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { spawn } from 'child_process';
|
||||||
import { getProxyForUrl } from 'proxy-from-env';
|
import { getProxyForUrl } from 'proxy-from-env';
|
||||||
import * as URL from 'url';
|
import * as URL from 'url';
|
||||||
import { getUbuntuVersionSync } from './ubuntuVersion';
|
import { getUbuntuVersionSync } from './ubuntuVersion';
|
||||||
|
import { NameValue } from '../protocol/channels';
|
||||||
|
|
||||||
// `https-proxy-agent` v5 is written in TypeScript and exposes generated types.
|
// `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
|
// 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> {
|
export async function calculateFileSha1(filename: string): Promise<string> {
|
||||||
const hashStream = new HashStream();
|
const hashStream = new HashStream();
|
||||||
const stream = fs.createReadStream(filename);
|
const stream = fs.createReadStream(filename);
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ it.afterAll(() => {
|
||||||
http.globalAgent = prevAgent;
|
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');
|
const response = await context.request.get(server.PREFIX + '/simple.json');
|
||||||
expect(response.url()).toBe(server.PREFIX + '/simple.json');
|
expect(response.url()).toBe(server.PREFIX + '/simple.json');
|
||||||
expect(response.status()).toBe(200);
|
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');
|
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}) => {
|
it('should not add context cookie if cookie header passed as a parameter', async ({context, server}) => {
|
||||||
await context.addCookies([{
|
await context.addCookies([{
|
||||||
name: 'username',
|
name: 'username',
|
||||||
|
|
|
||||||
15
types/types.d.ts
vendored
15
types/types.d.ts
vendored
|
|
@ -12646,6 +12646,11 @@ export interface FetchRequest {
|
||||||
*/
|
*/
|
||||||
method?: string;
|
method?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query parameters to be send with the URL.
|
||||||
|
*/
|
||||||
|
params?: { [key: string]: string; };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request timeout in milliseconds.
|
* Request timeout in milliseconds.
|
||||||
*/
|
*/
|
||||||
|
|
@ -12664,6 +12669,11 @@ export interface FetchRequest {
|
||||||
*/
|
*/
|
||||||
headers?: { [key: string]: string; };
|
headers?: { [key: string]: string; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query parameters to be send with the URL.
|
||||||
|
*/
|
||||||
|
params?: { [key: string]: string; };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request timeout in milliseconds.
|
* Request timeout in milliseconds.
|
||||||
*/
|
*/
|
||||||
|
|
@ -12687,6 +12697,11 @@ export interface FetchRequest {
|
||||||
*/
|
*/
|
||||||
headers?: { [key: string]: string; };
|
headers?: { [key: string]: string; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query parameters to be send with the URL.
|
||||||
|
*/
|
||||||
|
params?: { [key: string]: string; };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request timeout in milliseconds.
|
* Request timeout in milliseconds.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ class TestServer {
|
||||||
request.on('data', chunk => body = Buffer.concat([body, chunk]));
|
request.on('data', chunk => body = Buffer.concat([body, chunk]));
|
||||||
request.on('end', () => resolve(body));
|
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}`);
|
this.debugServer(`request ${request.method} ${pathName}`);
|
||||||
if (this._auths.has(pathName)) {
|
if (this._auths.has(pathName)) {
|
||||||
const auth = this._auths.get(pathName);
|
const auth = this._auths.get(pathName);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue