simgadget
Two runtime dependencies. Full TypeScript types. Every action answers with what actually happened.
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.
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",
});
No parsing the return string for the values. Everything is well typed.
const result = await sim.tap({ label: "Sound" });
// {
// acted: "activation",
// element: { AXLabel: "Sound", type: "Switch", … },
// before: "off",
// after: "on",
// }
upside_down.code and a payload,
so no caller ever regexes a message.swipe, typeText — the return is void. The
companion acks delivery and knows no more than you do.findByLabel, findByIdentifier and describePoint
return null for a clean miss.| 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
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();
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.When you really don't want to have to know how it works, just that it does.