The plugin drives a live Appium WebDriver session against a real device, reads the app's accessibility tree, and runs package-scoped defect predicates over it. Proven live on com.android.settings (Pixel 6) — a genuine undersized touch target, surfaced with source + screenshot evidence.
Ctrl/Cmd + wheel to zoom · drag to pan · double-click to fit · ⛶ opens full size
page_source + screenshots — but _detect only checked for crashes, error keywords, and foreground. It never analyzed the app. Adding a11y_defects.py and wiring it into _detect closed that drift: the plugin now surfaces real app-UI defects, not just crashes._detect() → error-keyword match, foreground check, stuck-screen hash. _inventory() was a stub returning []. The app's accessibility tree was captured to disk but nothing inspected it._detect() now calls _a11y_defects(page_source), running package-scoped, subtree-aware predicates over the live a11y tree. A flagged defect flows straight into the evidence bundle with its exact node + bounds.A system "isn't responding" / "has stopped" dialog is on top of the target app.
criticalA real control whose entire subtree has no text and no content-desc — TalkBack can't announce it.
mediumA clickable ImageView/ImageButton leaf with empty content-desc and no labelled child.
An on-screen control below 48 dp (Material / WCAG 2.5.8). The proven defect: a 24.5 dp Dismiss button.
lowPredicates skip any node whose package ≠ the target app — OS chrome (status bar, nav bar) is never flagged.
A clickable row whose child carries the label is not flagged — only leaf-or-fully-empty subtrees count as unlabeled.
Anonymous layout slivers (no resource-id, no label, generic *Layout) are structural, not controls — suppressed on A3 and A5.
A5 only counts nodes fully inside the viewport with non-zero area — edge-clipping list rows don't register.