AXe: iOS Simulator automation through HID events, and one binary for two Xcode majors
AXe is a CLI tool for interacting with Simulators using Apple's Private Accessibility APIs.
At a glance
- What is it?
- AXe is a Swift command line tool that drives the iOS simulator by injecting hardware input events and reading the interface, so you tap by accessibility label rather than by coordinate. It reaches for interfaces Apple does not document publicly, and that is both why it works where the documented ones do not and why the Xcode version in the readme is the number you have to keep an eye on.
- Who is it for?
- AXe is worth adopting if you are writing simulator tests today and have outgrown coordinate-based tapping, because targeting an element by its accessibility label is the difference between a test that survives a layout change and one that does not, and the readme's four-command smoke test is enough to judge that in a minute. Check three things before committing.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 71 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Hardware input events and an interface tree, neither of them a public API
The project describes itself two slightly different ways and the difference is worth noticing. The repository description says the tool interacts with simulators using Apple's private accessibility interfaces. The readme says it does so using the hardware input device functionality, which in this context means the synthetic touch and keyboard events a simulator already accepts, the same path a remote control or a test harness uses. Both statements are true of what the tool does and they describe two different surfaces. One is event injection, which is how a tap or a keystroke reaches the system. The other is reading the interface, which is how the tool knows a control exists and what it is called, and the command that dumps the current interface is the one that tells you this is the private side. That distinction is the key to evaluating the project. The event side is comparatively stable, because synthetic input is what the simulator is for. The interface side is not, because there is no compatibility promise attached to an interface that exists for the platform's own tooling. So the practical risk is concentrated in one half of the tool, and it shows up as a toolchain upgrade rather than as a bug. The file list supports that reading as well as the prose does: there is an entitlements file at the top level, which is what a signed binary needs before it is allowed to reach interfaces the platform does not publish, and there is a purpose-built playground application in the tree to develop against. Neither of those is needed by a tool that only injects events.
Two Xcode majors, one artefact, and validation down to the build number
The compatibility section is the most substantive part of the readme and it is unusually specific. The tool supports two Xcode majors. Simulator automation under the newer one uses a different application entirely, and the readme makes a point of saying the older simulator application is not required, which tells you the project has already done the work for the current toolchain rather than waiting for someone to file an issue. Then the claim that matters most: the release artefacts are built once, with whichever Xcode the release environment selects, and run unchanged under either supported version. A single binary working across two major toolchain versions is a strong claim, and it is the kind of claim that is either very carefully engineered or very optimistic. The readme then tells you how it was checked, down to the build numbers, for the older combination and for a beta of the newer one paired with a beta of its simulator runtime. Naming a beta as a supported configuration is honest and it is also a currency signal: this project moves when the toolchain moves, which is what you want from automation tooling and what makes it a moving target to depend on. The mechanism behind the cross-version claim is worth understanding. The tool does not talk to the toolchain directly; it builds another organisation's development bridge from a fork at a fixed revision, and it does so without applying any local patch queue. So the version coupling sits in the bridge, and the pinning of the bridge is what keeps one binary working across two toolchains. That is a much better design than maintaining a patch set per version, and it is also the thing to verify if a release ever stops working.
Tap by label, which is the whole argument for a tool like this
The usage section is four commands and one of them is the reason to install it. You list the simulators, find the identifier of one that is already booted, and put it in an environment variable so you are not repeating it. Then you dump the current interface, which tells you what is on screen and what each thing is called. Then you tap, and the tap takes a label rather than a pair of coordinates. Then you type a string. Then you take a screenshot to a file.
axe list-simulators
export UDID=<UDID>
axe describe-ui --udid "$UDID"
axe tap --label "Continue" --udid "$UDID"
axe type 'Hello world' --udid "$UDID"
axe screenshot --output ./screen.png --udid "$UDID"Five steps, and the third is the design. Coordinate-based simulator tests break when a layout moves, when a device class changes, or when a localisation changes the padding, and the failure is a tap in the wrong place that often does nothing rather than something that fails loudly. Targeting by accessibility label ties the test to what the user sees and to what the accessibility layer already exposes, which means a label that exists is a label an assistive technology user can reach too. That is a better reason to prefer it than convenience. Two things to know about the surface. The readme is a smoke test rather than a reference, so the commands shown are the common ones and the full set is on a separate documentation site, which is where you should look before concluding a gesture is missing. And the identifier is per boot rather than per device, so a script that finds a booted simulator has to handle the case where none is running, which is the most common way a fresh test script fails.
A name that belongs to a commercial product, and a licence file for the borrowed parts
The readme contains a disclaimer that is worth reading before anything else, and it is about the name. The project states that it is an independent open-source simulator automation project and is not affiliated with, endorsed by or associated with a specific company or its accessibility products of the same name. That is a trademark clarification, and the reason it is in the readme rather than only in a legal file is that the collision is close enough to cause real confusion when you search for it: a commercial accessibility auditing tool shares this name, and it is a well known one. The practical advice is to search by the repository or the package name rather than by the product name, and to read this disclaimer as the authors intended it, which is as a statement that nothing here comes from or is supported by that company. The licensing story is clean and it is handled properly. The project is under a permissive MIT licence, and there is a separate third-party notices file which the readme says contains the attribution for the borrowed component, naming the company whose development bridge it builds and stating that component's licence. Having a dedicated notices file rather than burying third-party terms in the main licence is the right practice, particularly when the third party is a large company's internal tooling that you are compiling from a fork. The topic tags describe the project as an automation framework, a command line tool, simulator and user-interface automation tooling, and name the borrowed bridge, which is consistent with everything else the readme says.
A development identity builds locally; a Developer ID one notarises
The example environment file is the most instructive file in the repository and it is worth reading line by line even if you never build the project. It opens with an instruction to copy it to a local file, states that the local file is ignored by version control, and that the build script loads it automatically. Then it states a precedence rule: anything already set in your shell environment takes precedence over the file. That single sentence prevents a class of confusing bug where a build behaves differently depending on what a developer exported in a previous session, and it is the kind of documentation that saves an hour. The signing section is the substance. There is a variable for the signing identity with a placeholder value, and an instruction for listing the identities available on your machine. Then the distinction that matters: a Developer ID Application identity is what you need for a notarisable release build, which the readme says is the default build command, while an Apple Development identity only works for a local development build and cannot be notarised. Those are two different kinds of certificate from Apple's own hierarchy and using the wrong one does not fail early or obviously. The notarisation section is scoped to the release path only, and it names the three values you need, where the private key file lives, that the directory holding it is ignored by version control, and which page of the developer portal the other two identifiers come from. So a contributor can build and run locally with no Apple account relationship at all, and the release path is the only part that needs credentials. That is a well drawn line and the file is the clearest evidence of it.
A make file for the loop, a shell script for the release, and a test bundle you can point at a shipped binary
The build has two front ends and the split is deliberate. A make file exposes four targets with a help target that prints them with one line of description each: build the tool, run the default tests, run the full end-to-end flow, and clean the build artefacts. Three of the four map to a single well-known package manager command, which means the everyday loop is not hidden behind the project's own scripts. The end-to-end target is different, because it delegates to a shell script at the top of the repository, and that script is where the real work is. It rebuilds the borrowed bridge frameworks, builds the tool, and runs the simulator tests using whichever Xcode the developer points it at through an environment variable or the system selection tool. It needs a project generation tool to produce the bridge's project files, and when the newer toolchain is selected it does something the older one does not: it starts the new simulator management application, picks an available device of a named model from the new runtime, and boots it with the command line simulator tool. So the end-to-end path is genuinely two paths, and a maintainer is testing two device configurations. The most interesting target is the validation procedure, and it is documented well. To check a release payload under the other supported Xcode, you build the test bundle with that Xcode and then run the test script against the path of the already-built executable, with a flag that skips the build step. And there is a constraint attached: the executable must keep its packaged frameworks beside it. That is the installation shape, and it is the one fact in this section that will change how you deploy the tool.
Entitlements, a playground app, a test plan, and a repository that teaches assistants too
The top-level file list is the best evidence about what kind of project this is, and three entries do most of the work. There is an entitlements file, which is what makes a signed binary on the platform eligible to reach the interfaces this tool needs, and its presence confirms the design rather than surprising it. There is a playground application, which is a small application built for the purpose of driving the tool against, so the end-to-end tests do not depend on somebody's real product. And there is a test plan file, which is the format the platform's test framework uses to declare a set of tests, so the tests are written in the standard framework rather than in a bespoke runner. Between them they say this is a maintained tool with a test harness, not a demonstration. The rest of the list is conventional and slightly telling. A package manifest and a resolved dependency file means the build is reproducible at the dependency level, and a plugins directory means the package manager is being used for what it is good at, compiling the borrowed frameworks. There is a changelog and a code of conduct, so the project has the administrative furniture of something that intends to keep going. And there is a skills directory plus two agent instruction files, which is a pattern that has started showing up in the repositories of companies that use coding assistants in their own development, and it means the project's own conventions for assistants are part of what it ships. A test run against a playground app, a test plan, and an entitlements file is a much stronger combination than a readme full of claims.
A release list that includes the staging builds, and what to verify first
One administrative detail to know about before you look at the releases. The most recent entries are a tagged release followed by two releases whose names are not version numbers at all but staging identifiers with a build number and a commit prefix, published minutes before the tagged one. So the release list is a mixture of real releases and continuous integration staging artefacts, and if you are writing a script that resolves the latest release you will get a staging build rather than a version. That is a small thing and it is a real operational hazard for anyone automating an upgrade, and it is the kind of thing a changelog discipline would normally prevent. The rest of the picture is healthy. The last push was in July 2026, the licence is the permissive MIT with third-party notices kept separately, the documentation lives on the project's own site rather than in the repository, and the community channel is a code of conduct rather than a chat link, which is a good sign about who is maintaining it. The support for a beta toolchain is the strongest signal in the readme, because it means the maintainer is working against the current release rather than the current stable one, which is what you want and also what makes the compatibility question permanent. So verify the toolchain pairing against the Xcode you actually run, install the whole bundle rather than the binary, and read the third-party notices before you build the borrowed bridge yourself from a fork.
Editorial conclusion
AXe is worth adopting if you are writing simulator tests today and have outgrown coordinate-based tapping, because targeting an element by its accessibility label is the difference between a test that survives a layout change and one that does not, and the readme's four-command smoke test is enough to judge that in a minute. Check three things before committing. The Xcode version, since the tool relies on interfaces that change with the toolchain and the second supported version is currently a beta. The installation shape, because the shipped artefact is a binary with packaged frameworks beside it and cannot be copied on its own, so whatever installs it for you has to install the whole bundle. And the third-party basis, since the project builds another organisation's development bridge from a fork of its own and says plainly that it applies no local patches, which means you can check that claim rather than take it. And read the disclaimer, because the name belongs to a commercial accessibility product and this is not it.
Frequently asked questions
What is the difference between this and coordinate-based simulator testing?
The tap command takes an accessibility label rather than a pair of coordinates, after a command that dumps the current interface. A test that targets a labelled control survives a layout change, a different device class or a localisation, where a coordinate tap silently lands somewhere else.
Which Xcode versions are supported?
Two majors, and release artefacts are built once and run unchanged under either, with simulator automation under the newer one using a different management application. The readme gives exact build numbers for the validation, and the newer pairing is a beta of both the toolchain and its simulator runtime.
What does AXe build from another project, and how is that pinned?
It builds a development bridge from another organisation, at a fixed revision of a fork configured in the build script, based on a verified upstream revision, and the readme states the build does not apply a local patch queue. Third-party notices, including that component's MIT attribution, are kept in a separate file at the top of the repository.
How do I install it and what do I get?
Through a Homebrew tap, either tapped and installed in two steps or installed in one with the fully qualified formula path, then verified with the help and simulator-listing commands. The shipped artefact is an executable with packaged frameworks beside it, so it cannot be copied on its own, and the release validation procedure says so explicitly.
What is the relationship to the commercial accessibility product of the same name?
None. The readme states the project is independent and is not affiliated with, endorsed by or associated with that company or its accessibility products. Search by the repository or package name rather than the product name, because the collision is close enough to matter when you search.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cameroncooke-axe)