mirror of
https://github.com/CharlesWiltgen/Axiom.git
synced 2026-09-20 19:58:20 +08:00
e996de9d79
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.
186 lines
6.9 KiB
Go
186 lines
6.9 KiB
Go
package main
|
||
|
||
import (
|
||
"reflect"
|
||
"testing"
|
||
)
|
||
|
||
// Argument-order independence (axiom-v9in): pregrant must accept --udid before
|
||
// or after its positionals and yield identical results. This is xcui's per-tool
|
||
// regression guard for the cross-tool standard.
|
||
func TestParsePregrantArgsOrderIndependent(t *testing.T) {
|
||
before := []string{"--udid", "ABC123", "com.example.app", "camera", "photos"}
|
||
after := []string{"com.example.app", "camera", "photos", "--udid", "ABC123"}
|
||
|
||
bb, sb, ub, _, cb := parsePregrantArgs(before)
|
||
ba, sa, ua, _, ca := parsePregrantArgs(after)
|
||
if cb != 0 || ca != 0 {
|
||
t.Fatalf("both orders must succeed: before=%d after=%d", cb, ca)
|
||
}
|
||
if bb != ba || ub != ua || !reflect.DeepEqual(sb, sa) {
|
||
t.Errorf("order changed result: before(bundle=%q udid=%q svcs=%v) != after(bundle=%q udid=%q svcs=%v)", bb, ub, sb, ba, ua, sa)
|
||
}
|
||
if ba != "com.example.app" || ua != "ABC123" || !reflect.DeepEqual(sa, []string{"camera", "photos"}) {
|
||
t.Errorf("unexpected parse: bundle=%q udid=%q svcs=%v", ba, ua, sa)
|
||
}
|
||
}
|
||
|
||
func TestParsePregrantArgsRequiresBundleAndService(t *testing.T) {
|
||
if _, _, _, _, code := parsePregrantArgs([]string{"com.example.app"}); code != 2 {
|
||
t.Errorf("expected usage error (2) with no service, got %d", code)
|
||
}
|
||
}
|
||
|
||
// permissionAlert is a camera-permission dialog: an alert container with the
|
||
// standard two-button "Don't Allow" / "Allow" layout (straight apostrophe).
|
||
const permissionAlert = `[
|
||
{
|
||
"AXUniqueId": null, "AXLabel": "App", "role": "AXApplication", "type": "Application",
|
||
"enabled": true, "frame": {"x":0,"y":0,"width":402,"height":874},
|
||
"children": [
|
||
{
|
||
"AXUniqueId": null, "AXLabel": "“App” Would Like to Access the Camera",
|
||
"role": "AXSheet", "type": "Alert", "enabled": true,
|
||
"frame": {"x":51,"y":337,"width":300,"height":200},
|
||
"children": [
|
||
{"AXUniqueId": null, "AXLabel": "Don't Allow", "role": "AXButton", "type": "Button",
|
||
"enabled": true, "frame": {"x":51,"y":480,"width":150,"height":44}, "children": []},
|
||
{"AXUniqueId": "allow.btn", "AXLabel": "Allow", "role": "AXButton", "type": "Button",
|
||
"enabled": true, "frame": {"x":201,"y":480,"width":150,"height":44}, "children": []}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]`
|
||
|
||
// locationAlert offers the three-way location choice; accept must prefer the
|
||
// most-permissive standard label that is present.
|
||
const locationAlert = `[
|
||
{"AXUniqueId": null, "role": "AXSheet", "type": "Alert", "enabled": true,
|
||
"frame": {"x":51,"y":337,"width":300,"height":260}, "children": [
|
||
{"AXUniqueId": null, "AXLabel": "Allow Once", "role": "AXButton", "type": "Button",
|
||
"enabled": true, "frame": {"x":51,"y":440,"width":300,"height":44}, "children": []},
|
||
{"AXUniqueId": null, "AXLabel": "Allow While Using App", "role": "AXButton", "type": "Button",
|
||
"enabled": true, "frame": {"x":51,"y":490,"width":300,"height":44}, "children": []},
|
||
{"AXUniqueId": null, "AXLabel": "Don’t Allow", "role": "AXButton", "type": "Button",
|
||
"enabled": true, "frame": {"x":51,"y":540,"width":300,"height":44}, "children": []}
|
||
]}
|
||
]`
|
||
|
||
// okAlert is a single-button informational alert.
|
||
const okAlert = `[
|
||
{"AXUniqueId": null, "role": "AXSheet", "type": "Alert", "enabled": true,
|
||
"frame": {"x":51,"y":337,"width":300,"height":150}, "children": [
|
||
{"AXUniqueId": null, "AXLabel": "OK", "role": "AXButton", "type": "Button",
|
||
"enabled": true, "frame": {"x":51,"y":440,"width":300,"height":44}, "children": []}
|
||
]}
|
||
]`
|
||
|
||
func TestFindAlertButtonAccept(t *testing.T) {
|
||
roots, _ := parseDescribeUI([]byte(permissionAlert))
|
||
btn, ok := findAlertButton(roots, intentAccept)
|
||
if !ok {
|
||
t.Fatal("expected to find an accept button")
|
||
}
|
||
if got := deref(btn.AXLabel); got != "Allow" {
|
||
t.Errorf("accept button = %q, want %q", got, "Allow")
|
||
}
|
||
}
|
||
|
||
func TestFindAlertButtonDismiss(t *testing.T) {
|
||
roots, _ := parseDescribeUI([]byte(permissionAlert))
|
||
btn, ok := findAlertButton(roots, intentDismiss)
|
||
if !ok {
|
||
t.Fatal("expected to find a dismiss button")
|
||
}
|
||
if got := deref(btn.AXLabel); got != "Don't Allow" {
|
||
t.Errorf("dismiss button = %q, want %q", got, "Don't Allow")
|
||
}
|
||
}
|
||
|
||
func TestFindAlertButtonDismissCurlyApostrophe(t *testing.T) {
|
||
roots, _ := parseDescribeUI([]byte(locationAlert))
|
||
btn, ok := findAlertButton(roots, intentDismiss)
|
||
if !ok {
|
||
t.Fatal("expected to match Don’t Allow with a curly apostrophe")
|
||
}
|
||
if got := deref(btn.AXLabel); got != "Don’t Allow" {
|
||
t.Errorf("dismiss button = %q, want the curly-apostrophe variant", got)
|
||
}
|
||
}
|
||
|
||
func TestFindAlertButtonAcceptPrefersMostPermissive(t *testing.T) {
|
||
roots, _ := parseDescribeUI([]byte(locationAlert))
|
||
btn, _ := findAlertButton(roots, intentAccept)
|
||
if got := deref(btn.AXLabel); got != "Allow While Using App" {
|
||
t.Errorf("accept button = %q, want %q (highest-preference present)", got, "Allow While Using App")
|
||
}
|
||
}
|
||
|
||
func TestFindAlertButtonSingleButtonAlert(t *testing.T) {
|
||
roots, _ := parseDescribeUI([]byte(okAlert))
|
||
// A one-button alert taps its only button for either intent.
|
||
for _, intent := range []alertIntent{intentAccept, intentDismiss} {
|
||
btn, ok := findAlertButton(roots, intent)
|
||
if !ok || deref(btn.AXLabel) != "OK" {
|
||
t.Errorf("intent %v: got (%q,%v), want (OK,true)", intent, deref(btn.AXLabel), ok)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestFindAlertButtonNoAlert(t *testing.T) {
|
||
// sampleTree (from main_test.go) has a "Play all" button but no alert and
|
||
// no standard alert-button labels, so neither intent resolves.
|
||
roots, _ := parseDescribeUI([]byte(sampleTree))
|
||
if _, ok := findAlertButton(roots, intentAccept); ok {
|
||
t.Error("expected no accept button on a normal screen")
|
||
}
|
||
if _, ok := findAlertButton(roots, intentDismiss); ok {
|
||
t.Error("expected no dismiss button on a normal screen")
|
||
}
|
||
}
|
||
|
||
func TestParseIntent(t *testing.T) {
|
||
if i, ok := parseIntent("accept"); !ok || i != intentAccept {
|
||
t.Errorf("accept => (%v,%v)", i, ok)
|
||
}
|
||
if i, ok := parseIntent("dismiss"); !ok || i != intentDismiss {
|
||
t.Errorf("dismiss => (%v,%v)", i, ok)
|
||
}
|
||
if _, ok := parseIntent("maybe"); ok {
|
||
t.Error("maybe should be rejected")
|
||
}
|
||
}
|
||
|
||
func TestTapArgsPrefersID(t *testing.T) {
|
||
roots, _ := parseDescribeUI([]byte(permissionAlert))
|
||
btn, _ := findAlertButton(roots, intentAccept) // "Allow" has AXUniqueId allow.btn
|
||
got := tapArgs(btn, "UDID1")
|
||
want := []string{"tap", "--tap-style", "physical", "--id", "allow.btn", "--udid", "UDID1"}
|
||
if !equalStrings(got, want) {
|
||
t.Errorf("tapArgs = %v, want %v", got, want)
|
||
}
|
||
}
|
||
|
||
func TestTapArgsFallsBackToLabel(t *testing.T) {
|
||
roots, _ := parseDescribeUI([]byte(permissionAlert))
|
||
btn, _ := findAlertButton(roots, intentDismiss) // "Don't Allow", no AXUniqueId
|
||
got := tapArgs(btn, "UDID1")
|
||
want := []string{"tap", "--tap-style", "physical", "--label", "Don't Allow", "--udid", "UDID1"}
|
||
if !equalStrings(got, want) {
|
||
t.Errorf("tapArgs = %v, want %v", got, want)
|
||
}
|
||
}
|
||
|
||
func equalStrings(a, b []string) bool {
|
||
if len(a) != len(b) {
|
||
return false
|
||
}
|
||
for i := range a {
|
||
if a[i] != b[i] {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|