playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Bug]: XHR PUT FormData postData is null

Open daniera3 opened this issue 6 months ago • 6 comments

Version

latest

Steps to reproduce

Image

cant get content of file uploaded

Expected behavior

get asdasdasd in post_data

Actual behavior

in pyptteer i can get this content with

    await cdp.send("Network.enable", {
        # Buffer up to 10 MB of POST/PUT data
        "maxPostDataSize": 0
    })
    async def on_request_will_be_sent(params):
        try:
            req = params["request"]
            if req.get("method") == "PUT" and "/api/file/raw" in req.get("url", ""):
                request_id = params["requestId"]
                # Ask Chrome: “Please give me the POST/PUT payload that you buffered.”
                resp = await cdp.send(
                    "Network.getRequestPostData",
                    {"requestId": request_id}
                )
                body = resp.get("postData", None)
                print("→ Caught PUT to /api/file/raw via Network:")
                print("   • request.postData →", body)
                # If you want bytes instead of a string, do:
                # raw_bytes = body.encode("utf-8")
                # print("   • as bytes:", raw_bytes)
        except Exception as e:
            # If "No post data available" still shows up, check that `maxPostDataSize` was set early enough.
            print("ERROR in on_request_will_be_sent:", e)

    cdp.on("Network.requestWillBeSent", lambda event: asyncio.create_task(on_request_will_be_sent(event)))

Additional context

No response

Environment

- Operating System: [Ubuntu 22.04]
- CPU: [arm64]
- Browser: [All, Chromium, Firefox, WebKit]
- Python Version: [3.12]
- Other info:

daniera3 avatar Jun 03 '25 12:06 daniera3

Playwright supports this out of the box: https://playwright.dev/python/docs/api/class-request#request-post-data

def handle_request(request):
    print(">>", request.method, request.url)
    print(request.post_data)
page.on("request", handle_request)

mxschmitt avatar Jun 04 '25 08:06 mxschmitt

this not give me content . request.post_data = empty string or mutipart/data without content

daniera3 avatar Jun 04 '25 16:06 daniera3

Would it be possible to share a repro?

mxschmitt avatar Jun 04 '25 18:06 mxschmitt

upload file "a.txt" and not see content but if use burp or pyppteer or other tool you will get content

import asyncio
from playwright.async_api import async_playwright
def handle_request(request):
    print(">>", request.method, request.url)
    print(request.post_data)


async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=False, channel="chrome")  # Set headless=False if you want to see the browser
        page = await browser.new_page()

        # Navigate to the login page
        await page.goto("https://brokencrystals.com/userlogin")

        # Fill in the email and password inputs
        await page.fill("#email", "admin")
        await page.fill("#password", "admin")

        # Click the submit button (assuming it’s a <button type="submit">)
        await page.click("button[type='submit']")

        # Optionally wait for navigation or some selector that appears after login
        await page.wait_for_load_state("networkidle")
        await page.click("#header > div > div > div > nav > ul > li:nth-child(2) > a")
        await asyncio.sleep(1)
        page.on("request", handle_request)
        await page.set_input_files("#file-input", "a.txt")
        await page.set_input_files("#feedback-file-input","a.txt")

        # Do other actions here, or close the browser
        await browser.close()


if __name__ == "__main__":
    asyncio.run(main())

daniera3 avatar Jun 05 '25 05:06 daniera3

I can reproduce using the following:

it('should return post data for PUT requests with XHR', async ({ page, server }) => {
  await page.goto(server.EMPTY_PAGE);
  const [request] = await Promise.all([
    page.waitForRequest('**'),
    page.evaluate(({ url }) => {
      return new Promise<void>(resolve => {
        const xhr = new XMLHttpRequest();
        xhr.open('PUT', url);
        xhr.onload = () => resolve();
        const payload = new FormData();
        const file = new Blob(['hello'], { type: 'text/plain' });
        payload.append('value', file);
        xhr.send(payload);
      });
    }, { url: server.PREFIX + '/title.html' })
  ]);
  expect(request.postData()).toContain('hello');
});

Looks like Chromium does not send anything back in the protocol.

mxschmitt avatar Jun 21 '25 17:06 mxschmitt

Related issue: https://github.com/microsoft/playwright/issues/6479

yury-s avatar Jun 23 '25 18:06 yury-s

a.txt not empty file

`import asyncio from playwright.async_api import async_playwright

def handle_request(request): print(">>", request.method, request.url) print(request.post_data)

async def main(): async with async_playwright() as p: browser = await p.chromium.launch(headless=False, channel="chrome") context = await browser.new_context() page = await context.new_page()

    # 1) Create a CDP session for this page
    client = await context.new_cdp_session(page)  # :contentReference[oaicite:0]{index=0}

    # 2) Enable Network so we'll get request events
    await client.send("Network.enable", {"maxPostDataSize": 0})

    # 3) Listen for the first request and grab its requestId
    async def on_request_will_be_sent(params):
        # you can add filtering here if you only care about specific URLs
        try:
            request_id = params["requestId"]
            result = await client.send(
                "Network.getRequestPostData",
                {"requestId": request_id}
            )
            if result:
                print(result.items())
                print("Post data:", result.get("postData"))
        except Exception as e:
            pass

    client.on("Network.requestWillBeSent", on_request_will_be_sent)

    # Now drive your page so that a POST happens...
    await page.goto("https://brokencrystals.com/userlogin")
    await page.fill("#email", "admin")
    await page.fill("#password", "admin")
    await page.click("button[type='submit']")
    await page.wait_for_load_state("networkidle")

    # Navigate to the upload form (adjust selector as needed)
    await page.click("#header > div > div > div > nav > ul > li:nth-child(2) > a")
    await asyncio.sleep(1)
    page.on("request", handle_request)
    await page.set_input_files("#file-input", "a.txt")
    # Trigger the multipart/form-data or JSON POST
    await page.set_input_files("#feedback-file-input","a.txt")

    # give the CDP event loop a moment
    await asyncio.sleep(1)



    await browser.close()

if name == "main": asyncio.run(main()) `

result :

`dict_items([('postData', '{"user":"admin","password":"admin","op":"basic"}')]) Post data: {"user":"admin","password":"admin","op":"basic"} dict_items([('postData', ' ]>')]) Post data: ]> dict_items([('postData', '{{="+1"}} {{=5589}} {{=55488}} {{=55}}')]) Post data: {{="+1"}} {{=5589}} {{=55488}} {{=55}} dict_items([('postData', ' ]>')]) Post data: ]>

PUT https://brokencrystals.com/api/users/one/admin/photo None dict_items([('postData', '------WebKitFormBoundaryPtsuD1pJpZxeQqTs\r\nContent-Disposition: form-data; name="admin"; filename="a.txt"\r\nContent-Type: text/plain\r\n\r\n\r\n------WebKitFormBoundaryPtsuD1pJpZxeQqTs--\r\n')]) Post data: ------WebKitFormBoundaryPtsuD1pJpZxeQqTs Content-Disposition: form-data; name="admin"; filename="a.txt" Content-Type: text/plain

------WebKitFormBoundaryPtsuD1pJpZxeQqTs--

PUT https://brokencrystals.com/api/file/raw?path=a.txt None GET https://brokencrystals.com/api/users/one/admin/photo None

Process finished with exit code 0`

daniera3 avatar Jun 26 '25 07:06 daniera3

Folding into https://github.com/microsoft/playwright/issues/6479

mxschmitt avatar Jun 30 '25 16:06 mxschmitt