Releases: enzomanuelmangano/ennio
v0.0.9 — MCP server, Android backend, in-house HID
Warning
Experimental. Ennio is at an early experimental stage. APIs, package
names, internals, and behavior may change without notice between releases.
iOS is the primary target; Android is newer (opt-in via --android) —
its full example suite runs green and stable on CI, but it has seen less
real-world use. The 0.0.x line is a public preview, not a stability commitment.
The biggest release so far: ennio grows an Android backend, an MCP server so AI agents can drive a device through the same pipeline as test flows, an in-house HID layer that drops the last external dependency, and a rewritten CLI. Install is now global-first:
npm install -g @reactiive/ennio.
MCP server
ennio mcp— serve ennio over the Model Context Protocol (stdio). An agent reads the screen (ennio_describe), decides, and acts (ennio_tap,ennio_swipe,ennio_input_text, …) through the same find → settle → actuate pipeline a test run uses; taps and swipes always go through the HID driver, never a passthrough (#59)- Versioned tool contract — structured
{ ok, data | error }envelopes on every tool, one selector model (testID|text| normalized[0,1]point), capability negotiation viaennio_status, side-effect-honestreadOnlyflags — all enforced by an executable conformance suite (#59) - Tool-agnostic by design: works identically with Claude Code, Cursor, Cline, or a hand-rolled client (#59)
Android backend (opt-in)
--android(orENNIO_PLATFORM=android) — drive an emulator/device over adb with in-process agent injection: deterministic JVMTI attach on debuggable builds, ptrace remote-dlopen on non-debuggable release builds (#52)- Focus-identity tracking, regex text matchers, and a network-idle submit gate in the Java agent (#58)
- Full 40-flow example suite green and stable on CI — three consecutive zero-retry runs; injection rides out emulator soft-lock windows with socket-bind verification and escalating relaunch backoff (#60)
In-house HID
- idb_companion is gone. Touches are posted by
enniohid, an in-house helper that links Xcode's own CoreSimulator / SimulatorKit and posts Indigo HID events directly — same pipeline as a physical finger, one persistent process per run, ~5 ms per event (#46)
New CLI
- Rewritten command-line experience: animated live view while flows run, global-install-ready, update notice, and crash reports (#56)
- Flags rationalized: app reuse is now default-on between flows (soft-reset instead of full relaunch — much faster suites);
--disable-reuse-appopts out (#54) - Fast grey-box profile:
--disable-animations+ reuse-app + settle tightening (#51)
Reliability
- iOS keyboard-swallow + autocorrect fixes — the Bluesky e2e suite runs 17/17 (#53)
- TCC permission grant that sticks, plus a loud warning when cross-process AX is blind
- Tab recovery asks the app for its tab bar instead of guessing from a name list
Docs
- READMEs refreshed: global install (
npm install -g @reactiive/ennio) instead of a devDependency, MCP section, full CLI command list, corrected injection wording (SIMCTL_CHILD_*, not simulator-wide env), current Android status (#55, #61)
Full Changelog: v0.0.8...v0.0.9
E2E fixtures v2
Example app APK (arm64-v8a + x86_64) with native Android action sheet (Alert) — enables g-action-sheet on Android. Supersedes v1 for the Android example e2e suite.
E2E fixture binaries
Prebuilt simulator app fixtures for CI e2e runs — the suites test the
runner, not the app, so these binaries are stable fixtures rather than
per-run builds.
| asset | what | sha256 |
|---|---|---|
| EnnioExample-app.zip | EnnioExample.app, Release-iphonesimulator, built from 2dd79a6-era example source |
a35c43c6ae36f44037f123c38260f24c2a5c9b1eb43610a11f3fb19a8e9d3998 |
Workflows verify the pinned SHA-256 before installing. To refresh after
an example-app source change: build Release for the simulator,
ditto -c -k --keepParent EnnioExample.app EnnioExample-app.zip,
upload here, and update the pinned hash in .github/workflows/ci-example.yml.
v0.0.8 — injection hardening & crash diagnosis
Warning
Experimental. Ennio is at an early experimental stage. APIs, package
names, internals, and behavior may change without notice between releases.
iOS only. Expect rough edges; do not rely on it for production-critical test
suites yet. The 0.0.x line is a public preview, not a stability commitment.
Hardening release driven by #44 (SIGSEGV under injection on RN 0.85 / iOS 26): the in-app swizzles are now provably safe to attach, crashes are diagnosed instead of masked, and there's a safe mode to fall back on.
Injection safety
- Signature-checked RN observer swizzle — mount-method candidates are type-encoding-checked before attaching; non-forwardable C++ signatures (e.g.
scheduleTransaction:taking a by-valuestd::shared_ptron RN 0.85) are skipped with their encoding logged. Wrappers forward viavoid *— zero ARC traffic on non-object args (#48) - Fabric commit signals preferred — on New-Architecture apps the observer now attaches a live Fabric mount method instead of the dead interop Paper selector; Paper apps unchanged. Measurably faster settle on New-Arch apps (#50)
Crash diagnosis
- When the app dies under injection, the CLI now reports what actually happened: exception type, faulting-thread frames, whether
libennio.dylibwas loaded, and the.ipscrash-report path — instead of a bareennio socket not connected(#47)
Safe mode & bisect flags
ennio test --safe-mode— run with all in-app hooks disabled; settle falls back to view-hash polling (#47)- Granular kill switches for bisecting conflicts:
ENNIO_DISABLE_RN_OBSERVER,ENNIO_DISABLE_TESTID_INDEX,ENNIO_DISABLE_SETTLE(#47)
CLI
- Verbose per-step output is now the default;
--quiet/-qsuppresses it,--verbosestill accepted and wins over--quiet(#49) - SHA-256 dylib verification — the prebuilt dylib is verified against
prebuilt/manifest.jsonbefore injection; mismatches refuse loudly. Local dev builds andENNIO_DYLIB_PATHoverrides skip the check (#50)
Docs
- README rewritten to match the shipped architecture: direct dylib injection, real
+loadgates, architecture-agnostic RN support (the New-Architecture requirement was wrong), and the actual prerequisite — a dev/debug simulator build (#50)
Full Changelog: v0.0.7...v0.0.8
v0.0.7
Warning
Experimental. Ennio is at an early experimental stage. APIs, package
names, internals, and behavior may change without notice between releases.
iOS only. Expect rough edges; do not rely on it for production-critical test
suites yet. The 0.0.x line is a public preview, not a stability commitment.
Ennio's biggest release yet: a ground-up v2 runtime, a hardened CLI, and a fully automated, provenance-signed release pipeline. Spans everything since
0.0.5(includes0.0.6).
Highlights — v2 architecture
A complete rewrite of how Ennio drives the simulator.
- Prebuilt universal dylib
DYLD_INSERT_LIBRARIES'd into the app at launch — one binary, every New-Architecture RN version (a884fec) - Unix-socket transport + idb gRPC HID backend — every tap/swipe goes through a typed channel (
a884fec) - Typed-RPC execution engine with a settle engine + structured CLI logging (#37)
- OOP foundation —
Runner/Connection/Session/Reporterseparation (#40)
CLI
ennio --versionflag (#41)- Hardened
doctor— Node / Xcode / idb / dylib / simulator checks with actionable output (#41) - npm update notifier — warns when a newer version is on the registry (#41)
- idb preflight — detects missing
idb_companion/fb-idband installs with consent (ENNIO_AUTO_INSTALL_IDB=1, never silent, never in CI) (#43)
Security & correctness
- Per-UDID socket isolation; dropped
ennio-expo-plugin(#38) - Lenient-warning + conditional retry on tiny controls + idb
maxBufferfix (#39) - Correctness + performance fixes (#36)
Release infrastructure
- npm OIDC Trusted Publishing — no long-lived
NPM_TOKEN; short-lived signed identity (#42) - Provenance generated automatically; published with
--provenance(3d03f62) - Third-party actions SHA-pinned; protected
releaseenvironment approval gate (#42) - Prereleases auto-routed to the
@betadist-tag, stable →latest(c1aa87a) build-dylibs.ymlis the canonical release workflow; symbol-surface allowlist diff guards the dylib (7f00577)
Install (as a dev dependency):
npm install --save-dev @reactiive/ennio
# or
bun add -d @reactiive/ennioFull changelog: v0.0.5...v0.0.7
v0.0.5 — iOS 26 tap reliability
Warning
Experimental. Ennio is at an early experimental stage. APIs, package
names, internals, and behavior may change without notice between releases.
iOS only. Expect rough edges; do not rely on it for production-critical test
suites yet. The 0.0.x line is a public preview, not a stability commitment.
Third preview. Headline: iOS 26 tap reliability. Three platform-level
races that previously dropped taps during sheet presents, keyboard-up
states, and RNS stack pushes are now closed end-to-end. Example suite
40/40. Habits sign-up-and-delete passes from cold launch to backend
deletion.
What's new
Tap reliability fixes
| Issue | Fix | Where |
|---|---|---|
| 50 ms held tap claimed by iOS 26 sheet's pan recogniser → tap routed to sheet drag instead of the inner UITextField | hidTap durations ≤ 50 ms clamp to instant down/up | writer.ts |
| Missed focus tap silently dropped subsequent HID keystrokes | typeText routes through pasteFromClipboard when bound to a testID — landing is keyed by testID, not the focused responder |
writer.ts |
RNS first-touch race: first tap on a freshly mounted screen swallowed by RNSScreenStackView |
assertVisible / assertNotVisible / inputText auto re-fire the last tap once when the expected post-tap state never arrives |
maestro-runner.ts |
Point taps doubled the sheet's surface offset (tapAt was screen-relative but the runner added the surface origin again) |
revert surfaceOffset from tapAt — point coordinates stay screen-relative |
writer.ts |
| Daemon vs. CLI parity: HID gRPC sometimes rejected fractional coordinates the CLI rounded | round float x / y to ints in the persistent HID daemon |
hid-daemon.py |
findLabelMatch picked the outer aggregator Pressable whose AX label happened to contain "Email" (RN auto-aggregates child Text into a parent's accessibilityLabel) |
rank candidates by label specificity — UITextField.placeholder exact > accessibilityLabel exact > CONTAINS — so a text tap on "Email" lands on the leaf RCTUITextField |
EnnioRuntimeHelper.mm |
viewIsHittableAtCenter only walked from hit-test result up toward the target; missed the RN shape where hit-test surfaces the wrapper and the target is one hop down |
bidirectional walk (target ancestor AND descendant accepted) | EnnioRuntimeHelper.mm |
getViewWindowFrameByLabel was returning screen-converted coords; sheets live in the same window so the second convertRect:toWindow:nil was a regression |
drop the screen conversion — return window-relative coords | EnnioRuntimeHelper.mm |
Podspec
React-FabricComponents was split out of React-Fabric in RN 0.78+; it owns
the TextInput shadow-tree headers. The podspec now adds the public/private
search paths and an explicit dependency so
buildReactNativeFromSource: true builds resolve them.
Cross-version verified
- Example suite, iOS 26: 40 / 40 PASS.
- Habits app
signup-delete.yaml, iOS 26: cold launch → sign up via email → today tab → settings tab → delete account → back to marketing screen.
Install
bun add @reactiive/ennio react-native-nitro-modules
bun add -d @reactiive/ennio-expo-pluginSame setup as v0.0.4 — add the plugin to app.json, npx expo prebuild --clean,
npx expo run:ios, write a Maestro YAML.
Limitations
Unchanged from v0.0.4 — iOS only, Bridgeless / Fabric only, Metro must be
running.
Full Changelog: v0.0.4...v0.0.5
v0.0.4 — experimental preview
Warning
Experimental. Ennio is at an early experimental stage. APIs, package
names, internals, and behavior may change without notice between releases.
iOS only. Expect rough edges; do not rely on it for production-critical test
suites yet. The 0.0.x line is a public preview, not a stability commitment.
Second preview release. Headline: native UIKit / SwiftUI component coverage.
Every common native iOS UI piece a React Native app uses is now driven from
Ennio end-to-end, including the iOS 26 SwiftUI-hosted variants where the
legacy UIKit class is gone from the view tree.
What's new
Native component handlers (Unix-domain control socket)
All routed through a new in-app socket so the handler isn't queued behind a
busy JS thread.
| Component | Mechanism | Why |
|---|---|---|
UITabBarController (bottom tabs) |
selectedIndex via UITabBarControllerDelegate (faster path) |
iOS 26 liquid-glass tab bar takes 2 s+ when routed through CDP — socket bypass makes it ~3 ms |
UIAlertController |
walk window scenes + invoke UIAlertAction.handler |
alert lives on its own UIWindowLevelAlert window |
Header items (headerLeft / headerRight) |
existing getViewWindowFrame + HID tap |
RNScreens custom views are reachable in the tree |
UIRefreshControl pull-to-refresh |
two consecutive HID swipes | single swipe is flaky on iOS 26 simulator |
UIPickerView (@react-native-picker/picker) |
selectRow:inComponent: + delegate notify |
HID swipes against wheels are unreliable |
UISearchBar (iOS 26 SwiftUI host) |
walk for UISearchBarTextField (the surviving private text-field class) |
iOS 26 replaced UISearchBar with InlineSearchBarViewRepresentation |
UISegmentedControl |
setSelectedSegmentIndex: + UIControlEventValueChanged |
text-tap retry loop was ~14 s per tap |
UIDatePicker spinner |
multi-component picker walk | inner 3-wheel UIPickerView reachable via the same picker handler |
Runner
tap()text-only fast-paths chained in order: tab → search-bar focus → segmented → picker → alert → HIDinputText/eraseTextauto-route to native UISearchBar ops when a search field is focused- Per-flow profile summary in verbose mode (
bucket / count / total / avg / pct) - Per-step
Δcolumn in--verboselog so the slow step is obvious - HID daemon gains
key,keyrep, andtextops —eraseText: 50drops from ~8 s of subprocess spawns to ~50 ms in one gRPC call
Cross-version verified
iOS 26 (iPhone Air) full suite: 41 / 41 PASS.
iOS 18.5 (iPhone 16 Pro) native subset: 7 / 7 PASS.
Install
bun add @reactiive/ennio react-native-nitro-modules
bun add -d @reactiive/ennio-expo-pluginSame setup as v0.0.3 — add the plugin to app.json, npx expo prebuild --clean,
npx expo run:ios, write a Maestro YAML.
Limitations
- iOS only. Android scaffolding exists but no runtime.
- Metro must be running. No Metro → no Hermes Inspector → CLI errors out.
- Bridgeless / Fabric only. Old-architecture RN is not supported.
- iOS 26 SwiftUI-hosted components. The search-bar handler walks
UISearchBarTextFieldrather thanUISearchBar; if Apple migrates more
components similarly (segmented control, picker) the same workaround pattern
applies.
Inventory not yet covered
UIMenu/UIContextMenuInteraction/ zeego dropdown — out-of-process UI,
needs private accessibility-audit APIs; deferred to a separate branch.- Native
UIActionSheet(deprecated). - Apple Pay (
PKPaymentAuthorizationViewController) — system sheet, can't be
driven from the app process.
Full Changelog: v0.0.3...v0.0.4
v0.0.3 — Experimental preview
Warning
Experimental. Ennio is at an early experimental stage. APIs, package
names, internals, and behavior may change without notice between releases.
iOS only. Expect rough edges; do not rely on it for production-critical test
suites yet. The 0.0.x line is a public preview, not a stability commitment.
First tagged release of Ennio — a Maestro-compatible E2E test runner for
React Native iOS that drives the app over Hermes Inspector (CDP) and actuates
real CoreSimulator touches via a persistent idb HID daemon.
run-flow.mp4
Install
bun add @reactiive/ennio react-native-nitro-modules
bun add -d @reactiive/ennio-expo-pluginThen add "@reactiive/ennio-expo-plugin" to your app.json plugins,
npx expo prebuild --clean, npx expo run:ios, and write a Maestro YAML.
Full walkthrough in the README.
Limitations
- iOS only. Some Android scaffolding exists in the source tree but no
runtime, no JNI hook, no JS-thread executor. Adding the plugin to an
Android-only build is a no-op. - Metro must be running. With no Metro, there is no Hermes Inspector to
connect to; the CLI errors out immediately. - Bridgeless / Fabric only. Old-architecture RN is not supported; the
shadow-tree traverser and the React-commit hook assume the new arch.
Full Changelog: https://github.com/enzomanuelmangano/ennio/commits/v0.0.3