| name | gstack |
| version | 1.1.0 |
| description | Fast headless browser for QA testing and site dogfooding. Navigate any URL, interact with
elements, verify page state, diff before/after actions, take annotated screenshots, check
responsive layouts, test forms and uploads, handle dialogs, and assert element states.
~100ms per command. Use when you need to test a feature, verify a deployment, dogfood a
user flow, or file a bug with evidence.
|
| allowed-tools | ["Bash","Read","AskUserQuestion"] |
Update Check (run first)
_UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true)
[ -n "$_UPD" ] && echo "$_UPD" || true
If output shows UPGRADE_AVAILABLE <old> <new>: read ~/.claude/skills/gstack/gstack-upgrade/SKILL.md and follow the "Inline upgrade flow" (AskUserQuestion → upgrade if yes, touch ~/.gstack/last-update-check if no). If JUST_UPGRADED <from> <to>: tell user "Running gstack v{to} (just updated!)" and continue.
gstack browse: QA Testing & Dogfooding
Persistent headless Chromium. First call auto-starts (~3s), then ~100-200ms per command.
Auto-shuts down after 30 min idle. State persists between calls (cookies, tabs, sessions).
SETUP (run this check BEFORE any browse command)
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
B=""
[ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/browse/dist/browse" ] && B="$_ROOT/.claude/skills/gstack/browse/dist/browse"
[ -z "$B" ] && B=~/.claude/skills/gstack/browse/dist/browse
if [ -x "$B" ]; then
echo "READY: $B"
else
echo "NEEDS_SETUP"
fi
If NEEDS_SETUP:
- Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait.
- Run:
cd <SKILL_DIR> && ./setup
- If
bun is not installed: curl -fsSL https://bun.sh/install | bash
IMPORTANT
- Use the compiled binary via Bash:
$B <command>
- NEVER use
mcp__claude-in-chrome__* tools. They are slow and unreliable.
- Browser persists between calls — cookies, login sessions, and tabs carry over.
- Dialogs (alert/confirm/prompt) are auto-accepted by default — no browser lockup.
QA Workflows
Test a user flow (login, signup, checkout, etc.)
$B goto https://app.example.com/login
$B snapshot -i
$B fill @e3 "test@example.com"
$B fill @e4 "password123"
$B click @e5
$B snapshot -D
$B is visible ".dashboard"
$B screenshot /tmp/after-login.png
Verify a deployment / check prod
$B goto https://yourapp.com
$B text
$B console
$B network
$B js "document.title"
$B is visible ".hero-section"
$B screenshot /tmp/prod-check.png
Dogfood a feature end-to-end
$B goto https://app.example.com/new-feature
$B snapshot -i -a -o /tmp/feature-annotated.png
$B snapshot -C
$B snapshot -i
$B click @e3
$B snapshot -D
$B is visible ".success-toast"
$B is enabled "#next-step-btn"
$B is checked "#agree-checkbox"
$B console
Test responsive layouts
$B goto https://yourapp.com
$B responsive /tmp/layout
$B viewport 375x812
$B screenshot /tmp/mobile.png
$B viewport 1440x900
$B screenshot /tmp/desktop.png
Test file upload
$B goto https://app.example.com/upload
$B snapshot -i
$B upload @e3 /path/to/test-file.pdf
$B is visible ".upload-success"
$B screenshot /tmp/upload-result.png
Test forms with validation
$B goto https://app.example.com/form
$B snapshot -i
$B click @e10
$B snapshot -D
$B is visible ".error-message"
$B fill @e3 "valid input"
$B click @e10
$B snapshot -D
Test dialogs (delete confirmations, prompts)
$B dialog-accept
$B click "#delete-button"
$B dialog
$B snapshot -D
$B dialog-accept "my answer"
$B click "#rename-button"
Test authenticated pages (import real browser cookies)
$B cookie-import-browser
$B cookie-import-browser comet --domain .github.com
$B goto https://github.com/settings/profile
$B snapshot -i
$B screenshot /tmp/github-profile.png
Compare two pages / environments
$B diff https://staging.app.com https://prod.app.com
Multi-step chain (efficient for long flows)
echo '[
["goto","https://app.example.com"],
["snapshot","-i"],
["fill","@e3","test@test.com"],
["fill","@e4","password"],
["click","@e5"],
["snapshot","-D"],
["screenshot","/tmp/result.png"]
]' | $B chain
Quick Assertion Patterns
$B is visible ".modal"
$B is enabled "#submit-btn"
$B is disabled "#submit-btn"
$B is checked "#agree"
$B is editable "#name-field"
$B is focused "#search-input"
$B js "document.body.textContent.includes('Success')"
$B js "document.querySelectorAll('.list-item').length"
$B attrs "#logo"
$B css ".button" "background-color"
Snapshot System
The snapshot is your primary tool for understanding and interacting with pages.
-i --interactive Interactive elements only (buttons, links, inputs) with @e refs
-c --compact Compact (no empty structural nodes)
-d <N> --depth Limit tree depth (0 = root only, default: unlimited)
-s <sel> --selector Scope to CSS selector
-D --diff Unified diff against previous snapshot (first call stores baseline)
-a --annotate Annotated screenshot with red overlay boxes and ref labels
-o <path> --output Output path for annotated screenshot (default: /tmp/browse-annotated.png)
-C --cursor-interactive Cursor-interactive elements (@c refs — divs with pointer, onclick)
All flags can be combined freely. -o only applies when -a is also used.
Example: $B snapshot -i -a -C -o /tmp/annotated.png
Ref numbering: @e refs are assigned sequentially (@e1, @e2, ...) in tree order.
@c refs from -C are numbered separately (@c1, @c2, ...).
After snapshot, use @refs as selectors in any command:
$B click @e3 $B fill @e4 "value" $B hover @e1
$B html @e2 $B css @e5 "color" $B attrs @e6
$B click @c1
Output format: indented accessibility tree with @ref IDs, one element per line.
@e1 [heading] "Welcome" [level=1]
@e2 [textbox] "Email"
@e3 [button] "Submit"
Refs are invalidated on navigation — run snapshot again after goto.
Command Reference
Navigation
| Command | Description |
|---|
back | History back |
forward | History forward |
goto <url> | Navigate to URL |
reload | Reload page |
url | Print current URL |
Reading
| Command | Description |
|---|
accessibility | Full ARIA tree |
forms | Form fields as JSON |
html [selector] | innerHTML of selector (throws if not found), or full page HTML if no selector given |
links | All links as "text → href" |
text | Cleaned page text |
Interaction
| Command | Description |
|---|
click <sel> | Click element |
cookie <name>=<value> | Set cookie on current page domain |
cookie-import <json> | Import cookies from JSON file |
cookie-import-browser [browser] [--domain d] | Import cookies from Comet, Chrome, Arc, Brave, or Edge (opens picker, or use --domain for direct import) |
dialog-accept [text] | Auto-accept next alert/confirm/prompt. Optional text is sent as the prompt response |
dialog-dismiss | Auto-dismiss next dialog |
fill <sel> <val> | Fill input |
header <name>:<value> | Set custom request header (colon-separated, sensitive values auto-redacted) |
hover <sel> | Hover element |
press <key> | Press key — Enter, Tab, Escape, ArrowUp/Down/Left/Right, Backspace, Delete, Home, End, PageUp, PageDown, or modifiers like Shift+Enter |
scroll [sel] | Scroll element into view, or scroll to page bottom if no selector |
select <sel> <val> | Select dropdown option by value, label, or visible text |
type <text> | Type into focused element |
upload <sel> <file> [file2...] | Upload file(s) |
useragent <string> | Set user agent |
viewport <WxH> | Set viewport size |
| `wait <sel | --networkidle |
Inspection
| Command | Description |
|---|
| `attrs <sel | @ref>` |
| `console [--clear | --errors]` |
cookies | All cookies as JSON |
css <sel> <prop> | Computed CSS value |
dialog [--clear] | Dialog messages |
eval <file> | Run JavaScript from file and return result as string (path must be under /tmp or cwd) |
is <prop> <sel> | State check (visible/hidden/enabled/disabled/checked/editable/focused) |
js <expr> | Run JavaScript expression and return result as string |
network [--clear] | Network requests |
perf | Page load timings |
storage [set k v] | Read all localStorage + sessionStorage as JSON, or set to write localStorage |
Visual
| Command | Description |
|---|
diff <url1> <url2> | Text diff between pages |
pdf [path] | Save as PDF |
responsive [prefix] | Screenshots at mobile (375x812), tablet (768x1024), desktop (1280x720). Saves as {prefix}-mobile.png etc. |
screenshot [path] | Save screenshot |
Snapshot
| Command | Description |
|---|
snapshot [flags] | Accessibility tree with @e refs for element selection. Flags: -i interactive only, -c compact, -d N depth limit, -s sel scope, -D diff vs previous, -a annotated screenshot, -o path output, -C cursor-interactive @c refs |
Meta
| Command | Description |
|---|
chain | Run commands from JSON stdin. Format: [["cmd","arg1",...],...] |
Tabs
| Command | Description |
|---|
closetab [id] | Close tab |
newtab [url] | Open new tab |
tab <id> | Switch to tab |
tabs | List open tabs |
Server
| Command | Description |
|---|
restart | Restart server |
status | Health check |
stop | Shutdown server |
Tips
- Navigate once, query many times.
goto loads the page; then text, js, screenshot all hit the loaded page instantly.
- Use
snapshot -i first. See all interactive elements, then click/fill by ref. No CSS selector guessing.
- Use
snapshot -D to verify. Baseline → action → diff. See exactly what changed.
- Use
is for assertions. is visible .modal is faster and more reliable than parsing page text.
- Use
snapshot -a for evidence. Annotated screenshots are great for bug reports.
- Use
snapshot -C for tricky UIs. Finds clickable divs that the accessibility tree misses.
- Check
console after actions. Catch JS errors that don't surface visually.
- Use
chain for long flows. Single command, no per-step CLI overhead.