mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17: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)
|
return fmt.Errorf("launch %s: %w", d.bundleID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.waitForAccessibilityReady(ctx)
|
||||||
|
|
||||||
if !d.pastePrimed {
|
if !d.pastePrimed {
|
||||||
if err := warmUpPaste(ctx, d.makeRunner()); err != nil {
|
if err := warmUpPaste(ctx, d.makeRunner()); err != nil {
|
||||||
fmt.Fprintf(d.output, "paste warm-up failed (continuing): %v\n", err)
|
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
|
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
|
// 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
|
// 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.
|
// 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 {
|
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
|
// requires a describe-all, so the fast keyboard path skips that round-trip
|
||||||
// and lets inputText send the key presses directly.
|
// and lets inputText send the key presses directly.
|
||||||
var field fieldTarget
|
var field fieldTarget
|
||||||
if _, skipped := typeString(text); len(skipped) > 0 {
|
if usesPasteboard(text) {
|
||||||
field = d.resolveInputField(ctx)
|
field = d.resolveInputField(ctx)
|
||||||
}
|
}
|
||||||
return inputText(ctx, d.makeRunner(), text, field)
|
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
|
// rawElement is one entry in the flat describe-all dump returned by the
|
||||||
// simulator companion. Only the fields the mapper consumes are declared;
|
// simulator companion. Only the fields the mapper consumes are declared;
|
||||||
// unknown fields are ignored.
|
// 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 {
|
type rawElement struct {
|
||||||
Frame rawFrame `json:"frame"`
|
Frame rawFrame `json:"frame"`
|
||||||
AXUniqueID *string `json:"AXUniqueId"`
|
AXUniqueID *string `json:"AXUniqueId"`
|
||||||
|
|||||||
Reference in new issue
Block a user