| name | plwr |
| description | Browser automation CLI using CSS selectors, built on Playwright. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.
|
Browser Automation with plwr
plwr uses CSS selectors (not element refs) and a persistent daemon session.
Every session must be explicitly started and stopped.
Quick Start
plwr start
plwr open https://example.com
plwr text h1
plwr click 'a'
plwr stop
Core Workflow
plwr start — launch browser (headless by default)
plwr open URL — navigate
- Interact/query using CSS selectors
plwr stop — shut down
⚠️ Key Concepts
- Always start/stop: every session needs
plwr start before and plwr stop after.
- CSS selectors, not refs: all commands take standard CSS selectors (e.g.
#id, .class, tag, [attr=val]).
- Auto-wait: interaction and query commands auto-wait for elements up to the timeout. You rarely need
plwr wait.
- Strict mode: if a selector matches multiple elements, commands like
text, click, attr fail. Use >> nth=N to pick one, or count/exists which handle multiple matches.
- Single-quote selectors in shell to avoid bash metacharacter issues (e.g.
$ in [href$=.pdf]).
Commands
Session Lifecycle
plwr start
plwr start --headed
plwr start --video recording.mp4
plwr start --ignore-cert-errors
plwr stop
Remote Debugging (CDP)
Connect to a running Chrome instead of launching a new browser. Shares
the user's cookies and login state. Requires the user to enable remote
debugging in Chrome (chrome://inspect/#remote-debugging) and click
Allow on the permission dialog.
plwr start --cdp
plwr start --cdp beta
plwr start --cdp ~/my-profile
plwr start --cdp ws://host:9222/...
plwr stop
--cdp is mutually exclusive with --headed and --video.
Navigation
plwr open 'https://example.com'
plwr reload
plwr url
Waiting
plwr wait '.my-element'
plwr wait-not '.loading-spinner' -T 10000
plwr wait-any '.success' '.error' '.timeout'
plwr wait-all '.header' '.sidebar' '.content'
Interaction
plwr click '#submit-btn'
plwr fill '#name-input' 'Alice'
plwr type 'hello world'
plwr type 'slow' --delay 100
plwr press Enter
plwr press Control+c
plwr dblclick '.editable-cell'
plwr hover '.dropdown-trigger'
plwr focus '#search'
plwr blur '#email'
plwr scroll '.footer'
type sends individual key events per character — use for contenteditable
elements (e.g. Twitter's compose box) where fill doesn't work. fill sets
the value of <input>/<textarea> directly. press sends a single keystroke
and supports special keys and chords that type cannot.
Click/dblclick modifiers:
plwr click '#item' --shift
plwr click '#item' --alt
plwr click '#item' --meta
plwr click '#item' --control
plwr click '#item' --right
plwr click '#item' --middle
Checkboxes, Radios, Selects
plwr check '#agree-terms'
plwr uncheck '#newsletter'
plwr select '#country' us
plwr select '#country' --label 'Canada'
plwr select '#colors' red green blue
Querying
plwr text h1
plwr inner-html '.content'
plwr attr a href
plwr input-value '#email'
plwr count '.list-item'
plwr exists '.sidebar'
plwr computed-style '.box' display width
Clipboard
plwr clipboard-copy '#source'
plwr focus '#target'
plwr clipboard-paste
Headers and Cookies
plwr header Authorization 'Bearer tok123'
plwr header --clear
plwr cookie session_id abc123
plwr cookie token xyz --url https://example.com
plwr cookie --list
plwr cookie --clear
Viewport
plwr viewport 1280 720
plwr viewport 375 667
File Uploads
plwr input-files 'input[type=file]' photo.png
plwr input-files '#upload' a.txt b.txt c.txt
Dialogs
Register a handler before the action that triggers the dialog:
plwr next-dialog accept
plwr click '#delete-btn'
plwr next-dialog dismiss
plwr click '#cancel-btn'
plwr next-dialog accept 'Alice'
plwr click '#rename-btn'
Console Logs
plwr console
plwr console --clear
Network Requests
Capture all HTTP requests (doc, CSS, JS, images, fonts, fetch, XHR, WebSocket)
with status codes for every resource type.
plwr network
plwr network --type fetch
plwr network --type css,js,img
plwr network --url '\.json$'
plwr network --type fetch --url '/api/'
plwr network --type ws --include-ws-messages
plwr network --clear
Types: doc, css, js, img, font, media, fetch, xhr, ws,
wasm, manifest, other.
Each entry: {type, url, status, method, size, duration, ts}.
WebSocket entries: {type, url, status, duration, ts}, plus messages
array with --include-ws-messages containing {dir, data, ts} per frame.
JavaScript
plwr eval 'document.title'
plwr eval '({a: 1, b: [2, 3]})'
plwr eval "(() => {
const rows = document.querySelectorAll('table tr');
return Array.from(rows).map(r => r.cells[0]?.textContent);
})()"
DOM Tree
plwr tree
plwr tree '.sidebar'
Screenshots and Video
plwr screenshot
plwr screenshot --selector '.chart' --path chart.png
plwr start --video recording.mp4
plwr stop
Sessions
plwr -S session-a start
plwr -S session-b start
plwr -S session-a open https://example.com
plwr -S session-b open https://other.com
plwr -S session-a text h1
plwr -S session-b text h1
plwr -S session-a stop
plwr -S session-b stop
Global Options
| Option | Description |
|---|
-S, --session | Session name (default: default, env: PLWR_SESSION) |
-T, --timeout | Timeout in ms (default: 5000, env: PLWR_TIMEOUT) |
Selectors
plwr uses Playwright's selector engine which extends CSS.
Basics
plwr click '#submit-btn'
plwr click '.btn.primary'
plwr click 'button'
plwr count 'input[type=email]'
plwr count '[data-testid=login-form]'
Combinators
plwr count '#list > li'
plwr count 'h1 + p'
plwr text '.card p'
Playwright Extensions
plwr click ':has-text("Sign in")'
plwr click 'text=Sign in'
plwr click 'li.item >> nth=0'
plwr click 'li.item >> nth=-1'
plwr count 'button:visible'
plwr text 'tr:has-text("Bob") >> td.name'
css= Prefix
Some pseudo-classes need css= to bypass Playwright's parser:
plwr text 'css=.list span:last-of-type'
plwr text 'css=li:nth-of-type(2)'
plwr count 'css=:is(.card, .sidebar)'
plwr text 'css=[data-testid="login-form"] button'
Need css=: :last-of-type, :first-of-type, :nth-of-type(),
:nth-last-child(), :is(), :where(), quoted [attr="val"].
Work without: :nth-child(), :first-child, :last-child, :not(),
:has(), :empty, :checked, :disabled, :visible, :has-text(),
text=, >> nth=N.
Strict Mode
plwr text 'li.item'
plwr text 'li.item >> nth=0'
plwr count 'li.item'
plwr exists 'li.item'
Example: Login Flow
plwr start
plwr open 'https://app.example.com/login'
plwr fill '#email' 'user@example.com'
plwr fill '#password' 'secret'
plwr click '#login-btn'
plwr wait '.dashboard'
plwr text '.welcome-message'
plwr stop
Example: Scraping a Table
plwr start
plwr open 'https://example.com/data'
plwr count 'table tbody tr'
plwr text 'table tbody tr >> nth=0'
plwr eval "(() => {
const rows = document.querySelectorAll('table tbody tr');
return Array.from(rows).map(r => ({
name: r.cells[0]?.textContent,
value: r.cells[1]?.textContent,
}));
})()"
plwr stop
Example: cctr E2E Tests
plwr is designed to work well with cctr corpus tests:
===
send a message
===
plwr fill '.chat-input' 'Hello agent'
plwr press Enter
---
===
agent responds
===
plwr wait '[data-role=assistant]:has-text("Hi")' -T 10000
---