0

I am trying to use Playwright to PUT multipart form-data with the Python API. Here is the relevant documentation.

However, it's not clear how to structure the multipart argument. The docs say:

multipart: Dict[str, str|float|bool|[ReadStream]|Dict] (optional)

   name: str
   File name

   mimeType: str
   File type

   buffer: bytes
   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 or as file-like object containing file name, mime-type and its content.

So, we pass it a dict with str keys, and either str, float, bool, ReadStream or Dict values. But what keys should we use? Also, how do I pass an fs.ReadStream, which is a JavaScript object, from a Python script? It looks like the keys should be 'name', 'mimeType' and 'buffer'. However, checking the generated HTTP request with nc -l -p 8080 shows that a new form part is created for each key in the multipart dictionary.

I'm essentially trying to replicate the following curl command:

curl 'localhost:8080' -X PUT --form file='@test.zip;filename=test.zip;type=application/x-zip-compressed'

Assuming that you have previously run nc -l -p 8080 | less, you will see the following HTTP request:

PUT / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.81.0
Accept: */*
Content-Length: 380
Content-Type: multipart/form-data; boundary=------------------------f3dde3ddff901cd3

--------------------------f3dde3ddff901cd3
Content-Disposition: form-data; name="file"; filename="test.zip"
Content-Type: application/x-zip-compressed

PK^C^D
^@^@^@^@^@<D1>`xW^N<93>ESC<91>
^@^@^@
^@^@^@^H^@^\^@test.txtUT        ^@^C^Z<F7>_e^Z<F7>_eux^K^@^A^D<E8>^C^@^@^D<E8>^C^@^@Test file
PK^A^B^^^C
^@^@^@^@^@<D1>`xW^N<93>ESC<91>
^@^@^@
^@^@^@^H^@^X^@^@^@^@^@^A^@^@^@<FF><81>^@^@^@^@test.txtUT^E^@^C^Z<F7>_eux^K^@^A^D<E8>^C^@^@^D<E8>^C^@^@PK^E^F^@^@^@^@^A^@^A^@N^@^@^@L^@^@^@^@^@
--------------------------f3dde3ddff901cd3--

Here is a failed attempt to achieve an equivalent HTTP request with Playwright:

from playwright.sync_api import sync_playwright

with sync_playwright() as playwright:
    browser = playwright.chromium.launch()
    context = browser.new_context()
    page = context.new_page()

    upload_file = "test.zip"
    with open(upload_file, "rb") as file:
        response = page.request.put(
            "http://localhost:8080",
            multipart={
                "name": "file",
                "filename": upload_file,
                "mimeType": "application/x-zip-compressed",
                "buffer": file.read(),
            }
        )

    print(response)

This produces the following (clearly wrong) HTTP request:

PUT / HTTP/1.1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/115.0.5790.24 Safari/537.36
accept: */*
accept-encoding: gzip,deflate,br
content-type: multipart/form-data; boundary=----WebKitFormBoundarynU6IBe6rgn1KUgpw
content-length: 365
Host: localhost:8080
Connection: close

------WebKitFormBoundarynU6IBe6rgn1KUgpw
content-disposition: form-data; name="name"

file
------WebKitFormBoundarynU6IBe6rgn1KUgpw
content-disposition: form-data; name="filename"

test.zip
------WebKitFormBoundarynU6IBe6rgn1KUgpw
content-disposition: form-data; name="mimeType"

application/x-zip-compressed
------WebKitFormBoundarynU6IBe6rgn1KUgpw--

How can I replicate the curl HTTP request with Playwright?

1 Answer 1

1

The Playwright docs are not super clear.

The structure of the multipart argument should be a dictionary with one key for each form part. In this case, we are only submitting a single 'part', so should only have a single key.

It is instructive to map the components of the desired multipart form submission to the Playwright multipart dictionary. For example, the following form part:

Content-Disposition: form-data; name="file"; filename="test.zip"
Content-Type: application/x-zip-compressed

maps to the following multipart dictionary:

multipart={
    "file": {                   # name="file"
        "name": "test.zip",     # filename="test.zip"
        "mimeType": "application/x-zip-compressed", # Content-Type value
        "buffer": file.read(),  # binary data from file
    }
}

So the full example is:

from playwright.sync_api import sync_playwright

with sync_playwright() as playwright:
    browser = playwright.chromium.launch()
    context = browser.new_context()
    page = context.new_page()

    upload_file = "test.zip"
    with open(upload_file, "rb") as file:
        response = page.request.put(
            "http://localhost:8080",
            multipart={
                "file": {
                    "name": upload_file,
                    "mimeType": "application/x-zip-compressed",
                    "buffer": file.read(),
                }
            },
        )

    print(response)

which produces the following HTTP request:

PUT / HTTP/1.1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/115.0.5790.24 Safari/537.36
accept: */*
accept-encoding: gzip,deflate,br
content-type: multipart/form-data; boundary=----WebKitFormBoundaryK3WyngXmdiXmbBHu
content-length: 376
Host: localhost:8080
Connection: close

------WebKitFormBoundaryK3WyngXmdiXmbBHu
content-disposition: form-data; name="file"; filename="test.zip"
content-type: application/x-zip-compressed

PK^C^D
^@^@^@^@^@<D1>`xW^N<93>ESC<91>
^@^@^@
^@^@^@^H^@^\^@test.txtUT        ^@^C^Z<F7>_e^Z<F7>_eux^K^@^A^D<E8>^C^@^@^D<E8>^C^@^@Test file
PK^A^B^^^C
^@^@^@^@^@<D1>`xW^N<93>ESC<91>
^@^@^@
^@^@^@^H^@^X^@^@^@^@^@^A^@^@^@<FF><81>^@^@^@^@test.txtUT^E^@^C^Z<F7>_eux^K^@^A^D<E8>^C^@^@^D<E8>^C^@^@PK^E^F^@^@^@^@^A^@^A^@N^@^@^@L^@^@^@^@^@
------WebKitFormBoundaryK3WyngXmdiXmbBHu--

Note this is not exactly the same as the curl request, but it seems close enough.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.