Files
charleswiltgen__axiom/tools/xcui/main.go
T
Charles Wiltgen e996de9d79 fix(xcui): send a physical tap, and refuse to guess the simulator
AXe's default tap style sends a simulator tap that SwiftUI controls
ignore while AXe still prints a success line. Measured on Xcode 27.1
with AXe 1.8.0, across iPhone 17 (iOS 27.0) and iPhone Duo (iOS 27.1):
neither a Button, a List row, a Button inside a List, a Menu, a row of
an open Menu nor a TabView tab activated under the default or the
`simulator` style, and `--tap-style physical` activated all six. The
same default left system alerts on screen while `dialog accept`
reported them handled.

`tap` and the `dialog` accept/dismiss taps now supply the physical
style unless the caller picks one, and an AXe older than 1.7.0 — which
has no `--tap-style` — is named as the cause instead of surfacing as an
unknown-flag failure.

With more than one simulator booted and no `--udid`, every device verb
now exits 2 and lists each booted device's UDID, name and runtime,
rather than driving whichever sorted first while reporting success.
`doctor` reports that list, fails its gate in that state, and leaves
`booted_udid` empty, because nothing may be targeted until the caller
chooses.
2026-09-19 15:43:23 -07:00

76 lines
3.0 KiB
Go

package main
import (
"fmt"
"os"
)
const version = "0.1.0-dev"
const usage = `xcui — scriptable iOS-simulator UI & accessibility testing for LLMs
Usage:
xcui doctor [--install] Verify AXe/brew/Xcode/booted-sim; --install adds AXe via brew
xcui wait --for-element <id> | --gone <id> | --idle Poll the a11y tree until a condition holds
xcui assert --id <id> [--label <s>] [--value <s>] [--trait <role>] [--single] Assert on an element
xcui a11y set --toggle <name> --value <on|off> [--app <bundle-id>] Set an accessibility setting
xcui a11y reset Clear xcui-set accessibility overrides
xcui dialog accept | dismiss Tap the right button on the frontmost system alert
xcui dialog pregrant <bundle-id> <service>... Grant permissions so no dialog appears
xcui voiceover traverse Emit the computed VoiceOver announcement sequence
xcui voiceover assert --sequence <file> Compare the announcement sequence to an expected one
xcui resize sweep --sizes <WxH,...> [--screenshot-dir <d>] [--assert-id <id>] [--strict]
Drive breakpoints, shoot and assert each (OS 27+)
Input (forwarded to AXe — same flags, output and exit code; tap also gets
--tap-style physical unless you pass a style):
xcui tap | slider | type | swipe | drag | touch | gesture Drive the UI
xcui button | key | key-sequence | key-combo Hardware buttons and keys
xcui screenshot Capture the display as PNG
Prefer these over calling 'axe' directly: they inherit xcui's SimulatorKit/
DEVELOPER_DIR handling, so they keep working under an Xcode that AXe cannot load
on its own. Run 'xcui tap --help' to see AXe's own flags for a verb.
Default output is JSON; pass --human for prose. Verbs auto-resolve the booted
simulator when exactly one is booted; with several, they exit 2 and list them —
pass --udid to name the one you mean.
Run 'xcui <command> --help' for per-command flags.
`
func main() {
if len(os.Args) < 2 {
fmt.Fprint(os.Stderr, usage)
os.Exit(2)
}
switch os.Args[1] {
case "doctor":
os.Exit(runDoctor(os.Stdout, os.Args[2:]))
case "wait":
os.Exit(runWait(os.Stdout, os.Args[2:]))
case "assert":
os.Exit(runAssert(os.Stdout, os.Args[2:]))
case "a11y":
os.Exit(runA11y(os.Stdout, os.Args[2:]))
case "dialog":
os.Exit(runDialog(os.Stdout, os.Args[2:]))
case "voiceover":
os.Exit(runVoiceOver(os.Stdout, os.Args[2:]))
case "resize":
os.Exit(runResize(os.Stdout, os.Args[2:]))
case "--version", "-v":
fmt.Println(version)
case "--help", "-h":
fmt.Print(usage)
default:
// Input verbs forward to AXe. Checked after the named commands so xcui's own
// verbs always win a name collision with a future AXe subcommand.
if _, ok := axeInputVerbs[os.Args[1]]; ok {
os.Exit(runInput(os.Stdout, os.Args[1], os.Args[2:]))
}
fmt.Fprintf(os.Stderr, "unknown command: %s\n\n%s", os.Args[1], usage)
os.Exit(2)
}
}