mirror of
https://github.com/lahfir/agent-desktop.git
synced 2026-07-30 02:39:18 +00:00
test: harden the e2e harness and unify fixture AX labels
The harness now fails setup loudly when the fixture build or AX trust is missing, rebuilds the fixture when sources are newer than the bundle, asserts hover only against a freshly observed state, and force-collapses the disclosure so the expand test proves a real flip. The slider/stepper labels live solely on the NSViews (the AX-actionable elements), removing the macOS-version-dependent race between two label sources, and the drag canvas reports a zero frame when detached from a window.
This commit is contained in:
parent
afad271112
commit
c73beb7f6f
3 changed files with 42 additions and 13 deletions
|
|
@ -124,7 +124,25 @@ trap cleanup EXIT
|
|||
# --- Setup -----------------------------------------------------------------
|
||||
note "Setup"
|
||||
[ -x "$bin" ] || { echo "release binary missing; run 'cargo build --release'"; exit 2; }
|
||||
[ -d "$fixture_app" ] || "$repo/tests/fixture-app/build.sh" >/dev/null
|
||||
ax_state="$("$bin" permissions 2>/dev/null | field "['data']['accessibility']['state']")"
|
||||
if [ "$ax_state" != "granted" ]; then
|
||||
echo "accessibility permission not granted (state='${ax_state:-unknown}')." >&2
|
||||
echo "Grant Accessibility trust to this terminal/runner in System Settings > Privacy & Security, then retry." >&2
|
||||
exit 2
|
||||
fi
|
||||
# Rebuild when the bundle is missing OR any fixture source is newer than it, so
|
||||
# the suite never silently exercises a stale fixture. Keep swiftc stderr
|
||||
# visible: a failed build must abort loudly, not cascade into launch noise.
|
||||
fixture_stale() {
|
||||
[ ! -d "$fixture_app" ] ||
|
||||
[ -n "$(find "$repo/tests/fixture-app" -maxdepth 1 -name '*.swift' -newer "$fixture_app" 2>/dev/null)" ]
|
||||
}
|
||||
if fixture_stale; then
|
||||
"$repo/tests/fixture-app/build.sh" >/dev/null || {
|
||||
echo "fixture build failed (swiftc output above); cannot run E2E" >&2
|
||||
exit 2
|
||||
}
|
||||
fi
|
||||
"$bin" close-app "$app" --force >/dev/null 2>&1 || true; sleep 1
|
||||
open "$fixture_app"
|
||||
ready=""; tries=0
|
||||
|
|
@ -202,10 +220,14 @@ def f(n):
|
|||
if r: return r
|
||||
print(f(d['data']['tree']) or '')" 2>/dev/null)"
|
||||
hv_b="$(read_value hover-status)"
|
||||
[ -n "$hv_xy" ] && "$bin" --headed hover --xy "$hv_xy" >/dev/null 2>&1; sleep 0.5
|
||||
hv_a="$(read_value hover-status)"
|
||||
assert "--headed hover triggers onHover" "$([ "$hv_a" = "hovered" ] && echo 1 || echo 0)" \
|
||||
"xy='$hv_xy' hover-status before='$hv_b' after='$hv_a'"
|
||||
if [ -n "$hv_xy" ]; then
|
||||
"$bin" --headed hover --xy "$hv_xy" >/dev/null 2>&1; sleep 0.5
|
||||
hv_a="$(read_value hover-status)"
|
||||
assert "--headed hover triggers onHover" "$([ "$hv_a" = "hovered" ] && echo 1 || echo 0)" \
|
||||
"xy='$hv_xy' hover-status before='$hv_b' after='$hv_a'"
|
||||
else
|
||||
assert "--headed hover triggers onHover" 0 "hover-target bounds not found in snapshot (no xy to hover)"
|
||||
fi
|
||||
|
||||
# --- Strict resolution -----------------------------------------------------
|
||||
# Two buttons share role+name "twin-control" and each records a distinct effect
|
||||
|
|
@ -364,16 +386,21 @@ dr_a="$(read_value drag-canvas-status)"
|
|||
assert "drag delivered a gesture" "$(echo "$dr_a" | grep -q '^dragged-' && echo 1 || echo 0)" "from='$from_xy' to='$to_xy' canvas before='$dr_b' after='$dr_a'"
|
||||
|
||||
note "expand a press-toggled disclosure (verified by disclosure value)"
|
||||
# Force-collapse first so the expand below proves a real state flip — an
|
||||
# already-expanded disclosure would make "expand succeeded" vacuous.
|
||||
"$bin" collapse "$(resolve disclosure disclosure-section)" >/dev/null 2>&1; sleep 0.4
|
||||
exp_b="$("$bin" get "$(resolve disclosure disclosure-section)" 2>/dev/null | field "['data']['value']")"
|
||||
eout="$("$bin" expand "$(resolve disclosure disclosure-section)" 2>&1)"; sleep 0.4
|
||||
exp_a="$("$bin" get "$(resolve disclosure disclosure-section)" 2>/dev/null | field "['data']['value']")"
|
||||
eok="$(echo "$eout" | field "['ok']")"
|
||||
if [ "$exp_a" = "true" ]; then
|
||||
assert "expand set disclosure expanded" 1 "value before='$exp_b' after='$exp_a' cmd_ok=$eok"
|
||||
if [ "$exp_b" = "true" ]; then
|
||||
skip "expand unprovable: pre-collapse did not land, disclosure already expanded [value before='$exp_b' after='$exp_a' cmd_ok=$eok]"
|
||||
elif [ "$exp_a" = "true" ]; then
|
||||
assert "expand flipped disclosure from collapsed to expanded" 1 "value before='$exp_b' after='$exp_a' cmd_ok=$eok"
|
||||
elif [ "$eok" = "False" ]; then
|
||||
skip "expand honestly failed (disclosure not AX-actionable) [value before='$exp_b' after='$exp_a' cmd_ok=$eok]"
|
||||
else
|
||||
assert "expand set disclosure expanded" 0 "claimed success but value before='$exp_b' after='$exp_a' cmd_ok=$eok"
|
||||
assert "expand flipped disclosure from collapsed to expanded" 0 "claimed success but value before='$exp_b' after='$exp_a' cmd_ok=$eok"
|
||||
fi
|
||||
|
||||
# --- Performance (CLI wall-clock per operation) ----------------------------
|
||||
|
|
|
|||
|
|
@ -166,11 +166,12 @@ struct ContentView: View {
|
|||
|
||||
// Native AppKit slider and stepper: unlike SwiftUI's, these expose a
|
||||
// working AX value/increment interface, so set-value can drive them.
|
||||
// The harness-facing labels (value-slider/value-stepper) live on the
|
||||
// NSViews themselves — the AX-actionable elements — so exactly one
|
||||
// label source exists regardless of how SwiftUI wraps them.
|
||||
NativeSlider(value: $nativeSliderValue).frame(width: 180, height: 20)
|
||||
.accessibilityLabel("value-slider")
|
||||
StatusReadout(name: "slider-status", value: String(Int(nativeSliderValue)))
|
||||
NativeStepper(value: $nativeStepperValue)
|
||||
.accessibilityLabel("value-stepper")
|
||||
StatusReadout(name: "stepper-status", value: String(Int(nativeStepperValue)))
|
||||
|
||||
DisclosureGroup("Disclosure Section", isExpanded: $disclosureExpanded) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ struct NativeSlider: NSViewRepresentable {
|
|||
let s = NSSlider(
|
||||
value: value, minValue: 0, maxValue: 100,
|
||||
target: context.coordinator, action: #selector(Coordinator.changed(_:)))
|
||||
s.setAccessibilityLabel("native-slider")
|
||||
s.setAccessibilityLabel("value-slider")
|
||||
return s
|
||||
}
|
||||
func updateNSView(_ view: NSSlider, context: Context) { view.doubleValue = value }
|
||||
|
|
@ -51,7 +51,7 @@ struct NativeStepper: NSViewRepresentable {
|
|||
s.valueWraps = false
|
||||
s.target = context.coordinator
|
||||
s.action = #selector(NativeControlCoordinator.changed(_:))
|
||||
s.setAccessibilityLabel("native-stepper")
|
||||
s.setAccessibilityLabel("value-stepper")
|
||||
return s
|
||||
}
|
||||
func updateNSView(_ view: NSStepper, context: Context) { view.doubleValue = value }
|
||||
|
|
@ -92,7 +92,8 @@ final class DragCanvasView: NSView {
|
|||
setAccessibilityLabel("drag-canvas")
|
||||
}
|
||||
override func accessibilityFrame() -> NSRect {
|
||||
window?.convertToScreen(convert(bounds, to: nil)) ?? bounds
|
||||
guard let window else { return .zero }
|
||||
return window.convertToScreen(convert(bounds, to: nil))
|
||||
}
|
||||
override func isAccessibilityElement() -> Bool { true }
|
||||
override func mouseDown(with e: NSEvent) { start = convert(e.locationInWindow, from: nil); dragged = false }
|
||||
|
|
|
|||
Loading…
Reference in a new issue