fix(ioscompanion): wait out unresolved accessibility values after launch

This commit is contained in:
pj committed 2026-06-07 00:28:40 +05:30
1 parent 93b62d4aa5
commit 2e817e3167
2 files changed
+42 -2

No files matched your search

+26 -2
View File
@@ -298,6 +298,8 @@ func (d *Driver) Launch(ctx context.Context, bundleID string, clearState bool, e
return fmt.Errorf("launch %s: %w", d.bundleID, err)
}
d.waitForAccessibilityReady(ctx)
if !d.pastePrimed {
if err := warmUpPaste(ctx, d.makeRunner()); err != nil {
fmt.Fprintf(d.output, "paste warm-up failed (continuing): %v\n", err)
@@ -307,6 +309,28 @@ func (d *Driver) Launch(ctx context.Context, bundleID string, clearState bool, e
return nil
}
// accessibilityReadyTimeout bounds how long Launch waits for the freshly
// started app's accessibility tree to stop reporting placeholder values.
const accessibilityReadyTimeout = 10 * time.Second
// waitForAccessibilityReady polls the accessibility dump until no element
// value reads as the bridge's unresolved-value sentinel. During app cold start
// the bridge reports "Invalid" for values it cannot resolve yet; a snapshot
// taken in that window shows phantom field content. Launch owns this wait so
// the first post-launch snapshot is trustworthy; the per-step settle cap is
// too short to absorb a cold start.
func (d *Driver) waitForAccessibilityReady(ctx context.Context) {
deadline := time.Now().Add(accessibilityReadyTimeout)
for time.Now().Before(deadline) && ctx.Err() == nil {
dump, err := d.describeAll(ctx)
if err == nil && !hasUnresolvedValues(dump) {
return
}
time.Sleep(StabilityPollInterval)
}
fmt.Fprintln(d.output, "accessibility tree still reports unresolved values after launch; first snapshot may be unreliable")
}
// clearAppState resets the app to a first-launch state. With an app path it
// uninstalls and reinstalls; without one it falls back to wiping the app's data
// container and warns once that a full reinstall needs the app path.
@@ -455,11 +479,11 @@ func (d *Driver) resolveSelectorCenter(ctx context.Context, selector string) (in
}
func (d *Driver) InputText(ctx context.Context, text string) error {
// The field target is only needed for the pasteboard fallback. Resolving it
// The field target is only needed for the pasteboard path. Resolving it
// requires a describe-all, so the fast keyboard path skips that round-trip
// and lets inputText send the key presses directly.
var field fieldTarget
if _, skipped := typeString(text); len(skipped) > 0 {
if usesPasteboard(text) {
field = d.resolveInputField(ctx)
}
return inputText(ctx, d.makeRunner(), text, field)
@@ -17,6 +17,22 @@ type rawFrame struct {
// rawElement is one entry in the flat describe-all dump returned by the
// simulator companion. Only the fields the mapper consumes are declared;
// unknown fields are ignored.
// unresolvedValueSentinel is what the accessibility bridge reports for a value
// it cannot resolve yet (typically during app cold start). It is bridge state,
// not app content.
const unresolvedValueSentinel = "Invalid"
// hasUnresolvedValues reports whether any element in the flat dump still
// carries the bridge's unresolved-value sentinel.
func hasUnresolvedValues(dump []byte) bool {
for _, element := range decodeDump(dump) {
if stringValue(element.AXValue) == unresolvedValueSentinel {
return true
}
}
return false
}
type rawElement struct {
Frame rawFrame `json:"frame"`
AXUniqueID *string `json:"AXUniqueId"`