agentsclimarketplace

Emulator testing

Skill uwuclxdy/agenticat/skills/emulator-testing

Some of my Agents & Skills, compatible with most AI coding tools

Install
npx -y skills add uwuclxdy/agenticat --skill emulator-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Boots and drives Android AVDs and iOS simulators from the CLI (adb, `xcrun simctl`, Flutter `integration_test`, Alchemist goldens). Use when running headless app tests, verifying screenshots, or debugging emulator boot/GPU issues.

SKILL.md

12.6 KB, as published. Nobody here has run it

Emulator Testing

Drive a booted Android emulator or iOS simulator from the CLI for agent-in-the-loop testing: launch it headless, wait for a real boot, act on it with adb/simctl, verify with a real file parser (not a vibe check), shut it down clean.

Out of scope: installing the Android SDK, creating AVDs, or setting up Xcode/simulators. Assume $ANDROID_HOME/PATH and an existing AVD, or Xcode + simulator runtimes, are already in place. This skill is about driving them.

§2's adb primitives work unmodified against a real physical device over USB (same commands, target it with adb -s <device_serial>). §1's boot/lifecycle flags and §4's simctl lane are emulator/simulator-only and don't apply to physical hardware.

Decision Tree

Target platform?
├─ Android → live on this host → §1 boot the emulator → §2 drive it → §3 Flutter tests
└─ iOS     → needs a Mac → run over ssh to your macOS host → §4 simctl lane → §3 Flutter tests

1. Android Emulator Lifecycle

Headless Boot

emulator -avd <avd_name> \
  -no-window -no-audio -no-boot-anim \
  -gpu swiftshader_indirect \
  -no-snapshot-save \
  -read-only &
FlagEffect
-no-windowno graphical window; drive it purely over adb/console. Standard flag for a headless/CI box
-gpu swiftshader_indirectsoftware GLES/Vulkan rendering on the CPU; the only reliable renderer with no display server
-gpu hostpasses through the host GPU (faster), but fails with "OpenGLES emulation failed to initialize" unless a real X/Wayland session (or Xvfb) is present. Don't use it headless
-no-audioskip audio backend init; some Linux/Windows audio drivers otherwise block emulator startup
-no-boot-animskip the boot animation, faster startup
-read-onlydon't write back to the AVD's userdata image; several emulator processes can share one base AVD without corrupting it. Standard pattern for parallel instances
-port <n>pick the console/adb port pair explicitly (default range 5554-5682, even numbers only). Required alongside -read-only to run more than one instance side by side
-no-snapshot-savequick-boot from a snapshot if one exists, skip saving state on exit
-no-snapshot-loadforce a full cold boot, ignoring any saved snapshot
-no-snapshotdisable Quick Boot entirely: neither load nor save
-wipe-datafactory-reset the userdata partition before boot (last resort, destroys all AVD state; not for routine use on every run)

Match the system image ABI to the host arch (x86_64 image on an x86_64 host); an ARM image on x86 needs per-instruction translation and is dramatically slower.

Boot-Completion Detection

adb wait-for-device only confirms the adb bridge is up, not that Android finished booting:

adb -s emulator-5554 wait-for-device
until [[ "$(adb -s emulator-5554 shell getprop sys.boot_completed | tr -d '\r')" == "1" ]]; do
  sleep 2
done
adb -s emulator-5554 shell input keyevent 82   # KEYCODE_MENU, dismiss the lock screen

Clean Shutdown

adb -s emulator-5554 emu kill

If the console doesn't respond (auth-token mismatch, hung boot), fall back to killing the host process: pkill -f "qemu.*-avd <avd_name>".


2. Driving It: adb Primitives

Works against any installed app, Flutter or not.

ActionCommand
Tapadb shell input tap <x> <y>
Swipeadb shell input swipe <x1> <y1> <x2> <y2> <duration_ms>
Textadb shell input text "hello" (spaces don't survive; replace them with %s as in input text hello%sworld, or send a literal space via input keyevent 62)
Key eventadb shell input keyevent <code> (e.g. 4=back, 82=menu/unlock, 3=home)
Screenshotadb exec-out screencap -p > shot.png
Screen recordadb shell screenrecord /sdcard/demo.mp4 (Ctrl-C to stop, then adb pull)
View hierarchyadb shell uiautomator dump /sdcard/window_dump.xml && adb pull /sdcard/window_dump.xml
Filtered logsadb logcat --pid=$(adb shell pidof -s com.example.app) or adb logcat -s <tag>
Install / uninstalladb install -r app.apk / adb uninstall com.example.app

No simulator equivalent: iOS's simctl (§4) has no touch/text input-injection primitive. Use integration_test/XCUITest-level tooling for iOS UI instead (see §4).

Verify Screenshots with a Real Parser, Not Prose

A vision-model "looks correct" read on a screenshot is not verification. Decode the file:

file shot.png   # expect: PNG image data, <W> x <H>, ...

To confirm an action actually changed the UI (not just that a file exists), diff two captures instead of trusting a description of them:

sha256sum before.png after.png   # identical hash = nothing changed on screen

3. Flutter Test Layers

LayerToolCommand
Unit / widgetflutter_test (SDK built-in)flutter test
GoldenAlchemist (successor to the unmaintained golden_toolkit)flutter test via Alchemist's goldenTest/--update-goldens
In-app, on a live emulator/simulatorintegration_test packageflutter test integration_test/ -d <device_id>
E2E, native-layerMaestro / Patrolout of scope here (higher-level frameworks sitting on top of the primitives in §2)

Prefer integration_test over the legacy flutter drive: flutter drive runs a separate driver process against the app process and can't share flutter_test APIs, while integration_test runs in the app's own isolate and compiles the tests into the binary itself.

Screenshots from integration_test

await binding.convertFlutterSurfaceToImage(); // Android ONLY, before the first screenshot
await tester.pumpAndSettle();
await binding.takeScreenshot('screen-1');

Skipping convertFlutterSurfaceToImage() on Android produces blank/black captures. Not needed on iOS or web.

Persistence caveat (verified empirically): under plain flutter test integration_test/, takeScreenshot() only buffers the PNG bytes in the binding; nothing writes them to disk. Persisting needs the flutter drive path with a driver that consumes them (integration_test's flutter_driver extension + a responseDataCallback writing files). Without that wiring, capture independently via adb exec-out screencap -p instead.

Dart & Flutter MCP Server (Dev-Loop Tooling, Not a Test Runner)

For live inspection while iterating against a running flutter run session: hot reload, widget-tree/selected-widget introspection, runtime errors, static-analysis fixes, pub.dev search. It complements integration_test, it doesn't replace it: there's no repeatable CI suite here, just a tight feedback loop for an agent driving development.

Requires Dart SDK 3.9 / Flutter 3.35 or later.

dart mcp-server                                          # runs stdio, start it directly to check it launches
claude mcp add --transport stdio dart -- dart mcp-server # register it for Claude Code

4. iOS Simulator Lane (Your macOS Host over ssh)

No Mac locally? Run everything in this section over ssh on your macOS host; the simulator is still driven from a Linux/CI box's shell, only the xcrun/simctl calls run remotely.

xcrun simctl list devicetypes                     # find a device type id
xcrun simctl list runtimes                          # find an iOS runtime id
xcrun simctl create MyTestPhone "iPhone 15" "iOS-17-5"
xcrun simctl boot MyTestPhone                        # or boot by UDID
xcrun simctl install booted /path/to/App.app
xcrun simctl launch booted com.example.app
xcrun simctl io booted screenshot --type=jpeg out.jpg
xcrun simctl io booted recordVideo out.mp4            # Ctrl-C to stop
xcrun simctl terminate booted com.example.app
xcrun simctl shutdown MyTestPhone

simctl has no touch/text input-injection primitive (no adb input tap/swipe/text equivalent, see §2). Use integration_test/XCUITest-level tooling (or idb) for iOS UI input, not raw simctl calls.

Flutter on the same host: flutter build ios --simulator --debug needs no code signing; flutter test integration_test/ -d <simulator_udid> runs directly against a booted sim. Physical-device builds need a Team ID + provisioning profile. Always target the simulator for agent-driven testing to sidestep signing entirely.

The ssh-Headless Gotcha (Load-Bearing, Verify Before Relying on This)

The iOS Simulator is a GUI app at heart and needs an active macOS GUI session to function, even when every command driving it arrives over ssh:

  • someone logged into the graphical console (even locked) → ssh-driven simctl/test-runner commands work fine.
  • nobody logged into the GUI → the simulator driver fails to start, surfacing as a driver-startup timeout or a hung simctl/xcodebuild call.
  • fix: enable automatic login for a user on your macOS host (System Settings → Users & Groups) so a GUI session always exists after boot/reboot, independent of ssh activity.

First-run gotcha: sudo xcodebuild -license accept is an interactive prompt the first time; accept it once by hand before handing the host to an agent.


Gotchas

#AreaGotchaFix
1adb servertwo adb binaries on PATH (platform-tools + a copy bundled with another tool) → adb server version doesn't match this client; killing...adb kill-server, make sure only one adb binary (matching platform-tools) resolves on PATH, retry
2Boot timingcold boot ≈30-90s vs Quick Boot snapshot restore ≈5-10sprefer snapshot restore for repeated runs; -no-snapshot-load forces a fresh cold boot when snapshot state is suspect
3GPU mode-gpu host fails ("OpenGLES emulation failed to initialize") with no display/GPU context-gpu swiftshader_indirect on headless boxes; -gpu host only with a real display session
4KVM (Linux)emulator silently falls back to software TCG emulation (~10x slower) without hardware-accel accessconfirm /dev/kvm exists and is accessible before assuming acceleration is active
5Boot detectionadb wait-for-device confirms only the adb bridge, not that Android finished bootingpoll adb shell getprop sys.boot_completed until it prints 1
6Flaky boot recoveryan AVD repeatedly fails to boot or hangs mid-bootretry with -no-snapshot-load (bypass a possibly-corrupt snapshot); -wipe-data as the last resort (destroys all AVD state)
7input textliteral spaces get droppedreplace spaces with %s, or send them via input keyevent 62
8Flutter screenshotsforgetting convertFlutterSurfaceToImage() before the first takeScreenshot() on Android → blank capturecall it once before the first screenshot; Android only
9Screenshot verificationa vision-model "looks right" read is not verificationparse the file for real (file/PNG header), diff two captures to confirm the UI changed
10iOS + sshsimulator driver fails to start with nobody logged into the macOS GUI consoleenable auto-login on the macOS host
11iOS code signingdevice builds need a Team + provisioning profile; simulator builds need nonetarget the simulator for agent-driven testing
12GPU cold startthe first app to render after a fresh headless swiftshader boot can sit 60-90s on the splash at 0 rendered frames, no error, process idlecheck dumpsys gfxinfo <pkg> | grep 'Total frames'; force-stop and relaunch once before calling it a hang
13First android buildAGP auto-downloads NDK/build-tools/CMake on the first flutter build/test (~3+ min); a short command timeout kills the download mid-flight and leaves a corrupt $ANDROID_HOME/ndk/<ver> stubrun first builds backgrounded or with a generous timeout; on source.properties errors delete the stub dir and rebuild
14Xcode first runxcodebuild -license accept is interactive, blocks headless automationaccept it once by hand before automating
15Physical device§2's adb primitives assume an emulator serial (emulator-5554)works unmodified on a real USB device too, target it with adb -s <device_serial>; §1/§4 boot-lifecycle content stays emulator/simulator-only
16iOS input injectionsimctl (§4) has no touch/text input-injection primitive, unlike adb input tap/swipe/text (§2)use integration_test/XCUITest-level tooling (or idb) for iOS UI input

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.