Agent Browser
quran/quran.com-frontend-next
Automates browser interactions for web testing, form filling, screenshots, and data extraction.
Run QA tests using agent-browser and post results to the qabot dashboard.
$ npx skills add shapeshift/web --skill qabot -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install shapeshift/web qabot --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/qabot .claude/skills/qabot && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "qabot" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/qabot into .claude/skills/qabot/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qabot", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/shapeshift/web/tree/develop/.claude/skills/qabotType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add shapeshift/web --skill qabot -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install shapeshift/web qabot --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/qabot .agents/skills/qabot && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "qabot" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/qabot into .agents/skills/qabot/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qabot", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add shapeshift/web --skill qabot -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install shapeshift/web qabot --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/qabot .cursor/skills/qabot && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "qabot" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/qabot into .cursor/skills/qabot/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qabot", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/shapeshift/web.git --path .claude/skills/qabot--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add shapeshift/web --skill qabot -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install shapeshift/web qabot --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/qabot .gemini/skills/qabot && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "qabot" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/qabot into .gemini/skills/qabot/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qabot", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install shapeshift/web qabotInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add shapeshift/web --skill qabot -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/qabot .github/skills/qabot && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "qabot" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/qabot into .github/skills/qabot/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qabot", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add shapeshift/web --skill qabot -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install shapeshift/web qabot --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shapeshift/web.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/qabot .opencode/skills/qabot && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "qabot" agent skill from https://github.com/shapeshift/web/tree/develop/.claude/skills/qabot into .opencode/skills/qabot/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qabot", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
qabotRun QA tests using agent-browser and post results to the qabot dashboard.
Qabot is an agent skill from shapeshift/web. Run QA tests using agent-browser and post results to the qabot dashboard. Interactive mode helps craft fixtures. With a fixture provided (or for automated runs like clawdbot releases), executes tests and reports results. Use when user says "qa test", "run qabot", "/qabot", or when running automated QA.
Its SKILL.md is about 8.2k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Productivity & Automation, covering Browser automation. The licence is MIT.
9 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 45096d2. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
BashReadWriteEditGlobGrepWebFetchAskUserQuestionFrom allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
curlgitpython3jqFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
release.shapeshift.comgithub.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
QABOT_API_KEYNATIVE_WALLET_PASSWORDFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Qabot loads about 8.2k tokens when it runs. Until then it costs about 77 tokens; SKILL.md has 2,714 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
Vite precedence: `.env.production` overrides `.env` (base). Check both files:# One-liner: merge .env + .env.production (later overrides), extract enabled chain flagsENABLED_FLAGS=$(cat "$WEB_REPO/.env" "$WEB_REPO/.env.production" 2>/dev/null | \Note: `.env.production` can explicitly disable chains that `.env` enables (e.g. `FLOWEVM=false`).allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestionAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from shapeshift/web at commit 45096d2, republished under its MIT licence (© shapeshift). 2,714 words, ~8,178 tokens.
.claude/skills/qabot/SKILL.md (or your agent's skills folder).You are running QA tests and reporting results to the qabot dashboard. qabot is a platform for QA reports - operators (humans with Claude, or clawdbot for automated runs) authenticate via a shared API key and push test results.
Requires agent-browser >= 0.20.0 (native Rust daemon, no more Node.js/Playwright).
Secrets are stored in ~/.secrets (sourced by ~/.zshrc). Required env vars:
QABOT_API_KEY - shared API key for write access to qabotQABOT_OPERATOR - your operator name (e.g. "gomes", "clawdbot") - labels who ran whatNATIVE_WALLET_PASSWORD - native wallet password for agent-browser wallet unlockQABOT_KEYSTORE_PATH - absolute path to a keystore JSON file used for keystore-based wallet import (e.g. ~/Desktop/thorswap-keystore-*.txt)# Verify env is set - all four MUST be present
echo "QABOT_API_KEY: ${QABOT_API_KEY:+set}"
echo "QABOT_OPERATOR: ${QABOT_OPERATOR:+set}"
echo "NATIVE_WALLET_PASSWORD: ${NATIVE_WALLET_PASSWORD:+set}"
echo "QABOT_KEYSTORE_PATH: ${QABOT_KEYSTORE_PATH:+set}"If any of these are missing, tell the user to add them to ~/.secrets. The API key is shared among trusted operators.
localhost:3000 (or $PORTLESS_URL if using Portless, e.g. http://<branch>.web.localhost:1355)localhost:8080 (dev) or deployed URLPortless note: If the dev server is running via Portless, PORTLESS_URL is set automatically (e.g. http://feat-x.web.localhost:1355). The qabot profile stores wallet state per-origin, so each Portless origin is separate from localhost:3000 - import the wallet once per new origin.
When the user says something like "qa test the trade page" or "run qabot" without a specific fixture:
e2e/fixtures/<name>.yamlWhen a specific fixture file is provided, or running as part of an automated flow (clawdbot release, CI trigger):
e2e/fixtures/depends_on, load those fixtures first (recursively)name: Test Name
description: What this tests
route: /trade
depends_on:
- wallet-health.yaml # runs this fixture first, wallet unlock etc.
steps:
- name: Step name
instruction: Natural language instruction for agent-browser
expected: What should be true after this step
screenshot: true # optional, take screenshot after stepFixtures can declare depends_on - a list of other fixture filenames that must run first.
fixtureFile for the run is the top-level fixture nameExample: eth-to-fox-swap.yaml depends on wallet-health.yaml. When you run eth-to-fox-swap:
On first visit to any origin (yeet.shapeshift.com, release.shapeshift.com, etc.), ShapeShift shows an onboarding splash dialog ("Self-Custody", "You own your keys") with "Skip" and "Next" buttons. The wallet-health fixture handles dismissing this. Always run wallet-health as a dependency for other fixtures.
IMPORTANT: Always use the qabot profile. The native wallet is stored in this profile's IndexedDB per-origin.
Use a shell-scoped command alias at session start to reduce command noise:
AB='agent-browser --session qabot --profile ~/.agent-browser/profiles/qabot'Then use $AB for all commands in that shell session:
$AB open <url>
$AB snapshot
$AB click "Connect Wallet"
$AB screenshot /tmp/step-0.pngWhen you need a headed run, append --headed only for that command:
$AB --headed open <url>agent-browser --session qabot --profile ~/.agent-browser/profiles/qabot open <url>The profile at ~/.agent-browser/profiles/qabot stores the native wallet (IndexedDB, localStorage, cookies) per origin. Import the wallet once per origin, then reuse.
First-time setup per origin (keystore import - works headless):
agent-browser --session qabot --profile ~/.agent-browser/profiles/qabot open <url>Only use --headed if the user explicitly wants to import via seed phrase (which requires visual interaction). Keystore import is fully automatable headless.
Keystore import flow (when no native wallet exists for the origin):
The keystore file path comes from $QABOT_KEYSTORE_PATH. If unset, ask the operator to export a keystore from their wallet and point that env var at it.
eval "document.querySelectorAll('button').forEach(b => { if(b.textContent.includes('Connect Wallet')) b.click() })"upload "input[type=file]" "$QABOT_KEYSTORE_PATH"fill "input[placeholder*=Password]" "$NATIVE_WALLET_PASSWORD"fill "input[placeholder*=nickname]" "test"fill "input[placeholder*='Enter Password']" "$NATIVE_WALLET_PASSWORD"fill "input[placeholder*='Confirm Password']" "$NATIVE_WALLET_PASSWORD"Origins where the wallet has been imported:
http://localhost:3000 (local dev, legacy)http://<branch>.web.localhost:1355 (local dev via Portless - origin varies per branch, e.g. develop.web.localhost:1355)https://release.shapeshift.com (release staging)The native wallet requires a password on each session start. The wallet-health fixture handles this. If running without wallet-health, handle it manually:
eval "document.querySelector('input[type=password], input[placeholder*=Password]')?.focus()"keyboard type (preferred) or press char-by-char (NOT fill - React controlled inputs need keypress events):# PREFERRED: keyboard type command
agent-browser --session qabot keyboard type "$NATIVE_WALLET_PASSWORD"
# LEGACY: press char-by-chareval "$(cat /tmp/click-next.js)"When using qabot for PR review validation on localhost:
Testing steps verbatim before adding extra assertions.agent-browser in the same live session:Send button).data-testid at the failing UI control." " ' ') which cause SyntaxError: Invalid or unexpected token in agent-browser eval.--stdin or --base64 to avoid smart quote issues entirely (no temp files needed):# PREFERRED: pipe JS via stdin (no temp files needed)
echo 'document.querySelectorAll("button").forEach(b => { if(b.textContent.includes("Skip")) b.click() })' | agent-browser --session qabot eval --stdin
# ALTERNATIVE: base64 encode to avoid all escaping issues
agent-browser --session qabot eval --base64 $(echo -n 'your JS code' | base64)
# LEGACY (still works): write to temp file
printf 'code here' > /tmp/click.js
agent-browser --session qabot eval "$(cat /tmp/click.js)"--stdin): Before executing any steps, create these reusable JS files in /tmp/. This avoids smart quote issues and speeds up execution:# Write all click helpers upfront
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.trim()==="Close"){btns[i].click();}}' > /tmp/click-close.js
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.trim()==="Maybe Later"){btns[i].click();}}' > /tmp/click-later.js
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.includes("Switch Assets")){btns[i].click();break;}}' > /tmp/click-switch.js
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.includes("Sign")&&btns[i].textContent.includes("Swap")){btns[i].click();break;}}' > /tmp/click-sign.js
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.trim()==="Confirm and Trade"){btns[i].click();break;}}' > /tmp/click-confirm.js
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.trim()==="I understand"){btns[i].click();break;}}' > /tmp/click-understand.js
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.trim()==="Preview Trade"){btns[i].click();break;}}' > /tmp/click-preview.js
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.trim()==="Got It"){btns[i].click();break;}}' > /tmp/click-gotit.js
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.trim()==="Skip"){btns[i].click();break;}}' > /tmp/click-skip.js
printf 'var btns=document.querySelectorAll("button"); for(var i=0;i<btns.length;i++){if(btns[i].textContent.trim()==="Next"){btns[i].click();break;}}' > /tmp/click-next.jsagent-browser --session qabot eval "$(cat /tmp/click-close.js)"# PREFERRED: interactive elements only (clean, flat list)
agent-browser --session qabot snapshot -i
# Full accessibility tree (verbose, for debugging layout)
agent-browser --session qabot snapshot
# Scope to specific element subtree
agent-browser --session qabot snapshot --selector "main"
# Include cursor-interactive elements (onclick, pointer)
agent-browser --session qabot snapshot -CUse wait commands instead of arbitrary sleep calls where possible:
# Network idle detection (page fully loaded):
agent-browser --session qabot wait --load networkidle
# Wait for specific text to appear:
agent-browser --session qabot wait --text "Preview Trade"
# Wait for text to disappear (loading states):
agent-browser --session qabot wait --fn "!document.body.innerText.includes('Loading...')"
# Wait for element to appear:
agent-browser --session qabot wait "[data-testid=trade-form]"Note: still use sleep 8-10 for wallet unlock since app hydration isn't detectable via wait.
Built-in network request capture (no more manual fetch interceptors):
# Capture network requests
agent-browser --session qabot network requests
agent-browser --session qabot network requests --filter "chaindefuser"
# Clear captured requests
agent-browser --session qabot network requests --clear
# Route/mock requests
agent-browser --session qabot network route "**/api/quote" --body '{"error":"test"}'
agent-browser --session qabot network unrouteClipboard read/write/copy/paste is available via agent-browser --session qabot clipboard <read|write|copy|paste>.
click --ref and click --text frequently time out on release.shapeshift.com (elements blocked by overlays or slow hydration). Always prefer JS eval for clicking on external origins..click() vs dispatchEvent: Some buttons on external origins don't respond to .click() (e.g. asset picker avatars). Use dispatchEvent(new MouseEvent("click",{bubbles:true,cancelable:true})) as a more reliable fallback. Example for asset avatar buttons:var btn=document.querySelector("button[class*=avatar]"); if(btn) btn.dispatchEvent(new MouseEvent("click",{bubbles:true,cancelable:true}));/tmp/step-N-name.png, NOT relative paths. Relative paths cause resolution issues between agent-browser's cwd and the shell's cwd. curl -F file@... will fail with exit code 26 if the path is wrong.agent-browser --session qabot screenshot "/tmp/step-0-dismiss-onboarding.png"/tmp/ only as a temp step before uploading to Vercel Blob. After uploading, rm the local file. Do NOT accumulate local screenshots.step-complete endpoint handles screenshot upload server-side. After a successful curl (HTTP 201), delete the local file with rm -f.--annotate to overlay numbered labels on interactive elements - great for qabot reports where reviewers need to see what was clickable:agent-browser --session qabot screenshot --annotate /tmp/step-0.png
# Prints a legend mapping numbers to element refsagentThought and actionTaken fields in results should read like a QA engineer's notes, NOT implementation details. Write "Focused password input, typed password" not "JS eval to focus input, press chars one by one". Describe what happened from a user's perspective, not the automation method used.-F field values get shell-expanded (e.g. $0.10 becomes /bin/zsh.10). Always use single quotes for -F values containing dollar signs: -F 'agentThought=Entered 10 cents'. Or avoid dollar signs entirely - write "10 cents" or "0.10 USD" instead of "$0.10".agent-browser --session qabot eval "$(cat /tmp/click-preview.js)"
sleep 2
agent-browser --session qabot eval "$(cat /tmp/click-understand.js)"
sleep 3
# Then verify Confirm Details screen appeared via snapshotwait --text for swap flow: Replace polling patterns with deterministic waits where possible:# Wait for quote to load
agent-browser --session qabot wait --text "Preview Trade"
# Wait for swap completion
agent-browser --session qabot wait --text "Complete" --timeout 120000When you encounter what looks like a bug, don't just report it — investigate it:
yieldId, accountId). A "discrepancy" between two different yields isn't a bug.agent-browser --session qabot network requests (or --filter for specific APIs) to see what the API actually returned vs what the UI shows. Include the raw API response in your agentThought.~/Sites/shapeshiftWeb. grep for the relevant component, selector, or API call. Understand WHERE the bug likely originates (frontend rendering? stale cache? API response?).$VAR as command doesn't work in zsh. ! negation in inline scripts causes "command not found: !". Use grep -v or numeric comparison instead. macOS date doesn't support %3N for milliseconds - use python3 -c 'import time; print(int(time.time()*1000))'. status is a read-only variable in zsh - use result_status instead.snapshot after every action to verify stateagent-browser --session qabot closesource ~/.secrets
QABOT="${QABOT_URL:-http://localhost:8080}"
# PORTLESS_URL is set automatically by Portless (e.g. http://develop.web.localhost:1355)
BASE_URL="${PORTLESS_URL:-${BASE_URL:-http://localhost:3000}}"All write requests use:
Authorization: Bearer $QABOT_API_KEYX-Qabot-Operator: $QABOT_OPERATORSome fixtures (e.g. send-receive.yaml) test multiple chains. Before executing, detect
which chains are actually enabled in the target environment. Use read-only operations only.
First-class chains (always enabled, no feature flag): Ethereum, Bitcoin, Bitcoin Cash, Dogecoin, Litecoin, Cosmos Hub, THORChain, Avalanche
Feature-flagged chains need VITE_FEATURE_<FLAG>=true in the effective env config.
Vite precedence: .env.production overrides .env (base). Check both files:
# WEB_REPO should already be set from section 4 (branch detection).
# If not, detect it from the port 3000 process or set it manually.
# One-liner: merge .env + .env.production (later overrides), extract enabled chain flags
ENABLED_FLAGS=$(cat "$WEB_REPO/.env" "$WEB_REPO/.env.production" 2>/dev/null | \
grep '^VITE_FEATURE_' | \
awk -F= '{flags[$1]=$2} END{for(f in flags) if(flags[f]=="true") print f}' | \
sed 's/VITE_FEATURE_//' | sort)
# $ENABLED_FLAGS now contains flag names like: ARBITRUM, BASE, BNBSMARTCHAIN, ...
# Cross-reference with the fixture's chain list to determine which chains to test.Flag name → chain mapping (from src/config.ts and src/constants/chains.ts):
OPTIMISM, BNBSMARTCHAIN, POLYGON, GNOSIS, ARBITRUM, SOLANA, STARKNET, TRON, SUI, NEAR,
TON, BASE, MONAD, HYPEREVM, PLASMA, MANTLE, INK, MEGAETH, BERACHAIN, CRONOS, KATANA,
FLOWEVM, CELO, PLUME, STORY, ZK_SYNC_ERA, BLAST, ETHEREAL, WORLDCHAIN, HEMI, SEI,
LINEA, SCROLL, SONIC, UNICHAIN, BOB, MODE, SONEIUM, MAYACHAIN, ZCASH
Note: .env.production can explicitly disable chains that .env enables (e.g. FLOWEVM=false).
# Read the fixture YAML
# If depends_on is present, load each dependency recursively
# Deduplicate (each fixture runs once even if referenced multiple times)
# Build ordered list: [dep1_steps, dep2_steps, ..., main_fixture_steps]
# Step indices are continuous: 0, 1, 2, ... across all fixturesBranch and commit must reflect the web app being tested, NOT the qabot repo. Use read-only git operations only (fetch, rev-parse) - NEVER switch branches.
GITHUB_REPO="shapeshift/web"
# Origin-to-branch mapping (CloudFlare Pages deployments):
# localhost:3000 → local branch (detected from dev server process)
# *.web.localhost:1355 → local branch (Portless, detected from dev process)
# release.shapeshift.com → release
# develop.shapeshift.com → develop
# app.shapeshift.com → main
if [[ "$BASE_URL" == *"localhost"* ]]; then
# Local dev: detect web repo from the process serving the dev server
# Try Portless proxy (1355) first, then direct port (3000), then any vite process
DEV_PID=$(lsof -i :1355 -sTCP:LISTEN -n -P -t 2>/dev/null | head -1)
if [ -z "$DEV_PID" ]; then
DEV_PID=$(lsof -i :3000 -sTCP:LISTEN -n -P -t 2>/dev/null | head -1)
fi
if [ -z "$DEV_PID" ]; then
DEV_PID=$(pgrep -f "vite.*--port" 2>/dev/null | head -1)
fi
if [ -n "$DEV_PID" ]; then
WEB_REPO=$(lsof -p "$DEV_PID" 2>/dev/null | awk '/cwd/{print $NF}')
fi
if [ -z "$WEB_REPO" ]; then
echo "ERROR: Could not detect web repo. Set WEB_REPO env var." >&2
exit 1
fi
BRANCH=$(git -C "$WEB_REPO" rev-parse --abbrev-ref HEAD)
COMMIT=$(git -C "$WEB_REPO" rev-parse HEAD)
else
# Remote origin: infer WEB_REPO from context for git fetch
# (any local clone of shapeshift/web works - agent should find it)
# Remote origin: map URL to branch, fetch latest upstream commit
case "$BASE_URL" in
*release.*) BRANCH="release" ;;
*develop.*) BRANCH="develop" ;;
*) BRANCH="main" ;; # app.shapeshift.com or unknown
esac
git -C "$WEB_REPO" fetch origin "$BRANCH" --quiet 2>/dev/null
COMMIT=$(git -C "$WEB_REPO" rev-parse "origin/$BRANCH" 2>/dev/null || echo "unknown")
fi
COMMIT_SHORT="${COMMIT:0:7}"
BRANCH_URL="https://github.com/$GITHUB_REPO/tree/$BRANCH"
COMMIT_URL="https://github.com/$GITHUB_REPO/commit/$COMMIT"The dashboard auto-generates GitHub permalinks from prBranch and commitSha:
https://github.com/shapeshift/web/tree/<branch>https://github.com/shapeshift/web/commit/<sha>IMPORTANT: Always pass the full (not short) commit SHA so the dashboard permalink works.
RUN_ID=$(curl -s -X POST "$QABOT/api/runs" \
-H "Authorization: Bearer $QABOT_API_KEY" \
-H "X-Qabot-Operator: $QABOT_OPERATOR" \
-H "Content-Type: application/json" \
-d '{"triggerType":"manual","fixtureFile":"<top-level-fixture>.yaml","url":"'"$BASE_URL"'","prBranch":"'"$BRANCH"'","commitSha":"'"$COMMIT"'"}' \
| jq -r '.id')
# URL is a run-level arg, NOT per-fixture. Fixtures define a `route` (e.g. /trade).
# The full URL = $BASE_URL + fixture route.
# For local dev: BASE_URL=http://localhost:3000
# For staging: BASE_URL=https://release.shapeshift.com
#
# For PR runs, also add: prNumber, prTitle, triggerType: "pr"
# For release runs, add: releaseTag, triggerType: "release"
# For cron/clawdbot runs, use: triggerType: "cron"Before executing any steps, transition the run from pending to running:
curl -s -X PATCH "$QABOT/api/runs/$RUN_ID" \
-H "Authorization: Bearer $QABOT_API_KEY" -H "X-Qabot-Operator: $QABOT_OPERATOR" \
-H "Content-Type: application/json" \
-d '{"status":"running"}'Run lifecycle: pending (created) -> running (agent-browser starts) -> passed/failed (all steps done)
CRITICAL: Process each step individually. After each step: take a screenshot and push the result immediately via the batch endpoint. Do NOT batch all results at the end.
# Pre-write all click helpers to /tmp/ (see Tips > JS Eval section above)
# Record the run start time ONCE before the loop:
RUN_START_MS=$(python3 -c 'import time; print(int(time.time()*1000))')
For EACH step across all fixtures (index 0, 1, 2, ...):
1. Execute the step's instruction via agent-browser commands
2. Take a snapshot and evaluate the `expected` condition
3. Determine status: "passed" if expected state is visible, "failed" if not
4. Calculate ELAPSED time since run start (NOT per-step duration):
ELAPSED_MS=$(($(python3 -c 'import time; print(int(time.time()*1000))') - RUN_START_MS))
5. ALWAYS take a screenshot using ABSOLUTE path in /tmp/:
agent-browser --session qabot screenshot "/tmp/step-$INDEX-<step-name-slug>.png"
6. Push screenshot + result in ONE call via the batch endpoint:
curl -s -X POST "$QABOT/api/runs/$RUN_ID/step-complete" \
-H "Authorization: Bearer $QABOT_API_KEY" -H "X-Qabot-Operator: $QABOT_OPERATOR" \
-F "stepIndex=$INDEX" \
-F "name=<group> > <step name>" \
-F "status=<passed|failed>" \
-F "durationMs=$ELAPSED_MS" \
-F "agentThought=<what you observed - user-facing QA language>" \
-F "actionTaken=<what happened from user perspective>" \
-F "file=@/tmp/step-$INDEX-<slug>.png" \
-F "label=<step-name>"
The server uploads the screenshot to Vercel Blob, inserts the result,
and recalculates run counters - all in one request.
On success, delete the local file: rm -f "/tmp/step-$INDEX-<slug>.png"
If the step has no screenshot, omit the "file" and "label" fields.
7. For failed steps, also add: -F "errorMessage=<what went wrong>"
and optionally: -F "errorStack=<stack trace or agent-browser output>"
8. If step failed and it's critical, you may stop earlyIMPORTANT: durationMs for each step is the total elapsed wall-clock time since the run started, NOT the duration of that individual step. This captures agent thinking time between steps (which is significant). The dashboard shows these as cumulative timestamps so the last step's duration = total run duration.
This way the dashboard updates live as each step completes.
The dashboard groups steps into collapsible sections using > as the separator. Use this convention in ALL step names:
<Fixture Name> > <step name>Wallet Health > Dismiss onboardingWallet Health > Unlock wallet<Chain Name> > <step name>Ethereum > Navigate to asset pageBitcoin > Enter amount and confirm send<Fixture Name> > <step name>ETH to FOX Swap > Select sell assetMulti-level nesting is supported by chaining separators:
Send Receive > Ethereum > Navigate to asset page (3 levels)Steps without > render flat (no grouping) for backwards compatibility.
CRITICAL: Always use > (space-arrow-space), never : or - as group separators. The dashboard only recognizes >.
STATUS="passed" # or "failed" if any step failed
TOTAL_MS=$(($(python3 -c 'import time; print(int(time.time()*1000))') - RUN_START_MS))
curl -s -X PATCH "$QABOT/api/runs/$RUN_ID" \
-H "Authorization: Bearer $QABOT_API_KEY" -H "X-Qabot-Operator: $QABOT_OPERATOR" \
-H "Content-Type: application/json" \
-d '{"status":"'"$STATUS"'","completedAt":"'$(date -u +%Y-%m-%dT%H:%M:%S.000Z)'","durationMs":'"$TOTAL_MS"'}'curl -s -X POST "$QABOT/api/github/comment" \
-H "Authorization: Bearer $QABOT_API_KEY" -H "X-Qabot-Operator: $QABOT_OPERATOR" \
-H "Content-Type: application/json" \
-d '{"runId":"'"$RUN_ID"'"}'ls e2e/fixtures/*.yamlFor each step, capture:
This context shows up in the qabot dashboard and PR comments.
© shapeshift, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/qabot of shapeshift/web.
Open the folder on GitHubat commit 45096d2
Qabot next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Qabot this skillshapeshift/web | 206 | — | ~8.2k | Automated safety check: Notes | MIT | |
| Agent Browserquran/quran.com-frontend-next | 1.9k | 42 repos | ~3.3k | Automated safety check: Pass | None | |
| Dev-Browser CLI AutomationSawyerHood/dev-browser | 6.7k | 1 repos | ~455 | Automated safety check: Pass | MIT | |
| Agent Browsersuperagent-ai/grok-cli | 3.5k | 1 repos | ~633 | Automated safety check: Pass | MIT | |
| Browser Automationopenclaw/openclaw | 392k | — | ~2.9k | Automated safety check: Pass | MIT | |
| Camoufox CLIBin-Huang/camoufox-cli | 350 | 1 repos | ~4.5k | Automated safety check: Pass | MIT |
quran/quran.com-frontend-next
Automates browser interactions for web testing, form filling, screenshots, and data extraction.
SawyerHood/dev-browser
Browser automation with persistent named pages via the dev-browser CLI. Use when users ask to navigate websites, fill forms, take screenshots, extract web…
superagent-ai/grok-cli
Use the host-side agent-browser CLI for local browser smoke tests, screenshots, snapshots, and simple UI validation against forwarded localhost URLs.
openclaw/openclaw
A skill your agent uses when controlling web pages with the OpenClaw browser tool, especially multi-step flows, login checks, tab management, or recovery from stale refs/timeouts.
Bin-Huang/camoufox-cli
Anti-detect browser automation CLI & Skills for AI agents. An agent skill from Bin-Huang/camoufox-cli.
VibiumDev/vibium
Automate browsers with the Vibium CLI. An agent skill from VibiumDev/vibium.
shapeshift/web
Comprehensive React and Next.js performance optimization guide with 40+ rules for eliminating waterfalls, optimizing bundles, and improving rendering.
shapeshift/web
Run a quality benchmark of the /translate skill by selecting stratified test keys, capturing ground truth, translating, judging with sub-agents, and compiling a regression report.
shapeshift/web
Create a new qabot E2E test fixture interactively. An agent skill from shapeshift/web.
shapeshift/web
Translate new/changed English UI strings into all supported languages using a translate-review-refine pipeline.
shapeshift/web
Integrate a new blockchain as a second-class citizen in ShapeShift Web.
shapeshift/web
Integrate new DEX aggregators, swappers, or bridge protocols (like Bebop, Portals, Jupiter, 0x, 1inch, etc.) into ShapeShift Web.
Categories
Run QA tests using agent-browser and post results to the qabot dashboard. Qabot is an agent skill from shapeshift/web. Run QA tests using agent-browser and post results to the qabot dashboard.
Qabot fits situations like: user says qa test; running automated QA.
Run `npx skills add shapeshift/web --skill qabot -a claude-code`. Or copy the skill folder (.claude/skills/qabot in shapeshift/web) into .claude/skills/qabot in your project. Claude Code loads it when a task matches its description.
Run `npx skills add shapeshift/web --skill qabot -a codex`. Or copy the skill folder (.claude/skills/qabot in shapeshift/web) into .agents/skills/qabot in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add shapeshift/web --skill qabot -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/qabot, .gemini/skills/qabot, .github/skills/qabot and .opencode/skills/qabot in your project.
Going by SKILL.md and its folder, Qabot needs the command-line tools its instructions call (curl, git, python3 and jq) and credentials named QABOT_API_KEY and NATIVE_WALLET_PASSWORD. Our summary lists: Node.js; A credential in QABOT_API_KEY. Its frontmatter pre-approves these tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion.
SKILL.md names 2 domains. In commands or code: release.shapeshift.com and github.com; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file; pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Qabot is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 8.2k tokens (SKILL.md is roughly 33k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Qabot: Agent Browser (quran/quran.com-frontend-next, 1.9k stars), Dev-Browser CLI Automation (SawyerHood/dev-browser, 6.7k stars), Agent Browser (superagent-ai/grok-cli, 3.5k stars) and Browser Automation (openclaw/openclaw, 392k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
shapeshift (a GitHub organization) maintains it in shapeshift/web, which has 206 GitHub stars. The repository holds 7 skills in this directory. The repository was last updated on October 7, 2026.
Source: shapeshift/web on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.