Add observe → act → observe feedback loop section with ASCII diagram
and Framer command palette example. Agents must take a snapshot after
every action to verify the result before proceeding.
Also change page creation pattern to reuse existing about:blank tabs
instead of always creating new ones, with a note to navigate immediately
in the same execute call to prevent concurrent agents from grabbing the
same tab.
Lessons from real debugging session where ~6 screenshot+image-agent roundtrips
wasted context when a single accessibility snapshot filter would have answered.
Changes:
- **checking page state**: snapshots are the default, screenshots only for visual/CSS
- **common mistakes**: added quote escaping in $'...' (#6), screenshot overuse warning (#7)
- **accessibility snapshots**: added JS filtering pattern for large snapshots
- **debugging web apps**: new section using getLatestLogs, DOM inspection via
evaluate, and combined snapshot+logs patterns
Buffer instances (from screenshots, PDFs, responses) were dumping hundreds of
chars of hex bytes via util.inspect. Long strings nested in objects (base64,
HTML) also wasted context window space.
- Add Buffer.prototype[util.inspect.custom] in all entry points (cli, mcp,
cdp-relay) so Buffers display as '<Buffer N bytes>' instead of hex dumps
- Add maxStringLength: 1000 to all util.inspect calls so strings inside
inspected objects get capped (top-level strings are untouched, the existing
10k char overall truncation handles those)
The extension now reports which playwriter version it was built with via a
`?v=` query param on its WebSocket connection to the relay. The relay stores
this and exposes it in `/extension/status` and `/extensions/status` endpoints.
CLI and MCP compare the extension's bundled version against their own VERSION.
If the extension was built with a newer playwriter, a warning is shown:
Playwriter 0.0.60 is outdated (extension requires 0.0.62).
Run `npm install -g playwriter@latest` or update the playwriter package.
This is fully backward compatible:
- Old extensions without `?v=` get `playwriterVersion: null`, no warning
- No WS protocol changes (version is in URL query params)
- Warning fires once per executor/session lifetime
Flow:
vite build reads playwriter/package.json → injects __PLAYWRITER_VERSION__
→ extension sends ?v=X.Y.Z on WS connect → relay stores in ExtensionInfo
→ /extension/status returns playwriterVersion → CLI/executor checks & warns
When the Playwriter extension can't connect because Chrome isn't running,
agents now have platform-specific commands (macOS, Linux, Windows) to start
Chrome from the CLI. Also documents the --allowlisted-extension-id and
--auto-accept-this-tab-capture flags for enabling automatic tab capture
for screen recording without manual extension clicks.
Closes#48
PLAYWRITER_HOST now accepts full URLs like `https://x-tunnel.traforo.dev`
in addition to plain hostnames. Previously it always constructed
`http://${host}:19988` which broke when using traforo tunnels (HTTPS on
port 443).
Added `parseRelayHost()` utility that detects URL protocols and returns
correct HTTP/WebSocket base URLs. Updated all consumers:
- cli.ts: getServerUrl()
- mcp.ts: getLogServerUrl(), checkRemoteServer()
- executor.ts: checkExtensionStatus()
- utils.ts: getCdpUrl() (ws:// → wss:// for HTTPS hosts)
Plain hostnames still work as before (appends :19988).
Added docs/remote-access.md covering:
- Architecture: playwriter serve + traforo tunnel through Cloudflare
- Host and remote machine setup with env vars
- Use cases: remote Mac mini, user support, multi-machine, VM/devcontainer
- Security model: non-guessable URLs, token auth, localhost-only extension
endpoint, no open ports, visible automation, instant revocation
Document the page-reuse pattern for the Framer project tab so repeated checks do not spawn duplicate tabs, and explicitly ban bringToFront in this guide to avoid interrupting the active browser window during local debugging.
Route Runtime.enable/disable through the incoming child sessionId instead of always using the tab root session so OOPIF iframe targets receive execution contexts reliably. Add a focused relay regression test for empty-src cross-origin iframe attach flow and update extension version/changelog for release tracking.
Retry the post-goto readyState check when Chromium tears down the execution context during ongoing navigation, so the relay navigation test validates page readiness without intermittent race failures.
getStableRefFromAttributes checked id before data-testid, opposite of
what getRefsForLocators does. Now both are consistent: test IDs first,
id second. Test IDs are developer-intentional and stable, id is often
auto-generated by frameworks.
Remove the custom WebSocket-based CDPSession class entirely. All CDP
communication now goes through PlaywrightCDPSessionAdapter which wraps
Playwright's existing CDP session, avoiding duplicate WebSocket connections
and Target.attachToTarget calls that the relay intercepts.
ICDPSession interface uses ProtocolMapping generics from devtools-protocol
so send/on/off methods are fully type-safe — return types are inferred
from the CDP command string (e.g. 'Page.getLayoutMetrics' returns
Protocol.Page.GetLayoutMetricsResponse).
- Remove CDPSession class and getCDPSessionForPage WebSocket factory
- Remove wsUrl parameter from all snapshot/label functions
- Remove CDPSession cache from executor (adapter is stateless)
- Remove 'as CDPSession' casts from debugger, editor, styles, react-source
- Rename getExistingCDPSessionForPage → getCDPSessionForPage
- Remove test for old getCDPSession through relay
Switch from getCDPSessionForPage (creates a new WS via Target.attachToTarget)
to getExistingCDPSessionForPage which reuses Playwright's internal CRSession.
This is critical for the relay server where Target.attachToTarget is intercepted
and cannot create real new sessions.
Also add FrameLocator support in accessibilitySnapshot — FrameLocator (from
locator.contentFrame()) is now auto-resolved to the real Frame object needed
for OOPIF CDP session attachment.
- New PlaywrightCDPSessionAdapter wrapping Playwright's CDPSession as ICDPSession
- New getExistingCDPSessionForPage() using context.getExistingCDPSession()
- resolveFrame() helper converts FrameLocator to Frame via elementHandle().contentFrame()
- getAriaSnapshot() frame param now accepts Frame | FrameLocator
- Executor CDP cache uses ICDPSession, borrowed sessions detach as no-op
- Test for getExistingCDPSession through the relay
- Updated framer iframe guide with alternative page.frames() example
- Bump playwright submodule to @xmorse/playwright-core@1.59.2
- Fix styles-api.md import path
When a user drags a tab into the playwriter group, connectTab() sets
its state to 'connecting'. syncTabGroup() previously only kept
'connected' tabs in the group, so it would immediately ungroup the
'connecting' tab. This ungroup fired chrome.tabs.onUpdated, which
called disconnectTab(). When the original connectTab() resolved, it
re-added the tab, triggering onUpdated again — creating an infinite
add/remove/add/remove loop.
Fix: include 'connecting' tabs in syncTabGroup's group membership
filter. This way syncTabGroup won't fight the user's intent by
removing tabs that are still being set up. Error-state tabs are
still correctly excluded from the group.
Chrome deprecated Debugger.setScriptSource in Chrome 142+ (removed in 145).
Falls back to Runtime.evaluate to re-execute modified scripts, which
works for scripts defining functions at global scope.
- Add "No changes since last call" hints for snapshots/html/markdown
- Adjust truncation message to suggest search
- Update skill.md snapshot behavior doc