mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
* fix(hierarchy): bounds-containment fallback for scoped and path queries Compose on iOS surfaces a testTag node as an empty leaf sibling of the content it labels instead of as an ancestor, so descendant search under the tagged node finds nothing and every path or scoped query returns null. When structural search yields no match, fall back to nodes whose bounds lie inside the scope node's bounds. * feat(sidecar): derive iOS clickable and editable from element type The XCTest hierarchy mapping dropped the element type, leaving no clickable or editable flags on iOS, so the fuzzer's tap and typing verbs never found a candidate inside the app. Map the raw accessibility tree directly and derive clickable, editable, scrollable, and class from the XCUIElementType raw value. * feat(proto): add EraseText RPC for InputText replace semantics * feat(driver): add EraseText to the device driver surface * fix(runner): erase existing field text before InputText InputText appended on native platforms, so repeated draws grew fields without bound. The folio fuzz run wedged on the add-account screen: each draw concatenated another name until the 40-character validation error became permanent. Replace semantics also makes retried typing idempotent. The web driver already replaced via select-all; native now matches. * feat(sidecar): EraseText backend support on android and ios * fix(folio): saturation-gate account creation in the spec The 2-3 step add-account loop outcompeted the 5-step transaction chain at every weighted re-draw, so runs filled with account creation and rarely exercised the balance properties. Stop offering add-account once three accounts exist; the renormalized weights then favor the transaction flow at every step of its chain. * fix(folio): author spec weights to match testing intent Revert the account saturation gate: it starved newAccountBalanceIsZero once it tripped, and a magic account count is app-state tuning, not intent. Instead weight the generators by what the properties need: the transaction chain leads, account creation stays exercised, and doubleTaps gets explicit weight everywhere because double-submission idempotency is what the spec is testing for. * fix(folio): lower doubleTaps weight to 5 * fix(sidecar): surface visible text on iOS static elements Static text and button strings live in the accessibility label on iOS, so the text attribute came through empty and every balance extractor parsed to zero, silently disarming both folio properties. Non-editable elements now fall back title, value, then label; editable fields keep value-only so an empty field's caption does not read as content. * feat(driver): native DoubleTap RPC for a tight inter-tap gap Composing two Tap round trips from the Go client spread the taps by hundreds of milliseconds on iOS, wide enough for the app to navigate between them, so double-submission races could never reproduce. The sidecar now lands both taps back-to-back next to the device transport. * feat(sidecar): pipeline iOS double-tap requests Queue the second tap at the XCTest runner while the first executes. The runner serializes handlers, so this is the tightest gap the transport allows (~350ms per tap round trip); recorded here with measurements for the iOS double-tap limitation.
123 lines
5.1 KiB
Go
123 lines
5.1 KiB
Go
// Package driver defines the platform-agnostic device automation interface and shared types.
|
|
package driver
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
)
|
|
|
|
// DeviceDriver abstracts the platform-specific UI automation backend. v0.1
|
|
// surface matches proto/driverpb/driver.proto. The sidecar implementation
|
|
// lives under driver/sidecar; the web implementation under driver/chrome;
|
|
// tests use driver/mock.
|
|
type DeviceDriver interface {
|
|
Launch(ctx context.Context, bundleID string, clearState bool, env map[string]string) error
|
|
Terminate(ctx context.Context) error
|
|
|
|
Tap(ctx context.Context, x, y int) error
|
|
TapSelector(ctx context.Context, selector string) error
|
|
DoubleTap(ctx context.Context, x, y int) error
|
|
DoubleTapSelector(ctx context.Context, selector string) error
|
|
InputText(ctx context.Context, text string) error
|
|
// EraseText deletes characterCount characters from the focused field.
|
|
// The runner calls it before InputText so the verb replaces existing
|
|
// content instead of appending to it.
|
|
EraseText(ctx context.Context, characterCount int) error
|
|
Swipe(ctx context.Context, fromX, fromY, toX, toY int, duration time.Duration) error
|
|
PressKey(ctx context.Context, key string) error
|
|
LongPress(ctx context.Context, x, y int) error
|
|
|
|
Hierarchy(ctx context.Context) (string, error)
|
|
Screenshot(ctx context.Context) (Image, error)
|
|
// Snapshot returns the hierarchy and screenshot captured back-to-back
|
|
// under a backend-side mutex, so the pair describes the same on-device
|
|
// frame. Prefer this over calling Hierarchy and Screenshot separately:
|
|
// independent reads can land on different frames during transitions.
|
|
Snapshot(ctx context.Context) (string, Image, error)
|
|
// RecentLogs returns log entries at or after `since`, filtered to
|
|
// `minLevel` or above. An empty minLevel defaults to "E".
|
|
RecentLogs(ctx context.Context, since time.Time, minLevel string) ([]LogEntry, error)
|
|
|
|
WaitForIdle(ctx context.Context, duration time.Duration) error
|
|
Health(ctx context.Context) (Health, error)
|
|
// Metrics samples the app's CPU and memory at the time of the call.
|
|
// CPUPercent is percent of a single core (multi-core apps can exceed
|
|
// 100). HeapBytes is resident set size; TotalMemoryBytes includes
|
|
// native allocations.
|
|
Metrics(ctx context.Context, bundleID string) (Metrics, error)
|
|
}
|
|
|
|
// ForegroundChecker is the optional capability for reporting which app is
|
|
// currently in the foreground. The runner uses it to keep exploration scoped
|
|
// to the app under test: when an action backs out of (or otherwise leaves) the
|
|
// app, the runner relaunches it before acting again. Drivers that cannot
|
|
// determine the foreground app simply do not implement this interface.
|
|
type ForegroundChecker interface {
|
|
// ForegroundApp returns the bundle id / package of the app currently in
|
|
// the foreground. An empty string means "unknown" and the runner skips
|
|
// enforcement for that step rather than relaunching blindly.
|
|
ForegroundApp(ctx context.Context) (string, error)
|
|
}
|
|
|
|
// FocusedWindowChecker is the optional capability for reporting which app owns
|
|
// the focused (on-screen) window. The startup gate prefers it over
|
|
// ForegroundChecker: the resumed-activity signal flips to a freshly launched
|
|
// app before its first frame draws, so observing on it alone can capture the
|
|
// previous app's screen. The focused window only names the app once its window
|
|
// is actually up.
|
|
type FocusedWindowChecker interface {
|
|
// FocusedWindowApp returns the package owning the focused window, or ""
|
|
// when no window is focused yet (e.g. mid-launch transition).
|
|
FocusedWindowApp(ctx context.Context) (string, error)
|
|
}
|
|
|
|
type LogEntry struct {
|
|
UnixMillis int64
|
|
Level string
|
|
Tag string
|
|
Message string
|
|
}
|
|
|
|
type Image struct {
|
|
PNG []byte
|
|
Width int
|
|
Height int
|
|
}
|
|
|
|
type Health struct {
|
|
Ready bool
|
|
Version string
|
|
Platform string
|
|
}
|
|
|
|
type Metrics struct {
|
|
CPUPercent float64
|
|
HeapBytes int64
|
|
TotalMemoryBytes int64
|
|
}
|
|
|
|
// WebDriver is the optional capability surface exposed by the chrome driver
|
|
// for the V8-native tick path. The runner type-asserts on this interface;
|
|
// mobile drivers stay binary-compatible by simply not implementing it.
|
|
//
|
|
// Element references never cross V8/host. V8 serializes targets as {x, y}
|
|
// (or bounds) into the returned WebAction JSON; the host dispatches via the
|
|
// normal DeviceDriver methods (Tap, InputText, etc.).
|
|
type WebDriver interface {
|
|
// InstallBundle injects the given JS source so it runs once per
|
|
// freshly-navigated document, plus immediately in the current page.
|
|
// The bundle is expected to register globals
|
|
// `__sanderlingExtractors__` and `__sanderlingNextAction__` on
|
|
// `window`.
|
|
InstallBundle(ctx context.Context, source []byte) error
|
|
// EvaluateExtractors invokes the extractor table installed by the
|
|
// bundle and returns each extractor's JSON-encoded current value
|
|
// keyed by its registration index.
|
|
EvaluateExtractors(ctx context.Context) (map[int]json.RawMessage, error)
|
|
// NextActionFromV8 invokes the action generator installed by the
|
|
// bundle and returns the resulting Action JSON for the host to
|
|
// dispatch. The shape mirrors verifier.Action's JSON form.
|
|
NextActionFromV8(ctx context.Context) (json.RawMessage, error)
|
|
}
|