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.
The holocron docs framework has been published as @holocron.so/vite on npm.
The local package is no longer needed in this repo.
Removed files:
- holocron/src/ (vite plugin, components, styles, config, sync, mdx processing)
- holocron/AGENTS.md
- holocron/package.json, tsconfig.json, schema.json
Session: ses_1da6d3925ffeK4qAOv03OUyIfl
Replace the old changelog instructions with the repo changesets workflow so future package changes add .changeset entries instead of editing generated CHANGELOG.md files or manually bumping public package versions.
Keep the Chrome extension manifest bump requirement separate because extension releases are not driven by npm changesets, and add a .changeset/readme.md so agents can quickly see what belongs in the folder.
Move the pending playwriter typecheck emit change into a changeset and revert the manual package version bump from the prior commit, leaving version updates to the publish flow.
Session: ses_1f763e4ecffenZcbrWQbx0Oh5F
Run the playwriter package typecheck script through plain tsc instead of tsc --noEmit so local validation keeps compiled dist files up to date.
Bump the package version to 0.1.7 so the local dist-backed shim reports the updated package version after rebuilding.
Session: ses_1f763e4ecffenZcbrWQbx0Oh5F
Mention .cpuprofile recording via CDP Profiler.* commands and
profano CLI for inspecting hot functions when vitals alone are
not enough to diagnose performance issues.
Session: ses_208943d84ffenqdbjxK0cO0iak
bippy.normalizeFileName strips "/app-pages-browser/" but not the
parenthesized form "/(app-pages-browser)/" that Next.js webpack
actually uses in source maps. Added cleanFileName/cleanName that
runs after normalizeFileName and strips any /(layer-name)/ prefix
using regex, plus trailing "./".
Covers all Next.js webpack layer names: (app-pages-browser), (ssr),
(rsc), (action-browser), (pages-dir-browser), (pages-dir-edge),
(pages-dir-node).
Applied in both:
- Extension context menu handler (background.ts)
- MCP react-source.ts (getReactSource + getReactComponentInfo)
Also applied oracle review fixes:
- userGesture: true on clipboard Runtime.evaluate calls to prevent
NotAllowedError when page loses focus after context menu
- Defensive fileName null check before building clipboard text
Session: ses_208943d84ffenqdbjxK0cO0iak
New context menu item that copies the React component source file path
to clipboard when right-clicking an element on a React page.
Uses bippy (React fiber introspection library) to walk the fiber tree
from the right-clicked DOM element up to the nearest composite component
with source location info. Tries getSource() first (direct __source prop
from JSX transform), then falls back to getOwnerStack() for builds with
source maps.
Copies "path/to/file.tsx:42" format to clipboard on success (green flash),
shows red flash if the element is not part of a React app or no source
maps are available.
Also refactored all inline JS strings in the context menu handlers to use
string-dedent with descriptive variable names for syntax highlighting.
extension@0.0.93
Session: ses_208943d84ffenqdbjxK0cO0iak
Reviewer flagged the loopback-bypass added in 0.1.5 as a full auth
bypass under the recommended remote-access topology: tunnel agents
(traforo, ngrok, cloudflared) run as local processes and forward
public traffic to localhost:19988, so every public request reaches
the relay from 127.0.0.1. The bypass made `/cli/execute` (RCE),
`/cli/session/*`, `/recording/*` (file writes via outputPath), and
`/mcp-log` callable by anyone with the public tunnel URL.
Fix
---
1. cdp-relay.ts: privilegedRouteMiddleware now requires the token on
every request, regardless of source. Loopback bypass removed.
2. cli.ts (`serve` action): exports `process.env.PLAYWRITER_TOKEN` at
startup so in-process callers can attach Authorization: Bearer ...
when calling the relay's own privileged endpoints.
3. screen-recording.ts: new `recordingHeaders()` helper reads
PLAYWRITER_TOKEN from env and attaches the Bearer header to all
four /recording/* calls (start, stop, status, cancel). Mirrors
what mcp.ts already does for /mcp-log.
4. cli.ts (`executeCode`): refactored the inline header-build into
the shared `buildAuthHeaders` helper so the six /cli/* fetch
sites stay in sync.
5. connect-cdp-demo.ts: replaced the hardcoded 'secret_token'
fallback with a hard error so the demo can never accidentally be
the source of a leaked credential warning.
Bumps playwriter to 0.1.6 + CHANGELOG entry. Adds a MEMORY.md note
so the next person doesn't re-introduce the bypass.
Session: ses_20d25dcc7ffeJhVK7aC8Zh7OkW
Replaces old CHANGELOG.md instructions with changesets. Also picks up
latest shared snippet updates from agentsdotmd@1.1.5.
Session: ses_20d092d48ffey0lmjedA7ZMTed
Builds on Ivan Leo's auth-header pass with the missing pieces needed to
actually run `playwriter serve --token` end to end against a remote relay.
Fixes
-----
1. `--token` (or PLAYWRITER_TOKEN) now plumbed through every remote
subcommand: `session new/list/delete/reset` and `browser list`. Until
now only `playwriter -e` sent the Bearer header, so against a token-
protected relay every other subcommand returned 401.
2. In-process `ExecutorManager` carries `token` in its `cdpConfig`, so the
executor's own `chromium.connectOverCDP(ws://127.0.0.1:19988/cdp/<id>)`
call passes the relay's own auth check.
3. `/mcp-log` is now under `privilegedRouteMiddleware`. Previously open,
so any reachable client could spam the log file.
4. `mcp.ts` `sendLogToRelayServer` sends `Authorization: Bearer …` when
`PLAYWRITER_TOKEN` is set.
5. Loopback connections (127.0.0.1, ::1, ::ffff:127.0.0.1) bypass the
token check on `/cli/*`, `/recording/*`, and `/mcp-log`. They can only
originate from in-process executor code which already runs downstream
of an authenticated `/cdp/*` WebSocket, so re-checking the token would
mean smuggling it into every internal helper for no real security
gain. Resolves the broken `/recording/*` calls from
`screen-recording.ts`.
6. New `connect-cdp-demo.ts` script: minimal example showing an external
Playwright client connecting through `/cdp/<id>?token=<token>` over a
tunnel.
Bumps `playwriter` to 0.1.5 and updates CHANGELOG.
Co-authored-by: Ivan Leo <ivanleomk@gmail.com>
Session: ses_20d25dcc7ffeJhVK7aC8Zh7OkW
Remove the natural-language wrapper around pinned element clipboard text so toolbar and context-menu pins copy a shell-ready playwriter command. This keeps the copied text compact while still delegating DOM and React inspection to inspectPinnedElement.
Session: ses_221fdd7c5ffexkyCmOtfmgwjoI
Expose getReactComponentInfo so agents can inspect the React component name, parent hierarchy, sanitized props, and source metadata for a locator or element handle. Non-React elements return null so the helper is safe to call from generic DOM inspection flows.
Update toolbar and context-menu pinned element copy to use a short inspectPinnedElement(url, expression) call. The wrapper resolves the pinned element, logs outerHTML plus React info internally, and keeps copied prompts small.
Session: ses_221fdd7c5ffexkyCmOtfmgwjoI
Add a generated website resource that shows how to profile a real site with Playwriter using CDP, Navigation Timing, and PerformanceObserver.
The new guide focuses on the questions people actually ask during performance work: whether rendering is good or bad, which requests dominate transfer cost, what usually blocks first paint or LCP, and what is most likely blocking interactivity. It also includes ready-to-run Playwriter commands plus reusable TypeScript examples so the profiling workflow can be copied directly.
Session: ses_22252b7b8ffee2VFXqmaCzA2NR
Document that Playwright artifact APIs resolve paths inside Playwright client code instead of the sandboxed fs, so relative paths can land under the relay cwd.
Update the skill examples to use absolute paths for screenshots, PDFs, downloads, resize inputs, and recordings, and bump playwriter to 0.1.1 so the guidance ships with the release notes.
Session: ses_2411fc3d3ffe2bK1VDhxJd0u15