修改元素选择器

This commit is contained in:
zyj
2025-11-09 21:11:13 +08:00
parent 0971b3ec31
commit 7469cf22ea
2 changed files with 302 additions and 170 deletions

View File

@@ -4,13 +4,19 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"go-mobile-uiautomator/adb" "go-mobile-uiautomator/adb"
selector "go-mobile-uiautomator/libs"
"go-mobile-uiautomator/services" "go-mobile-uiautomator/services"
"time" "time"
) )
const serial = "192.168.4.103:5555"
const addr = "127.0.0.1:5037"
func main() { func main() {
addr := "127.0.0.1:5037" selector.Example()
serial := "emulator-5556" }
func LaunchUiautomator() {
FilePushInstall() FilePushInstall()
go adb.LaunchUiautomator(addr, serial) go adb.LaunchUiautomator(addr, serial)
select {} select {}
@@ -18,8 +24,6 @@ func main() {
// 文件推送加安装 // 文件推送加安装
func FilePushInstall() { func FilePushInstall() {
addr := "127.0.0.1:5037"
serial := "emulator-5556"
payload, _ := adb.ListDevicesRaw(addr, 15*time.Second) payload, _ := adb.ListDevicesRaw(addr, 15*time.Second)
m := adb.ParseDevicesPayload(payload) m := adb.ParseDevicesPayload(payload)
b2, _ := json.MarshalIndent(m, "", " ") b2, _ := json.MarshalIndent(m, "", " ")
@@ -33,7 +37,6 @@ func FilePushInstall() {
// 文件推送验证 // 文件推送验证
func FilePush() { func FilePush() {
// edit these for your environment // edit these for your environment
addr := "127.0.0.1:5037"
local := "C:/Users/01/Desktop/aaa.PNG" local := "C:/Users/01/Desktop/aaa.PNG"
remote := "/sdcard/ccc.PNG" remote := "/sdcard/ccc.PNG"
mode := 0644 mode := 0644
@@ -66,7 +69,6 @@ func FilePush() {
// 连接验证 // 连接验证
func Connect() { func Connect() {
addr := "127.0.0.1:5037"
targetProduct := "23113RKC6C" targetProduct := "23113RKC6C"
serial, err := adb.FindSerialByProduct(addr, targetProduct) serial, err := adb.FindSerialByProduct(addr, targetProduct)

View File

@@ -1,98 +1,71 @@
package libs package selector
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"sort" "strconv"
"strings"
) )
const ( // FieldMeta 保存每个字段对应的 mask 位和默认值nil 表示无默认值)
MASK_TEXT uint32 = 0x00000001 type FieldMeta struct {
MASK_TEXT_CONTAINS uint32 = 0x00000002 Bit uint32
MASK_TEXT_MATCHES uint32 = 0x00000004 Default interface{}
MASK_TEXT_STARTS_WITH uint32 = 0x00000008
MASK_CLASS_NAME uint32 = 0x00000010
MASK_CLASS_NAME_MATCHES uint32 = 0x00000020
MASK_DESCRIPTION uint32 = 0x00000040
MASK_DESCRIPTION_CONTAINS uint32 = 0x00000080
MASK_DESCRIPTION_MATCHES uint32 = 0x00000100
MASK_DESCRIPTION_STARTS_WITH uint32 = 0x00000200
MASK_CHECKABLE uint32 = 0x00000400
MASK_CHECKED uint32 = 0x00000800
MASK_CLICKABLE uint32 = 0x00001000
MASK_LONG_CLICKABLE uint32 = 0x00002000
MASK_SCROLLABLE uint32 = 0x00004000
MASK_ENABLED uint32 = 0x00008000
MASK_FOCUSABLE uint32 = 0x00010000
MASK_FOCUSED uint32 = 0x00020000
MASK_SELECTED uint32 = 0x00040000
MASK_PACKAGE_NAME uint32 = 0x00080000
MASK_PACKAGE_NAME_MATCHES uint32 = 0x00100000
MASK_RESOURCE_ID uint32 = 0x00200000
MASK_RESOURCE_ID_MATCHES uint32 = 0x00400000
MASK_INDEX uint32 = 0x00800000
MASK_INSTANCE uint32 = 0x01000000
)
// Relationship type for building child/sibling chains.
type relationType string
const (
relChild relationType = "child"
relSibling relationType = "sibling"
)
// Field metadata mirrors Python __fields: name -> (mask, default)
type fieldMeta struct {
Mask uint32
Default any
} }
var allowedFields = map[string]fieldMeta{ // Selector 表示一个 UiSelector 的构造器
"text": {MASK_TEXT, nil},
"textContains": {MASK_TEXT_CONTAINS, nil},
"textMatches": {MASK_TEXT_MATCHES, nil},
"textStartsWith": {MASK_TEXT_STARTS_WITH, nil},
"className": {MASK_CLASS_NAME, nil},
"classNameMatches": {MASK_CLASS_NAME_MATCHES, nil},
"description": {MASK_DESCRIPTION, nil},
"descriptionContains": {MASK_DESCRIPTION_CONTAINS, nil},
"descriptionMatches": {MASK_DESCRIPTION_MATCHES, nil},
"descriptionStartsWith": {MASK_DESCRIPTION_STARTS_WITH, nil},
"checkable": {MASK_CHECKABLE, false},
"checked": {MASK_CHECKED, false},
"clickable": {MASK_CLICKABLE, false},
"longClickable": {MASK_LONG_CLICKABLE, false},
"scrollable": {MASK_SCROLLABLE, false},
"enabled": {MASK_ENABLED, false},
"focusable": {MASK_FOCUSABLE, false},
"focused": {MASK_FOCUSED, false},
"selected": {MASK_SELECTED, false},
"packageName": {MASK_PACKAGE_NAME, nil},
"packageNameMatches": {MASK_PACKAGE_NAME_MATCHES, nil},
"resourceId": {MASK_RESOURCE_ID, nil},
"resourceIdMatches": {MASK_RESOURCE_ID_MATCHES, nil},
"index": {MASK_INDEX, 0},
"instance": {MASK_INSTANCE, 0},
}
// Selector replicates Python Selector(dict) behavior with explicit APIs.
type Selector struct { type Selector struct {
// 存放字段及其值(只包含显式设置的字段)
fields map[string]interface{}
// mask 值(通过设置/删除字段自动维护)
mask uint32 mask uint32
fields map[string]any // only allowedFields keys permitted
childOrSibling []relationType // childOrSibling 顺序列表,元素为 "child" 或 "sibling"
childOrSiblingSelect []*Selector childOrSibling []string
// 对应的嵌套 Selector 列表,长度与 childOrSibling 相同
childOrSiblingSelector []*Selector
} }
// NewSelector initializes with kwargs like Python's Selector(**kwargs). // 字段元数据(与 Python 版本一致)
func NewSelector(kwargs map[string]any) (*Selector, error) { var fieldDefs = map[string]FieldMeta{
s := &Selector{ "text": {Bit: 0x01, Default: nil},
mask: 0, "textContains": {Bit: 0x02, Default: nil},
fields: make(map[string]any), "textMatches": {Bit: 0x04, Default: nil},
childOrSibling: make([]relationType, 0), "textStartsWith": {Bit: 0x08, Default: nil},
childOrSiblingSelect: make([]*Selector, 0), "className": {Bit: 0x10, Default: nil},
"classNameMatches": {Bit: 0x20, Default: nil},
"description": {Bit: 0x40, Default: nil},
"descriptionContains": {Bit: 0x80, Default: nil},
"descriptionMatches": {Bit: 0x0100, Default: nil},
"descriptionStartsWith": {Bit: 0x0200, Default: nil},
"checkable": {Bit: 0x0400, Default: false},
"checked": {Bit: 0x0800, Default: false},
"clickable": {Bit: 0x1000, Default: false},
"longClickable": {Bit: 0x2000, Default: false},
"scrollable": {Bit: 0x4000, Default: false},
"enabled": {Bit: 0x8000, Default: false},
"focusable": {Bit: 0x010000, Default: false},
"focused": {Bit: 0x020000, Default: false},
"selected": {Bit: 0x040000, Default: false},
"packageName": {Bit: 0x080000, Default: nil},
"packageNameMatches": {Bit: 0x100000, Default: nil},
"resourceId": {Bit: 0x200000, Default: nil},
"resourceIdMatches": {Bit: 0x400000, Default: nil},
"index": {Bit: 0x800000, Default: 0},
"instance": {Bit: 0x01000000, Default: 0},
} }
for k, v := range kwargs {
// New creates a Selector and可选传入初始字段
func New(initial map[string]interface{}) (*Selector, error) {
s := &Selector{
fields: make(map[string]interface{}),
childOrSibling: []string{},
childOrSiblingSelector: []*Selector{},
mask: 0,
}
for k, v := range initial {
if err := s.Set(k, v); err != nil { if err := s.Set(k, v); err != nil {
return nil, err return nil, err
} }
@@ -100,124 +73,281 @@ func NewSelector(kwargs map[string]any) (*Selector, error) {
return s, nil return s, nil
} }
// Set assigns a field and updates the bitmask. Only allowed fields permitted. // MustNew 跟 New 相同,但在错误时 panic便于简洁示例
func (s *Selector) Set(key string, val any) error { func MustNew(initial map[string]interface{}) *Selector {
meta, ok := allowedFields[key] s, err := New(initial)
if err != nil {
panic(err)
}
return s
}
// validateValue 对给定字段和值做类型校验(布尔字段与整数字段)
func validateValue(key string, val interface{}) error {
meta, ok := fieldDefs[key]
if !ok { if !ok {
return fmt.Errorf("field %s is not allowed", key)
}
if meta.Default == false {
// 期望 bool
_, ok := val.(bool)
if !ok {
return fmt.Errorf("%s must be bool", key)
}
return nil
}
// 对整数字段Default 为 int 类型)要求 int
switch d := meta.Default.(type) {
case int:
// 支持 int 和可被转为 int 的数值(如 int64
switch val.(type) {
case int, int8, int16, int32, int64:
return nil
case uint, uint8, uint16, uint32, uint64:
return nil
default:
return fmt.Errorf("%s must be integer type, default=%v", key, d)
}
default:
// 其它字段没有特别要求
return nil
}
}
// Set 设置字段并更新 mask若字段非法或类型不对则返回错误
func (s *Selector) Set(key string, val interface{}) error {
if _, ok := fieldDefs[key]; !ok {
return fmt.Errorf("%s is not allowed", key) return fmt.Errorf("%s is not allowed", key)
} }
if err := validateValue(key, val); err != nil {
return err
}
s.fields[key] = val s.fields[key] = val
s.mask |= meta.Mask s.mask = s.mask | fieldDefs[key].Bit
return nil return nil
} }
// Delete removes a field and clears its bit in mask. // Delete 删除字段并更新 mask幂等删除不存在字段不报错
func (s *Selector) Delete(key string) error { func (s *Selector) Delete(key string) error {
meta, ok := allowedFields[key] if _, ok := fieldDefs[key]; !ok {
if !ok {
return fmt.Errorf("%s is not allowed", key) return fmt.Errorf("%s is not allowed", key)
} }
if _, present := s.fields[key]; present {
delete(s.fields, key) delete(s.fields, key)
s.mask &^= meta.Mask // AND NOT s.mask = s.mask & ^fieldDefs[key].Bit
}
return nil return nil
} }
// Get retrieves a field (nil if absent). // Mask 返回当前 mask只读
func (s *Selector) Get(key string) (any, bool) { func (s *Selector) Mask() uint32 {
v, ok := s.fields[key] return s.mask
return v, ok
} }
// Mask returns current bit mask. // Child 在末尾添加 child
func (s *Selector) Mask() uint32 { return s.mask } func (s *Selector) Child(initial map[string]interface{}) (*Selector, error) {
child, err := New(initial)
// Child appends a child selector (in-place) and returns the receiver.
func (s *Selector) Child(kwargs map[string]any) (*Selector, error) {
sub, err := NewSelector(kwargs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.childOrSibling = append(s.childOrSibling, relChild) s.childOrSibling = append(s.childOrSibling, "child")
s.childOrSiblingSelect = append(s.childOrSiblingSelect, sub) s.childOrSiblingSelector = append(s.childOrSiblingSelector, child)
return s, nil return s, nil
} }
// Sibling appends a sibling selector (in-place) and returns the receiver. // Sibling 在末尾添加 sibling
func (s *Selector) Sibling(kwargs map[string]any) (*Selector, error) { func (s *Selector) Sibling(initial map[string]interface{}) (*Selector, error) {
sub, err := NewSelector(kwargs) child, err := New(initial)
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.childOrSibling = append(s.childOrSibling, relSibling) s.childOrSibling = append(s.childOrSibling, "sibling")
s.childOrSiblingSelect = append(s.childOrSiblingSelect, sub) s.childOrSiblingSelector = append(s.childOrSiblingSelector, child)
return s, nil return s, nil
} }
// Clone deep-copies selector, including child/sibling chains. // UpdateInstance 更新最后一个 childOrSiblingSelector 的 instance 字段(或根 selector
func (s *Selector) Clone() *Selector {
cp := &Selector{
mask: s.mask,
fields: make(map[string]any, len(s.fields)),
childOrSibling: make([]relationType, len(s.childOrSibling)),
childOrSiblingSelect: make([]*Selector, 0, len(s.childOrSiblingSelect)),
}
for k, v := range s.fields {
cp.fields[k] = v
}
copy(cp.childOrSibling, s.childOrSibling)
for _, sub := range s.childOrSiblingSelect {
cp.childOrSiblingSelect = append(cp.childOrSiblingSelect, sub.Clone())
}
return cp
}
// UpdateInstance updates 'instance' on the last child/sibling selector if present;
// otherwise updates the current selector's 'instance'.
func (s *Selector) UpdateInstance(i int) error { func (s *Selector) UpdateInstance(i int) error {
if len(s.childOrSiblingSelect) > 0 { n := len(s.childOrSiblingSelector)
last := s.childOrSiblingSelect[len(s.childOrSiblingSelect)-1] if n > 0 {
return last.Set("instance", i) return s.childOrSiblingSelector[n-1].Set("instance", i)
} }
return s.Set("instance", i) return s.Set("instance", i)
} }
// String prints Selector [k='v', ...] while skipping mask and empty child/sibling groups. // Clone 深拷贝 Selector包括子/兄弟
// Deterministic order for readability. func (s *Selector) Clone() *Selector {
func (s *Selector) String() string { clone := &Selector{
parts := make([]string, 0, len(s.fields)+2) fields: make(map[string]interface{}, len(s.fields)),
mask: s.mask,
// stable sort keys for consistent output childOrSibling: append([]string{}, s.childOrSibling...),
keys := make([]string, 0, len(s.fields)) childOrSiblingSelector: make([]*Selector, 0, len(s.childOrSiblingSelector)),
for k := range s.fields {
keys = append(keys, k)
} }
sort.Strings(keys) for k, v := range s.fields {
// 简单深拷贝对于常见类型string,bool,int直接赋值即可。
for _, k := range keys { // 若值为复杂结构,调用方应使用 ToMap/ToJSON 再 Parse 得到深拷贝。
// repr-like rendering clone.fields[k] = v
parts = append(parts, fmt.Sprintf("%s=%q", k, fmtAny(s.fields[k]))) }
for _, c := range s.childOrSiblingSelector {
clone.childOrSiblingSelector = append(clone.childOrSiblingSelector, c.Clone())
}
return clone
} }
// child/sibling only if present // ToMap 序列化为 map便于 RPC 调用或 JSON 编码
if len(s.childOrSibling) > 0 && len(s.childOrSiblingSelect) > 0 { func (s *Selector) ToMap() map[string]interface{} {
// render as chain: rel(type)->subselector out := make(map[string]interface{}, len(s.fields)+3)
chunks := make([]string, 0, len(s.childOrSibling)) for k, v := range s.fields {
for i, rel := range s.childOrSibling { out[k] = v
chunks = append(chunks, fmt.Sprintf("%s(%s)", rel, s.childOrSiblingSelect[i].String()))
} }
parts = append(parts, "chain=["+strings.Join(chunks, ", ")+"]") out["mask"] = s.mask
if len(s.childOrSibling) > 0 {
out["childOrSibling"] = append([]string{}, s.childOrSibling...)
cs := make([]map[string]interface{}, 0, len(s.childOrSiblingSelector))
for _, c := range s.childOrSiblingSelector {
cs = append(cs, c.ToMap())
}
out["childOrSiblingSelector"] = cs
}
return out
} }
return "Selector [" + strings.Join(parts, ", ") + "]" // ToJSON 返回 ToMap 的 JSON 编码
func (s *Selector) ToJSON() ([]byte, error) {
return json.Marshal(s.ToMap())
} }
func fmtAny(v any) string { // FromMap 从 map 恢复 Selector简单实现忽略非法字段
switch t := v.(type) { func FromMap(data map[string]interface{}) (*Selector, error) {
case string: // 提取根字段
return t root := &Selector{
case fmt.Stringer: fields: make(map[string]interface{}),
return t.String() childOrSibling: []string{},
childOrSiblingSelector: []*Selector{},
mask: 0,
}
// 读取已知字段
for k, meta := range fieldDefs {
if v, ok := data[k]; ok {
// 尝试 Set 以做类型校验并设置 mask
if err := root.Set(k, v); err != nil {
return nil, err
}
// 注意Set 已经更新了 mask
_ = meta
}
}
// 恢复 mask如果提供了 mask并且为数值
if m, ok := data["mask"]; ok {
switch mv := m.(type) {
case float64:
root.mask = uint32(mv)
case uint32:
root.mask = mv
case int:
root.mask = uint32(mv)
case int64:
root.mask = uint32(mv)
default: default:
return fmt.Sprintf("%v", v) // 忽略不能解析的 mask
} }
} }
// 恢复 childOrSibling 列表和对应 selector期望 childOrSiblingSelector 为 []map[string]interface{}
if cs, ok := data["childOrSibling"]; ok {
if arr, ok := cs.([]interface{}); ok {
for _, e := range arr {
if sname, ok := e.(string); ok {
root.childOrSibling = append(root.childOrSibling, sname)
}
}
}
}
if css, ok := data["childOrSiblingSelector"]; ok {
if arr, ok := css.([]interface{}); ok {
for _, item := range arr {
if m, ok := item.(map[string]interface{}); ok {
c, err := FromMap(m)
if err != nil {
return nil, err
}
root.childOrSiblingSelector = append(root.childOrSiblingSelector, c)
}
}
}
}
return root, nil
}
// UpdateAtPath 在指定路径child 索引链)上更新字段
// path: 逐级索引,例如 [0,2] 表示 childOrSiblingSelector[0].childOrSiblingSelector[2]
func (s *Selector) UpdateAtPath(path []int, updates map[string]interface{}) error {
node := s
for _, idx := range path {
if idx < 0 || idx >= len(node.childOrSiblingSelector) {
return errors.New("path out of range")
}
node = node.childOrSiblingSelector[idx]
}
for k, v := range updates {
if err := node.Set(k, v); err != nil {
return err
}
}
return nil
}
// String 实现 fmt.Stringer输出友好可读的 Selector 表示(类似 Python 的 __str__
func (s *Selector) String() string {
m := s.ToMap()
// 删除空的 childOrSibling 字段以保持简洁
if _, ok := m["childOrSibling"]; !ok {
delete(m, "childOrSibling")
delete(m, "childOrSiblingSelector")
}
b, _ := json.Marshal(m)
return "Selector " + string(b)
}
// Example 用法示例(不是正式测试,仅供快速手动运行)
func Example() {
// 初始化根 selector
root := MustNew(map[string]interface{}{
"className": "android.widget.LinearLayout",
})
// 添加 child
root.Child(map[string]interface{}{
"text": "下一步",
"instance": 0,
})
// 更新最后一个 child 的 instance
_ = root.UpdateInstance(2)
// 深拷贝
cpy := root.Clone()
// 序列化到 JSON
j, _ := cpy.ToJSON()
fmt.Println(string(j))
}
// 简单测试函数(你可在 package 内使用 testing 包将其改写成真正的单元测试)
func SimpleTests() {
// set & delete
s := MustNew(map[string]interface{}{"text": "hello"})
fmt.Println("mask after set:", strconv.FormatUint(uint64(s.Mask()), 10))
_ = s.Delete("text")
fmt.Println("mask after delete:", strconv.FormatUint(uint64(s.Mask()), 10))
// bool 类型校验
_, err := New(map[string]interface{}{"checkable": "yes"})
fmt.Println("expected error for bad bool:", err != nil)
// clone 深拷贝检查
s2 := MustNew(map[string]interface{}{"text": "a"})
s2.Child(map[string]interface{}{"text": "b", "instance": 1})
c := s2.Clone()
c.childOrSibling[0] = "sibling"
fmt.Println("original childOrSibling:", s2.childOrSibling[0], "clone childOrSibling:", c.childOrSibling[0])
}