fix: iOS spec driving, InputText replace semantics, native DoubleTap (#58)

* 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.
This commit is contained in:
pj authored and GitHub committed 2026-06-05 23:42:39 +05:30
1 parent ce65cc32a4
commit 19a470121f
19 files changed
+981 -190

No files matched your search

+146 -92
View File
@@ -213,6 +213,50 @@ func (x *Text) GetValue() string {
return ""
}
type EraseTextRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
CharacterCount int32 `protobuf:"varint,1,opt,name=character_count,json=characterCount,proto3" json:"character_count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EraseTextRequest) Reset() {
*x = EraseTextRequest{}
mi := &file_driverpb_driver_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EraseTextRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EraseTextRequest) ProtoMessage() {}
func (x *EraseTextRequest) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EraseTextRequest.ProtoReflect.Descriptor instead.
func (*EraseTextRequest) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{4}
}
func (x *EraseTextRequest) GetCharacterCount() int32 {
if x != nil {
return x.CharacterCount
}
return 0
}
type Selector struct {
state protoimpl.MessageState `protogen:"open.v1"`
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
@@ -222,7 +266,7 @@ type Selector struct {
func (x *Selector) Reset() {
*x = Selector{}
mi := &file_driverpb_driver_proto_msgTypes[4]
mi := &file_driverpb_driver_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -234,7 +278,7 @@ func (x *Selector) String() string {
func (*Selector) ProtoMessage() {}
func (x *Selector) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[4]
mi := &file_driverpb_driver_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -247,7 +291,7 @@ func (x *Selector) ProtoReflect() protoreflect.Message {
// Deprecated: Use Selector.ProtoReflect.Descriptor instead.
func (*Selector) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{4}
return file_driverpb_driver_proto_rawDescGZIP(), []int{5}
}
func (x *Selector) GetValue() string {
@@ -268,7 +312,7 @@ type Image struct {
func (x *Image) Reset() {
*x = Image{}
mi := &file_driverpb_driver_proto_msgTypes[5]
mi := &file_driverpb_driver_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -280,7 +324,7 @@ func (x *Image) String() string {
func (*Image) ProtoMessage() {}
func (x *Image) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[5]
mi := &file_driverpb_driver_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -293,7 +337,7 @@ func (x *Image) ProtoReflect() protoreflect.Message {
// Deprecated: Use Image.ProtoReflect.Descriptor instead.
func (*Image) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{5}
return file_driverpb_driver_proto_rawDescGZIP(), []int{6}
}
func (x *Image) GetPng() []byte {
@@ -326,7 +370,7 @@ type HierarchyJSON struct {
func (x *HierarchyJSON) Reset() {
*x = HierarchyJSON{}
mi := &file_driverpb_driver_proto_msgTypes[6]
mi := &file_driverpb_driver_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -338,7 +382,7 @@ func (x *HierarchyJSON) String() string {
func (*HierarchyJSON) ProtoMessage() {}
func (x *HierarchyJSON) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[6]
mi := &file_driverpb_driver_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -351,7 +395,7 @@ func (x *HierarchyJSON) ProtoReflect() protoreflect.Message {
// Deprecated: Use HierarchyJSON.ProtoReflect.Descriptor instead.
func (*HierarchyJSON) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{6}
return file_driverpb_driver_proto_rawDescGZIP(), []int{7}
}
func (x *HierarchyJSON) GetJson() string {
@@ -371,7 +415,7 @@ type SnapshotResponse struct {
func (x *SnapshotResponse) Reset() {
*x = SnapshotResponse{}
mi := &file_driverpb_driver_proto_msgTypes[7]
mi := &file_driverpb_driver_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -383,7 +427,7 @@ func (x *SnapshotResponse) String() string {
func (*SnapshotResponse) ProtoMessage() {}
func (x *SnapshotResponse) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[7]
mi := &file_driverpb_driver_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -396,7 +440,7 @@ func (x *SnapshotResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SnapshotResponse.ProtoReflect.Descriptor instead.
func (*SnapshotResponse) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{7}
return file_driverpb_driver_proto_rawDescGZIP(), []int{8}
}
func (x *SnapshotResponse) GetHierarchy() *HierarchyJSON {
@@ -422,7 +466,7 @@ type Duration struct {
func (x *Duration) Reset() {
*x = Duration{}
mi := &file_driverpb_driver_proto_msgTypes[8]
mi := &file_driverpb_driver_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -434,7 +478,7 @@ func (x *Duration) String() string {
func (*Duration) ProtoMessage() {}
func (x *Duration) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[8]
mi := &file_driverpb_driver_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -447,7 +491,7 @@ func (x *Duration) ProtoReflect() protoreflect.Message {
// Deprecated: Use Duration.ProtoReflect.Descriptor instead.
func (*Duration) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{8}
return file_driverpb_driver_proto_rawDescGZIP(), []int{9}
}
func (x *Duration) GetMillis() int64 {
@@ -468,7 +512,7 @@ type HealthStatus struct {
func (x *HealthStatus) Reset() {
*x = HealthStatus{}
mi := &file_driverpb_driver_proto_msgTypes[9]
mi := &file_driverpb_driver_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -480,7 +524,7 @@ func (x *HealthStatus) String() string {
func (*HealthStatus) ProtoMessage() {}
func (x *HealthStatus) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[9]
mi := &file_driverpb_driver_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -493,7 +537,7 @@ func (x *HealthStatus) ProtoReflect() protoreflect.Message {
// Deprecated: Use HealthStatus.ProtoReflect.Descriptor instead.
func (*HealthStatus) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{9}
return file_driverpb_driver_proto_rawDescGZIP(), []int{10}
}
func (x *HealthStatus) GetReady() bool {
@@ -528,7 +572,7 @@ type SwipeRequest struct {
func (x *SwipeRequest) Reset() {
*x = SwipeRequest{}
mi := &file_driverpb_driver_proto_msgTypes[10]
mi := &file_driverpb_driver_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -540,7 +584,7 @@ func (x *SwipeRequest) String() string {
func (*SwipeRequest) ProtoMessage() {}
func (x *SwipeRequest) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[10]
mi := &file_driverpb_driver_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -553,7 +597,7 @@ func (x *SwipeRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use SwipeRequest.ProtoReflect.Descriptor instead.
func (*SwipeRequest) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{10}
return file_driverpb_driver_proto_rawDescGZIP(), []int{11}
}
func (x *SwipeRequest) GetFrom() *Point {
@@ -588,7 +632,7 @@ type PressKeyRequest struct {
func (x *PressKeyRequest) Reset() {
*x = PressKeyRequest{}
mi := &file_driverpb_driver_proto_msgTypes[11]
mi := &file_driverpb_driver_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -600,7 +644,7 @@ func (x *PressKeyRequest) String() string {
func (*PressKeyRequest) ProtoMessage() {}
func (x *PressKeyRequest) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[11]
mi := &file_driverpb_driver_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -613,7 +657,7 @@ func (x *PressKeyRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use PressKeyRequest.ProtoReflect.Descriptor instead.
func (*PressKeyRequest) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{11}
return file_driverpb_driver_proto_rawDescGZIP(), []int{12}
}
func (x *PressKeyRequest) GetKey() string {
@@ -636,7 +680,7 @@ type RecentLogsRequest struct {
func (x *RecentLogsRequest) Reset() {
*x = RecentLogsRequest{}
mi := &file_driverpb_driver_proto_msgTypes[12]
mi := &file_driverpb_driver_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -648,7 +692,7 @@ func (x *RecentLogsRequest) String() string {
func (*RecentLogsRequest) ProtoMessage() {}
func (x *RecentLogsRequest) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[12]
mi := &file_driverpb_driver_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -661,7 +705,7 @@ func (x *RecentLogsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RecentLogsRequest.ProtoReflect.Descriptor instead.
func (*RecentLogsRequest) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{12}
return file_driverpb_driver_proto_rawDescGZIP(), []int{13}
}
func (x *RecentLogsRequest) GetSinceUnixMillis() int64 {
@@ -687,7 +731,7 @@ type LogEntries struct {
func (x *LogEntries) Reset() {
*x = LogEntries{}
mi := &file_driverpb_driver_proto_msgTypes[13]
mi := &file_driverpb_driver_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -699,7 +743,7 @@ func (x *LogEntries) String() string {
func (*LogEntries) ProtoMessage() {}
func (x *LogEntries) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[13]
mi := &file_driverpb_driver_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -712,7 +756,7 @@ func (x *LogEntries) ProtoReflect() protoreflect.Message {
// Deprecated: Use LogEntries.ProtoReflect.Descriptor instead.
func (*LogEntries) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{13}
return file_driverpb_driver_proto_rawDescGZIP(), []int{14}
}
func (x *LogEntries) GetEntries() []*LogEntry {
@@ -734,7 +778,7 @@ type LogEntry struct {
func (x *LogEntry) Reset() {
*x = LogEntry{}
mi := &file_driverpb_driver_proto_msgTypes[14]
mi := &file_driverpb_driver_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -746,7 +790,7 @@ func (x *LogEntry) String() string {
func (*LogEntry) ProtoMessage() {}
func (x *LogEntry) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[14]
mi := &file_driverpb_driver_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -759,7 +803,7 @@ func (x *LogEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use LogEntry.ProtoReflect.Descriptor instead.
func (*LogEntry) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{14}
return file_driverpb_driver_proto_rawDescGZIP(), []int{15}
}
func (x *LogEntry) GetUnixMillis() int64 {
@@ -799,7 +843,7 @@ type MetricsRequest struct {
func (x *MetricsRequest) Reset() {
*x = MetricsRequest{}
mi := &file_driverpb_driver_proto_msgTypes[15]
mi := &file_driverpb_driver_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -811,7 +855,7 @@ func (x *MetricsRequest) String() string {
func (*MetricsRequest) ProtoMessage() {}
func (x *MetricsRequest) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[15]
mi := &file_driverpb_driver_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -824,7 +868,7 @@ func (x *MetricsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use MetricsRequest.ProtoReflect.Descriptor instead.
func (*MetricsRequest) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{15}
return file_driverpb_driver_proto_rawDescGZIP(), []int{16}
}
func (x *MetricsRequest) GetBundleId() string {
@@ -845,7 +889,7 @@ type MetricsResponse struct {
func (x *MetricsResponse) Reset() {
*x = MetricsResponse{}
mi := &file_driverpb_driver_proto_msgTypes[16]
mi := &file_driverpb_driver_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -857,7 +901,7 @@ func (x *MetricsResponse) String() string {
func (*MetricsResponse) ProtoMessage() {}
func (x *MetricsResponse) ProtoReflect() protoreflect.Message {
mi := &file_driverpb_driver_proto_msgTypes[16]
mi := &file_driverpb_driver_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -870,7 +914,7 @@ func (x *MetricsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use MetricsResponse.ProtoReflect.Descriptor instead.
func (*MetricsResponse) Descriptor() ([]byte, []int) {
return file_driverpb_driver_proto_rawDescGZIP(), []int{16}
return file_driverpb_driver_proto_rawDescGZIP(), []int{17}
}
func (x *MetricsResponse) GetCpuPercent() float64 {
@@ -912,7 +956,9 @@ const file_driverpb_driver_proto_rawDesc = "" +
"\x01x\x18\x01 \x01(\x05R\x01x\x12\f\n" +
"\x01y\x18\x02 \x01(\x05R\x01y\"\x1c\n" +
"\x04Text\x12\x14\n" +
"\x05value\x18\x01 \x01(\tR\x05value\" \n" +
"\x05value\x18\x01 \x01(\tR\x05value\";\n" +
"\x10EraseTextRequest\x12'\n" +
"\x0fcharacter_count\x18\x01 \x01(\x05R\x0echaracterCount\" \n" +
"\bSelector\x12\x14\n" +
"\x05value\x18\x01 \x01(\tR\x05value\"G\n" +
"\x05Image\x12\x10\n" +
@@ -957,13 +1003,16 @@ const file_driverpb_driver_proto_rawDesc = "" +
"cpuPercent\x12\x1d\n" +
"\n" +
"heap_bytes\x18\x02 \x01(\x03R\theapBytes\x12,\n" +
"\x12total_memory_bytes\x18\x03 \x01(\x03R\x10totalMemoryBytes2\xff\b\n" +
"\x12total_memory_bytes\x18\x03 \x01(\x03R\x10totalMemoryBytes2\x98\n" +
"\n" +
"\x06Driver\x12J\n" +
"\x06Launch\x12#.sanderling.driver.v1.LaunchRequest\x1a\x1b.sanderling.driver.v1.Empty\x12E\n" +
"\tTerminate\x12\x1b.sanderling.driver.v1.Empty\x1a\x1b.sanderling.driver.v1.Empty\x12?\n" +
"\x03Tap\x12\x1b.sanderling.driver.v1.Point\x1a\x1b.sanderling.driver.v1.Empty\x12J\n" +
"\x03Tap\x12\x1b.sanderling.driver.v1.Point\x1a\x1b.sanderling.driver.v1.Empty\x12E\n" +
"\tDoubleTap\x12\x1b.sanderling.driver.v1.Point\x1a\x1b.sanderling.driver.v1.Empty\x12J\n" +
"\vTapSelector\x12\x1e.sanderling.driver.v1.Selector\x1a\x1b.sanderling.driver.v1.Empty\x12D\n" +
"\tInputText\x12\x1a.sanderling.driver.v1.Text\x1a\x1b.sanderling.driver.v1.Empty\x12H\n" +
"\tInputText\x12\x1a.sanderling.driver.v1.Text\x1a\x1b.sanderling.driver.v1.Empty\x12P\n" +
"\tEraseText\x12&.sanderling.driver.v1.EraseTextRequest\x1a\x1b.sanderling.driver.v1.Empty\x12H\n" +
"\x05Swipe\x12\".sanderling.driver.v1.SwipeRequest\x1a\x1b.sanderling.driver.v1.Empty\x12N\n" +
"\bPressKey\x12%.sanderling.driver.v1.PressKeyRequest\x1a\x1b.sanderling.driver.v1.Empty\x12E\n" +
"\tLongPress\x12\x1b.sanderling.driver.v1.Point\x1a\x1b.sanderling.driver.v1.Empty\x12F\n" +
@@ -990,66 +1039,71 @@ func file_driverpb_driver_proto_rawDescGZIP() []byte {
return file_driverpb_driver_proto_rawDescData
}
var file_driverpb_driver_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
var file_driverpb_driver_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
var file_driverpb_driver_proto_goTypes = []any{
(*Empty)(nil), // 0: sanderling.driver.v1.Empty
(*LaunchRequest)(nil), // 1: sanderling.driver.v1.LaunchRequest
(*Point)(nil), // 2: sanderling.driver.v1.Point
(*Text)(nil), // 3: sanderling.driver.v1.Text
(*Selector)(nil), // 4: sanderling.driver.v1.Selector
(*Image)(nil), // 5: sanderling.driver.v1.Image
(*HierarchyJSON)(nil), // 6: sanderling.driver.v1.HierarchyJSON
(*SnapshotResponse)(nil), // 7: sanderling.driver.v1.SnapshotResponse
(*Duration)(nil), // 8: sanderling.driver.v1.Duration
(*HealthStatus)(nil), // 9: sanderling.driver.v1.HealthStatus
(*SwipeRequest)(nil), // 10: sanderling.driver.v1.SwipeRequest
(*PressKeyRequest)(nil), // 11: sanderling.driver.v1.PressKeyRequest
(*RecentLogsRequest)(nil), // 12: sanderling.driver.v1.RecentLogsRequest
(*LogEntries)(nil), // 13: sanderling.driver.v1.LogEntries
(*LogEntry)(nil), // 14: sanderling.driver.v1.LogEntry
(*MetricsRequest)(nil), // 15: sanderling.driver.v1.MetricsRequest
(*MetricsResponse)(nil), // 16: sanderling.driver.v1.MetricsResponse
nil, // 17: sanderling.driver.v1.LaunchRequest.EnvEntry
(*EraseTextRequest)(nil), // 4: sanderling.driver.v1.EraseTextRequest
(*Selector)(nil), // 5: sanderling.driver.v1.Selector
(*Image)(nil), // 6: sanderling.driver.v1.Image
(*HierarchyJSON)(nil), // 7: sanderling.driver.v1.HierarchyJSON
(*SnapshotResponse)(nil), // 8: sanderling.driver.v1.SnapshotResponse
(*Duration)(nil), // 9: sanderling.driver.v1.Duration
(*HealthStatus)(nil), // 10: sanderling.driver.v1.HealthStatus
(*SwipeRequest)(nil), // 11: sanderling.driver.v1.SwipeRequest
(*PressKeyRequest)(nil), // 12: sanderling.driver.v1.PressKeyRequest
(*RecentLogsRequest)(nil), // 13: sanderling.driver.v1.RecentLogsRequest
(*LogEntries)(nil), // 14: sanderling.driver.v1.LogEntries
(*LogEntry)(nil), // 15: sanderling.driver.v1.LogEntry
(*MetricsRequest)(nil), // 16: sanderling.driver.v1.MetricsRequest
(*MetricsResponse)(nil), // 17: sanderling.driver.v1.MetricsResponse
nil, // 18: sanderling.driver.v1.LaunchRequest.EnvEntry
}
var file_driverpb_driver_proto_depIdxs = []int32{
17, // 0: sanderling.driver.v1.LaunchRequest.env:type_name -> sanderling.driver.v1.LaunchRequest.EnvEntry
6, // 1: sanderling.driver.v1.SnapshotResponse.hierarchy:type_name -> sanderling.driver.v1.HierarchyJSON
5, // 2: sanderling.driver.v1.SnapshotResponse.screenshot:type_name -> sanderling.driver.v1.Image
18, // 0: sanderling.driver.v1.LaunchRequest.env:type_name -> sanderling.driver.v1.LaunchRequest.EnvEntry
7, // 1: sanderling.driver.v1.SnapshotResponse.hierarchy:type_name -> sanderling.driver.v1.HierarchyJSON
6, // 2: sanderling.driver.v1.SnapshotResponse.screenshot:type_name -> sanderling.driver.v1.Image
2, // 3: sanderling.driver.v1.SwipeRequest.from:type_name -> sanderling.driver.v1.Point
2, // 4: sanderling.driver.v1.SwipeRequest.to:type_name -> sanderling.driver.v1.Point
14, // 5: sanderling.driver.v1.LogEntries.entries:type_name -> sanderling.driver.v1.LogEntry
15, // 5: sanderling.driver.v1.LogEntries.entries:type_name -> sanderling.driver.v1.LogEntry
1, // 6: sanderling.driver.v1.Driver.Launch:input_type -> sanderling.driver.v1.LaunchRequest
0, // 7: sanderling.driver.v1.Driver.Terminate:input_type -> sanderling.driver.v1.Empty
2, // 8: sanderling.driver.v1.Driver.Tap:input_type -> sanderling.driver.v1.Point
4, // 9: sanderling.driver.v1.Driver.TapSelector:input_type -> sanderling.driver.v1.Selector
3, // 10: sanderling.driver.v1.Driver.InputText:input_type -> sanderling.driver.v1.Text
10, // 11: sanderling.driver.v1.Driver.Swipe:input_type -> sanderling.driver.v1.SwipeRequest
11, // 12: sanderling.driver.v1.Driver.PressKey:input_type -> sanderling.driver.v1.PressKeyRequest
2, // 13: sanderling.driver.v1.Driver.LongPress:input_type -> sanderling.driver.v1.Point
0, // 14: sanderling.driver.v1.Driver.Screenshot:input_type -> sanderling.driver.v1.Empty
0, // 15: sanderling.driver.v1.Driver.Hierarchy:input_type -> sanderling.driver.v1.Empty
0, // 16: sanderling.driver.v1.Driver.Snapshot:input_type -> sanderling.driver.v1.Empty
12, // 17: sanderling.driver.v1.Driver.RecentLogs:input_type -> sanderling.driver.v1.RecentLogsRequest
8, // 18: sanderling.driver.v1.Driver.WaitForIdle:input_type -> sanderling.driver.v1.Duration
0, // 19: sanderling.driver.v1.Driver.Health:input_type -> sanderling.driver.v1.Empty
15, // 20: sanderling.driver.v1.Driver.Metrics:input_type -> sanderling.driver.v1.MetricsRequest
0, // 21: sanderling.driver.v1.Driver.Launch:output_type -> sanderling.driver.v1.Empty
0, // 22: sanderling.driver.v1.Driver.Terminate:output_type -> sanderling.driver.v1.Empty
0, // 23: sanderling.driver.v1.Driver.Tap:output_type -> sanderling.driver.v1.Empty
0, // 24: sanderling.driver.v1.Driver.TapSelector:output_type -> sanderling.driver.v1.Empty
0, // 25: sanderling.driver.v1.Driver.InputText:output_type -> sanderling.driver.v1.Empty
0, // 26: sanderling.driver.v1.Driver.Swipe:output_type -> sanderling.driver.v1.Empty
0, // 27: sanderling.driver.v1.Driver.PressKey:output_type -> sanderling.driver.v1.Empty
0, // 28: sanderling.driver.v1.Driver.LongPress:output_type -> sanderling.driver.v1.Empty
5, // 29: sanderling.driver.v1.Driver.Screenshot:output_type -> sanderling.driver.v1.Image
6, // 30: sanderling.driver.v1.Driver.Hierarchy:output_type -> sanderling.driver.v1.HierarchyJSON
7, // 31: sanderling.driver.v1.Driver.Snapshot:output_type -> sanderling.driver.v1.SnapshotResponse
13, // 32: sanderling.driver.v1.Driver.RecentLogs:output_type -> sanderling.driver.v1.LogEntries
0, // 33: sanderling.driver.v1.Driver.WaitForIdle:output_type -> sanderling.driver.v1.Empty
9, // 34: sanderling.driver.v1.Driver.Health:output_type -> sanderling.driver.v1.HealthStatus
16, // 35: sanderling.driver.v1.Driver.Metrics:output_type -> sanderling.driver.v1.MetricsResponse
21, // [21:36] is the sub-list for method output_type
6, // [6:21] is the sub-list for method input_type
2, // 9: sanderling.driver.v1.Driver.DoubleTap:input_type -> sanderling.driver.v1.Point
5, // 10: sanderling.driver.v1.Driver.TapSelector:input_type -> sanderling.driver.v1.Selector
3, // 11: sanderling.driver.v1.Driver.InputText:input_type -> sanderling.driver.v1.Text
4, // 12: sanderling.driver.v1.Driver.EraseText:input_type -> sanderling.driver.v1.EraseTextRequest
11, // 13: sanderling.driver.v1.Driver.Swipe:input_type -> sanderling.driver.v1.SwipeRequest
12, // 14: sanderling.driver.v1.Driver.PressKey:input_type -> sanderling.driver.v1.PressKeyRequest
2, // 15: sanderling.driver.v1.Driver.LongPress:input_type -> sanderling.driver.v1.Point
0, // 16: sanderling.driver.v1.Driver.Screenshot:input_type -> sanderling.driver.v1.Empty
0, // 17: sanderling.driver.v1.Driver.Hierarchy:input_type -> sanderling.driver.v1.Empty
0, // 18: sanderling.driver.v1.Driver.Snapshot:input_type -> sanderling.driver.v1.Empty
13, // 19: sanderling.driver.v1.Driver.RecentLogs:input_type -> sanderling.driver.v1.RecentLogsRequest
9, // 20: sanderling.driver.v1.Driver.WaitForIdle:input_type -> sanderling.driver.v1.Duration
0, // 21: sanderling.driver.v1.Driver.Health:input_type -> sanderling.driver.v1.Empty
16, // 22: sanderling.driver.v1.Driver.Metrics:input_type -> sanderling.driver.v1.MetricsRequest
0, // 23: sanderling.driver.v1.Driver.Launch:output_type -> sanderling.driver.v1.Empty
0, // 24: sanderling.driver.v1.Driver.Terminate:output_type -> sanderling.driver.v1.Empty
0, // 25: sanderling.driver.v1.Driver.Tap:output_type -> sanderling.driver.v1.Empty
0, // 26: sanderling.driver.v1.Driver.DoubleTap:output_type -> sanderling.driver.v1.Empty
0, // 27: sanderling.driver.v1.Driver.TapSelector:output_type -> sanderling.driver.v1.Empty
0, // 28: sanderling.driver.v1.Driver.InputText:output_type -> sanderling.driver.v1.Empty
0, // 29: sanderling.driver.v1.Driver.EraseText:output_type -> sanderling.driver.v1.Empty
0, // 30: sanderling.driver.v1.Driver.Swipe:output_type -> sanderling.driver.v1.Empty
0, // 31: sanderling.driver.v1.Driver.PressKey:output_type -> sanderling.driver.v1.Empty
0, // 32: sanderling.driver.v1.Driver.LongPress:output_type -> sanderling.driver.v1.Empty
6, // 33: sanderling.driver.v1.Driver.Screenshot:output_type -> sanderling.driver.v1.Image
7, // 34: sanderling.driver.v1.Driver.Hierarchy:output_type -> sanderling.driver.v1.HierarchyJSON
8, // 35: sanderling.driver.v1.Driver.Snapshot:output_type -> sanderling.driver.v1.SnapshotResponse
14, // 36: sanderling.driver.v1.Driver.RecentLogs:output_type -> sanderling.driver.v1.LogEntries
0, // 37: sanderling.driver.v1.Driver.WaitForIdle:output_type -> sanderling.driver.v1.Empty
10, // 38: sanderling.driver.v1.Driver.Health:output_type -> sanderling.driver.v1.HealthStatus
17, // 39: sanderling.driver.v1.Driver.Metrics:output_type -> sanderling.driver.v1.MetricsResponse
23, // [23:40] is the sub-list for method output_type
6, // [6:23] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
@@ -1066,7 +1120,7 @@ func file_driverpb_driver_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_driverpb_driver_proto_rawDesc), len(file_driverpb_driver_proto_rawDesc)),
NumEnums: 0,
NumMessages: 18,
NumMessages: 19,
NumExtensions: 0,
NumServices: 1,
},
+12
View File
@@ -11,8 +11,16 @@ service Driver {
rpc Terminate(Empty) returns (Empty);
rpc Tap(Point) returns (Empty);
// DoubleTap lands two taps as close together as the platform allows.
// Composing two Tap calls from the client spreads them by hundreds of
// milliseconds on iOS, wide enough for navigation to interleave, which
// defeats double-submission testing.
rpc DoubleTap(Point) returns (Empty);
rpc TapSelector(Selector) returns (Empty);
rpc InputText(Text) returns (Empty);
// EraseText deletes characters from the focused field so InputText can
// replace existing content instead of appending to it.
rpc EraseText(EraseTextRequest) returns (Empty);
rpc Swipe(SwipeRequest) returns (Empty);
rpc PressKey(PressKeyRequest) returns (Empty);
rpc LongPress(Point) returns (Empty);
@@ -47,6 +55,10 @@ message Text {
string value = 1;
}
message EraseTextRequest {
int32 character_count = 1;
}
message Selector {
string value = 1;
}
+88
View File
@@ -22,8 +22,10 @@ const (
Driver_Launch_FullMethodName = "/sanderling.driver.v1.Driver/Launch"
Driver_Terminate_FullMethodName = "/sanderling.driver.v1.Driver/Terminate"
Driver_Tap_FullMethodName = "/sanderling.driver.v1.Driver/Tap"
Driver_DoubleTap_FullMethodName = "/sanderling.driver.v1.Driver/DoubleTap"
Driver_TapSelector_FullMethodName = "/sanderling.driver.v1.Driver/TapSelector"
Driver_InputText_FullMethodName = "/sanderling.driver.v1.Driver/InputText"
Driver_EraseText_FullMethodName = "/sanderling.driver.v1.Driver/EraseText"
Driver_Swipe_FullMethodName = "/sanderling.driver.v1.Driver/Swipe"
Driver_PressKey_FullMethodName = "/sanderling.driver.v1.Driver/PressKey"
Driver_LongPress_FullMethodName = "/sanderling.driver.v1.Driver/LongPress"
@@ -43,8 +45,16 @@ type DriverClient interface {
Launch(ctx context.Context, in *LaunchRequest, opts ...grpc.CallOption) (*Empty, error)
Terminate(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
Tap(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Empty, error)
// DoubleTap lands two taps as close together as the platform allows.
// Composing two Tap calls from the client spreads them by hundreds of
// milliseconds on iOS, wide enough for navigation to interleave, which
// defeats double-submission testing.
DoubleTap(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Empty, error)
TapSelector(ctx context.Context, in *Selector, opts ...grpc.CallOption) (*Empty, error)
InputText(ctx context.Context, in *Text, opts ...grpc.CallOption) (*Empty, error)
// EraseText deletes characters from the focused field so InputText can
// replace existing content instead of appending to it.
EraseText(ctx context.Context, in *EraseTextRequest, opts ...grpc.CallOption) (*Empty, error)
Swipe(ctx context.Context, in *SwipeRequest, opts ...grpc.CallOption) (*Empty, error)
PressKey(ctx context.Context, in *PressKeyRequest, opts ...grpc.CallOption) (*Empty, error)
LongPress(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Empty, error)
@@ -98,6 +108,16 @@ func (c *driverClient) Tap(ctx context.Context, in *Point, opts ...grpc.CallOpti
return out, nil
}
func (c *driverClient) DoubleTap(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, Driver_DoubleTap_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *driverClient) TapSelector(ctx context.Context, in *Selector, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
@@ -118,6 +138,16 @@ func (c *driverClient) InputText(ctx context.Context, in *Text, opts ...grpc.Cal
return out, nil
}
func (c *driverClient) EraseText(ctx context.Context, in *EraseTextRequest, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, Driver_EraseText_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *driverClient) Swipe(ctx context.Context, in *SwipeRequest, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
@@ -225,8 +255,16 @@ type DriverServer interface {
Launch(context.Context, *LaunchRequest) (*Empty, error)
Terminate(context.Context, *Empty) (*Empty, error)
Tap(context.Context, *Point) (*Empty, error)
// DoubleTap lands two taps as close together as the platform allows.
// Composing two Tap calls from the client spreads them by hundreds of
// milliseconds on iOS, wide enough for navigation to interleave, which
// defeats double-submission testing.
DoubleTap(context.Context, *Point) (*Empty, error)
TapSelector(context.Context, *Selector) (*Empty, error)
InputText(context.Context, *Text) (*Empty, error)
// EraseText deletes characters from the focused field so InputText can
// replace existing content instead of appending to it.
EraseText(context.Context, *EraseTextRequest) (*Empty, error)
Swipe(context.Context, *SwipeRequest) (*Empty, error)
PressKey(context.Context, *PressKeyRequest) (*Empty, error)
LongPress(context.Context, *Point) (*Empty, error)
@@ -259,12 +297,18 @@ func (UnimplementedDriverServer) Terminate(context.Context, *Empty) (*Empty, err
func (UnimplementedDriverServer) Tap(context.Context, *Point) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method Tap not implemented")
}
func (UnimplementedDriverServer) DoubleTap(context.Context, *Point) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method DoubleTap not implemented")
}
func (UnimplementedDriverServer) TapSelector(context.Context, *Selector) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method TapSelector not implemented")
}
func (UnimplementedDriverServer) InputText(context.Context, *Text) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method InputText not implemented")
}
func (UnimplementedDriverServer) EraseText(context.Context, *EraseTextRequest) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method EraseText not implemented")
}
func (UnimplementedDriverServer) Swipe(context.Context, *SwipeRequest) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method Swipe not implemented")
}
@@ -370,6 +414,24 @@ func _Driver_Tap_Handler(srv interface{}, ctx context.Context, dec func(interfac
return interceptor(ctx, in, info, handler)
}
func _Driver_DoubleTap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Point)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DriverServer).DoubleTap(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Driver_DoubleTap_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DriverServer).DoubleTap(ctx, req.(*Point))
}
return interceptor(ctx, in, info, handler)
}
func _Driver_TapSelector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Selector)
if err := dec(in); err != nil {
@@ -406,6 +468,24 @@ func _Driver_InputText_Handler(srv interface{}, ctx context.Context, dec func(in
return interceptor(ctx, in, info, handler)
}
func _Driver_EraseText_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EraseTextRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DriverServer).EraseText(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Driver_EraseText_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DriverServer).EraseText(ctx, req.(*EraseTextRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Driver_Swipe_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SwipeRequest)
if err := dec(in); err != nil {
@@ -605,6 +685,10 @@ var Driver_ServiceDesc = grpc.ServiceDesc{
MethodName: "Tap",
Handler: _Driver_Tap_Handler,
},
{
MethodName: "DoubleTap",
Handler: _Driver_DoubleTap_Handler,
},
{
MethodName: "TapSelector",
Handler: _Driver_TapSelector_Handler,
@@ -613,6 +697,10 @@ var Driver_ServiceDesc = grpc.ServiceDesc{
MethodName: "InputText",
Handler: _Driver_InputText_Handler,
},
{
MethodName: "EraseText",
Handler: _Driver_EraseText_Handler,
},
{
MethodName: "Swipe",
Handler: _Driver_Swipe_Handler,
+2
View File
@@ -16,8 +16,10 @@ func TestDriverServiceDescriptor(t *testing.T) {
"Launch": true,
"Terminate": true,
"Tap": true,
"DoubleTap": true,
"TapSelector": true,
"InputText": true,
"EraseText": true,
"Swipe": true,
"PressKey": true,
"LongPress": true,