Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

Relay server logs

The relay server logs everything: extension connections, CDP events, MCP messages, and errors. Two log files are created each time the server starts:
playwriter logfile # prints paths like: # ~/.playwriter/relay-server.log # ~/.playwriter/cdp.jsonl
relay-server.log contains human-readable logs from the extension, MCP, and WebSocket server.
cdp.jsonl is a JSON Lines file with every CDP command, response, and event. Long strings are truncated. Use jq to analyze traffic:
# Count CDP messages by direction and method jq -r '.direction + "\t" + (.message.method // "response")' ~/.playwriter/cdp.jsonl | uniq -c # Find errors grep -i error ~/.playwriter/relay-server.log | tail -20
Both files are recreated every time the server starts, so they only contain logs from the current session.

Known issues

All pages return about:blank

This is a Chrome bug in the chrome.debugger API. Fix: restart Chrome completely (quit and reopen), then click the extension icon again.

Browser switches to light mode on connect

Playwright triggers a theme change when connecting via CDP. This is an upstream Playwright issue. The browser returns to its normal theme when the session disconnects.

Common problems

"Extension is not connected"

The extension isn't connected to the relay. Check:
  1. Is Chrome running?
  2. Did you click the extension icon on at least one tab? (Icon should be green)
  3. Is the relay server running? The CLI starts it automatically, but if you killed it manually, run playwriter serve or just use any CLI command to restart it

"No browser tabs have Playwriter enabled"

You need to click the extension icon on a tab to make it controllable. The extension only attaches to tabs where you explicitly enable it.

Connection is stale or broken

Reset the session to reconnect:
playwriter session reset <sessionId>
If that doesn't help, restart the relay by killing the process on port 19988 and running any CLI command:
# macOS/Linux PIDS=$(lsof -ti :19988) [ -n "$PIDS" ] && kill $PIDS # Windows (PowerShell) Get-NetTCPConnection -LocalPort 19988 -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }

Timeout errors

Increase the timeout for slow operations:
playwriter -s 1 --timeout 30000 -e 'await page.goto("https://slow-site.com")'
The default timeout is 10 seconds.

Port 19988 already in use

Another instance of the relay is running. Kill it first:
# macOS/Linux PIDS=$(lsof -ti :19988) [ -n "$PIDS" ] && kill $PIDS # Or use --replace flag playwriter serve --token MY_SECRET --replace

Skill recorder will not start, or stop is ambiguous

The in-page Record button talks to the local relay. It no longer fails just because several playwriter sessions are open. If start still fails:
  1. Is the extension icon green on the tab?
  2. Is the relay up? Run any playwriter command or playwriter serve
  3. Read playwriter logfile and look for Record start endpoint error
If playwriter recorder stop says Multiple active recordings, it prints each recording id with the current or last page URL. Pick the matching one:
playwriter recorder status playwriter recorder stop 3
See Skill Recorder for the full command list and more cases.

Dialogs (alert, confirm, prompt) disappear instantly

Playwright auto-dismisses JavaScript dialogs when no handler is registered. This is core Playwright behavior, not Playwriter-specific. When a dialog opens and nothing handles it:
  • alert is dismissed (same as clicking OK)
  • confirm is dismissed (same as clicking Cancel)
  • prompt is dismissed (same as clicking Cancel)
  • beforeunload is accepted (lets navigation proceed)
This happens because an open JS dialog blocks all JavaScript execution on the page. Every evaluate(), click, and navigation hangs until the dialog is closed. Playwright auto-dismisses to prevent the page from freezing.
The consequence for agents: confirm() dialogs always return false (cancelled) unless the agent explicitly handles them.
Fix: register a dialog handler before the action that triggers the dialog:
// Must be set up BEFORE clicking the button that opens the dialog state.page.on('dialog', async (dialog) => { console.log(`Dialog: ${dialog.type()} - ${dialog.message()}`) await dialog.accept() // or dialog.dismiss() }) await state.page.click('button.delete')
The handler must be registered first because Playwright fires the auto-dismiss synchronously when Chrome sends the CDP event. If there is no handler at that exact moment, the dialog is already gone.