feat(library): accept FormData in fetch (#32602)
Closes https://github.com/microsoft/playwright/issues/26520 by accepting `FormData`, which became stable in Node.js in v21.
This commit is contained in:
parent
cd4dabef8b
commit
48c7fb6b06
|
|
@ -159,7 +159,10 @@ context cookies from the response. The method will automatically follow redirect
|
||||||
### option: APIRequestContext.delete.data = %%-js-python-csharp-fetch-option-data-%%
|
### option: APIRequestContext.delete.data = %%-js-python-csharp-fetch-option-data-%%
|
||||||
* since: v1.17
|
* since: v1.17
|
||||||
|
|
||||||
### option: APIRequestContext.delete.form = %%-js-python-fetch-option-form-%%
|
### option: APIRequestContext.delete.form = %%-js-fetch-option-form-%%
|
||||||
|
* since: v1.17
|
||||||
|
|
||||||
|
### option: APIRequestContext.delete.form = %%-python-fetch-option-form-%%
|
||||||
* since: v1.17
|
* since: v1.17
|
||||||
|
|
||||||
### option: APIRequestContext.delete.form = %%-csharp-fetch-option-form-%%
|
### option: APIRequestContext.delete.form = %%-csharp-fetch-option-form-%%
|
||||||
|
|
@ -332,7 +335,10 @@ If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/
|
||||||
### option: APIRequestContext.fetch.data = %%-js-python-csharp-fetch-option-data-%%
|
### option: APIRequestContext.fetch.data = %%-js-python-csharp-fetch-option-data-%%
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
|
|
||||||
### option: APIRequestContext.fetch.form = %%-js-python-fetch-option-form-%%
|
### option: APIRequestContext.fetch.form = %%-js-fetch-option-form-%%
|
||||||
|
* since: v1.16
|
||||||
|
|
||||||
|
### option: APIRequestContext.fetch.form = %%-python-fetch-option-form-%%
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
|
|
||||||
### option: APIRequestContext.fetch.form = %%-csharp-fetch-option-form-%%
|
### option: APIRequestContext.fetch.form = %%-csharp-fetch-option-form-%%
|
||||||
|
|
@ -442,7 +448,10 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query
|
||||||
### option: APIRequestContext.get.data = %%-js-python-csharp-fetch-option-data-%%
|
### option: APIRequestContext.get.data = %%-js-python-csharp-fetch-option-data-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
### option: APIRequestContext.get.form = %%-js-python-fetch-option-form-%%
|
### option: APIRequestContext.get.form = %%-js-fetch-option-form-%%
|
||||||
|
* since: v1.26
|
||||||
|
|
||||||
|
### option: APIRequestContext.get.form = %%-python-fetch-option-form-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
### option: APIRequestContext.get.form = %%-csharp-fetch-option-form-%%
|
### option: APIRequestContext.get.form = %%-csharp-fetch-option-form-%%
|
||||||
|
|
@ -504,7 +513,10 @@ context cookies from the response. The method will automatically follow redirect
|
||||||
### option: APIRequestContext.head.data = %%-js-python-csharp-fetch-option-data-%%
|
### option: APIRequestContext.head.data = %%-js-python-csharp-fetch-option-data-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
### option: APIRequestContext.head.form = %%-js-python-fetch-option-form-%%
|
### option: APIRequestContext.head.form = %%-python-fetch-option-form-%%
|
||||||
|
* since: v1.26
|
||||||
|
|
||||||
|
### option: APIRequestContext.head.form = %%-js-fetch-option-form-%%
|
||||||
* since: v1.26
|
* since: v1.26
|
||||||
|
|
||||||
### option: APIRequestContext.head.form = %%-csharp-fetch-option-form-%%
|
### option: APIRequestContext.head.form = %%-csharp-fetch-option-form-%%
|
||||||
|
|
@ -566,7 +578,10 @@ context cookies from the response. The method will automatically follow redirect
|
||||||
### option: APIRequestContext.patch.data = %%-js-python-csharp-fetch-option-data-%%
|
### option: APIRequestContext.patch.data = %%-js-python-csharp-fetch-option-data-%%
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
|
|
||||||
### option: APIRequestContext.patch.form = %%-js-python-fetch-option-form-%%
|
### option: APIRequestContext.patch.form = %%-js-fetch-option-form-%%
|
||||||
|
* since: v1.16
|
||||||
|
|
||||||
|
### option: APIRequestContext.patch.form = %%-python-fetch-option-form-%%
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
|
|
||||||
### option: APIRequestContext.patch.form = %%-csharp-fetch-option-form-%%
|
### option: APIRequestContext.patch.form = %%-csharp-fetch-option-form-%%
|
||||||
|
|
@ -749,7 +764,10 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar
|
||||||
### option: APIRequestContext.post.data = %%-js-python-csharp-fetch-option-data-%%
|
### option: APIRequestContext.post.data = %%-js-python-csharp-fetch-option-data-%%
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
|
|
||||||
### option: APIRequestContext.post.form = %%-js-python-fetch-option-form-%%
|
### option: APIRequestContext.post.form = %%-js-fetch-option-form-%%
|
||||||
|
* since: v1.16
|
||||||
|
|
||||||
|
### option: APIRequestContext.post.form = %%-python-fetch-option-form-%%
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
|
|
||||||
### option: APIRequestContext.post.form = %%-csharp-fetch-option-form-%%
|
### option: APIRequestContext.post.form = %%-csharp-fetch-option-form-%%
|
||||||
|
|
@ -811,7 +829,10 @@ context cookies from the response. The method will automatically follow redirect
|
||||||
### option: APIRequestContext.put.data = %%-js-python-csharp-fetch-option-data-%%
|
### option: APIRequestContext.put.data = %%-js-python-csharp-fetch-option-data-%%
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
|
|
||||||
### option: APIRequestContext.put.form = %%-js-python-fetch-option-form-%%
|
### option: APIRequestContext.put.form = %%-python-fetch-option-form-%%
|
||||||
|
* since: v1.16
|
||||||
|
|
||||||
|
### option: APIRequestContext.put.form = %%-js-fetch-option-form-%%
|
||||||
* since: v1.16
|
* since: v1.16
|
||||||
|
|
||||||
### option: APIRequestContext.put.form = %%-csharp-fetch-option-form-%%
|
### option: APIRequestContext.put.form = %%-csharp-fetch-option-form-%%
|
||||||
|
|
|
||||||
|
|
@ -405,8 +405,16 @@ Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to d
|
||||||
Whether to throw on response codes other than 2xx and 3xx. By default response object is returned
|
Whether to throw on response codes other than 2xx and 3xx. By default response object is returned
|
||||||
for all status codes.
|
for all status codes.
|
||||||
|
|
||||||
## js-python-fetch-option-form
|
## js-fetch-option-form
|
||||||
* langs: js, python
|
* langs: js
|
||||||
|
- `form` <[Object]<[string], [string]|[float]|[boolean]>|[FormData]>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## python-fetch-option-form
|
||||||
|
* langs: python
|
||||||
- `form` <[Object]<[string], [string]|[float]|[boolean]>>
|
- `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
|
Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ export type FetchOptions = {
|
||||||
method?: string,
|
method?: string,
|
||||||
headers?: Headers,
|
headers?: Headers,
|
||||||
data?: string | Buffer | Serializable,
|
data?: string | Buffer | Serializable,
|
||||||
form?: { [key: string]: string|number|boolean; };
|
form?: { [key: string]: string|number|boolean; } | FormData;
|
||||||
multipart?: { [key: string]: string|number|boolean|fs.ReadStream|FilePayload; };
|
multipart?: { [key: string]: string|number|boolean|fs.ReadStream|FilePayload; } | FormData;
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
failOnStatusCode?: boolean,
|
failOnStatusCode?: boolean,
|
||||||
ignoreHTTPSErrors?: boolean,
|
ignoreHTTPSErrors?: boolean,
|
||||||
|
|
@ -202,7 +202,16 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
||||||
throw new Error(`Unexpected 'data' type`);
|
throw new Error(`Unexpected 'data' type`);
|
||||||
}
|
}
|
||||||
} else if (options.form) {
|
} else if (options.form) {
|
||||||
formData = objectToArray(options.form);
|
if (globalThis.FormData && options.form instanceof FormData) {
|
||||||
|
formData = [];
|
||||||
|
for (const [name, value] of options.form.entries()) {
|
||||||
|
if (typeof value !== 'string')
|
||||||
|
throw new Error(`Expected string for options.form["${name}"], found File. Please use options.multipart instead.`);
|
||||||
|
formData.push({ name, value });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formData = objectToArray(options.form);
|
||||||
|
}
|
||||||
} else if (options.multipart) {
|
} else if (options.multipart) {
|
||||||
multipartData = [];
|
multipartData = [];
|
||||||
if (globalThis.FormData && options.multipart instanceof FormData) {
|
if (globalThis.FormData && options.multipart instanceof FormData) {
|
||||||
|
|
|
||||||
14
packages/playwright-core/types/types.d.ts
vendored
14
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -16559,7 +16559,7 @@ export interface APIRequestContext {
|
||||||
* as this request body. If this parameter is specified `content-type` header will be set to
|
* as this request body. If this parameter is specified `content-type` header will be set to
|
||||||
* `application/x-www-form-urlencoded` unless explicitly provided.
|
* `application/x-www-form-urlencoded` unless explicitly provided.
|
||||||
*/
|
*/
|
||||||
form?: { [key: string]: string|number|boolean; };
|
form?: { [key: string]: string|number|boolean; }|FormData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
||||||
|
|
@ -16689,7 +16689,7 @@ export interface APIRequestContext {
|
||||||
* as this request body. If this parameter is specified `content-type` header will be set to
|
* as this request body. If this parameter is specified `content-type` header will be set to
|
||||||
* `application/x-www-form-urlencoded` unless explicitly provided.
|
* `application/x-www-form-urlencoded` unless explicitly provided.
|
||||||
*/
|
*/
|
||||||
form?: { [key: string]: string|number|boolean; };
|
form?: { [key: string]: string|number|boolean; }|FormData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
||||||
|
|
@ -16807,7 +16807,7 @@ export interface APIRequestContext {
|
||||||
* as this request body. If this parameter is specified `content-type` header will be set to
|
* as this request body. If this parameter is specified `content-type` header will be set to
|
||||||
* `application/x-www-form-urlencoded` unless explicitly provided.
|
* `application/x-www-form-urlencoded` unless explicitly provided.
|
||||||
*/
|
*/
|
||||||
form?: { [key: string]: string|number|boolean; };
|
form?: { [key: string]: string|number|boolean; }|FormData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
||||||
|
|
@ -16893,7 +16893,7 @@ export interface APIRequestContext {
|
||||||
* as this request body. If this parameter is specified `content-type` header will be set to
|
* as this request body. If this parameter is specified `content-type` header will be set to
|
||||||
* `application/x-www-form-urlencoded` unless explicitly provided.
|
* `application/x-www-form-urlencoded` unless explicitly provided.
|
||||||
*/
|
*/
|
||||||
form?: { [key: string]: string|number|boolean; };
|
form?: { [key: string]: string|number|boolean; }|FormData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
||||||
|
|
@ -16979,7 +16979,7 @@ export interface APIRequestContext {
|
||||||
* as this request body. If this parameter is specified `content-type` header will be set to
|
* as this request body. If this parameter is specified `content-type` header will be set to
|
||||||
* `application/x-www-form-urlencoded` unless explicitly provided.
|
* `application/x-www-form-urlencoded` unless explicitly provided.
|
||||||
*/
|
*/
|
||||||
form?: { [key: string]: string|number|boolean; };
|
form?: { [key: string]: string|number|boolean; }|FormData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
||||||
|
|
@ -17107,7 +17107,7 @@ export interface APIRequestContext {
|
||||||
* as this request body. If this parameter is specified `content-type` header will be set to
|
* as this request body. If this parameter is specified `content-type` header will be set to
|
||||||
* `application/x-www-form-urlencoded` unless explicitly provided.
|
* `application/x-www-form-urlencoded` unless explicitly provided.
|
||||||
*/
|
*/
|
||||||
form?: { [key: string]: string|number|boolean; };
|
form?: { [key: string]: string|number|boolean; }|FormData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
||||||
|
|
@ -17193,7 +17193,7 @@ export interface APIRequestContext {
|
||||||
* as this request body. If this parameter is specified `content-type` header will be set to
|
* as this request body. If this parameter is specified `content-type` header will be set to
|
||||||
* `application/x-www-form-urlencoded` unless explicitly provided.
|
* `application/x-www-form-urlencoded` unless explicitly provided.
|
||||||
*/
|
*/
|
||||||
form?: { [key: string]: string|number|boolean; };
|
form?: { [key: string]: string|number|boolean; }|FormData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
* Allows to set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by
|
||||||
|
|
|
||||||
|
|
@ -960,6 +960,22 @@ it('should support application/x-www-form-urlencoded', async function({ context,
|
||||||
expect(params.get('file')).toBe('f.js');
|
expect(params.get('file')).toBe('f.js');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support application/x-www-form-urlencoded with param lists', async function({ context, page, server }) {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('foo', '1');
|
||||||
|
form.append('foo', '2');
|
||||||
|
const [req] = await Promise.all([
|
||||||
|
server.waitForRequest('/empty.html'),
|
||||||
|
context.request.post(server.EMPTY_PAGE, { form })
|
||||||
|
]);
|
||||||
|
expect(req.method).toBe('POST');
|
||||||
|
expect(req.headers['content-type']).toBe('application/x-www-form-urlencoded');
|
||||||
|
const body = (await req.postBody).toString('utf8');
|
||||||
|
const params = new URLSearchParams(body);
|
||||||
|
expect(req.headers['content-length']).toBe(String(params.toString().length));
|
||||||
|
expect(params.getAll('foo')).toEqual(['1', '2']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should encode to application/json by default', async function({ context, page, server }) {
|
it('should encode to application/json by default', async function({ context, page, server }) {
|
||||||
const data = {
|
const data = {
|
||||||
firstName: 'John',
|
firstName: 'John',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue