Workshop setup guide. For the maintained setup and testing workflow, use Sui Pilot.
← Contract Hero · WorkLearnPlugins

Sui dApp Test Automation

Mirroring the chrome-devtools-mcp + Slush wallet setup

Sui

One-time Setup

Three things that must exist before any session:

  • Chrome for Testing (separate binary)
  • Persistent wallet profile at ~/dev-chrome
  • MCP attach hook in ~/.claude/hooks/

Chrome for Testing

Download from googlechromelabs.github.io/chrome-for-testing. Place in /Applications/. Separate binary — never conflicts with regular Chrome.

~/dev-chrome wallet profile

Create once by launching with --user-data-dir=$HOME/dev-chrome. Install Slush (opcgpfmipidbgpenhmajoajpbobppdil) and MetaMask (nkbihfbeogaeaoehlefnkodbefgpgknn), unlock both. Profile persists on disk indefinitely.

patch-chrome-devtools-mcp.sh (SessionStart hook)

Injects --browser-url=http://127.0.0.1:9222 into the installed plugin's config every session — so MCP attaches to your Chrome instead of spawning a fresh wallet-less one. Register in ~/.claude/settings.json under hooks.SessionStart.

Sui

Launch Chrome on :9222

Kill any existing instance first (prevents profile-lock), then launch the single debuggable wallet Chrome:

pkill -f "Google Chrome for Testing" 2>/dev/null; sleep 1

"/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" \
  --remote-debugging-port=9222 \
  --user-data-dir="$HOME/dev-chrome" \
  > /dev/null 2>&1 &

sleep 2

--remote-debugging-port=9222 — exposes the CDP WebSocket endpoint that both chrome-devtools-mcp and scripts/cdp.py attach to.

--user-data-dir=$HOME/dev-chrome — loads your wallet profile (extensions, accounts, unlock state) instead of a throwaway profile.

Kill-first guarantees exactly one Chrome-for-Testing owns the profile and holds the debug port.

Sui

Two Ways to End Up Without a Wallet

PROFILE-LOCK TRAP

Chrome owns ~/dev-chrome but without :9222

A second launch can't lock the busy profile and silently falls back to a fresh, extension-less profile. The port comes up, MCP attaches, the connect modal is empty.

Detect: curl 127.0.0.1:9222/json | grep chrome-extension → no wallet ids

Fix: kill the no-port Chrome first, then relaunch.

ATTACH TRAP

MCP spawns its own Chrome instead of attaching

Without --browser-url, chrome-devtools-mcp ignores your :9222 instance and opens its own wallet-less Chrome. Two windows appear; list_pages succeeds but the dapp's wallet modal is always empty.

Detect: ps aux | grep "Google Chrome.app" | grep -v "for Testing" | grep "chrome-devtools-mcp"

Fix: run patch-chrome-devtools-mcp.sh, restart the MCP connection.

Sui

Verify Before You Automate

PORT CHECK

:9222 is up

curl -sS -m 1 \
  http://127.0.0.1:9222/json/version \
  >/dev/null && echo "up" || echo "down"

Necessary but not sufficient — a fallback profile also brings up 9222.

EXTENSION CHECK

Wallet extensions loaded

curl -s http://127.0.0.1:9222/json \
  | grep -oE 'chrome-extension://(nkbihfbeogaeaoehlefnkodbefgpgknn|opcgpfmipidbgpenhmajoajpbobppdil)' \
  | sort -u

At least one line for Slush or MetaMask must appear. Zero lines = profile-lock trap fired.

MCP ATTACH CHECK

MCP is attaching, not spawning

ps aux \
  | grep "Google Chrome.app" \
  | grep -v "for Testing" \
  | grep "chrome-devtools-mcp"

Any output = attach trap active. Run the patch hook and restart the MCP server connection (config changes don't hot-reload).

Sui

Two Worlds, Two Tools

DAPP PAGES — https://…

chrome-devtools-mcp

navigate_page · take_snapshot · click · evaluate_script

Standard MCP tools work on any http(s) page. Use them to click the connect button, drive dapp flows, and verify the post-login state.

SLUSH POPUPS — chrome-extension://…

scripts/cdp.py

targets · inspect · click · eval

Extension pages never appear in list_pages. cdp.py talks raw CDP over the :9222 WebSocket — the only way to reach approval popups. Bundled with the drive-slush-wallet skill; zero dependencies (Python stdlib only).

Sui

Driving Every Slush Approval

Discover

python3 scripts/cdp.py \
  targets opcgpfmi...

Find the popup by extension id. The URL hash tells you what's being asked: #approve-connection, #sign-personal-message, #approve-transaction.

Inspect

python3 scripts/cdp.py \
  inspect #approve-connection

Prints title, visible body, button labels, and hasPasswordField. Always read before clicking — never assume button names.

Click

python3 scripts/cdp.py \
  click <target> "Approve"

Matches by trimmed button text (case-insensitive). If no match, available labels are printed so you can retry. Popup closes on resolve.

Verify

Back on chrome-devtools-mcp — check the dapp reflects the new state via evaluate_script. A successful click is not proof; the dapp's updated UI is.

If the popup vanished before the click, it timed out — re-trigger from the dapp and repeat.

Sui

===================================================== SLIDE 1: Cover =====================================================

===================================================== SLIDE 2: Prerequisites & one-time setup =====================================================

===================================================== SLIDE 3: Launch command =====================================================

===================================================== SLIDE 4: The two silent failure modes =====================================================

===================================================== SLIDE 5: Verify before automating =====================================================

===================================================== SLIDE 6: Two worlds mental model =====================================================

===================================================== SLIDE 7: Core approval loop =====================================================