fix(fetch): use data, form and multipart for different post data (#9248)

This commit is contained in:
Yury Semikhatsky 2021-10-01 12:11:33 -07:00 committed by GitHub
parent f3648a66a3
commit 235eaca34a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 237 additions and 158 deletions

View file

@ -38,13 +38,11 @@ If set changes the fetch method (e.g. PUT or POST). If not specified, GET method
Allows to set HTTP headers.
### option: FetchRequest.fetch.data
- `data` <[string]|[Buffer]|[Serializable]>
### option: FetchRequest.fetch.data = %%-fetch-option-data-%%
Allows to set post data of the fetch. If the data parameter is an object, it will be serialized the following way:
* If `content-type` header is set to `application/x-www-form-urlencoded` the object will be serialized as html form using `application/x-www-form-urlencoded` encoding.
* If `content-type` header is set to `multipart/form-data` the object will be serialized as html form using `multipart/form-data` encoding.
* Otherwise the object will be serialized to json string and `content-type` header will be set to `application/json`.
### option: FetchRequest.fetch.form = %%-fetch-option-form-%%
### option: FetchRequest.fetch.multipart = %%-fetch-option-multipart-%%
### option: FetchRequest.fetch.timeout
- `timeout` <[float]>
@ -114,13 +112,11 @@ Query parameters to be send with the URL.
Allows to set HTTP headers.
### option: FetchRequest.post.data
- `data` <[string]|[Buffer]|[Serializable]>
### option: FetchRequest.post.data = %%-fetch-option-data-%%
Allows to set post data of the fetch. If the data parameter is an object, it will be serialized the following way:
* If `content-type` header is set to `application/x-www-form-urlencoded` the object will be serialized as html form using `application/x-www-form-urlencoded` encoding.
* If `content-type` header is set to `multipart/form-data` the object will be serialized as html form using `multipart/form-data` encoding.
* Otherwise the object will be serialized to json string and `content-type` header will be set to `application/json`.
### option: FetchRequest.post.form = %%-fetch-option-form-%%
### option: FetchRequest.post.multipart = %%-fetch-option-multipart-%%
### option: FetchRequest.post.timeout
- `timeout` <[float]>

View file

@ -298,6 +298,32 @@ Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Us
Emulates consistent window screen size available inside web page via `window.screen`. Is only used when the
[`option: viewport`] is set.
## fetch-option-form
- `form` <[Object]<[string], [string]|[float]|[boolean]>>
Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as
this request body. If this parameter is specified `content-type` header will be set to `application/x-www-form-urlencoded`
unless explicitly provided.
## fetch-option-multipart
- `multipart` <[Object]<[string], [string]|[float]|[boolean]|[ReadStream]|[Object]>>
- `name` <[string]> File name
- `mimeType` <[string]> File type
- `buffer` <[Buffer]> File content
Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as
this request body. If this parameter is specified `content-type` header will be set to `multipart/form-data`
unless explicitly provided. File values can be passed either as [`fs.ReadStream`](https://nodejs.org/api/fs.html#fs_class_fs_readstream)
or as file-like object containing file name, mime-type and its content.
## fetch-option-data
- `data` <[string]|[Buffer]|[Serializable]>
Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string
and `content-type` header will be set to `application/json` if not explicitly set. Otherwise the `content-type` header will be
set to `application/octet-stream` if not explicitly set.
## evaluate-expression
- `expression` <[string]>

View file

@ -33,6 +33,8 @@ export type FetchOptions = {
method?: string,
headers?: Headers,
data?: string | Buffer | Serializable,
form?: { [key: string]: string|number|boolean; };
multipart?: { [key: string]: string|number|boolean|fs.ReadStream|FilePayload; };
timeout?: number,
failOnStatusCode?: boolean,
ignoreHTTPSErrors?: boolean,
@ -68,16 +70,7 @@ export class FetchRequest extends ChannelOwner<channels.FetchRequestChannel, cha
});
}
async post(
urlOrRequest: string | api.Request,
options?: {
params?: { [key: string]: string; };
headers?: { [key: string]: string; };
data?: string | Buffer | Serializable;
timeout?: number;
failOnStatusCode?: boolean;
ignoreHTTPSErrors?: boolean,
}): Promise<FetchResponse> {
async post(urlOrRequest: string | api.Request, options?: Omit<FetchOptions, 'method'>): Promise<FetchResponse> {
return this.fetch(urlOrRequest, {
...options,
method: 'POST',
@ -88,40 +81,46 @@ export class FetchRequest extends ChannelOwner<channels.FetchRequestChannel, cha
return this._wrapApiCall(async (channel: channels.FetchRequestChannel) => {
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((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
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() ;
const headers = headersObj ? headersObjectToArray(headersObj) : undefined;
let formData: any;
let jsonData: any;
let formData: channels.NameValue[] | undefined;
let multipartData: channels.FormField[] | undefined;
let postDataBuffer: Buffer | undefined;
if (options.data) {
if (isString(options.data)) {
if (options.data !== undefined) {
if (isString(options.data))
postDataBuffer = Buffer.from(options.data, 'utf8');
} else if (Buffer.isBuffer(options.data)) {
else if (Buffer.isBuffer(options.data))
postDataBuffer = options.data;
} else if (typeof options.data === 'object') {
formData = {};
// Convert file-like values to ServerFilePayload structs.
for (const [name, value] of Object.entries(options.data)) {
if (isFilePayload(value)) {
const payload = value as FilePayload;
if (!Buffer.isBuffer(payload.buffer))
throw new Error(`Unexpected buffer type of 'data.${name}'`);
formData[name] = filePayloadToJson(payload);
} else if (value instanceof fs.ReadStream) {
formData[name] = await readStreamToJson(value as fs.ReadStream);
} else {
formData[name] = value;
}
}
} else {
else if (typeof options.data === 'object')
jsonData = options.data;
else
throw new Error(`Unexpected 'data' type`);
} else if (options.form) {
formData = objectToArray(options.form);
} else if (options.multipart) {
multipartData = [];
// Convert file-like values to ServerFilePayload structs.
for (const [name, value] of Object.entries(options.multipart)) {
if (isFilePayload(value)) {
const payload = value as FilePayload;
if (!Buffer.isBuffer(payload.buffer))
throw new Error(`Unexpected buffer type of 'data.${name}'`);
multipartData.push({ name, file: filePayloadToJson(payload) });
} else if (value instanceof fs.ReadStream) {
multipartData.push({ name, file: await readStreamToJson(value as fs.ReadStream) });
} else {
multipartData.push({ name, value: String(value) });
}
}
if (postDataBuffer === undefined && formData === undefined)
postDataBuffer = request?.postDataBuffer() || undefined;
}
if (postDataBuffer === undefined && jsonData === undefined && formData === undefined && multipartData === undefined)
postDataBuffer = request?.postDataBuffer() || undefined;
const postData = (postDataBuffer ? postDataBuffer.toString('base64') : undefined);
const result = await channel.fetch({
url,
@ -129,7 +128,9 @@ export class FetchRequest extends ChannelOwner<channels.FetchRequestChannel, cha
method,
headers,
postData,
jsonData,
formData,
multipartData,
timeout: options.timeout,
failOnStatusCode: options.failOnStatusCode,
ignoreHTTPSErrors: options.ignoreHTTPSErrors,

View file

@ -14,13 +14,12 @@
* limitations under the License.
*/
import { Request, Response, Route, WebSocket } from '../server/network';
import * as channels from '../protocol/channels';
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
import { FrameDispatcher } from './frameDispatcher';
import { CallMetadata } from '../server/instrumentation';
import { FetchRequest } from '../server/fetch';
import { arrayToObject, headersArrayToObject } from '../utils/utils';
import { CallMetadata } from '../server/instrumentation';
import { Request, Response, Route, WebSocket } from '../server/network';
import { Dispatcher, DispatcherScope, existingDispatcher, lookupNullableDispatcher } from './dispatcher';
import { FrameDispatcher } from './frameDispatcher';
export class RequestDispatcher extends Dispatcher<Request, channels.RequestInitializer, channels.RequestEvents> implements channels.RequestChannel {
@ -186,17 +185,7 @@ export class FetchRequestDispatcher extends Dispatcher<FetchRequest, channels.Fe
}
async fetch(params: channels.FetchRequestFetchParams, metadata?: channels.Metadata): Promise<channels.FetchRequestFetchResult> {
const { fetchResponse, error } = await this._object.fetch({
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,
formData: params.formData,
timeout: params.timeout,
failOnStatusCode: params.failOnStatusCode,
ignoreHTTPSErrors: params.ignoreHTTPSErrors,
});
const { fetchResponse, error } = await this._object.fetch(params);
let response;
if (fetchResponse) {
response = {

View file

@ -150,6 +150,16 @@ export type SerializedError = {
value?: SerializedValue,
};
export type FormField = {
name: string,
value?: string,
file?: {
name: string,
mimeType: string,
buffer: Binary,
},
};
export type InterceptedResponse = {
request: RequestChannel,
status: number,
@ -172,7 +182,9 @@ export type FetchRequestFetchParams = {
method?: string,
headers?: NameValue[],
postData?: Binary,
formData?: any,
jsonData?: any,
formData?: NameValue[],
multipartData?: FormField[],
timeout?: number,
failOnStatusCode?: boolean,
ignoreHTTPSErrors?: boolean,
@ -182,7 +194,9 @@ export type FetchRequestFetchOptions = {
method?: string,
headers?: NameValue[],
postData?: Binary,
formData?: any,
jsonData?: any,
formData?: NameValue[],
multipartData?: FormField[],
timeout?: number,
failOnStatusCode?: boolean,
ignoreHTTPSErrors?: boolean,

View file

@ -214,6 +214,17 @@ SerializedError:
stack: string?
value: SerializedValue?
FormField:
type: object
properties:
name: string
value: string?
file:
type: object?
properties:
name: string
mimeType: string
buffer: binary
InterceptedResponse:
type: object
@ -242,7 +253,13 @@ FetchRequest:
type: array?
items: NameValue
postData: binary?
formData: json?
jsonData: json?
formData:
type: array?
items: NameValue
multipartData:
type: array?
items: FormField
timeout: number?
failOnStatusCode: boolean?
ignoreHTTPSErrors: boolean?

View file

@ -147,6 +147,15 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
})),
value: tOptional(tType('SerializedValue')),
});
scheme.FormField = tObject({
name: tString,
value: tOptional(tString),
file: tOptional(tObject({
name: tString,
mimeType: tString,
buffer: tBinary,
})),
});
scheme.InterceptedResponse = tObject({
request: tChannel('Request'),
status: tNumber,
@ -159,7 +168,9 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
method: tOptional(tString),
headers: tOptional(tArray(tType('NameValue'))),
postData: tOptional(tBinary),
formData: tOptional(tAny),
jsonData: tOptional(tAny),
formData: tOptional(tArray(tType('NameValue'))),
multipartData: tOptional(tArray(tType('FormField'))),
timeout: tOptional(tNumber),
failOnStatusCode: tOptional(tBoolean),
ignoreHTTPSErrors: tOptional(tBoolean),

View file

@ -23,7 +23,7 @@ import zlib from 'zlib';
import { HTTPCredentials } from '../../types/types';
import * as channels from '../protocol/channels';
import { TimeoutSettings } from '../utils/timeoutSettings';
import { assert, createGuid, getPlaywrightVersion, isFilePayload, monotonicTime } from '../utils/utils';
import { assert, createGuid, getPlaywrightVersion, monotonicTime } from '../utils/utils';
import { BrowserContext } from './browserContext';
import { CookieStore, domainMatches } from './cookieStore';
import { MultipartFormData } from './formData';
@ -32,8 +32,7 @@ import { Playwright } from './playwright';
import * as types from './types';
import { HeadersArray, ProxySettings } from './types';
export type FetchRequestOptions = {
type FetchRequestOptions = {
userAgent: string;
extraHTTPHeaders?: HeadersArray;
httpCredentials?: HTTPCredentials;
@ -84,7 +83,7 @@ export abstract class FetchRequest extends SdkObject {
return uid;
}
async fetch(params: types.FetchOptions): Promise<{fetchResponse?: Omit<types.FetchResponse, 'body'> & { fetchUid: string }, error?: string}> {
async fetch(params: channels.FetchRequestFetchParams): Promise<{fetchResponse?: Omit<types.FetchResponse, 'body'> & { fetchUid: string }, error?: string}> {
try {
const headers: { [name: string]: string } = {};
const defaults = this._defaultOptions();
@ -98,7 +97,7 @@ export abstract class FetchRequest extends SdkObject {
}
if (params.headers) {
for (const [name, value] of Object.entries(params.headers))
for (const { name, value } of params.headers)
headers[name.toLowerCase()] = value;
}
@ -130,19 +129,17 @@ export abstract class FetchRequest extends SdkObject {
const requestUrl = new URL(params.url, defaults.baseURL);
if (params.params) {
for (const [name, value] of Object.entries(params.params))
for (const { name, value } of params.params)
requestUrl.searchParams.set(name, value);
}
let postData;
if (['POST', 'PUSH', 'PATCH'].includes(method))
postData = params.formData ? serializeFormData(params.formData, headers) : params.postData;
else if (params.postData || params.formData)
postData = serializePostData(params, headers);
else if (params.postData || params.jsonData || params.formData || params.multipartData)
throw new Error(`Method ${method} does not accept post data`);
if (postData) {
if (postData)
headers['content-length'] = String(postData.byteLength);
headers['content-type'] ??= 'application/octet-stream';
}
const fetchResponse = await this._sendRequest(requestUrl, options, postData);
const fetchUid = this._storeResponseBody(fetchResponse.body);
if (params.failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400))
@ -458,30 +455,31 @@ function parseCookie(header: string): types.NetworkCookie | null {
return cookie;
}
function serializeFormData(data: any, headers: { [name: string]: string }): Buffer {
const contentType = headers['content-type'] || 'application/json';
if (contentType === 'application/json') {
const json = JSON.stringify(data);
headers['content-type'] ??= contentType;
function serializePostData(params: channels.FetchRequestFetchParams, headers: { [name: string]: string }): Buffer | undefined {
assert((params.postData ? 1 : 0) + (params.jsonData ? 1 : 0) + (params.formData ? 1 : 0) + (params.multipartData ? 1 : 0) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
if (params.jsonData) {
const json = JSON.stringify(params.jsonData);
headers['content-type'] ??= 'application/json';
return Buffer.from(json, 'utf8');
} else if (contentType === 'application/x-www-form-urlencoded') {
} else if (params.formData) {
const searchParams = new URLSearchParams();
for (const [name, value] of Object.entries(data))
searchParams.append(name, String(value));
for (const { name, value } of params.formData)
searchParams.append(name, value);
headers['content-type'] ??= 'application/x-www-form-urlencoded';
return Buffer.from(searchParams.toString(), 'utf8');
} else if (contentType === 'multipart/form-data') {
} else if (params.multipartData) {
const formData = new MultipartFormData();
for (const [name, value] of Object.entries(data)) {
if (isFilePayload(value)) {
const payload = value as types.FilePayload;
formData.addFileField(name, payload);
} else if (value !== undefined) {
formData.addField(name, String(value));
}
for (const field of params.multipartData) {
if (field.file)
formData.addFileField(field.name, field.file);
else if (field.value)
formData.addField(field.name, field.value);
}
headers['content-type'] = formData.contentTypeHeader();
headers['content-type'] ??= formData.contentTypeHeader();
return formData.finish();
} else {
throw new Error(`Cannot serialize data using content type: ${contentType}`);
} else if (params.postData) {
headers['content-type'] ??= 'application/octet-stream';
return Buffer.from(params.postData, 'base64');
}
return undefined;
}

View file

@ -372,30 +372,6 @@ export type SetStorageState = {
origins?: OriginStorage[]
};
export type FileInfo = {
name: string,
mimeType?: string,
buffer: Buffer,
};
export type FormField = {
name: string,
value?: string,
file?: FileInfo,
};
export type FetchOptions = {
url: string,
params?: { [name: string]: string },
method?: string,
headers?: { [name: string]: string },
postData?: Buffer,
formData?: FormField[],
timeout?: number,
failOnStatusCode?: boolean,
ignoreHTTPSErrors?: boolean,
};
export type FetchResponse = {
url: string,
status: number,

View file

@ -384,12 +384,12 @@ class HashStream extends stream.Writable {
}
}
export function objectToArray(map?: { [key: string]: string }): NameValue[] | undefined {
export function objectToArray(map?: { [key: string]: any }): NameValue[] | undefined {
if (!map)
return undefined;
const result = [];
for (const [name, value] of Object.entries(map))
result.push({ name, value });
result.push({ name, value: String(value) });
return result;
}

View file

@ -726,10 +726,7 @@ it('should support application/x-www-form-urlencoded', async function({ context,
const [req] = await Promise.all([
server.waitForRequest('/empty.html'),
context._request.post(server.EMPTY_PAGE, {
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
data: {
form: {
firstName: 'John',
lastName: 'Doe',
file: 'f.js',
@ -784,10 +781,7 @@ it('should support multipart/form-data', async function({ context, page, server
const [{ error, fields, files, serverRequest }, response] = await Promise.all([
formReceived,
context._request.post(server.EMPTY_PAGE, {
headers: {
'content-type': 'multipart/form-data'
},
data: {
multipart: {
firstName: 'John',
lastName: 'Doe',
file
@ -819,10 +813,7 @@ it('should support multipart/form-data with ReadSream values', async function({
const [{ error, fields, files, serverRequest }, response] = await Promise.all([
formReceived,
context._request.post(server.EMPTY_PAGE, {
headers: {
'content-type': 'multipart/form-data'
},
data: {
multipart: {
firstName: 'John',
lastName: 'Doe',
readStream
@ -841,17 +832,24 @@ it('should support multipart/form-data with ReadSream values', async function({
expect(response.status()).toBe(200);
});
it('should throw nice error on unsupported encoding', async function({ context, server }) {
const error = await context._request.post(server.EMPTY_PAGE, {
headers: {
'content-type': 'unknown'
},
data: {
firstName: 'John',
lastName: 'Doe',
}
}).catch(e => e);
expect(error.message).toContain('Cannot serialize data using content type: unknown');
it('should serialize data to json regardless of content-type', async function({ context, server }) {
const data = {
firstName: 'John',
lastName: 'Doe',
};
const [req] = await Promise.all([
server.waitForRequest('/empty.html'),
context._request.post(server.EMPTY_PAGE, {
headers: {
'content-type': 'unknown'
},
data
}),
]);
expect(req.method).toBe('POST');
expect(req.headers['content-type']).toBe('unknown');
const body = (await req.postBody).toString('utf8');
expect(body).toEqual(JSON.stringify(data));
});
it('should throw nice error on unsupported data type', async function({ context, server }) {
@ -867,9 +865,6 @@ it('should throw nice error on unsupported data type', async function({ context,
it('should throw when data passed for unsupported request', async function({ context, server }) {
const error = await context._request.fetch(server.EMPTY_PAGE, {
method: 'GET',
headers: {
'content-type': 'application/json'
},
data: {
foo: 'bar'
}

79
types/types.d.ts vendored
View file

@ -18,6 +18,7 @@ import { Protocol } from './protocol';
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { Readable } from 'stream';
import { ReadStream } from 'fs';
import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from './structs';
type PageWaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & {
@ -12682,12 +12683,9 @@ export interface FetchRequest {
*/
fetch(urlOrRequest: string|Request, options?: {
/**
* Allows to set post data of the fetch. If the data parameter is an object, it will be serialized the following way:
* - If `content-type` header is set to `application/x-www-form-urlencoded` the object will be serialized as html form
* using `application/x-www-form-urlencoded` encoding.
* - If `content-type` header is set to `multipart/form-data` the object will be serialized as html form using
* `multipart/form-data` encoding.
* - Otherwise the object will be serialized to json string and `content-type` header will be set to `application/json`.
* Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string and
* `content-type` header will be set to `application/json` if not explicitly set. Otherwise the `content-type` header will
* be set to `application/octet-stream` if not explicitly set.
*/
data?: string|Buffer|Serializable;
@ -12696,6 +12694,13 @@ export interface FetchRequest {
*/
failOnStatusCode?: boolean;
/**
* Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as
* this request body. If this parameter is specified `content-type` header will be set to
* `application/x-www-form-urlencoded` unless explicitly provided.
*/
form?: { [key: string]: string|number|boolean; };
/**
* Allows to set HTTP headers.
*/
@ -12711,6 +12716,29 @@ export interface FetchRequest {
*/
method?: string;
/**
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request
* body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly
* provided. File values can be passed either as [`fs.ReadStream`](https://nodejs.org/api/fs.html#fs_class_fs_readstream)
* or as file-like object containing file name, mime-type and its content.
*/
multipart?: { [key: string]: string|number|boolean|ReadStream|{
/**
* File name
*/
name: string;
/**
* File type
*/
mimeType: string;
/**
* File content
*/
buffer: Buffer;
}; };
/**
* Query parameters to be send with the URL.
*/
@ -12763,12 +12791,9 @@ export interface FetchRequest {
*/
post(urlOrRequest: string|Request, options?: {
/**
* Allows to set post data of the fetch. If the data parameter is an object, it will be serialized the following way:
* - If `content-type` header is set to `application/x-www-form-urlencoded` the object will be serialized as html form
* using `application/x-www-form-urlencoded` encoding.
* - If `content-type` header is set to `multipart/form-data` the object will be serialized as html form using
* `multipart/form-data` encoding.
* - Otherwise the object will be serialized to json string and `content-type` header will be set to `application/json`.
* Allows to set post data of the request. If the data parameter is an object, it will be serialized to json string and
* `content-type` header will be set to `application/json` if not explicitly set. Otherwise the `content-type` header will
* be set to `application/octet-stream` if not explicitly set.
*/
data?: string|Buffer|Serializable;
@ -12777,6 +12802,13 @@ export interface FetchRequest {
*/
failOnStatusCode?: boolean;
/**
* Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as
* this request body. If this parameter is specified `content-type` header will be set to
* `application/x-www-form-urlencoded` unless explicitly provided.
*/
form?: { [key: string]: string|number|boolean; };
/**
* Allows to set HTTP headers.
*/
@ -12787,6 +12819,29 @@ export interface FetchRequest {
*/
ignoreHTTPSErrors?: boolean;
/**
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this request
* body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless explicitly
* provided. File values can be passed either as [`fs.ReadStream`](https://nodejs.org/api/fs.html#fs_class_fs_readstream)
* or as file-like object containing file name, mime-type and its content.
*/
multipart?: { [key: string]: string|number|boolean|ReadStream|{
/**
* File name
*/
name: string;
/**
* File type
*/
mimeType: string;
/**
* File content
*/
buffer: Buffer;
}; };
/**
* Query parameters to be send with the URL.
*/

View file

@ -17,6 +17,7 @@ import { Protocol } from './protocol';
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { Readable } from 'stream';
import { ReadStream } from 'fs';
import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from './structs';
type PageWaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & {