The library · simgadget

The simulator, as an object.

Two runtime dependencies. Full TypeScript types. Every action answers with what actually happened.

$npm install simgadget
01 The pitch

You don't need a protocol between you and a simulator.

You have a shell and a Node runtime. That's enough.

createSimulator doesn't return until the simulator is genuinely driveable — not when simctl boot returns, which is a minute or more early. Every piece of hard-won knowledge about that boot is folded into the call.

signup.tsTypeScript
import { createSimulator } from "simgadget";

const sim = await createSimulator({ deviceType: "iPhone 16 Pro" });
await sim.installApp("./build/MyApp.app");
await sim.launchApp("com.example.myapp");

await sim.tap({ label: "Sign Up" });
await sim.typeText("test@example.com");

const shot = await sim.screenshot({
  format: "png",
  path: "./signup.png",
});
02 Every action

Well defined schema.

No parsing the return string for the values. Everything is well typed.

what a tap tells you
const result = await sim.tap({ label: "Sound" });

// {
//   acted:   "activation",
//   element: { AXLabel: "Sound", type: "Switch", … },
//   before:  "off",
//   after:   "on",
// }
  • A toggle tells you the state it read back — and when it can't read it back, it says so rather than claiming success.
  • A touch tells you where it landed and which element it resolved.
  • A rotate tells you which orientation the interface adopted, not which one you asked for. Apps decline orientations; no Face ID iPhone ever adopts upside_down.
  • A failure is a typed error with a code and a payload, so no caller ever regexes a message.
  • Where there is genuinely nothing to read back — swipe, typeText — the return is void. The companion acks delivery and knows no more than you do.
  • "Absent" is an answer, not an exception. findByLabel, findByIdentifier and describePoint return null for a clean miss.
03 Requirements

System requirements

macOS 16 or higher on Apple Silicon
Xcode 25+ with the Simulator runtime (and its associated binaries).
Node v18 or higher. It has two dependencies (@grpc/grpc-js and @bufbuild/protobuf npm will install).

The server downloads the latest idb_companion binary from the github repo, caching it for faster startups the next time. For CI environments you can run

$npx simgadget prefetch
04 Reference

API reference

Full API documentation. simgadget.dev/api has the full documentation including examples.
signup.jsJavaScript
import { createSimulator } from "simgadget";

// Creates on the latest installed iOS runtime and waits until the device is
// actually driveable
const sim = await createSimulator({ deviceType: "iPhone 16 Pro" });
console.log(sim.udid, sim.lastBoot); // { ready: true, waitedMs: 41000, ... }

await sim.installApp("./build/MyApp.app");
await sim.launchApp("com.example.myapp"); // -> { pid: 18900 }

// Resolves the element and taps it. Refuses if the element is disabled or not visible.
const result = await sim.tap({ label: "Sign Up" });
// { acted: "touch", x: 201, y: 442, count: 1, durationSeconds: 0.1, element: {...} }

await sim.tap({ label: "Email" });
await sim.typeText("test@example.com");

// null is the ordinary "not on screen" answer, not an error.
const banner = await sim.findByLabel("Welcome back");
if (banner === null) {
  console.log("Can't find Welcome back");
}

await sim.screenshot({ path: "/tmp/signup.png" });
// And finally delete the sim when you're finished.
await sim.delete();

Top-level functions

Everything else hangs off the Simulator handle that these return.

  • createSimulator(opts?) Creates a device on the newest installed iOS runtime and, by default, boots it and waits until it is ready.
  • attachSimulator(udid) Adopts a simulator that already exists.
  • listSimulators() Reports every simulator simctl knows about, without creating or attaching anything.
  • prefetchCompanion() Downloads the companion ahead of time. This happens automatically when it is needed, but you can prefetch it if that is useful.

Boot a sim and drive it, easily.

When you really don't want to have to know how it works, just that it does.

$npm install simgadget
import { createSimulator } from "simgadget"
const sim = await createSimulator({ deviceType: "iPhone 16 Pro" });