From dbc6c73863bcf1a40e914cdfbf336c44d335014c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Thu, 25 Apr 2024 19:57:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Eui2=E6=8E=A7=E4=BB=B6?= =?UTF-8?q?=E7=82=B9=E5=87=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hrp/pkg/uixt/android_adb_driver.go | 63 +++++------------------------ hrp/pkg/uixt/android_layout.go | 63 +++++++++++++++++++++++++++++ hrp/pkg/uixt/android_test.go | 12 ++++++ hrp/pkg/uixt/android_uia2_driver.go | 30 +++++++++++++- 4 files changed, 112 insertions(+), 56 deletions(-) create mode 100644 hrp/pkg/uixt/android_layout.go diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index e3b15520..c1d6c2cc 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -34,56 +34,6 @@ type adbDriver struct { logcat *AdbLogcat } -type Hierarchy struct { - XMLName xml.Name `xml:"hierarchy"` - Nodes []Node `xml:"node"` -} - -type Node struct { - Index string `xml:"index,attr"` - Text string `xml:"text,attr"` - ResourceID string `xml:"resource-id,attr"` - Class string `xml:"class,attr"` - Package string `xml:"package,attr"` - ContentDesc string `xml:"content-desc,attr"` - Checkable string `xml:"checkable,attr"` - Checked string `xml:"checked,attr"` - Clickable string `xml:"clickable,attr"` - Enabled string `xml:"enabled,attr"` - Focusable string `xml:"focusable,attr"` - Focused string `xml:"focused,attr"` - Scrollable string `xml:"scrollable,attr"` - LongClickable string `xml:"long-clickable,attr"` - Password string `xml:"password,attr"` - Selected string `xml:"selected,attr"` - Bounds *Bounds `xml:"bounds,attr"` - Children []Node `xml:"node"` -} - -type Bounds struct { - X1, Y1, X2, Y2 int -} - -func (b *Bounds) UnmarshalXMLAttr(attr xml.Attr) error { - // 正则表达式用于解析格式为"[x1,y1][x2,y2]" - re := regexp.MustCompile(`\[(\d+),(\d+)]\[(\d+),(\d+)]`) - matches := re.FindStringSubmatch(attr.Value) - if matches == nil { - return fmt.Errorf("bounds format is incorrect") - } - // 转换字符串为整数 - b.X1, _ = strconv.Atoi(matches[1]) - b.Y1, _ = strconv.Atoi(matches[2]) - b.X2, _ = strconv.Atoi(matches[3]) - b.Y2, _ = strconv.Atoi(matches[4]) - return nil -} - -// Center 方法计算并返回 Bounds 中心点的坐标 -func (b *Bounds) Center() (float64, float64) { - return float64(b.X1+b.X2) / 2, float64(b.Y1+b.Y2) / 2 -} - func NewAdbDriver() *adbDriver { log.Info().Msg("init adb driver") return &adbDriver{} @@ -532,6 +482,11 @@ func (ad *adbDriver) Screenshot() (raw *bytes.Buffer, err error) { } func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) { + _, err = ad.adbClient.RunShellCommand("rm", "-rf", "/sdcard/window_dump.xml") + if err != nil { + return + } + // 高版本报错 ERROR: null root node returned by UiTestAutomationBridge. _, err = ad.adbClient.RunShellCommand("uiautomator", "dump") if err != nil { return @@ -562,7 +517,7 @@ func (ad *adbDriver) TapByText(text string, options ...ActionOption) error { } func (ad *adbDriver) tapByTextUsingHierarchy(hierarchy *Hierarchy, text string, options ...ActionOption) error { - bounds := ad.searchNodes(hierarchy.Nodes, text, options...) + bounds := ad.searchNodes(hierarchy.Layout, text, options...) actionOptions := NewActionOptions(options...) if len(bounds) == 0 { if actionOptions.IgnoreNotFoundError { @@ -596,11 +551,11 @@ func (ad *adbDriver) TapByTexts(actions ...TapTextAction) error { return nil } -func (ad *adbDriver) searchNodes(nodes []Node, text string, options ...ActionOption) []Bounds { +func (ad *adbDriver) searchNodes(nodes []Layout, text string, options ...ActionOption) []Bounds { actionOptions := NewActionOptions(options...) var results []Bounds for _, node := range nodes { - result := ad.searchNodes(node.Children, text, options...) + result := ad.searchNodes(node.Layout, text, options...) results = append(results, result...) if actionOptions.Regex { // regex on, check if match regex @@ -610,7 +565,7 @@ func (ad *adbDriver) searchNodes(nodes []Node, text string, options ...ActionOpt } else { // regex off, check if match exactly if node.Text != text { - ad.searchNodes(node.Children, text, options...) + ad.searchNodes(node.Layout, text, options...) continue } } diff --git a/hrp/pkg/uixt/android_layout.go b/hrp/pkg/uixt/android_layout.go new file mode 100644 index 00000000..471ded39 --- /dev/null +++ b/hrp/pkg/uixt/android_layout.go @@ -0,0 +1,63 @@ +package uixt + +import ( + "encoding/xml" + "fmt" + "regexp" + "strconv" +) + +// Define a common struct for shared attributes +type Attributes struct { + Index int `xml:"index,attr"` + Package string `xml:"package,attr"` + Class string `xml:"class,attr"` + Text string `xml:"text,attr"` + ResourceId string `xml:"resource-id,attr"` + Checkable bool `xml:"checkable,attr"` + Checked bool `xml:"checked,attr"` + Clickable bool `xml:"clickable,attr"` + Enabled bool `xml:"enabled,attr"` + Focusable bool `xml:"focusable,attr"` + Focused bool `xml:"focused,attr"` + LongClickable bool `xml:"long-clickable,attr"` + Password bool `xml:"password,attr"` + Scrollable bool `xml:"scrollable,attr"` + Selected bool `xml:"selected,attr"` + Bounds *Bounds `xml:"bounds,attr"` + Displayed bool `xml:"displayed,attr"` +} + +type Hierarchy struct { + XMLName xml.Name `xml:"hierarchy"` + Attributes + Layout []Layout `xml:",any"` +} + +type Layout struct { + Attributes + Layout []Layout `xml:",any"` +} + +type Bounds struct { + X1, Y1, X2, Y2 int +} + +func (b *Bounds) Center() (float64, float64) { + return float64(b.X1+b.X2) / 2, float64(b.Y1+b.Y2) / 2 +} + +func (b *Bounds) UnmarshalXMLAttr(attr xml.Attr) error { + // 正则表达式用于解析格式为"[x1,y1][x2,y2]" + re := regexp.MustCompile(`\[(\d+),(\d+)]\[(\d+),(\d+)]`) + matches := re.FindStringSubmatch(attr.Value) + if matches == nil { + return fmt.Errorf("bounds format is incorrect") + } + // 转换字符串为整数 + b.X1, _ = strconv.Atoi(matches[1]) + b.Y1, _ = strconv.Atoi(matches[2]) + b.X2, _ = strconv.Atoi(matches[3]) + b.Y2, _ = strconv.Atoi(matches[4]) + return nil +} diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index 4b85f5ca..03c7a555 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -132,6 +132,18 @@ func TestDriver_Source(t *testing.T) { t.Log(source) } +func TestDriver_TapByText(t *testing.T) { + driver, err := NewUIADriver(nil, uiaServerURL) + if err != nil { + t.Fatal(err) + } + + err = driver.TapByText("安装") + if err != nil { + t.Fatal(err) + } +} + func TestDriver_BatteryInfo(t *testing.T) { driver, err := NewUIADriver(nil, uiaServerURL) if err != nil { diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index 51e2cbbc..d20ef014 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" "encoding/json" + "encoding/xml" "fmt" "net" "net/http" @@ -599,10 +600,35 @@ func (ud *uiaDriver) Source(srcOpt ...SourceOption) (source string, err error) { return } +func (ud *uiaDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy, err error) { + source, err := ud.Source() + sourceTree = new(Hierarchy) + err = xml.Unmarshal([]byte(source), sourceTree) + if err != nil { + return + } + return +} + func (ud *uiaDriver) TapByText(text string, options ...ActionOption) error { - return ud.adbDriver.TapByText(text, options...) + sourceTree, err := ud.sourceTree() + if err != nil { + return err + } + return ud.tapByTextUsingHierarchy(sourceTree, text, options...) } func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error { - return ud.adbDriver.TapByTexts(actions...) + sourceTree, err := ud.sourceTree() + if err != nil { + return err + } + + for _, action := range actions { + err := ud.tapByTextUsingHierarchy(sourceTree, action.Text, action.Options...) + if err != nil { + return err + } + } + return nil }