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:
<button onclick="fetch('/api?status=204').then(() => out.textContent = 'done 204')">fetch 204</button>
<button onclick="fetch('/api?status=200').then(() => out.textContent = 'done 200')">fetch 200</button>
<p id="out"></p>
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
"""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"""<!doctype html><title>repro</title>
<button onclick="fetch('/api?status=204').then(() => out.textContent = 'done 204')">fetch 204</button>
<button onclick="fetch('/api?status=200').then(() => out.textContent = 'done 200')">fetch 200</button>
<p id="out"></p>"""
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.
What's going on?
browser_clicktakes about 6 s when the click makes the pagefetcha URL that answers204 No Content. The same click answered with200takes 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
fetchand writing to the DOM when it resolves:The server answers
/api?status=204with204and an empty body, and/api?status=200with200and{}(Content-Type: application/json). Driving@playwright/mcp@0.0.82 --headless --isolatedover stdio (browser_navigate,browser_snapshot, thenbrowser_clickon each button):Same result on 0.0.81. The full script (Python MCP client plus the tiny server, ~60 lines) is below.
repro.py
Expected: a click whose request gets a 204 returns as fast as one that gets a 200.
Version
@playwright/mcp0.0.82 (server reports Playwright 1.64.0-alpha-1789764292000). macOS 26.6, Node 24.11, headless Chromium.