From 021c5c108c6a1166c1d2b701aafb9f021fe0cfca Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 26 Mar 2024 12:36:35 -0700 Subject: [PATCH] chore: revert support of duplicate form data fields in multipart post data (#30127) We want to adopt FormData API for the requests. This is a revert of 4b3c596874156ac4c75c270466ab2c08e3d7132c and a849ea97411714d50cda0a0d5c156decbdc2d814 Reference https://github.com/microsoft/playwright/issues/28070 --- docs/src/api/class-apirequestcontext.md | 2 +- docs/src/api/class-formdata.md | 69 +-------- docs/src/api/params.md | 5 +- packages/playwright-core/src/client/fetch.ts | 27 ++-- packages/playwright-core/types/types.d.ts | 140 ++----------------- tests/library/browsercontext-fetch.spec.ts | 34 ----- 6 files changed, 27 insertions(+), 250 deletions(-) diff --git a/docs/src/api/class-apirequestcontext.md b/docs/src/api/class-apirequestcontext.md index addf5baacb..dacd5dd343 100644 --- a/docs/src/api/class-apirequestcontext.md +++ b/docs/src/api/class-apirequestcontext.md @@ -566,7 +566,7 @@ api_request_context.post("https://example.com/api/createBook", data=data) ```csharp var data = new Dictionary() { - { "firstName", "John" }, + { "firstNam", "John" }, { "lastName", "Doe" } }; await request.PostAsync("https://example.com/api/createBook", new() { DataObject = data }); diff --git a/docs/src/api/class-formdata.md b/docs/src/api/class-formdata.md index 76a1f85619..c578021653 100644 --- a/docs/src/api/class-formdata.md +++ b/docs/src/api/class-formdata.md @@ -14,72 +14,6 @@ FormData form = FormData.create() page.request().post("http://localhost/submit", RequestOptions.create().setForm(form)); ``` -## method: FormData.add -* since: v1.43 -- returns: <[FormData]> - -Adds a field to the form. File values can be passed either as `Path` or as `FilePayload`. -Multiple fields with the same name can be added. - -```java -import com.microsoft.playwright.options.FormData; -... -FormData form = FormData.create() - // Only name and value are set. - .add("firstName", "John") - // Name and value are set, filename and Content-Type are inferred from the file path. - .add("attachment", Paths.get("pic.jpg")) - // Name, value, filename and Content-Type are set. - .add("attachment", new FilePayload("table.csv", "text/csv", Files.readAllBytes(Paths.get("my-tble.csv")))); -page.request().post("http://localhost/submit", RequestOptions.create().setForm(form)); -``` - -```csharp -var multipart = Context.APIRequest.CreateFormData(); -// Only name and value are set. -multipart.Add("firstName", "John"); -// Name, value, filename and Content-Type are set. -multipart.Add("attachment", new FilePayload() -{ - Name = "pic.jpg", - MimeType = "image/jpeg", - Buffer = File.ReadAllBytes("john.jpg") -}); -// Name, value, filename and Content-Type are set. -multipart.Add("attachment", new FilePayload() -{ - Name = "table.csv", - MimeType = "text/csv", - Buffer = File.ReadAllBytes("my-tble.csv") -}); -await Page.APIRequest.PostAsync("https://localhost/submit", new() { Multipart = multipart }); -``` - -### param: FormData.add.name -* since: v1.43 -- `name` <[string]> - -Field name. - -### param: FormData.add.value -* since: v1.43 -- `value` <[string]|[boolean]|[int]|[Path]|[Object]> - - `name` <[string]> File name - - `mimeType` <[string]> File type - - `buffer` <[Buffer]> File content - -Field value. - -### param: FormData.add.value -* since: v1.43 -* langs: csharp -- `value` <[string]|[boolean]|[int]|[Object]> - - `name` <[string]> File name - - `mimeType` <[string]> File type - - `buffer` <[Buffer]> File content - -Field value. - ## method: FormData.create * since: v1.18 * langs: java @@ -102,7 +36,7 @@ FormData form = FormData.create() // Name and value are set, filename and Content-Type are inferred from the file path. .set("profilePicture1", Paths.get("john.jpg")) // Name, value, filename and Content-Type are set. - .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg")))) + .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg")))); .set("age", 30); page.request().post("http://localhost/submit", RequestOptions.create().setForm(form)); ``` @@ -118,7 +52,6 @@ multipart.Set("profilePicture", new FilePayload() MimeType = "image/jpeg", Buffer = File.ReadAllBytes("john.jpg") }); -multipart.Set("age", 30); await Page.APIRequest.PostAsync("https://localhost/submit", new() { Multipart = multipart }); ``` diff --git a/docs/src/api/params.md b/docs/src/api/params.md index e64c77f1dc..e3b2894c3c 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -405,7 +405,7 @@ An instance of [FormData] can be created via [`method: APIRequestContext.createF ## js-python-fetch-option-multipart * langs: js, python -- `multipart` <[Object]<[string], [string]|[float]|[boolean]|[ReadStream]|[Object]|Array<[string]|[float]|[boolean]|[ReadStream]|[Object]>>> +- `multipart` <[Object]<[string], [string]|[float]|[boolean]|[ReadStream]|[Object]>> - `name` <[string]> File name - `mimeType` <[string]> File type - `buffer` <[Buffer]> File content @@ -413,8 +413,7 @@ An instance of [FormData] can be created via [`method: APIRequestContext.createF 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. If the value is an array, each element -will be sent as a separate field with the same name. +or as file-like object containing file name, mime-type and its content. ## csharp-fetch-option-multipart * langs: csharp diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index f959b47fb0..8dc3caa570 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -36,7 +36,7 @@ export type FetchOptions = { headers?: Headers, data?: string | Buffer | Serializable, form?: { [key: string]: string|number|boolean; }; - multipart?: { [key: string]: string|number|boolean|fs.ReadStream|FilePayload|Array; }; + multipart?: { [key: string]: string|number|boolean|fs.ReadStream|FilePayload; }; timeout?: number, failOnStatusCode?: boolean, ignoreHTTPSErrors?: boolean, @@ -188,11 +188,15 @@ export class APIRequestContext extends ChannelOwner { - if (isFilePayload(value)) { - const payload = value as FilePayload; - if (!Buffer.isBuffer(payload.buffer)) - throw new Error(`Unexpected buffer type of 'data.${name}'`); - return { name, file: filePayloadToJson(payload) }; - } else if (value instanceof fs.ReadStream) { - return { name, file: await readStreamToJson(value as fs.ReadStream) }; - } else { - return { name, value: String(value) }; - } -} - function isJsonParsable(value: any) { if (typeof value !== 'string') return false; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index bba91a4496..cd92c1894c 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -15689,8 +15689,7 @@ export interface APIRequestContext { * 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. If the value is an array, each element will be sent as a separate field with the - * same name. + * name, mime-type and its content. */ multipart?: { [key: string]: string|number|boolean|ReadStream|{ /** @@ -15707,22 +15706,7 @@ export interface APIRequestContext { * File content */ buffer: Buffer; - }|Array; }; + }; }; /** * Query parameters to be sent with the URL. @@ -15839,8 +15823,7 @@ export interface APIRequestContext { * 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. If the value is an array, each element will be sent as a separate field with the - * same name. + * name, mime-type and its content. */ multipart?: { [key: string]: string|number|boolean|ReadStream|{ /** @@ -15857,22 +15840,7 @@ export interface APIRequestContext { * File content */ buffer: Buffer; - }|Array; }; + }; }; /** * Query parameters to be sent with the URL. @@ -15949,8 +15917,7 @@ export interface APIRequestContext { * 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. If the value is an array, each element will be sent as a separate field with the - * same name. + * name, mime-type and its content. */ multipart?: { [key: string]: string|number|boolean|ReadStream|{ /** @@ -15967,22 +15934,7 @@ export interface APIRequestContext { * File content */ buffer: Buffer; - }|Array; }; + }; }; /** * Query parameters to be sent with the URL. @@ -16045,8 +15997,7 @@ export interface APIRequestContext { * 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. If the value is an array, each element will be sent as a separate field with the - * same name. + * name, mime-type and its content. */ multipart?: { [key: string]: string|number|boolean|ReadStream|{ /** @@ -16063,22 +16014,7 @@ export interface APIRequestContext { * File content */ buffer: Buffer; - }|Array; }; + }; }; /** * Query parameters to be sent with the URL. @@ -16141,8 +16077,7 @@ export interface APIRequestContext { * 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. If the value is an array, each element will be sent as a separate field with the - * same name. + * name, mime-type and its content. */ multipart?: { [key: string]: string|number|boolean|ReadStream|{ /** @@ -16159,22 +16094,7 @@ export interface APIRequestContext { * File content */ buffer: Buffer; - }|Array; }; + }; }; /** * Query parameters to be sent with the URL. @@ -16288,8 +16208,7 @@ export interface APIRequestContext { * 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. If the value is an array, each element will be sent as a separate field with the - * same name. + * name, mime-type and its content. */ multipart?: { [key: string]: string|number|boolean|ReadStream|{ /** @@ -16306,22 +16225,7 @@ export interface APIRequestContext { * File content */ buffer: Buffer; - }|Array; }; + }; }; /** * Query parameters to be sent with the URL. @@ -16384,8 +16288,7 @@ export interface APIRequestContext { * 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. If the value is an array, each element will be sent as a separate field with the - * same name. + * name, mime-type and its content. */ multipart?: { [key: string]: string|number|boolean|ReadStream|{ /** @@ -16402,22 +16305,7 @@ export interface APIRequestContext { * File content */ buffer: Buffer; - }|Array; }; + }; }; /** * Query parameters to be sent with the URL. diff --git a/tests/library/browsercontext-fetch.spec.ts b/tests/library/browsercontext-fetch.spec.ts index 7129803b2e..bb0bb15362 100644 --- a/tests/library/browsercontext-fetch.spec.ts +++ b/tests/library/browsercontext-fetch.spec.ts @@ -983,40 +983,6 @@ it('should support multipart/form-data and keep the order', async function({ con expect(response.status()).toBe(200); }); -it('should support repeating names in multipart/form-data', async function({ context, server }) { - it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28070' }); - const postBodyPromise = new Promise(resolve => { - server.setRoute('/empty.html', async (req, res) => { - resolve((await req.postBody).toString('utf-8')); - res.writeHead(200, { - 'content-type': 'text/plain', - }); - res.end('OK.'); - }); - }); - const [postBody, response] = await Promise.all([ - postBodyPromise, - context.request.post(server.EMPTY_PAGE, { - multipart: { - firstName: 'John', - lastName: 'Doe', - file: [{ - name: 'f1.js', - mimeType: 'text/javascript', - buffer: Buffer.from('var x = 10;\r\n;console.log(x);') - }, { - name: 'f2.txt', - mimeType: 'text/plain', - buffer: Buffer.from('hello') - }] - } - }) - ]); - expect(postBody).toContain(`content-disposition: form-data; name="file"; filename="f1.js"\r\ncontent-type: text/javascript\r\n\r\nvar x = 10;\r\n;console.log(x);`); - expect(postBody).toContain(`content-disposition: form-data; name="file"; filename="f2.txt"\r\ncontent-type: text/plain\r\n\r\nhello`); - expect(response.status()).toBe(200); -}); - it('should serialize data to json regardless of content-type', async function({ context, server }) { const data = { firstName: 'John',