Skip to content

Commit

Permalink
feat(library): accept FormData in fetch (#32602)
Browse files Browse the repository at this point in the history
Closes #26520 by accepting
`FormData`, which became stable in Node.js in v21.
  • Loading branch information
Skn0tt committed Sep 13, 2024
1 parent cd4dabe commit 48c7fb6
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 19 deletions.
35 changes: 28 additions & 7 deletions docs/src/api/class-apirequestcontext.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-%%
* 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

### option: APIRequestContext.delete.form = %%-csharp-fetch-option-form-%%
Expand Down Expand Up @@ -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-%%
* 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

### option: APIRequestContext.fetch.form = %%-csharp-fetch-option-form-%%
Expand Down Expand Up @@ -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-%%
* 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

### option: APIRequestContext.get.form = %%-csharp-fetch-option-form-%%
Expand Down Expand Up @@ -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-%%
* 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

### option: APIRequestContext.head.form = %%-csharp-fetch-option-form-%%
Expand Down Expand Up @@ -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-%%
* 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

### option: APIRequestContext.patch.form = %%-csharp-fetch-option-form-%%
Expand Down Expand Up @@ -749,7 +764,10 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar
### option: APIRequestContext.post.data = %%-js-python-csharp-fetch-option-data-%%
* 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

### option: APIRequestContext.post.form = %%-csharp-fetch-option-form-%%
Expand Down Expand Up @@ -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-%%
* 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

### option: APIRequestContext.put.form = %%-csharp-fetch-option-form-%%
Expand Down
12 changes: 10 additions & 2 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
for all status codes.

## js-python-fetch-option-form
* langs: js, python
## js-fetch-option-form
* 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]>>

Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as
Expand Down
15 changes: 12 additions & 3 deletions packages/playwright-core/src/client/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,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; };
form?: { [key: string]: string|number|boolean; } | FormData;
multipart?: { [key: string]: string|number|boolean|fs.ReadStream|FilePayload; } | FormData;
timeout?: number,
failOnStatusCode?: boolean,
ignoreHTTPSErrors?: boolean,
Expand Down Expand Up @@ -202,7 +202,16 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
throw new Error(`Unexpected 'data' type`);
}
} 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) {
multipartData = [];
if (globalThis.FormData && options.multipart instanceof FormData) {
Expand Down
14 changes: 7 additions & 7 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16559,7 +16559,7 @@ export interface APIRequestContext {
* 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; };
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
Expand Down Expand Up @@ -16689,7 +16689,7 @@ export interface APIRequestContext {
* 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; };
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
Expand Down Expand Up @@ -16807,7 +16807,7 @@ export interface APIRequestContext {
* 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; };
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
Expand Down Expand Up @@ -16893,7 +16893,7 @@ export interface APIRequestContext {
* 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; };
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
Expand Down Expand Up @@ -16979,7 +16979,7 @@ export interface APIRequestContext {
* 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; };
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
Expand Down Expand Up @@ -17107,7 +17107,7 @@ export interface APIRequestContext {
* 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; };
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
Expand Down Expand Up @@ -17193,7 +17193,7 @@ export interface APIRequestContext {
* 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; };
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
Expand Down
16 changes: 16 additions & 0 deletions tests/library/browsercontext-fetch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,22 @@ it('should support application/x-www-form-urlencoded', async function({ context,
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 }) {
const data = {
firstName: 'John',
Expand Down

0 comments on commit 48c7fb6

Please sign in to comment.