DEV Community

Takuya Auto
Takuya Auto

Posted on

12 traps when Claude Code drives your real Chrome through Playwright MCP (and the fixes)

I have Claude Code drive my everyday Chrome — the real, logged-in profile — through the Playwright MCP server. It checks admin screens, fills forms and verifies that posts actually went out, every day.

Automating a real browser is not like a headless test browser. Clicks wait 30 seconds and fail, non-ASCII text arrives with characters missing, and "I typed it" does not mean "it was saved". Here are 12 traps I actually hit, with the fix that worked. My setup is Windows 11 and Chrome.

1. You cannot attach a debugging port to your normal Chrome profile

Recent Chrome (136 and later) refuses --remote-debugging-port on the default profile.

chrome.exe --remote-debugging-port=9225 --user-data-dir="C:/Users/you/chrome-cdp-work" --no-first-run
Enter fullscreen mode Exit fullscreen mode
  • Create a dedicated --user-data-dir for automation and log in there once by hand.
  • Connect to 127.0.0.1, not localhost (name resolution can go to IPv6 and fail).
  • Pin one port and one profile per purpose, so you always know which account is being driven (for example 9225 for work A, 9226 for work B).

On the Playwright MCP side, use a CDP connection:

{ "command": "npx", "args": ["@playwright/mcp@latest", "--cdp-endpoint", "http://127.0.0.1:9225"] }
Enter fullscreen mode Exit fullscreen mode

2. "visible, enabled and stable" times out after 30 s → the window is covered

This is the most common failure.

TimeoutError: locator.click: Timeout 30000ms exceeded.
  - waiting for element to be visible, enabled and stable
Enter fullscreen mode Exit fullscreen mode

It looks like an element problem. Almost always, the real cause is that the window is completely covered by another window, so the page is at document.visibilityState === 'hidden'. A hidden tab stops rendering, and Playwright cannot perform a real click.

// check this first
document.visibilityState  // "hidden" means it is not the element's fault
Enter fullscreen mode Exit fullscreen mode

The fix is to bring that Chrome window to the front. With several profiles on one monitor they overlap, so I use a small PowerShell script that calls Windows SetWindowPos (HWND_TOPMOST) to pin just that window on top, and unpins it afterwards. If you forget to unpin, you get in the human's way, so I treat "pin → work → unpin" as one unit.

3. A new tab is hidden right after it opens

Tabs opened with browser_tabs new or window.open can start in the background as hidden. Call page.bringToFront() before doing anything.

4. Display off or a locked screen makes everything hidden

After some idle time the display turns off and every window becomes hidden. You can prevent display-off with power settings or periodic input, but you cannot get past the Windows lock screen. A person has to unlock it, so for overnight work I lean on methods that do not need a click (APIs and evaluate). The most reliable way I found to detect the lock state was the output of quser.

5. Use insertText for non-ASCII input

Sending key events one character at a time drops or reorders Japanese text. CDP's Input.insertText (page.keyboard.insertText() in Playwright) inserts the whole string reliably.

await page.locator('.cm-content').click();
await page.keyboard.insertText(longMarkdown);
Enter fullscreen mode Exit fullscreen mode

This works for CodeMirror editors too. Note that CodeMirror does not render off-screen lines, so innerText.length looks shorter than what you inserted. Confirm in the preview instead.

6. In React rich-text editors, "select all → delete" can leave the content behind

In a Draft.js-style editor, Ctrl+A → Delete cleared what I could see while the React state kept the old text, and submitting posted the old text. Select with document.execCommand('selectAll') before typing, and confirm with a real number on screen such as the character counter.

It also happens in the other direction. In one caption field, after pasting, the screen showed only the last line while the character counter showed the full length. If I had trusted the counter alone, I would have posted without the body. What you see and what the app holds can drift apart either way, so check both.

7. ProseMirror editors accept formatting through an HTML paste event

For ProseMirror-style editors (product description fields and the like), dispatching a paste event that carries text/html keeps headings, lists and bold intact.

const ed = document.querySelector('.ProseMirror');
ed.focus();
const r = document.createRange(); r.selectNodeContents(ed);
getSelection().removeAllRanges(); getSelection().addRange(r);
const dt = new DataTransfer();
dt.setData('text/html', html);
dt.setData('text/plain', 'x');
ed.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true }));
Enter fullscreen mode Exit fullscreen mode

The first heading sometimes merges into the paragraph before it, so after pasting I count the structure (number of h2, number of li) to confirm.

8. To hand a local file to the page, go through a hidden input[type=file]

Inside browser_run_code, import('fs') was not always available. When I need to pass a long draft to the page, I create a hidden file input, call setInputFiles, and read it on the page side.

await page.evaluate(() => {
  const i = document.createElement('input');
  i.type = 'file'; i.id = 'tmp-file'; i.style.display = 'none';
  document.body.appendChild(i);
});
await page.locator('#tmp-file').setInputFiles('C:\\path\\to\\article.md');
const text = await page.evaluate(() => document.getElementById('tmp-file').files[0].text());
Enter fullscreen mode Exit fullscreen mode

The same goes for image uploads: on sites where a transparent overlay sits on top of the button and swallows the click, calling setInputFiles directly on the input[type=file] is the reliable route.

9. Confirm "it is published" while logged out

A browser logged in as the admin sees the newest, cache-bypassed page. An ordinary visitor may still see an old cache or an unpublished state. Before reporting "done", open the page in a clean profile.

chrome.exe --headless=new --user-data-dir="%TEMP%\anon-check" --virtual-time-budget=15000 --dump-dom "https://example.com/page" > page.html
Enter fullscreen mode Exit fullscreen mode

For JavaScript-rendered sites curl returns an empty shell, so I take the rendered DOM with --dump-dom and then count the headings and links I expect.

10. A beforeunload dialog freezes everything

Navigating away from an editor with unsaved changes raises a beforeunload confirmation, and every later action hangs. If it appears when you thought you had published, that is the signal that you had not. Dismiss the dialog, check the save state, and redo it.

11. Small Windows traps: BOM and line endings

  • A .ps1 containing non-ASCII text must be saved as UTF-8 with BOM, or Windows PowerShell 5.1 fails to parse it.
  • Python on Windows writes CRLF with open(path, "w"). For files you upload to a server, pass newline="\n" explicitly.
  • Command output captured over ssh can have stderr warnings mixed in. Add 2>/dev/null before you use the output as content.

12. Traps when you also run your own Chrome extension (MV3)

  • Inline handlers such as onclick are silently ignored under CSP. Use addEventListener.
  • The service worker stops after about 30 seconds. Long waits built on setTimeout vanish midway.
  • For an unpacked extension, the old service worker keeps running after you replace the files; you have to reload the extension. Bumping version in manifest.json every time tells you which one is live.

Summary

When real-browser automation fails, check these three before you suspect the element or the selector:

  1. Is document.visibilityState visible (is the window actually showing)?
  2. Did the input reach the app's state (confirm with a counter, the save state or a preview, not with what you see)?
  3. Did you confirm the result while logged out?

How I make Claude Code follow this "verify, then report" rule is in my previous post: I don't write code. Here's how I made Claude Code the steward of my one-person business. The whole setup is also written up as a short Kindle book, Let Claude Code Run Your One-Person Business (in Kindle Unlimited).

Top comments (0)