microsoft / microsoft/playwright

[MCP]: browser_click takes ~5 s longer when the click's fetch gets a 204 No Content

Open
#42,786 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
96.3k
Forks
6.5k
Avg merge
1d 6h
Merged PRs (30d)
180

Description

### What's going on?

`browser_click` takes about 6 s when the click makes the page `fetch` a URL that answers **`204 No Content`**. The same click answered with `200` takes about 1 s. The page is done within a few milliseconds either way, so the extra ~5 s looks like the post-action wait running to its timeout because it never sees the 204 request finish.

Real apps hit this often: many APIs return 204 for writes (save, delete, "mark as read"), so every such click costs an agent ~5 s.

**Repro.** A page with two buttons, each doing a `fetch` and writing to the DOM when it resolves:

```html
fetch 204
fetch 200


```

The server answers `/api?status=204` with `204` and an empty body, and `/api?status=200` with `200` and `{}` (`Content-Type: application/json`). Driving `@playwright/mcp@0.0.82 --headless --isolated` over stdio (`browser_navigate`, `browser_snapshot`, then `browser_click` on each button):

```
fetch 204: browser_click took 6.04 s
fetch 200: browser_click took 1.03 s
fetch 204: browser_click took 6.03 s
fetch 200: browser_click took 1.03 s
```

Same result on 0.0.81. The full script (Python MCP client plus the tiny server, ~60 lines) is below.

repro.py

```python
"""Playwright MCP: browser_click stalls ~6 s when the click triggers a fetch answered with 204 No Content."""

import asyncio
import http.server
import threading
import time

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

PAGE = b"""repro
fetch 204
fetch 200

"""

class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path.startswith("/api"):
status = int(self.path.split("=")[1])
body = b"" if status == 204 else b"{}"
self.send_response(status)
if status == 200:
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
else:
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(PAGE)))
self.end_headers()
self.wfile.write(PAGE)

def log_message(self, *a):
pass

async def main():
srv = http.server.ThreadingHTTPServer(("127.0.0.1", 0), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()
url = f"http://127.0.0.1:{srv.server_port}/"
p = StdioServerParameters(command="npx", args=["-y", "@playwright/mcp@0.0.82", "--headless", "--isolated"])
async with stdio_client(p) as (r, w), ClientSession(r, w) as s:
info = await s.initialize()
print("server:", info.server_info.name, info.server_info.version)
await s.call_tool("browser_navigate", {"url": url})
snap = (await s.call_tool("browser_snapshot", {})).content[0].text
import re
refs = dict(re.findall(r'button "(fetch \d+)" \[ref=(e\d+)\]', snap))
for _ in range(2):
for label in ("fetch 204", "fetch 200"):
t0 = time.perf_counter()
await s.call_tool("browser_click", {"element": label, "target": refs[label]})
print(f"{label}: browser_click took {time.perf_counter() - t0:.2f} s")

asyncio.run(main())
```

**Expected:** a click whose request gets a 204 returns as fast as one that gets a 200.

### Version

`@playwright/mcp` 0.0.82 (server reports Playwright 1.64.0-alpha-1789764292000). macOS 26.6, Node 24.11, headless Chromium.

Contributor guide

Open the contributing guide

Research direction

Start by running the provided repro.py script with the browser_click entry point and compare the 204 and 200 timings. Trace the post-action wait and network completion handling for the 204 request, then add regression coverage showing that browser_click returns promptly for both responses; done means the 204 case is close to the 200 case and the regression passes.

Written by the indexing model from the issue text.

Assessment

Tech stack
playwright, typescript
Domain
testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.