The ghost cursor painted its arrow with
`background-image: url("data:image/svg+xml;base64,…")`. Pages with a
strict `img-src` CSP refuse to load that URL, so on Hacker News every
navigation logged a CSP violation and the cursor rendered as an empty
box.
Changes:
- `setInnerSvg()` parses the markup with `DOMParser`, validates the root
is a real SVG element, imports it into the document, and appends it to
the cursor. No image URL is involved, so no `img-src` rule applies.
- The generated cursor module now exports raw SVG markup
(`pointer-macos-tahoe-svg.ts`) instead of a base64 data URL
(`pointer-macos-tahoe-data-url.ts`). `encode-screenstudio-cursor.ts`
writes the markup with `JSON.stringify`.
- `applyRuntimeVisualOptions()` clears previous children and resets
`background-image` on every style switch, so toggling between
`screenstudio`, `minimal`, and `dot` never stacks two arrows.
The always-on cursor test now asserts the injected element contains a
literal `<svg>` and no `data:image/svg` reference.
Session: ses_fe45ea8e3ffepbcZz6OQQ9sFSH
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
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
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
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
Bundle the extension into the CLI so fresh AVPS and local source checkouts can start a Playwriter-ready Chrome for Testing or Chromium without separate installation steps. Also disable the packaged welcome tab, reuse Playwright\s managed Chromium fallback, surface searched browser paths on discovery failures, and document that managed browser launches already enable the tab-capture flags needed for recording on normal web pages.
Soak test that creates a session, opens one page, then loops for hours
executing 8 Playwright operations every 5 seconds (title, evaluate x3,
url, snapshot, locator count, goto). Tracks success/failure counts and
prints a summary on exit.
First 30-minute run: 2,272 operations, 0 failures, 100% success rate.
Usage: bun playwriter/scripts/long-running-stability-test.ts --duration 2
Clarify recording guidance to prefer interaction-driven flows (click, hover, typing) so ghost cursor motion is visible during demos.
Tune ghost cursor movement defaults for faster, human-like motion and align cursor encoder script path handling with __dirname plus path.resolve conventions.
Introduce a first-class ghost cursor system for recordings and screenshots with namespace APIs (, ) while preserving backward-compatible top-level recording helpers.
Improve reliability after relay restarts by consolidating extension wait logic around , reducing false disconnected states during and execute flows.
Refactor recording glue into dedicated modules (, in ) to keep executor orchestration thin and make cursor lifecycle state explicit and testable.
- Consolidate build scripts into single build-client-bundles.ts
- Add @mozilla/readability for Firefox Reader View content extraction
- New getPageMarkdown() utility extracts main page content as plain text
- Supports search and diff-since-last-call like other utilities
- Add test with file snapshot
- Add a11y-client.ts: browser-side code using dom-accessibility-api (41KB)
- Single page.evaluate() call instead of multiple CDP roundtrips
- Zero element handles - everything runs in page context
- Stable refs from test IDs (data-testid, data-test-id, id) instead of e1, e2
- Add locator param to scope snapshots to subtrees
- Fixes cross-frame 'Unable to adopt element handle' error (issue #39)
- Move skills/playwriter/SKILL.md to playwriter/src/skill.md (source of truth)
- Add 'playwriter skill' CLI command to output full docs
- Make skills/playwriter/SKILL.md a lightweight stub pointing to CLI
- Generate prompt.md to dist/ instead of src/
- Strip CLI-only content from MCP prompt
- Add build-resources.ts script to generate markdown API docs
- Resources now use https://playwriter.dev/resources/*.md URLs
- Output to both playwriter/dist/ and website/public/resources/
- Simplify mcp.ts resource handlers to read pre-built files
- Add bippy library for React fiber introspection
- Create build-bippy.ts and build-selector-generator.ts using Bun.build
- Use CDP Runtime.evaluate instead of addScriptTag to bypass CSP
- Add getReactSource utility function exposed in MCP VMContext
- Works on local React dev servers with JSX transform (not production builds)