mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
fix(ioscompanion): wait out unresolved accessibility values after launch
This commit is contained in:
1 parent
93b62d4aa5
commit
2e817e3167
2 files changed
+42
-2
No files matched your search
@@ -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"`
|
||||
|
||||
Reference in new issue
Block a user