Playwright cli cloakbrowser
Agent skills for building, shipping, and growing software products
npx -y skills add tenequm/skills --skill playwright-cli-cloakbrowserAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Drive CloakBrowser Manager stealth profiles with @playwright/cli over CDP. Use for browser automation that needs a persistent logged-in session, anti-detect fingerprints, or to pass Cloudflare - attach reuses the profile's cookies and stealth where a fresh browser does not.
SKILL.md
12.7 KB, as published. Nobody here has run it
playwright-cli on CloakBrowser stealth profiles
CloakBrowser Manager runs isolated stealth Chromium profiles - unique fingerprint, optional proxy, persistent cookies, built-in noVNC live view - in a single Docker container. Every running profile exposes a CDP endpoint. playwright-cli attach connects to the profile's persistent context (contexts[0]), so automation inherits the profile's logins and stealth fingerprint; playwright-cli open would launch a plain local browser with neither. Always attach for this setup.
No Manager running yet? See Setting up the Manager below.
For the full command reference, run playwright-cli --help and open the Agent skill: path it prints (the official skill bundled with @playwright/cli). This file only covers the CloakBrowser-specific setup and gotchas.
Connect
# generic: attach to any CDP endpoint
playwright-cli attach --cdp=<CDP_URL>
# CloakBrowser Manager: per-profile CDP endpoint (shown via the toolbar
# code icon while a profile is running)
playwright-cli attach --cdp=http://localhost:8080/api/profiles/<profile-id>/cdp
# then drive normally
playwright-cli goto https://example.com
playwright-cli snapshot # a11y tree with refs (e3, e15, ...)
playwright-cli snapshot --depth=6 # bound the tree on huge pages
playwright-cli click e15
playwright-cli --raw eval "document.title" # --raw = return value only (JSON-serialized), no snapshot noise
playwright-cli detach # leave the browser running for next time
- The profile must be running first - click Launch in the Manager GUI, or
curl -X POST http://localhost:8080/api/profiles/<profile-id>/launch. --cdp=<url>and--cdp <url>both work.attachcreates an implicit session nameddefaultand prints a--s=defaulthint - that flag is only needed when juggling multiple sessions; plain commands use the default session.-s=<name>opens a second isolated session on the same CDP (e.g.-s=probefor in-page fetch checks whiledefaultdrives the page).- playwright-cli writes a
.playwright-cli/scratch dir (snapshot.ymlfiles, console logs) into the cwd - run from a scratch directory or gitignore it. - Never
close/close-all/kill-allon an attached stealth profile - that can stop the shared browser and its session. Usedetachto leave it running.
Logging into a site
The profile persists cookies, localStorage, and cache across restarts. To establish a login: open the Manager GUI at http://localhost:8080, view the running profile in the built-in noVNC viewer, and sign in there once. playwright-cli then drives the same session logged-in - the VNC view and the CDP connection share one browser.
Importing an existing login (two limits worth knowing):
- Anti-automation sites (Google, Facebook) may flag the CDP-driven login and not persist a durable session even though the form submits. Bypass: import a real-browser session as Playwright storage-state (
playwright-cli state-save/state-load, or capture withnpx playwright codegen --save-storage=auth.json <url>) instead of logging in through VNC. - A session is often IP-bound: a cookie/state minted at your real location, replayed from a profile whose proxy egress is a different geo, gets forced back to login + 2FA. Transfer only works when the profile's proxy geo matches where the session was created.
Gotchas (all observed in practice)
attachhangs ~30 s, then times out - the profile has a stale or heavy pre-existing tab that never finishes Playwright'sconnectOverCDPpage-init handshake. Fix: close extra tabs via the noVNC view (or stop/relaunch the profile), then re-attach - drops to under a second. A cleanabout:blankprofile attaches instantly.- New-tab creation can be rejected ("Failed to open a new tab") - the stealth browser may forbid
Target.createTargetin some states. Prefer navigating the current tab (goto) overtab-new. If a page closes or replaces its own tab (challenge redirects, heavy detail pages), the driver desyncs - just re-attachand continue. - JS-rendered content (prices, dynamic cards) often is not in the a11y
snapshot- scrape the DOM witheval:playwright-cli --raw eval "[...document.querySelectorAll('[data-card]')].map(e=>e.innerText)". - Heavy detail pages are slow through a profile proxy and can timeout or desync. Read what you need from the list/results page when possible; treat opening detail pages as best-effort.
- Cookie/consent overlays block clicks ("element covered"). Dismiss via JS:
playwright-cli eval "document.querySelector('#onetrust-accept-btn-handler')?.click()". - Use
--rawwhenever scraping or piping - without it output is wrapped in markdown header blocks (### Result,### Page,### Snapshot- varies by command), which mangles piping. Even with--raw, eval values come back JSON-serialized (strings quoted). - "Logged in" can be false. A CDP-attached context may render a login form (geo often defaults to US). Verify auth with an
evalrender check -playwright-cli --raw eval "!!document.querySelector('[href*=logout], [aria-label*=account]')"- notcookie-get, which can read the automation's own blank page and false-negative. gotomay throwTimeoutErroron heavy JS sites (it waits ondomcontentloaded) yet a follow-upsnapshot/evalstill returns usable content - a nav timeout is not fatal, retry the read. (Extends gotcha 4.)click "<CSS selector>"often fails to match even when the element exists. Prefer snapshoteNrefs, or click via JS:playwright-cli eval "document.querySelector('...')?.click()".- To wait, poll - do not
sleep. Loop on in-page state viaeval(document.readyState, element presence) with a bounded max; cap any Cloudflare "Just a moment..." wait explicitly, it is otherwise unbounded. - Browsing a logged-in session can log you out mid-run - anti-automation defenses fire on real activity. Treat a mid-run logout as a site defense, not a tool bug; re-establish the session and slow down.
Patterns for logged-in & JS-heavy sites
- Reverse-engineer the site's own API. From the logged-in context, call same-origin authenticated endpoints directly - far more robust than DOM scraping:
Discover the endpoints by monkeypatching fetch first:playwright-cli -s=probe --raw eval "(async()=>await (await fetch('/api/whatever',{headers:{'X-Requested-With':'XMLHttpRequest'}})).json())()"eval "window.__u=[];const f=fetch;window.fetch=(...a)=>{window.__u.push(a[0]?.toString?.()||a[0]);return f(...a)}", exercise the page, then readwindow.__u. - Use CDP-level network inspection when in-page hooks fall short. A page reload wipes an injected fetch/XHR hook, and service-worker / iframe traffic is invisible to a main-context hook. Use
playwright-cli requeststhenplaywright-cli --raw response-body <id>to capture what the page actually fetched. - Lazy-loaded lists: scroll-until-count, not fixed sleeps. Loop: scroll, poll the item count, stop on no-growth OR target OR max-rounds. For content inside a scrollable element, scroll that element's
scrollHeight, not the window:playwright-cli --raw eval "(async()=>{let p=0;for(let i=0;i<30;i++){scrollTo(0,document.body.scrollHeight);await new Promise(r=>setTimeout(r,700));const n=document.querySelectorAll('[data-card]').length;if(n===p)break;p=n}return p})()" - Retry hard blocks with a fresh exit IP. On 403/429/503/empty content, retry once against a fresh context so a rotating proxy picks a new exit IP (only helps if the profile's proxy rotates).
Minimal working pattern
CDP=http://localhost:8080/api/profiles/<profile-id>/cdp
playwright-cli attach --cdp=$CDP
playwright-cli goto "https://www.amazon.com/s?k=iphone+16+pro+max"
playwright-cli --raw eval "[...document.querySelectorAll('[data-asin]')].slice(0,5).map(e=>e.innerText.split('\n')[0]).join(' | ')"
playwright-cli detach # leave the stealth browser running
Setting up the Manager (local laptop)
Requirements: Docker 20.10+, ~2 GB disk (the image bundles the CloakBrowser binary; the Manager is ready seconds after start), ~512 MB RAM per running profile.
docker run -d -p 8080:8080 -v cloakprofiles:/data cloakhq/cloakbrowser-manager
Binds to localhost. If 8080 is taken, map another host port (-p 18080:8080) and adjust every URL in this file accordingly. Profiles, cookies, and session data live in the cloakprofiles volume and survive container updates (docker pull cloakhq/cloakbrowser-manager + recreate). By default there is no authentication - fine for local use; add -e AUTH_TOKEN=your-secret-token to protect it, and pass Authorization: Bearer <token> on API calls.
Open http://localhost:8080 - the Manager web GUI. Create a profile (set proxy, timezone, and locale together if you need specific geo) and click Launch. Or headless via API:
# create -> returns {"id": "<profile-id>", ...}
curl -s -X POST http://localhost:8080/api/profiles \
-H 'Content-Type: application/json' \
-d '{"name": "agent", "platform": "macos"}'
# launch
curl -s -X POST http://localhost:8080/api/profiles/<profile-id>/launch
# list profiles / find the id later
curl -s http://localhost:8080/api/profiles
The noVNC live view is built into the GUI: click the running profile to watch and interact with the browser in a tab. Use it to log into sites once, watch automation live (VNC and CDP share the same browser), and clear stuck tabs when attach hangs (gotcha 1).
Playwright/Puppeteer scripts work against the same CDP URL:
const { chromium } = require("playwright");
const browser = await chromium.connectOverCDP(
"http://localhost:8080/api/profiles/<profile-id>/cdp"
);
const page = browser.contexts()[0].pages()[0];
await page.goto("https://example.com");
Remote Manager on a Linux VM over SSH
The same setup runs on any Linux VM you can SSH into - a VPS, home server, or cloud instance. Size it at ~512 MB RAM per running profile plus ~1 GB for the Manager itself.
On the VM:
# install Docker (Ubuntu/Debian; skip if present)
curl -fsSL https://get.docker.com | sh
# run the Manager, localhost-bound so nothing is exposed publicly
docker run -d --restart unless-stopped --name cloakbrowser \
-p 127.0.0.1:8080:8080 -v cloakprofiles:/data cloakhq/cloakbrowser-manager
On your laptop:
ssh -L 8080:localhost:8080 user@vm
Everything above now works unchanged: the GUI, the noVNC login flow, and the CDP endpoint all ride the same tunneled port, so playwright-cli attach --cdp=http://localhost:8080/api/profiles/<id>/cdp is identical to the local case. Binding to 127.0.0.1 on the VM makes the SSH tunnel the auth boundary, so AUTH_TOKEN stays unnecessary.
If you must expose the Manager directly instead (no tunnel), set AUTH_TOKEN and put it behind an HTTPS reverse proxy - but note that playwright-cli attach cannot inject a Bearer header on the CDP WebSocket, so a token-protected Manager needs a small local proxy in front that injects the header. Prefer the tunnel.
Notes
- Egress IP is whatever the profile's proxy is set to (or the Docker host's IP with no proxy). Sites see that location for geo, currency, and language - configure the profile's proxy, timezone, and locale together in the Manager. To force English on a geoip'd EU marketplace, a
&language=URL param is not enough; override the header (route/run-codewithAccept-Language: en-GB,en;q=0.9). - One profile = one persistent identity. Use separate profiles for separate accounts; that is the point of the Manager.
- CloakBrowser is the stealth Chromium engine; cloakbrowser.dev is the project site. The Manager GUI is MIT; the CloakBrowser binary has its own license.