Replace brittle CSS selectors with stable semantic attributes.
Tests that survive every refactor — forever.
CSS selectors tie tests to DOM position, not element meaning. The moment a developer reorganises markup — tests fail. Not because the feature broke. Because a div moved.
div.container > form
> div:nth-child(2)
> input.form-control
[data-ai-id="login-email"]
Add data-ai-* attributes that describe what an element is — not where it sits. They survive every refactor, forever.
login-email, submit-btn — not DOM depthThree components. One seamless pipeline. Everything runs 100% locally — no cloud, no telemetry, no registration.
useAiSnapshot()useAiSnapshot() + AiSdkPluginAiSdkService<script> tag| Attribute | Values / Purpose |
|---|---|
| data-ai-id | Unique stable key — snapshot primary ID |
| data-ai-role | input · action · display · nav |
| data-ai-label | Human label (falls back to textContent) |
| data-ai-action | "submits login form" |
| data-ai-context | Parent form / section name |
| data-ai-required | "true" for required fields |
| data-ai-state | disabled · loading · active · empty |
Every element becomes a structured descriptor with stable selector, role, label, state, and source. Claude reads this to generate precise tests.
const snap = window.__aiSdk.getSnapshot();
// Returns:
{
url: "https://app.example/login",
elements: [
{
id: "login-email",
role: "input",
label: "Email Address",
context: "login-form",
required: true,
selector: "[data-ai-id='login-email']",
source: "manual"
}
],
meta: {
manualCount: 4,
autoCount: 2,
sdkVersion: "0.1.4"
}
}
Auto-tagging discovers elements on any page — even with zero data-ai-* attributes. Auto-discovered elements are marked source:"auto".
input, textarea, selectinputbutton, [type=submit]actiona[href]navh1 – h6displayFramework Adapters
useAiSnapshot
const { snapshot, refresh, isReady }
= useAiSnapshot({ autoTag: true });
composable + plugin
const { snapshot } = useAiSnapshot();
// OR: app.use(AiSdkPlugin)
AiSdkService
constructor(private aiSdk: AiSdkService) {}
ngOnInit() { this.snap = this.aiSdk.getSnapshot(); }
Claude orchestrates the full pipeline by chaining tools automatically. You never write test code.
A full dark-mode UI served at GET /. No cloud. No account. Start with one command.
node dist/index.js --port 3100
/healthliveness probe/snapshotcapture page/runrun scenario/runslist all runs/reportgenerate HTML/XMLThe reusable composite GitHub Action runs PhantomUI in any workflow — takes a URL and an API key, publishes JUnit results.
# .github/workflows/ui-tests.yml
- uses: ./.github/actions/run-tests
with:
url: https://staging.example.com
format: junit
anthropic-api-key:
${{ secrets.ANTHROPIC_API_KEY }}
hints: "test auth flow + dashboard"
# Automatically:
# ✓ Installs Playwright + Chromium
# ✓ Runs all test scenarios
# ✓ Publishes JUnit test panel
# ✓ Uploads HTML as artifact
# ✓ Fails build on test failure
npm install @phantomui/sdk
claude mcp add --transport stdio phantomui \
node /path/to/server/dist/index.js
"Test the login form at localhost:3000
and save an HTML report"
| ANTHROPIC_API_KEY | Required for Claude |
| LLM_PROVIDER | anthropic · ollama · openai-compatible |
| AI_MODEL | Override model (e.g. claude-opus-4-6) |
| RESULT_STORE_PATH | Default: ~/.phantomui/runs |
| HEADLESS | false = show browser window |
| WEBHOOK_URL | POST results after every run |
{
"mcpServers": {
"phantomui": {
"command": "node",
"args": ["/path/to/server/dist/index.js"]
}
}
}