Phase 3 — Dynamic analysis (DroidBot)¶
The third phase compares the UI navigation graph of the BEFORE and AFTER APKs. It detects screens that exist in only one state and edges that appear or disappear, labelling them as regression, addition, or no-change.
Inputs¶
phase1/before/,phase1/after/— shadow copies from Phase 1.- A running Android emulator (locally or via
reactivecircus/android-emulator-runnerin CI).
Outputs¶
phase3/
├── before.utg/ # DroidBot UTG, BEFORE APK
├── after.utg/ # DroidBot UTG, AFTER APK
└── ui_regressions.json
What happens¶
- Assemble the debug APK on the BEFORE shadow copy with
./gradlew :<android-module>:assembleDebug. If this fails, mark Phase 3BLOCKED — APK assembly failed (BEFORE)and stop. - Same for AFTER.
- Install the BEFORE APK on the emulator. Run DroidBot for the configured budget — defaults
count = 100,timeout = 90,policy = dfs_greedy,-grant_perm,-is_emulator. Persist the UTG tophase3/before.utg/. - Uninstall the BEFORE APK, install AFTER. Repeat DroidBot. Persist to
phase3/after.utg/. - Diff the two UTGs at the state level. State identity is based on the activity name plus the set of clickable element IDs — DroidBot's default heuristic.
Output sample¶
{
"status": "completed",
"blocked_reason": "",
"before_screens": ["MainActivity", "DetailActivity"],
"after_screens": ["MainActivity", "DetailActivity"],
"diffs": [],
"activity_coverage_before": { "MainActivity": 18, "DetailActivity": 6 },
"activity_coverage_after": { "MainActivity": 17, "DetailActivity": 7 },
"nodes_before": 14, "nodes_after": 14,
"structures_before": 6, "structures_after": 6
}
On failure the same model carries a blocked status and an empty payload:
{
"status": "blocked",
"blocked_reason": "DroidBot produced no UTG artifact",
"before_screens": [],
"after_screens": [],
"diffs": []
}
Edge cases¶
- Build failure on BEFORE or AFTER. Most common cause is Kotlin 2.x without the Compose Compiler plugin. See L4.
- Android module not detected. The workflow probes the module names listed in Reference → GitHub Action. If none match, expose your real module under one of those aliases.
- Empty UTG. The merge step rejects empty UTG folders on purpose — an empty artifact would mask a real failure.
- Emulator unavailable. Locally, fall back to
--skip-dynamic. In CI, the emulator is provided byreactivecircus/android-emulator-runner.
Contracts¶
Next phase¶
Phase 4 reads phase2/impact_graph.json and phase3/ui_regressions.json and produces the canonical consolidated.json.