Route extension relay connections by their per-profile install id before falling back to Google account identity. This keeps two Chrome profiles signed into the same account from replacing each other in the relay.
Keep Playwright-created tabs in the intended Chrome window by remembering the window where the extension was activated and using that window for Target.createTarget and initial tabs.
Handle Chrome debugger cancellation as a global detach event. When Chrome reports canceled_by_user, clear all tracked debugger tabs and emit detach events so Playwright stops sending commands to tabs Chrome already detached from. Also avoid resurrecting stale error tab state if the detach happens while attachTab is still unwinding.
Validation:
- pnpm test --run src/relay-state.test.ts
- pnpm typecheck
- pnpm build in extension/
- Manual two-profile Hacker News navigation and comment extraction
- Mythos review found no blocking issues
Session: ses_120923e0fffe5l4GT77WwyXEGP
Move each comparison (vs Playwright MCP, vs Playwright CLI, vs BrowserMCP,
vs Claude Extension) from inline tables on the homepage into dedicated
pages under docs/. Add a new in-depth "vs Chrome Direct CDP" page with a
screenshot of Chrome permission dialog that blocks autonomous agents.
Homepage comparison section now links to the individual pages instead of
showing inline tables. Sidebar has a new "Comparison" group with distinct
lucide icons per page: shield-alert, drama, terminal, puzzle, bot.
Session: ses_120bfd373ffex2vJTLlX06mQ83
- Create website/src/docs/remote-access.mdx with full remote access guide
(traforo tunnel setup, Docker/devcontainer, LAN, security, env vars)
- Add docs/remote-access to website navigation in docs.json
- Add "Remote access" section to skill.md with quick-start snippet
linking to https://playwriter.dev/docs/remote-access
- Converted original docs/remote-access.md content to MDX with
diagram code fences and Aside components
Session: ses_1257fc4baffeU7BhTtBImAFQ1a
- Add HeroSection component with VideoBackgroundShader (dot-grid fluid sim)
using hero-bg.mp4 video and orange dot color at 70% opacity
- Two CTA buttons: Chrome Extension (white) and GitHub (outlined)
- 4.7 star rating badge with "20k+ users" linking to Chrome Web Store
- Serif title: "let agents control your Chrome browser."
- "Learn more" arrow linking to #getting-started
- Replace old Hero/Logo block with Above + HeroSection in index.mdx
- Set rendering: static for prerendered HTML at build time
- Set primary color to #FF9500 (bright orange, matching screenshot labels)
- Update @holocron.so/vite to 0.21.0 and wrangler to ^4.101.0
- Fix spiceflow duplication via pnpm dedupe
Session: ses_12885eca3ffenoMavJE7csK9E1
The runtime block was too aggressive; the command might be useful in
some cases. Instead, rely on the skill doc rule so agents know not to
use it unless explicitly asked.
Session: ses_12890788fffeo527ik9Os483KR
These CDP commands are profile-wide destructive operations that wipe ALL
cookies/cache across every domain in the user Chrome profile, not just
the current page. A previous incident wiped Gmail, GitHub, and every
authenticated session.
Changes:
- cdp-relay.ts: added blocklist at top of routeCdpCommand() that throws
a descriptive error if an agent or script tries to send either command.
The error message explains the safe alternative (Network.getCookies +
Network.deleteCookies for per-domain cookie clearing).
- skill.md: added prominent warning in the cookies section with the
correct per-domain cookie clearing pattern using getCookies/deleteCookies.
- MEMORY.md: documented the incident for future sessions.
Session: ses_12890788fffeo527ik9Os483KR
Remove downlevelIteration from the shared TypeScript base config because modern tsgolint rejects the option before it can build a TS program.
TypeScript typecheck still passes after removing the option. lintcn now gets past tsconfig parsing and reports regular source lint findings instead of failing on configuration.
Session: ses_13e63a376ffeFVwrB0h7PF3D5h
Auto page creation is now the default policy instead of per-client plumbing. MCP and CLI both create an initial blank tab when no Playwriter-enabled pages are available, which prevents a connected extension with zero targets from failing before the agent can act.
PLAYWRITER_AUTO_ENABLE=false is the single opt-out. The relay and executor both consult the same shouldAutoEnablePlaywriter helper so Target.setAutoAttach auto-creation and executor fallback page creation stay aligned.
This removes the earlier CLI-only autoEnable request flow from session creation and CDP URLs because auto-enable is no longer a special CLI capability.
Session: ses_143538111ffe2cKnv4TqZ7ZoqQ
Release Playwriter 0.3.0 with the pending user-facing changes since 0.2.0.
Included release notes:
1. Add `sinceLastCall` to `getLatestLogs()` so agents can inspect only new browser logs after each action.
2. Rotate `~/.playwriter/cdp.jsonl` automatically to prevent unbounded disk growth.
3. Include page runtime errors and related frame target logs in `getLatestLogs({ page })`.
4. Let CLI-created sessions auto-open initial extension tabs when MCP owns the shared relay.
5. Send bearer tokens on remote extension status checks.
6. Handle concurrent relay startup races without surfacing `EADDRINUSE` crashes.
7. Improve browser names in multi-browser session selection.
8. Document `getLatestLogs()` as the preferred page diagnostics API for agents.
Fixes#75Fixes#85Fixes#92
Session: ses_unknown
Update the website dependency set before publishing Playwriter so the workspace starts from a clean tree.
- update @holocron.so/vite to 0.16.0
- update spiceflow RSC build to 1.26.0-rsc.1
- update wrangler and workerd through the lockfile
- allow workerd as an approved built dependency for pnpm
Session: ses_unknown
CLI sessions requested auto-enable only by setting PLAYWRITER_AUTO_ENABLE when they spawned the relay process. That fails when MCP already owns the shared relay, because the CLI reuses the existing process and no per-session intent reaches the CDP connection.
Send an explicit autoEnable flag when creating CLI extension sessions, carry it through the generated CDP URL, and let the relay auto-create an initial tab for that specific Playwright client even if the process env was not set at startup.
This keeps MCP behavior unchanged while making CLI sessions resilient after the last enabled tab is closed.
Session: ses_143538111ffe2cKnv4TqZ7ZoqQ
Use full user-agent client hints when the browser exposes them so the extension can report more specific Chromium-family names before falling back to low-entropy brands and the user agent string.
This keeps detection inside the browser instead of guessing from local filesystem installs. If a browser only exposes the generic Google Chrome brand, Playwriter keeps showing Chrome.
Bump the extension manifest to 0.0.95 and add a playwriter patch changeset for the browser list improvement.
Session: ses_143f600f9ffe0ftfzwvYK56gQV
Oracle review fixes for CDP log rotation:
- Wrap rotate() in try/catch so failures do not break the write queue
- Use temp file + fs.rename for atomic rotation (no corruption on crash)
- Add resolvePositiveInt() to validate maxEntries from env/options
- Remove redundant `rotating` flag (queue serialization is sufficient)
- Expose flush() on CdpLogger so tests can await the queue
- Rewrite tests to use flush() instead of setTimeout (14ms vs 1.3s)
- Assert exact retained entry IDs via inline snapshot
- Add test for atomic rename (no .tmp file left behind)
- Add patch changeset for playwriter package
Session: ses_1545c4395ffeRR6V3ko2KSiVHT
The cdp.jsonl file grew without bound during long relay sessions,
causing disk exhaustion (5.7 GB reported in issue #92).
Added rotation logic to createCdpLogger:
- New `maxEntries` option (default 10,000, env PLAYWRITER_CDP_LOG_MAX_ENTRIES)
- When line count exceeds maxEntries, file is trimmed to keep the last half
- Rotation is chained on the async write queue, safe with concurrent writes
- File oscillates between maxEntries/2 and maxEntries lines
Added unit tests:
- Rotation triggers correctly at threshold
- No rotation when under limit
- Multiple rotations work with small maxEntries
Fixes#92
Session: ses_1545c4395ffeRR6V3ko2KSiVHT
clearAllLogs() now also clears pageLogCursor so subsequent sinceLastCall
calls do not see stale cursors that would cause them to return empty arrays.
Added changeset describing the sinceLastCall option and persistent logs
behavior change.
Session: ses_154681872ffeqje784ZqFUspy5
getLatestLogs() now supports a sinceLastCall option that returns only new
console logs and page errors since the previous call. The first call returns
all buffered logs including pre-existing ones. This lets agents check for
errors after every action without seeing duplicates.
Browser console logs no longer clear on page navigation (framenavigated).
Logs persist across gotos and reloads so errors from page transitions are
never missed. The existing 5000-entry-per-page cap prevents unbounded growth.
When entries are shifted out at the cap, per-page cursors are decremented to
stay in sync.
Updated skill.md interaction feedback loop to instruct agents to always call
getLatestLogs({ page: state.page, sinceLastCall: true }) after every action
(goto, click, submit) to catch hydration errors, failed network requests,
and runtime exceptions.
Updated tests: two assertions that expected logs to be cleared on reload now
expect logs to persist (lines 831 and 978).
Session: ses_154681872ffeqje784ZqFUspy5
process.exit() would kill the relay server, affecting all sessions.
process.chdir() would change the relay cwd, affecting all sessions.
Both now throw descriptive errors instead.
Also simplified the Proxy: always use it (no conditional on sessionCwd),
falling back to real process.cwd() when sessionCwd is null.
Session: ses_16234add5ffeXGeXpSdt8Jttuc
- Add `process` to usefulGlobals so it is available in the VM sandbox
- Override `process.cwd()` via Proxy to return the session cwd
(where `playwriter session new` was invoked) instead of the
relay server cwd
- Document `process` in the skill.md context variables section
- Document sandboxed fs write restrictions (only session cwd,
/tmp, os.tmpdir() are writable)
- Document unavailable globals: __dirname, __filename, import
Session: ses_16234add5ffeXGeXpSdt8Jttuc
When two CLI/MCP commands start simultaneously with no relay running,
both detect the relay is down and both spawn one. The second process
crashes with EADDRINUSE on port 19988.
Three changes fix this:
1. cdp-relay.ts: switch from serve() to createAdaptorServer() so we
inject WebSocket upgrade handlers before binding, then explicitly
call server.listen() with once(listening)/once(error) handlers.
EADDRINUSE is now a catchable promise rejection instead of an
unhandled async error event.
2. start-relay-server.ts: catch EADDRINUSE in the spawned relay
process. Poll /version for up to 2 seconds to check if another
relay won the race. If yes, exit cleanly (code 0) instead of
crashing with a scary error in the logs.
3. relay-client.ts: add module-level promise dedup so concurrent
ensureRelayServer() calls within the same process only spawn once.
When a process is found on the port but /version does not respond,
poll /version for 2 seconds before killing it (prevents accidentally
killing a relay that is still starting during a race).
Shared waitForRelayVersion() helper extracted to avoid duplicating the
poll loop in two places.
Session: ses_16355a286ffekJzHDlexPGKG56
Update the Playwriter skill to tell agents to use getLatestLogs when inspecting browser logs instead of manually attaching page.on('console') listeners.
Manual console listeners only see future events, which can miss logs emitted during page startup or React hydration. The docs now explain that getLatestLogs captures browser console logs and pageerror entries from page start, and the examples now show page-scoped and hydration-error lookups.
Also add a playwriter patch changeset for the docs change.
Verification:
- pnpm typecheck
Known local check limitations:
- lintcn still reports pre-existing unrelated errors in aria-snapshot.ts, cdp-relay.ts, mcp.ts, and wait-for-page-load.ts
Session: ses_1913ab7ecffeHGoDq9rqCrGbCK
Capture uncaught page runtime errors in getLatestLogs by listening to Playwright pageerror events and storing browser logs by Page object instead of target id.
This fixes page-scoped log lookup for pages where useful runtime errors surface outside the main page target, including React hydration failures on unframer.co.
Also add a regression assertion for pageerror entries and a playwriter patch changeset.
Verification:
- pnpm typecheck
- manual playwriter CLI check on https://unframer.co/ returns [pageerror] Minified React error #418
Known local check limitations:
- targeted Vitest could not launch because the local Playwright Chromium binary is missing
- lintcn still reports pre-existing unrelated errors in other files
Session: ses_1913ab7ecffeHGoDq9rqCrGbCK
Release script now zips the contents of dist-release/ instead of the folder
itself, so unzipping produces the extension files at the top level. Zip is
named playwriter-VERSION.zip (e.g. playwriter-0.0.94.zip) instead of
extension.zip. Updated AGENTS.md instructions to match.
Session: ses_197aa7c50ffeN6KEKgB6WOZPOZ
Agents should create a CLI GitHub release (--latest) after every npm publish,
and an extension release (--latest=false) with extension.zip attached whenever
extension/ code changed. Extension releases use --latest=false so they do not
override the CLI as the "Latest" GitHub release.
Session: ses_197aa7c50ffeN6KEKgB6WOZPOZ
When using `--token` with a remote `--host`, the CLI and executor
status check fetches (`/extensions/status`, `/extension/status`) were
not sending the bearer token. The host validation middleware rejects
non-localhost requests without a valid token, so remote `session new`
and `session list` would silently fail with "no connected browsers".
**cli.ts**: `fetchExtensionsStatus` now accepts `{ host, token }` and
passes auth headers via `buildAuthHeaders` to both status endpoints.
**executor.ts**: `checkExtensionStatus` reads `token` from
`this.cdpConfig` (falling back to `PLAYWRITER_TOKEN` env) and attaches
`Authorization: Bearer` header to all status fetch calls.
`relay-client.ts` left unchanged since those functions are hardcoded
to `127.0.0.1` (local-only, always passes host validation).
Closes#91Fixes#85
Session: ses_1b4c572a4ffev2Dct63TjK77gJ
DNS rebinding lets an attacker serve a page from http://evil.com:19988,
then rebind the DNS to 127.0.0.1. The browser treats subsequent requests
as same-origin, bypassing Sec-Fetch-Site, CORS preflight, and
Content-Type enforcement. This gives full access to privileged HTTP
routes like /cli/execute without any existing defense catching it.
The new global middleware rejects requests whose Host header hostname is
not localhost, 127.0.0.1, [::1], or ::1. Requests carrying a valid
bearer/query token bypass the check so remote clients still work.
The Host parser handles IPv6 brackets, bare ::1, case-insensitive
hostnames, and port suffixes via indexOf instead of a fragile regex.
Unit tests use raw http module (Node fetch forbids overriding Host) to
verify evil.com is rejected and all localhost variants pass, including
uppercase LOCALHOST.
Session: ses_1bfec4b08ffeL3NJunw0EC9plq
Merges all changes since 0.1.0 (last published npm version):
- New -f/--file flag for executing JS from files
- React component inspection for pinned elements
- Performance profiling guide resource
- Shell tab completions via goke
- Security: token required on all requests regardless of source
- --token works on every remote subcommand
- Fixed Next.js webpack layer prefixes in React source paths
- Skill docs require absolute paths for saved artifacts
Session: ses_1bff6e68fffe5Ls42bfN8RUyVp
The old toast just said "Copied pin #N" which gave no hint about what to do next. Now it says "Copied playwriter element reference, use it in your agent prompt" so users know to paste it into their AI agent.
Bump extension to 0.0.94.
Session: ses_1d03be05affeS58tMdQDs6GMbj
Add "Execute from file" subsection to skill.md documenting the -f flag
for executing JavaScript from a file instead of inline -e code.
Session: ses_1d0419706ffelv53Gc4OrfwZ4L
Update vite in root and extension packages from ^8.0.3 to ^8.0.12.
Lockfile reflects all transitive dependency updates from this session:
goke 6.12.1, @holocron.so/vite 0.8.0, dompurify 3.4.3, katex 0.16.46,
and various @csstools patches.
Upgrade @holocron.so/vite from ^0.7.1 to ^0.8.0.
Use the new `<Logo>` component in the hero section instead of a plain
`<h1>`, and switch `<img>` to the framework `<Image>` component.
Add icon fields to the GitHub and Releases anchors in docs.json
(github, log). Fix docs.json indentation.
Add website/styles.css with Tailwind `@source` directive for the
new holocron vite plugin version.
Add `-f, --file <path>` option so users can execute JavaScript from a file
instead of inline `-e` strings. The two flags are mutually exclusive.
Enable `cli.completions()` from goke for shell tab completion support.
Change `cli.parse()` to `await cli.parse()` so async command actions
properly propagate errors and exit codes.
Add tests for unknown command and unknown subcommand error handling,
verifying they exit with code 1 and print helpful messages.
Bump goke from ^6.6.0 to ^6.12.1 which includes the completions API
and improved error messages for unknown commands.