diff --git a/devices/devicekit/source.go b/devices/devicekit/source.go index b77ea45f..d1e0fb04 100644 --- a/devices/devicekit/source.go +++ b/devices/devicekit/source.go @@ -24,6 +24,9 @@ type sourceTreeElement struct { Value *string `json:"value"` PlaceholderValue *string `json:"placeholderValue"` RawIdentifier *string `json:"rawIdentifier"` + Enabled *bool `json:"enabled"` + Selected *bool `json:"selected"` + HasFocus *bool `json:"hasFocus"` Rect sourceTreeElementRect `json:"rect"` Children []sourceTreeElement `json:"children"` } @@ -78,6 +81,9 @@ func filterSourceElements(source sourceTreeElement) []types.ScreenElement { Value: source.Value, Placeholder: source.PlaceholderValue, Identifier: source.RawIdentifier, + Enabled: source.Enabled, + Selected: source.Selected, + Focused: source.HasFocus, Rect: types.ScreenElementRect{ X: int(source.Rect.X), Y: int(source.Rect.Y), diff --git a/devices/devicekit/source_test.go b/devices/devicekit/source_test.go index ad063066..8ca6fb2e 100644 --- a/devices/devicekit/source_test.go +++ b/devices/devicekit/source_test.go @@ -334,6 +334,63 @@ func TestFilterSourceElementsRejectsUntaggedContainersWithEmptyIdentifier(t *tes } } +func boolPtr(b bool) *bool { + return &b +} + +func TestFilterSourceElementsPreservesStateAttributes(t *testing.T) { + // devicekit-ios emits enabled (only when false), selected and hasFocus + // (only when true); they must survive filtering onto the ScreenElement. + tree := sourceTreeElement{ + Type: "XCUIElementTypeOther", + Rect: visibleRect(0, 0, 402, 874), + Children: []sourceTreeElement{ + { + Type: "XCUIElementTypeButton", + Label: strPtr("Disabled Submit"), + Enabled: boolPtr(false), + Rect: visibleRect(24, 538, 354, 52), + }, + { + Type: "XCUIElementTypeButton", + Label: strPtr("Active Tab"), + Selected: boolPtr(true), + HasFocus: boolPtr(true), + Rect: visibleRect(0, 800, 100, 50), + }, + { + Type: "XCUIElementTypeButton", + Label: strPtr("Plain"), + Rect: visibleRect(200, 800, 100, 50), + }, + }, + } + + output := filterSourceElements(tree) + + if len(output) != 3 { + t.Fatalf("expected 3 elements, got %d: %+v", len(output), output) + } + + disabled := output[0] + if disabled.Enabled == nil || *disabled.Enabled != false { + t.Errorf("expected 'Disabled Submit' to have Enabled=false, got %+v", disabled.Enabled) + } + + active := output[1] + if active.Selected == nil || *active.Selected != true { + t.Errorf("expected 'Active Tab' to have Selected=true, got %+v", active.Selected) + } + if active.Focused == nil || *active.Focused != true { + t.Errorf("expected 'Active Tab' to have Focused=true, got %+v", active.Focused) + } + + plain := output[2] + if plain.Enabled != nil || plain.Selected != nil || plain.Focused != nil { + t.Errorf("expected 'Plain' to have no state attributes, got %+v", plain) + } +} + func TestFilterSourceElementsOmitsChildrenFromJsonWhenEmpty(t *testing.T) { // Leaf elements must not serialize an empty "children" array, so the // JSON output stays unchanged for consumers that expect leaves. diff --git a/types/screen.go b/types/screen.go index 7e18d2ca..88e41fbc 100644 --- a/types/screen.go +++ b/types/screen.go @@ -16,7 +16,7 @@ type ScreenElement struct { Placeholder *string `json:"placeholder,omitempty"` Identifier *string `json:"identifier,omitempty"` Rect ScreenElementRect `json:"rect"` - Focused *bool `json:"focused,omitempty"` // currently only on android tv + Focused *bool `json:"focused,omitempty"` // android tv, and ios when hasFocus is reported Enabled *bool `json:"enabled,omitempty"` // only set when false Checked *bool `json:"checked,omitempty"` // only set when true Selected *bool `json:"selected,omitempty"` // only set when true