From d7d6f76c9350c15b699c8b2271aaedff2f411bf3 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 15:02:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=8C=89=E6=8E=A7?= =?UTF-8?q?=E4=BB=B6=E7=82=B9=E5=87=BB=EF=BC=8C=E8=8E=B7=E5=8F=96=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=BA=94=E7=94=A8=EF=BC=8C=E4=BF=AE=E6=94=B9=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hrp/internal/builtin/utils.go | 7 ++ hrp/pkg/gadb/device.go | 64 ++++++++-- hrp/pkg/gadb/device_test.go | 50 +++++++- hrp/pkg/uixt/action.go | 5 + hrp/pkg/uixt/android_adb_driver.go | 177 ++++++++++++++++++++++++---- hrp/pkg/uixt/android_device.go | 78 ++++++------ hrp/pkg/uixt/android_test.go | 33 ++++-- hrp/pkg/uixt/android_uia2_driver.go | 8 ++ hrp/pkg/uixt/interface.go | 5 + hrp/pkg/uixt/ios_driver.go | 8 ++ 10 files changed, 345 insertions(+), 90 deletions(-) diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 918530b3..9f1b24d5 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -483,3 +483,10 @@ func ConvertToStringSlice(val interface{}) ([]string, error) { } return nil, fmt.Errorf("invalid type for conversion to []string") } + +func GetCurrentDay() string { + now := time.Now() + // 格式化日期为 yyyyMMdd + formattedDate := now.Format("20060102") + return formattedDate +} diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index c9f61602..37092e84 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -107,25 +107,38 @@ func (d *Device) features() (features Features, err error) { return features, nil } -func (d Device) HasAttribute(key string) bool { +func (d *Device) HasAttribute(key string) bool { _, ok := d.attrs[key] return ok } -func (d Device) Product() (string, error) { +func (d *Device) Product() (string, error) { if d.HasAttribute("product") { return d.attrs["product"], nil } return "", errors.New("does not have attribute: product") } -func (d Device) Model() (string, error) { +func (d *Device) Model() (string, error) { if d.HasAttribute("model") { return d.attrs["model"], nil } return "", errors.New("does not have attribute: model") } +func (d *Device) Brand() (string, error) { + if d.HasAttribute("brand") { + return d.attrs["brand"], nil + } + brand, err := d.RunShellCommand("getprop", "ro.product.brand") + brand = strings.TrimSpace(brand) + if err != nil { + return "", errors.New("does not have attribute: brand") + } + d.attrs["brand"] = brand + return brand, nil +} + func (d *Device) Usb() (string, error) { if d.HasAttribute("usb") { return d.attrs["usb"], nil @@ -133,7 +146,7 @@ func (d *Device) Usb() (string, error) { return "", errors.New("does not have attribute: usb") } -func (d Device) transportId() (string, error) { +func (d *Device) transportId() (string, error) { if d.HasAttribute("transport_id") { return d.attrs["transport_id"], nil } @@ -524,7 +537,7 @@ func (d *Device) Pull(remotePath string, dest io.Writer) (err error) { return } -func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) { +func (d *Device) installViaABBExec(apk io.ReadSeeker, args ...string) (raw []byte, err error) { var ( tp transport filesize int64 @@ -537,8 +550,11 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) { return nil, err } defer func() { _ = tp.Close() }() - - cmd := fmt.Sprintf("abb_exec:package\x00install\x00-t\x00-S\x00%d", filesize) + cmd := "abb_exec:package\x00install\x00-t" + for _, arg := range args { + cmd += "\x00" + arg + } + cmd += fmt.Sprintf("\x00-S\x00%d", filesize) if err = tp.SendWithCheck(cmd); err != nil { return nil, err } @@ -555,7 +571,7 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) { return } -func (d *Device) InstallAPK(apk io.ReadSeeker) (string, error) { +func (d *Device) InstallAPK(apk io.ReadSeeker, args ...string) (string, error) { haserr := func(ret string) bool { return strings.Contains(ret, "Failure") } @@ -575,8 +591,9 @@ func (d *Device) InstallAPK(apk io.ReadSeeker) (string, error) { if err != nil { return "", fmt.Errorf("error pushing: %v", err) } - - res, err := d.RunShellCommand("pm", "install", "-f", remote) + args = append([]string{"install"}, args...) + args = append(args, "-f", remote) + res, err := d.RunShellCommand("pm", args...) if err != nil { return "", errors.Wrap(err, "install apk failed") } @@ -603,6 +620,33 @@ func (d *Device) Uninstall(packageName string, keepData ...bool) (string, error) return d.RunShellCommand("pm", args...) } +func (d *Device) ListPackages() ([]string, error) { + args := []string{"list", "packages"} + resRaw, err := d.RunShellCommand("pm", args...) + if err != nil { + return []string{}, err + } + lines := strings.Split(resRaw, "\n") + var packages []string + for _, line := range lines { + packageName := strings.TrimPrefix(line, "package:") + packages = append(packages, packageName) + } + return packages, nil +} + +func (d *Device) IsPackagesInstalled(packageName string) bool { + packages, err := d.ListPackages() + if err != nil { + return false + } + packageName = strings.ReplaceAll(packageName, " ", "") + if len(packageName) == 0 { + return false + } + return builtin.Contains(packages, packageName) +} + func (d *Device) ScreenCap() ([]byte, error) { if d.HasFeature(FeatShellV2) { return d.RunShellCommandV2WithBytes("screencap", "-p") diff --git a/hrp/pkg/gadb/device_test.go b/hrp/pkg/gadb/device_test.go index 50b37175..5f3ba45c 100644 --- a/hrp/pkg/gadb/device_test.go +++ b/hrp/pkg/gadb/device_test.go @@ -60,7 +60,10 @@ func TestDevice_Product(t *testing.T) { for i := range devices { dev := devices[i] - product := dev.Product() + product, err := dev.Product() + if err != nil { + t.Fatal(err) + } t.Log(dev.Serial(), product) } } @@ -70,7 +73,24 @@ func TestDevice_Model(t *testing.T) { for i := range devices { dev := devices[i] - t.Log(dev.Serial(), dev.Model()) + model, err := dev.Model() + if err != nil { + t.Fatal(err) + } + t.Log(dev.Serial(), model) + } +} + +func TestDevice_Brand(t *testing.T) { + setupDevices(t) + + for i := range devices { + dev := devices[i] + brand, err := dev.Brand() + if err != nil { + t.Fatal(err) + } + t.Log(dev.Serial(), brand) } } @@ -79,7 +99,15 @@ func TestDevice_Usb(t *testing.T) { for i := range devices { dev := devices[i] - t.Log(dev.Serial(), dev.Usb(), dev.IsUsb()) + usb, err := dev.Usb() + if err != nil { + t.Fatal(err) + } + isUsb, err := dev.IsUsb() + if err != nil { + t.Fatal(err) + } + t.Log(dev.Serial(), usb, isUsb) } } @@ -315,6 +343,22 @@ func TestDevice_InstallAPK(t *testing.T) { } } +func TestDevice_ListPackages(t *testing.T) { + setupDevices(t) + for _, dev := range devices { + res, err := dev.ListPackages() + if err != nil { + t.Fatal(err) + } + t.Log(res) + installed, err := dev.IsPackagesInstalled("io.appium.uiautomator2.server") + if err != nil { + t.Fatal(err) + } + t.Log(installed) + } +} + func TestDevice_HasFeature(t *testing.T) { setupDevices(t) diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index 2dbe889f..6ca6bcce 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -320,6 +320,11 @@ func NewActionOptions(options ...ActionOption) *ActionOptions { return actionOptions } +type TapTextAction struct { + Text string + Options []ActionOption +} + type ActionOption func(o *ActionOptions) func WithCustomOption(key string, value interface{}) ActionOption { diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index 8bf2d066..e3b15520 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -2,27 +2,30 @@ package uixt import ( "bytes" + "encoding/xml" "fmt" "io/fs" - "io/ioutil" + "os" "path/filepath" "regexp" "strconv" "strings" "time" + "github.com/httprunner/funplugin/myexec" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/httprunner/funplugin/myexec" "github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/env" "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" "github.com/httprunner/httprunner/v4/hrp/pkg/utf7" ) -const AdbKeyBoardPackageName = "com.android.adbkeyboard/.AdbIME" -const UnicodeImePackageName = "io.appium.settings/.UnicodeIME" +const ( + AdbKeyBoardPackageName = "com.android.adbkeyboard/.AdbIME" + UnicodeImePackageName = "io.appium.settings/.UnicodeIME" +) type adbDriver struct { Driver @@ -31,6 +34,56 @@ 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{} @@ -479,10 +532,95 @@ func (ad *adbDriver) Screenshot() (raw *bytes.Buffer, err error) { } func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) { - err = errDriverNotImplemented + _, err = ad.adbClient.RunShellCommand("uiautomator", "dump") + if err != nil { + return + } + source, err = ad.adbClient.RunShellCommand("cat", "/sdcard/window_dump.xml") + if err != nil { + return + } return } +func (ad *adbDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy, err error) { + source, err := ad.Source() + sourceTree = new(Hierarchy) + err = xml.Unmarshal([]byte(source), sourceTree) + if err != nil { + return + } + return +} + +func (ad *adbDriver) TapByText(text string, options ...ActionOption) error { + sourceTree, err := ad.sourceTree() + if err != nil { + return err + } + return ad.tapByTextUsingHierarchy(sourceTree, text, options...) +} + +func (ad *adbDriver) tapByTextUsingHierarchy(hierarchy *Hierarchy, text string, options ...ActionOption) error { + bounds := ad.searchNodes(hierarchy.Nodes, text, options...) + actionOptions := NewActionOptions(options...) + if len(bounds) == 0 { + if actionOptions.IgnoreNotFoundError { + log.Info().Msg("not found element by text " + text) + return nil + } + return errors.New("not found element by text " + text) + } + for _, bound := range bounds { + width, height := bound.Center() + err := ad.TapFloat(width, height, options...) + if err != nil { + return err + } + } + return nil +} + +func (ad *adbDriver) TapByTexts(actions ...TapTextAction) error { + sourceTree, err := ad.sourceTree() + if err != nil { + return err + } + + for _, action := range actions { + err := ad.tapByTextUsingHierarchy(sourceTree, action.Text, action.Options...) + if err != nil { + return err + } + } + return nil +} + +func (ad *adbDriver) searchNodes(nodes []Node, text string, options ...ActionOption) []Bounds { + actionOptions := NewActionOptions(options...) + var results []Bounds + for _, node := range nodes { + result := ad.searchNodes(node.Children, text, options...) + results = append(results, result...) + if actionOptions.Regex { + // regex on, check if match regex + if !regexp.MustCompile(text).MatchString(node.Text) { + continue + } + } else { + // regex off, check if match exactly + if node.Text != text { + ad.searchNodes(node.Children, text, options...) + continue + } + } + if node.Bounds != nil { + results = append(results, *node.Bounds) + } + } + return results +} + func (ad *adbDriver) AccessibleSource() (source string, err error) { err = errDriverNotImplemented return @@ -517,7 +655,7 @@ func (ad *adbDriver) StartCaptureLog(identifier ...string) (err error) { } // start logcat - err = ad.logcat.CatchLogcat() + err = ad.logcat.CatchLogcat("iesqaMonitor:V") if err != nil { err = errors.Wrap(code.AndroidCaptureLogError, fmt.Sprintf("start adb log recording failed: %v", err)) @@ -527,17 +665,15 @@ func (ad *adbDriver) StartCaptureLog(identifier ...string) (err error) { } func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { - log.Info().Msg("stop adb log recording") - err = ad.logcat.Stop() - if err != nil { - log.Error().Err(err).Msg("failed to get adb log recording") - err = errors.Wrap(code.AndroidCaptureLogError, - fmt.Sprintf("get adb log recording failed: %v", err)) - return "", err - } - content := ad.logcat.logBuffer.String() - log.Info().Str("logcat content", content).Msg("display logcat content") - pointRes := ConvertPoints(content) + defer func() { + log.Info().Msg("stop adb log recording") + err = ad.logcat.Stop() + if err != nil { + log.Error().Err(err).Msg("failed to get adb log recording") + } + }() + pointRes := ConvertPoints(ad.logcat.reader) + // 没有解析到打点日志,走兜底逻辑 if len(pointRes) == 0 { log.Info().Msg("action log is null, use action file >>>") @@ -551,7 +687,6 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { } return nil }) - // 先保持原有状态码不变,这里不return error if err != nil { log.Error().Err(err).Msg("read log file fail") @@ -563,13 +698,13 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { return pointRes, nil } - data, err := ioutil.ReadFile(files[0]) + reader, err := os.Open(files[0]) if err != nil { - log.Info().Msg("read File error") + log.Info().Msg("open File error") return pointRes, nil } - pointRes = ConvertPoints(string(data)) + pointRes = ConvertPoints(reader) } return pointRes, nil } diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index f8d5ef11..b154a405 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -1,18 +1,19 @@ package uixt import ( + "bufio" "bytes" "context" - "encoding/base64" "fmt" + "io" "net" "os/exec" "strings" + "github.com/httprunner/funplugin/myexec" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/httprunner/funplugin/myexec" "github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/json" "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" @@ -23,7 +24,6 @@ var ( AdbServerPort = gadb.AdbServerPort // 5037 UIA2ServerHost = "localhost" UIA2ServerPort = 6790 - DeviceTempPath = "/data/local/tmp" ) const forwardToPrefix = "forward-to-" @@ -148,18 +148,6 @@ func GetAndroidDevices(serial ...string) (devices []*gadb.Device, err error) { return deviceList, nil } -func encodeUnicode(c int32) string { - var buffer bytes.Buffer - // Convert each rune (character) into two bytes - buffer.WriteByte(byte(c >> 8)) - buffer.WriteByte(byte(c & 0xFF)) - // Convert buffer bytes to base64 encoding - encoded := base64.StdEncoding.EncodeToString(buffer.Bytes()) - // Replace "/" with "," and remove trailing "=" - encoded = strings.ReplaceAll(encoded, "/", ",") - return strings.TrimRight(encoded, "=") -} - type AndroidDevice struct { d *gadb.Device logcat *AdbLogcat @@ -290,26 +278,27 @@ func getFreePort() (int, error) { } type AdbLogcat struct { - serial string - logBuffer *bytes.Buffer - errs []error - stopping chan struct{} - done chan struct{} - cmd *exec.Cmd + serial string + // logBuffer *bytes.Buffer + errs []error + stopping chan struct{} + done chan struct{} + cmd *exec.Cmd + reader io.Reader } func NewAdbLogcat(serial string) *AdbLogcat { return &AdbLogcat{ - serial: serial, - logBuffer: new(bytes.Buffer), - stopping: make(chan struct{}), - done: make(chan struct{}), + serial: serial, + // logBuffer: new(bytes.Buffer), + stopping: make(chan struct{}), + done: make(chan struct{}), } } // CatchLogcatContext starts logcat with timeout context func (l *AdbLogcat) CatchLogcatContext(timeoutCtx context.Context) (err error) { - if err = l.CatchLogcat(); err != nil { + if err = l.CatchLogcat(""); err != nil { return } go func() { @@ -344,7 +333,7 @@ func (l *AdbLogcat) Errors() (err error) { return } -func (l *AdbLogcat) CatchLogcat() (err error) { +func (l *AdbLogcat) CatchLogcat(filter string) (err error) { if l.cmd != nil { log.Warn().Msg("logcat already start") return nil @@ -354,12 +343,19 @@ func (l *AdbLogcat) CatchLogcat() (err error) { if err = myexec.RunCommand("adb", "-s", l.serial, "shell", "logcat", "-c"); err != nil { return } - + args := []string{"-s", l.serial, "logcat", "--format", "time"} + if filter != "" { + args = append(args, "-s", filter) + } // start logcat - l.cmd = myexec.Command("adb", "-s", l.serial, - "logcat", "--format", "time", "-s", "iesqaMonitor:V") - l.cmd.Stderr = l.logBuffer - l.cmd.Stdout = l.logBuffer + l.cmd = myexec.Command("adb", args...) + // l.cmd.Stderr = l.logBuffer + // l.cmd.Stdout = l.logBuffer + reader, err := l.cmd.StdoutPipe() + if err != nil { + return err + } + l.reader = reader if err = l.cmd.Start(); err != nil { return } @@ -370,17 +366,7 @@ func (l *AdbLogcat) CatchLogcat() (err error) { } l.done <- struct{}{} }() - return -} -func (l *AdbLogcat) BufferedLogcat() (err error) { - // -d: dump the current buffered logcat result and exits - cmd := myexec.Command("adb", "-s", l.serial, "logcat", "-d") - cmd.Stdout = l.logBuffer - cmd.Stderr = l.logBuffer - if err = cmd.Run(); err != nil { - return - } return } @@ -394,9 +380,11 @@ type ExportPoint struct { RunTime int `json:"run_time,omitempty" yaml:"run_time,omitempty"` } -func ConvertPoints(data string) (eps []ExportPoint) { - lines := strings.Split(data, "\n") - for _, line := range lines { +func ConvertPoints(reader io.Reader) (eps []ExportPoint) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Text() + log.Info().Str("logcat content", line) if strings.Contains(line, "ext") { idx := strings.Index(line, "{") if idx == -1 { diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index ac177bf6..4b85f5ca 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -3,8 +3,6 @@ package uixt import ( - "encoding/json" - "fmt" "io/ioutil" "os" "testing" @@ -427,15 +425,15 @@ func TestDriver_AppTerminate(t *testing.T) { } } -func TestConvertPoints(t *testing.T) { - data := "10-09 20:16:48.216 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317808206,\"ext\":\"输入\",\"from\":{\"x\":0.0,\"y\":0.0},\"operation\":\"Gtf-SendKeys\",\"run_time\":627,\"start\":1665317807579,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":0.0,\"y\":0.0}}\n10-09 20:18:22.899 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317902898,\"ext\":\"进入直播间\",\"from\":{\"x\":717.0,\"y\":2117.5},\"operation\":\"Gtf-Tap\",\"run_time\":121,\"start\":1665317902777,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":717.0,\"y\":2117.5}}\n10-09 20:18:32.063 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317912062,\"ext\":\"第一次上划\",\"from\":{\"x\":1437.0,\"y\":2409.9},\"operation\":\"Gtf-Swipe\",\"run_time\":32,\"start\":1665317912030,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":1437.0,\"y\":2409.9}}" - eps := ConvertPoints(data) - if len(eps) != 3 { - t.Fatal() - } - jsons, _ := json.Marshal(eps) - println(fmt.Sprintf("%v", string(jsons))) -} +//func TestConvertPoints(t *testing.T) { +// data := "10-09 20:16:48.216 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317808206,\"ext\":\"输入\",\"from\":{\"x\":0.0,\"y\":0.0},\"operation\":\"Gtf-SendKeys\",\"run_time\":627,\"start\":1665317807579,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":0.0,\"y\":0.0}}\n10-09 20:18:22.899 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317902898,\"ext\":\"进入直播间\",\"from\":{\"x\":717.0,\"y\":2117.5},\"operation\":\"Gtf-Tap\",\"run_time\":121,\"start\":1665317902777,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":717.0,\"y\":2117.5}}\n10-09 20:18:32.063 I/iesqaMonitor(17845): {\"duration\":0,\"end\":1665317912062,\"ext\":\"第一次上划\",\"from\":{\"x\":1437.0,\"y\":2409.9},\"operation\":\"Gtf-Swipe\",\"run_time\":32,\"start\":1665317912030,\"start_first\":0,\"start_last\":0,\"to\":{\"x\":1437.0,\"y\":2409.9}}" +// eps := ConvertPoints(data) +// if len(eps) != 3 { +// t.Fatal() +// } +// jsons, _ := json.Marshal(eps) +// println(fmt.Sprintf("%v", string(jsons))) +//} func TestDriver_ShellInputUnicode(t *testing.T) { device, _ := NewAndroidDevice() @@ -456,3 +454,16 @@ func TestDriver_ShellInputUnicode(t *testing.T) { t.Log(os.WriteFile("s1.png", raw.Bytes(), 0o600)) } + +func TestTapTexts(t *testing.T) { + setupAndroid(t) + actions := []TapTextAction{ + {Text: "^.*无视风险安装$", Options: []ActionOption{WithTapOffset(100, 0), WithRegex(true), WithIgnoreNotFoundError(true)}}, + {Text: "已了解此应用未经检测.*", Options: []ActionOption{WithTapOffset(-450, 0), WithRegex(true), WithIgnoreNotFoundError(true)}}, + {Text: "^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|安装|授权本次安装|继续安装|重新安装)$", Options: []ActionOption{WithRegex(true), WithIgnoreNotFoundError(true)}}, + } + err := driverExt.Driver.TapByTexts(actions...) + if err != nil { + t.Fatal(err) + } +} diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index b182b6ed..51e2cbbc 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -598,3 +598,11 @@ func (ud *uiaDriver) Source(srcOpt ...SourceOption) (source string, err error) { source = reply.Value return } + +func (ud *uiaDriver) TapByText(text string, options ...ActionOption) error { + return ud.adbDriver.TapByText(text, options...) +} + +func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error { + return ud.adbDriver.TapByTexts(actions...) +} diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index 07885f56..3fccf5ba 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -589,6 +589,11 @@ type WebDriver interface { // Source Return application elements tree Source(srcOpt ...SourceOption) (string, error) + + TapByText(text string, options ...ActionOption) error + + TapByTexts(actions ...TapTextAction) error + // AccessibleSource Return application elements accessibility tree AccessibleSource() (string, error) diff --git a/hrp/pkg/uixt/ios_driver.go b/hrp/pkg/uixt/ios_driver.go index e274044a..9acc164c 100644 --- a/hrp/pkg/uixt/ios_driver.go +++ b/hrp/pkg/uixt/ios_driver.go @@ -759,6 +759,14 @@ func (wd *wdaDriver) Source(srcOpt ...SourceOption) (source string, err error) { return } +func (wd *wdaDriver) TapByText(text string, options ...ActionOption) error { + return errDriverNotImplemented +} + +func (wd *wdaDriver) TapByTexts(actions ...TapTextAction) error { + return errDriverNotImplemented +} + func (wd *wdaDriver) AccessibleSource() (source string, err error) { // [[FBRoute GET:@"/wda/accessibleSource"] respondWithTarget:self action:@selector(handleGetAccessibleSourceCommand:)] // [[FBRoute GET:@"/wda/accessibleSource"].withoutSession