--- name: qabot description: 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. allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion --- # qabot - QA Testing Skill 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. ## Environment **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 qabot - `QABOT_OPERATOR` - your operator name (e.g. "gomes", "clawdbot") - labels who ran what - `NATIVE_WALLET_PASSWORD` - native wallet password for agent-browser wallet unlock - `QABOT_KEYSTORE_PATH` - absolute path to a keystore JSON file used for keystore-based wallet import (e.g. `~/Desktop/thorswap-keystore-*.txt`) ```bash # 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. ## Ports - **Web dev server** (ShapeShift app): `localhost:3000` (or `$PORTLESS_URL` if using Portless, e.g. `http://.web.localhost:1355`) - **qabot API/dashboard**: `localhost:8080` (dev) or deployed URL **Portless 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. ## Modes ### Interactive Mode (no fixture provided) When the user says something like "qa test the trade page" or "run qabot" without a specific fixture: 1. **Ask what to test** - use AskUserQuestion to clarify what functionality to test 2. **Help craft a fixture** - build a YAML fixture collaboratively with the user 3. **Save the fixture** - write to `e2e/fixtures/.yaml` 4. **Execute the fixture** - run the tests via agent-browser 5. **Report results** - push to qabot ### Fixture Mode (fixture provided or automated) When a specific fixture file is provided, or running as part of an automated flow (clawdbot release, CI trigger): 1. **Load the fixture** - read the YAML file from `e2e/fixtures/` 2. **Resolve dependencies** - if the fixture has `depends_on`, load those fixtures first (recursively) 3. **Execute all steps** - run dependency fixtures first, then the main fixture, all in one browser session 4. **Capture results** - screenshots + pass/fail per step (step indices are continuous across all fixtures) 5. **Report to qabot** - create run, upload screenshots, push results, post PR comment if applicable ## Fixture Format ```yaml 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 step ``` ### Composability Fixtures can declare `depends_on` - a list of other fixture filenames that must run first. - Dependencies are resolved recursively and deduplicated (each fixture runs at most once) - All fixtures run sequentially in one browser session - the page state carries over - Step indices are continuous across all fixtures (dep fixture steps come first) - All steps from all fixtures go into a single qabot run - The `fixtureFile` for the run is the top-level fixture name Example: `eth-to-fox-swap.yaml` depends on `wallet-health.yaml`. When you run eth-to-fox-swap: 1. wallet-health runs first (7 steps: dismiss onboarding, unlock wallet, verify page) 2. eth-to-fox-swap steps run next (5 steps: select assets, enter amount, verify quote) 3. Total: 12 steps in one run, indices 0-11 ### Onboarding Dialog 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. ## agent-browser Session **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: ```bash AB='agent-browser --session qabot --profile ~/.agent-browser/profiles/qabot' ``` Then use `$AB` for all commands in that shell session: ```bash $AB open $AB snapshot $AB click "Connect Wallet" $AB screenshot /tmp/step-0.png ``` When you need a headed run, append `--headed` only for that command: ```bash $AB --headed open ``` ```bash agent-browser --session qabot --profile ~/.agent-browser/profiles/qabot open ``` 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): ```bash agent-browser --session qabot --profile ~/.agent-browser/profiles/qabot open ``` 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. 1. Click "Connect Wallet" button (use JS eval if click times out): `eval "document.querySelectorAll('button').forEach(b => { if(b.textContent.includes('Connect Wallet')) b.click() })"` 2. Click "Import existing" 3. Click "Keystore" (the "Import Keystore File" option) 4. Upload the keystore file to the hidden file input: `upload "input[type=file]" "$QABOT_KEYSTORE_PATH"` 5. Fill the keystore password: `fill "input[placeholder*=Password]" "$NATIVE_WALLET_PASSWORD"` 6. Click "Import Keystore" 7. On the "Create a New Password" screen: - Fill nickname: `fill "input[placeholder*=nickname]" "test"` - Fill password: `fill "input[placeholder*='Enter Password']" "$NATIVE_WALLET_PASSWORD"` - Fill confirm: `fill "input[placeholder*='Confirm Password']" "$NATIVE_WALLET_PASSWORD"` - Click "Next" 8. Skip onboarding carousel if shown 9. Wallet "test" should appear in top-right. Subsequent runs reuse the persisted profile. Origins where the wallet has been imported: - `http://localhost:3000` (local dev, legacy) - `http://.web.localhost:1355` (local dev via Portless - origin varies per branch, e.g. `develop.web.localhost:1355`) - `https://release.shapeshift.com` (release staging) ### Wallet Unlock The native wallet requires a password on each session start. The wallet-health fixture handles this. If running without wallet-health, handle it manually: 1. Check for onboarding dialog first - click "Skip" if present 2. The "Enter Your Password" dialog may or may not appear automatically depending on the page: - On some pages, the wallet modal opens automatically with the unlock prompt - On other pages (e.g. /trade), you'll see "Connect Wallet" in the top-right nav instead - If you see "Connect Wallet", click it, then **wait 5 seconds** for native wallets to load in the modal - If the "test" wallet appears in the wallet list on the left side of the modal, click it to get the password prompt - Note: the nav button may still say "Connect Wallet" even while the wallet is loading/connecting - this is normal 3. Click the wallet name button (e.g. "test", "teest") if wallet selection is shown 4. Focus the password input via JS eval (click --ref often times out on external origins): `eval "document.querySelector('input[type=password], input[placeholder*=Password]')?.focus()"` 5. Type the password - use `keyboard type` (preferred) or `press` char-by-char (NOT `fill` - React controlled inputs need keypress events): ```bash # PREFERRED: keyboard type command agent-browser --session qabot keyboard type "$NATIVE_WALLET_PASSWORD" # LEGACY: press char-by-char ``` 6. Click "Next" via JS eval: `eval "$(cat /tmp/click-next.js)"` 7. Wait 8+ seconds for external origins to fully hydrate ### PR Review Reliability Checklist (localhost) When using qabot for PR review validation on localhost: 1. Follow PR `Testing` steps verbatim before adding extra assertions. 2. Do a manual-first pass with `agent-browser` in the same live session: - reach exact page/state - confirm account/wallet assumptions - validate selectors and click-path before reporting 3. Keep wallet setup as preflight only (never as reported qabot steps), but ensure required preconditions are visible before step 1 (e.g. `Send` button). 4. Only after manual flow is stable, create/report qabot run steps. 5. If automation friction is selector-related, add/ask for precise `data-testid` at the failing UI control. ### Tips #### JS Eval & Smart Quotes (CRITICAL) - **Smart quotes kill JS eval**: Claude's output produces unicode smart quotes (`"` `"` `'` `'`) which cause `SyntaxError: Invalid or unexpected token` in agent-browser eval. - **PREFERRED: Use `--stdin` or `--base64`** to avoid smart quote issues entirely (no temp files needed): ```bash # 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)" ``` - **Pre-write common click helpers at session start** (can also be done with `--stdin`): Before executing any steps, create these reusable JS files in `/tmp/`. This avoids smart quote issues and speeds up execution: ```bash # Write all click helpers upfront printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-close.js printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-later.js printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-switch.js printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-sign.js printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-confirm.js printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-understand.js printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-preview.js printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-gotit.js printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-skip.js printf 'var btns=document.querySelectorAll("button"); for(var i=0;i /tmp/click-next.js ``` Then use them: `agent-browser --session qabot eval "$(cat /tmp/click-close.js)"` #### Snapshots ```bash # 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 -C ``` #### Wait Commands (prefer over sleep) Use `wait` commands instead of arbitrary `sleep` calls where possible: ```bash # 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. #### Network Request Capture Built-in network request capture (no more manual fetch interceptors): ```bash # 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 unroute ``` #### Clipboard Clipboard read/write/copy/paste is available via `agent-browser --session qabot clipboard `. #### Clicking on External Origins - **Clicking on external origins**: `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: ```js var btn=document.querySelector("button[class*=avatar]"); if(btn) btn.dispatchEvent(new MouseEvent("click",{bubbles:true,cancelable:true})); ``` - **Cookie/tracking banner**: On external origins, a cookie banner ("Our dApp uses anonymized click tracking...") appears with a "Got It" button. **Dismiss this immediately after page load** - it can block interactions. The wallet-health fixture should handle this. #### Asset Picker - **Asset picker multi-chain**: Assets like FOX exist on multiple chains. Clicking the asset button once expands to show chain variants - click the specific chain variant (e.g. "Ethereum (FOX)") from the expanded list. Primary assets like SOL, BTC, RUNE don't need expansion. - **Asset picker interaction**: Open sell asset picker by JS-clicking the sell asset avatar button. Search by focusing the search input via JS eval then pressing characters. Select results by JS-clicking the matching button. - **Switch Assets is unreliable after swaps**: After completing a swap, the "Switch Assets" button may not reverse assets. Always verify via snapshot after clicking. If it didn't work, fall back to manually selecting assets via the pickers. #### Screenshots - **Always use absolute paths for screenshots**: Use `/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. ```bash agent-browser --session qabot screenshot "/tmp/step-0-dismiss-onboarding.png" ``` - **Screenshots are temporary**: Screenshots are saved to `/tmp/` only as a temp step before uploading to Vercel Blob. After uploading, `rm` the local file. Do NOT accumulate local screenshots. - **Delete after successful push**: The `step-complete` endpoint handles screenshot upload server-side. After a successful curl (HTTP 201), delete the local file with `rm -f`. - **Annotated screenshots**: Use `--annotate` to overlay numbered labels on interactive elements - great for qabot reports where reviewers need to see what was clickable: ```bash agent-browser --session qabot screenshot --annotate /tmp/step-0.png # Prints a legend mapping numbers to element refs ``` - **Screenshots timing**: Always take screenshots AFTER verifying the expected state via snapshot, not before. Early screenshots capture intermediate states. #### Agent Thought / Action Logging - **Agent thoughts must be user-facing**: `agentThought` 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. - **No developer jargon**: Never use terms like "React controlled input", "nativeInputValueSetter", "dispatchEvent", "HStack", "Chakra modal". Write like a human QA tester: "Entered amount in the input field", "Clicked the account button", "Toggled to dollar input mode". - **Shell expansion in curl fields**: Dollar signs in `-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". #### Swap Flow Gotchas - **Check balances before setting amounts**: Always snapshot and read the balance display before entering a swap amount. If the wallet balance is less than the intended amount + gas, reduce the amount. Example: SOL balance was $0.989, $1 + gas failed. Reduced to $0.50. - **Fiat toggle persists between swaps**: After the first swap, fiat mode may already be active. Always check the input placeholder before toggling - if it already shows "$0", skip the toggle. - **Warning dialogs during swap**: Two common warnings appear after clicking Preview Trade or Confirm and Trade: 1. "Below recommended minimum" - for small amounts. Click "I understand" to proceed. 2. "Price impact" - for high slippage (common with small THORChain trades). Click "I understand" to proceed. - **Preview Trade loading loop**: On release origins, clicking "Preview Trade" sometimes shows "Loading... Preview Trade" indefinitely then bounces back to enabled. The fix: click Preview Trade, then immediately (within 2-3s) check for and click "I understand" on the below-minimum warning. The warning appearing while loading causes the loop. Sequence: ```bash 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 snapshot ``` - **Use `wait --text` for swap flow**: Replace polling patterns with deterministic waits where possible: ```bash # 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 120000 ``` - **THORChain swap timing**: SOL->RUNE completes in ~10s, RUNE->SOL can take ~90s. Always poll with 120s timeout. - **Feedback dialog after swap**: A "How was your trade experience?" dialog appears after swaps complete. Dismiss with "Maybe Later" button. #### Bug Investigation (CRITICAL) When you encounter what looks like a bug, **don't just report it — investigate it**: 1. **Verify identity**: Is this the exact same yield ID, same account ID, same chain? Check the URL params (`yieldId`, `accountId`). A "discrepancy" between two different yields isn't a bug. 2. **Check network requests**: Use `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. 3. **Read the codebase**: You have access to `~/Sites/shapeshiftWeb`. `grep` for the relevant component, selector, or API call. Understand WHERE the bug likely originates (frontend rendering? stale cache? API response?). 4. **Cross-reference surfaces**: Check the same data across multiple views (yield detail page, My Positions list, DeFi drawer, wallet drawer). Note exactly which surfaces show correct vs incorrect data. 5. **Navigate freely**: You can explore the entire app to verify bugs — click around, check different pages, use filters. Just don't execute transactions outside fixture constraints. 6. **Write it up with confidence**: In agentThought, explain: what you expected, what you saw, what the API returned, what the code does, and your conclusion on where the bug is. Don't just say "possible bug" — say "confirmed bug in X component because Y". #### Shell & Environment - **zsh gotchas**: `$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. - Use `snapshot` after every action to verify state - Close the session when done: `agent-browser --session qabot close` - External origins (release) are slower than localhost - use longer waits (8-10s after wallet unlock) ## Execution Flow ### 1. Set up auth ```bash source ~/.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_KEY` - `X-Qabot-Operator: $QABOT_OPERATOR` ### 2. Detect enabled chains (for multi-chain fixtures) Some 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_=true` in the effective env config. Vite precedence: `.env.production` overrides `.env` (base). Check both files: ```bash # 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`). ### 3. Resolve fixture dependencies ```bash # 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 fixtures ``` ### 4. Detect branch and commit Branch 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. ```bash 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`: - Branch → `https://github.com/shapeshift/web/tree/` - Commit → `https://github.com/shapeshift/web/commit/` ### 5. Create a run **IMPORTANT**: Always pass the full (not short) commit SHA so the dashboard permalink works. ```bash 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":".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" ``` ### 6. Mark run as running Before executing any steps, transition the run from `pending` to `running`: ```bash 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) ### 7. Execute fixture steps (one at a time) **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. ```text # 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-.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= > " \ -F "status=" \ -F "durationMs=$ELAPSED_MS" \ -F "agentThought=" \ -F "actionTaken=" \ -F "file=@/tmp/step-$INDEX-.png" \ -F "label=" 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-.png" If the step has no screenshot, omit the "file" and "label" fields. 7. For failed steps, also add: -F "errorMessage=" and optionally: -F "errorStack=" 8. If step failed and it's critical, you may stop early ``` **IMPORTANT**: `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. #### Step Naming Convention (Grouping) The dashboard groups steps into collapsible sections using ` > ` as the separator. Use this convention in ALL step names: - **Dependency fixture steps**: ` > ` - Example: `Wallet Health > Dismiss onboarding` - Example: `Wallet Health > Unlock wallet` - **Template fixture steps** (multi-chain): ` > ` - Example: `Ethereum > Navigate to asset page` - Example: `Bitcoin > Enter amount and confirm send` - **Regular fixture steps**: ` > ` - Example: `ETH to FOX Swap > Select sell asset` Multi-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 ` > `. ### 8. Complete the run ```bash 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"'}' ``` ### 9. Post PR comment (if PR run) ```bash 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"'"}' ``` ## Available Fixtures ```bash ls e2e/fixtures/*.yaml ``` ## Agent Thought / Action Logging For each step, capture: - **agentThought**: Your reasoning about what you see and whether the expected condition is met - **actionTaken**: The actual agent-browser commands you ran - **errorMessage**: If the step failed, what went wrong - **errorStack**: Any error output from agent-browser This context shows up in the qabot dashboard and PR comments.