Help us improve
Share bugs, ideas, or general feedback.
From ac
Flutter QA workflow patterns and flutter-skill MCP tool routing for Claude Code agents. Reference for /ac:flutter-qa command.
npx claudepluginhub anilcancakir/claude-code-plugin --plugin acHow this skill is triggered — by the user, by Claude, or both
Slash command
/ac:flutter-qaThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Flutter app QA testing via flutter-skill MCP server. This skill provides workflow patterns, token efficiency strategies, and self-healing patterns used by the `/ac:flutter-qa` command. Report format lives in `references/report-format.md`. Shared cross-backend patterns (knowledge system, test modes, parallel execution, evidence persistence) live in the plugin-level `../../references/qa-patterns....
Drives running Flutter apps: tap/scroll/type widgets via semantic snapshots, fill forms, hot-reload code, navigate routes. Use for UI interactions and iteration.
Automates mobile app testing on iOS and Android using Maestro MCP: launches apps, interacts with UI elements, captures screenshots, runs flows, collects evidence. Use to verify implementations before completion.
Guides Flutter development for cross-platform iOS, Android, Web apps. Covers non-interactive CLI, project setup, state management, widgets, GoRouter navigation, platform channels, testing, CI/CD.
Share bugs, ideas, or general feedback.
Flutter app QA testing via flutter-skill MCP server. This skill provides workflow patterns, token efficiency strategies, and self-healing patterns used by the /ac:flutter-qa command. Report format lives in references/report-format.md. Shared cross-backend patterns (knowledge system, test modes, parallel execution, evidence persistence) live in the plugin-level ../../references/qa-patterns.md — read it for conventions shared with browser-qa and maestro-qa. This file distills orchestration knowledge and MCP-specific patterns.
npm install -g flutter-skill.mcp.json:{
"flutter-skill": {
"command": "flutter-skill",
"args": ["server"]
}
}
mcp__flutter_skill__scan_and_connect discovers running Flutter apps on the local machine and connects to the first available one. Preferred for most workflowsmcp__flutter_skill__launch_app to start the app with --dart-define flags as neededmcp__flutter_skill__connect_app with explicit observatory/VM service URI when targeting a specific instancemcp__flutter_skill__list_sessions — returns connected app instances with session IDsmcp__flutter_skill__disconnect — cleanly detach from app. Always disconnect at end of test runmcp__flutter_skill__get_connection_status — verify connection is alive before resuming after long waitsmcp__flutter_skill__hot_reload pushes code changes without losing state. Use after minor code fixes during test iterationmcp__flutter_skill__hot_restart restarts app preserving connection but resetting state. Use between test cases for clean statemcp__flutter_skill__reset_app full app reset including navigation stack. Use when hot restart is insufficientmcp__flutter_skill__snapshot returns a compact JSON representation of the current widget tree — ~200 tokens for a typical screen. This is the primary inspection tool.
mcp__flutter_skill__screenshot captures a PNG screenshot — ~4000 tokens when read into context. Use only as FAIL evidence, never for routine inspection.
Rule: Always prefer snapshot over screenshot. The 20x token difference compounds across test cases.
Priority order for interaction tools:
key: — ValueKey / widget key (most stable, preferred)text: — visible text content (fuzzy matching via smart_* tools)type: — widget type name (e.g., ElevatedButton, TextField)semantic: — semantic label from Semantics widgetCombined targeting: mcp__flutter_skill__tap(key: "login_button") or mcp__flutter_skill__tap(text: "Login", type: "ElevatedButton")
| Tool | Purpose | Key Params |
|---|---|---|
mcp__flutter_skill__scan_and_connect | Discover running Flutter apps and connect | none — auto-discovers |
mcp__flutter_skill__connect_app | Connect to specific app instance | uri (required, VM service URI) |
mcp__flutter_skill__launch_app | Launch Flutter app and connect | target (optional, entry point path), device (optional, device ID), args (optional, additional flutter run args) |
mcp__flutter_skill__disconnect | Disconnect from current app | none |
mcp__flutter_skill__list_sessions | List active connected sessions | none |
mcp__flutter_skill__stop_app | Stop the connected Flutter app | none |
mcp__flutter_skill__get_connection_status | Check if connection is alive | none |
| Tool | Purpose | Key Params |
|---|---|---|
mcp__flutter_skill__tap | Tap a widget | At least one of: key (string), text (string), type (string), semantic (string). Optional: index (int, disambiguate) |
mcp__flutter_skill__enter_text | Enter text into focused field | text (required) |
mcp__flutter_skill__swipe | Swipe gesture | direction (required: up/down/left/right), key or text or type (target widget) |
mcp__flutter_skill__scroll_to | Scroll until widget is visible | key or text or type (target), scrollable_key (optional, specific scrollable) |
mcp__flutter_skill__long_press | Long press a widget | Same targeting as tap |
mcp__flutter_skill__go_back | Pop current route (Navigator.pop) | none |
mcp__flutter_skill__double_tap | Double tap a widget | Same targeting as tap |
mcp__flutter_skill__drag | Drag from one widget to another | from_key or from_text (required), to_key or to_text (required) |
| Tool | Purpose | Key Params |
|---|---|---|
mcp__flutter_skill__snapshot | Get compact widget tree JSON (~200 tokens) | none — returns current screen state |
mcp__flutter_skill__inspect | Get detailed properties of a specific widget | key or text or type (target widget) |
mcp__flutter_skill__find_by_type | Find all widgets of a given type | type (required, e.g., TextField, ElevatedButton) |
mcp__flutter_skill__get_widget_tree | Get full widget tree (verbose) | depth (optional, max depth). Warning: can be very large — prefer snapshot |
mcp__flutter_skill__wait_for_element | Wait until widget appears | key or text or type (target), timeout (optional, ms) |
mcp__flutter_skill__wait_for_idle | Wait for animations/async to settle | timeout (optional, ms) |
| Tool | Purpose | Key Params |
|---|---|---|
mcp__flutter_skill__screenshot | Capture screen as PNG | path (optional, save location). ~4000 tokens — use only as FAIL evidence |
| Tool | Purpose | Key Params |
|---|---|---|
mcp__flutter_skill__assert_visible | Assert widget is visible on screen | key or text or type (target) |
mcp__flutter_skill__assert_text | Assert specific text exists | text (required), exact (optional, bool, default false) |
mcp__flutter_skill__assert_not_visible | Assert widget is NOT visible | key or text or type (target) |
mcp__flutter_skill__assert_batch | Run multiple assertions in one call | assertions (required, array of assertion objects: {type, key?, text?, exact?}) |
| Tool | Purpose | Key Params |
|---|---|---|
mcp__flutter_skill__smart_tap | Tap with fuzzy matching and auto-retry | Same targeting as tap — flutter-skill handles retry internally |
mcp__flutter_skill__smart_enter_text | Enter text with field discovery and retry | text (required), key or type (optional hint) — finds best matching field |
mcp__flutter_skill__smart_assert | Assert with fuzzy matching and tolerance | text or key (required), type (optional) — tolerates minor text differences |
| Tool | Purpose | Key Params |
|---|---|---|
mcp__flutter_skill__hot_reload | Push code changes, preserve state | none |
mcp__flutter_skill__hot_restart | Restart app, reset state | none |
mcp__flutter_skill__reset_app | Full reset including navigation stack | none |
| Tool | Purpose | Key Params |
|---|---|---|
mcp__flutter_skill__visual_baseline_save | Save current screen as baseline | name (required, baseline identifier), path (optional, save directory) |
mcp__flutter_skill__visual_baseline_compare | Compare current screen against saved baseline | name (required, baseline identifier), threshold (optional, diff tolerance 0.0-1.0) |
mcp__flutter_skill__scan_and_connect to discover and attach to running app. Fall back to mcp__flutter_skill__launch_app if none foundmcp__flutter_skill__snapshot returns compact widget tree JSON. Parse for target widgets only — never dump full tree into contextmcp__flutter_skill__tap, mcp__flutter_skill__enter_text, mcp__flutter_skill__go_back. Re-snapshot after each action (observe-act-observe)mcp__flutter_skill__assert_visible, mcp__flutter_skill__assert_text after interactions that change statemcp__flutter_skill__screenshot + mcp__flutter_skill__snapshot on every FAILmcp__flutter_skill__disconnect when doneWhen running in parallel mode, each agent uses its own session via separate scan_and_connect or launch_app targeting different devices. Sessions are fully isolated — no cross-agent state bleed.
mcp__flutter_skill__hot_restart (or mcp__flutter_skill__reset_app for deeper reset) per bug to avoid state pollutionmcp__flutter_skill__screenshot + mcp__flutter_skill__snapshot. Evidence is mandatory for FAIL verdictsKey: Isolation is paramount. Each bug gets a fresh hot_restart or reset_app cycle. If bug doc lacks structure, extract bugs by paragraph/section breaks.
When running in parallel mode, each agent connects to its own device instance. hot_restart per bug on that device.
Done when: blocks from plan file. Fall back to bulleted checklist itemsmcp__flutter_skill__scan_and_connect, mcp__flutter_skill__snapshot, interact by key/text, assertions via mcp__flutter_skill__assert_visible, mcp__flutter_skill__assert_text, mcp__flutter_skill__assert_batchmcp__flutter_skill__snapshot to confirm widget tree matches expectationsKey: Use mcp__flutter_skill__assert_batch for multi-assertion criteria. Clean state per test case via mcp__flutter_skill__hot_restart.
When running in parallel mode, each agent connects to its own device for all MCP calls in that agent's scope.
.ac/flutter-qa/{testName}.json (testName derived from original target) — stop if not foundmcp__flutter_skill__hot_restartKey: Output a diff table showing previous to current verdict changes.
When running in parallel mode, each agent uses its own device to keep re-run sessions isolated from any concurrently running agents.
This mode uses flutter test CLI via Bash — NOT MCP tools.
*_test.dart in target directory (typically test/)flutter test --machine <path> — outputs JSONL events to stdouttype field:
start — test run startedtestStart — individual test started (id, name, groupIDs)testDone — individual test completed (id, result: success/failure/error)error — test error details (testID, error, stackTrace)done — test run complete (success: bool, time: ms)Key: This is the only mode that does NOT use flutter-skill MCP. Run flutter test --machine for machine-parseable output. Use flutter test --machine --name=<pattern> to filter specific tests. For integration tests, use flutter test --machine integration_test/ with a running device.
mcp__flutter_skill__scan_and_connect to running appmcp__flutter_skill__wait_for_idle to ensure animations completemcp__flutter_skill__visual_baseline_save(name: "screen-name") saves current screen as golden referencemcp__flutter_skill__visual_baseline_compare(name: "screen-name", threshold: 0.05) compares current screen against saved baselinemcp__flutter_skill__screenshot as evidence alongside the diff reportmcp__flutter_skill__visual_baseline_save with same name to update the golden fileKey: Always wait_for_idle before saving or comparing — animation frames cause false positives. Start with a low threshold (0.01-0.05) and increase only if platform rendering differences cause false failures. Baseline files persist across runs — store in a project-local directory.
mcp__flutter_skill__snapshot returns ~200 tokens (compact JSON widget tree) vs mcp__flutter_skill__screenshot at ~4000 tokens (PNG). ~95% fewer tokens. Always snapshot first; screenshot only as FAIL evidencemcp__flutter_skill__get_widget_tree output to a file via Bash, then search it with Grep. Never dump verbose trees into contextmcp__flutter_skill__assert_batch replaces N individual assertion calls with 1 MCP call. Use for multi-criteria verificationmcp__flutter_skill__smart_tap, mcp__flutter_skill__smart_enter_text, mcp__flutter_skill__smart_assert handle fuzzy matching and retry internally. One tool call instead of 3-retry loops in agent logichot_restart between cases resets state without reconnection overhead. Only mcp__flutter_skill__disconnect when the full run is completeFlutter-skill provides built-in self-healing via smart_* tools. These handle fuzzy matching and retry internally — the agent receives a clean success/failure response without retry noise.
Use mcp__flutter_skill__smart_tap, mcp__flutter_skill__smart_enter_text, mcp__flutter_skill__smart_assert as default interaction tools. Flutter-skill handles:
When smart tools return failure (element genuinely not found):
mcp__flutter_skill__snapshot to get fresh widget treemcp__flutter_skill__tap (or other interaction tool) with updated targetingBLOCKED with note: "Element not found after 3 retries: [description]"Never fall back to coordinate-based taps — they are brittle and break across device sizes, orientations, and text scale settings. Key and text selectors are the only acceptable targeting strategies.
During a test run, capture non-obvious discoveries that would save the next agent time. Do not capture trivial facts (e.g., "app has a bottom navigation bar" — the next agent can see that). Only capture what required effort to discover.
Before executing any test cases, load existing project knowledge:
.ac/qa/knowledge/project.jsonl via Read tool. If the file doesn't exist, proceed with empty knowledge.PRIOR_KNOWLEDGE from the parent command (if provided). On same-key conflict, file-based knowledge wins.EFFECTIVE_KNOWLEDGE — use throughout execution for selector hints, timing guidance, widget tree structure awareness, and state management patterns.Project knowledge is cumulative across all test runs. A fact learned during ad-hoc testing of the login screen benefits a later plan-verify run that touches the same flows.
Write facts to disk immediately after each test case — do not wait until the end of the run.
Write pattern (via Bash — agent has no Write tool):
mkdir -p .ac/qa/knowledge/
echo '{"type":"selector","key":"login-btn","value":"key:login_button works, text:Login flaky on Android emulator","confidence":"high"}' >> .ac/qa/knowledge/.fqa-{SESSION_NAME}.jsonl
File naming: .fqa-{SESSION_NAME}.jsonl — each agent writes to its own temp file. Parent merges all temp files into project.jsonl after execution.
When to write:
high or medium confidenceWhy immediate writes matter:
/home, /profile/:id), navigator structure, drawer paths, back stack behavior{"type":"widget_tree","key":"settings-toggle","value":"DarkMode toggle is inside CustomScrollView > SliverList > SettingsTile, not directly in Column. Use scroll_to(key:'dark_mode_switch') before tap","confidence":"high"}
{"type":"widget_tree","key":"nested-form-fields","value":"Email field is inside Form > AnimatedContainer > Column > TextField(key:'email'). Container animates on focus — wait_for_idle before assert","confidence":"medium"}
{"type":"state","key":"onboarding-gate","value":"App shows onboarding on first launch. SharedPreferences 'onboarding_complete' must be true to skip. hot_restart resets in-memory state but NOT SharedPreferences — use reset_app for clean onboarding test","confidence":"high"}
{"type":"state","key":"auth-token-cache","value":"Login persists auth token in secure storage. hot_restart preserves it — user stays logged in. reset_app clears it. For logged-out test cases, always reset_app not hot_restart","confidence":"high"}
{"type": "selector|flow|timing|gotcha|permission|navigation|widget_tree|state", "key": "<kebab-case-id>", "value": "<what to remember>", "confidence": "high|medium"}
Anti-pattern: Do not log obvious/trivial facts. If the next agent would discover it in one snapshot, skip it. Only capture discoveries that required multiple attempts or non-obvious reasoning to reach.
${CLAUDE_PLUGIN_ROOT}/skills/flutter-qa/references/report-format.mdLoad report-format.md for the structured JSON schema used in .ac/flutter-qa/{testName}.json persistence.
| Anti-Pattern | Why Wrong | Do Instead |
|---|---|---|
Using screenshot when snapshot suffices | ~4000 tokens vs ~200 tokens per call — 20x waste | Use mcp__flutter_skill__snapshot; screenshots only as FAIL evidence |
| Coordinate-based taps | Brittle — break across device sizes, orientations, text scale | Use key:, text:, or type: selectors exclusively |
| Not disconnecting at end of run | Leaked connections consume device resources, block subsequent runs | Always mcp__flutter_skill__disconnect when done |
| Using all 253 flutter-skill tools in agent | Bloated tool list wastes context tokens and confuses routing | Only expose the ~30 core agent tools listed in this reference |
| Using flutter-skill MCP for unit tests | MCP tools test the running app UI — unit tests need flutter test CLI | Use flutter test --machine <path> via Bash for TEST_RUN mode |
visual_baseline_compare without prior baseline | Comparison fails with no reference — wastes a tool call | Always visual_baseline_save first, then visual_baseline_compare on subsequent runs |
Dumping full get_widget_tree into context | Verbose tree can be thousands of tokens on complex screens | Write to file via Bash, search with Grep. Use snapshot for routine inspection |
| Waiting until end to write knowledge | Facts lost on crash, parallel agents can't benefit mid-run | Write to .fqa-{SESSION_NAME}.jsonl after each test case |
hot_restart when reset_app needed | hot_restart preserves SharedPreferences and secure storage | Use reset_app when persistent storage must be cleared |