feat: 新增按控件点击,获取设备应用,修改日志获取

This commit is contained in:
余泓铮
2024-04-25 15:02:38 +08:00
parent d3aa72a713
commit d7d6f76c93
10 changed files with 345 additions and 90 deletions

View File

@@ -483,3 +483,10 @@ func ConvertToStringSlice(val interface{}) ([]string, error) {
} }
return nil, fmt.Errorf("invalid type for conversion to []string") return nil, fmt.Errorf("invalid type for conversion to []string")
} }
func GetCurrentDay() string {
now := time.Now()
// 格式化日期为 yyyyMMdd
formattedDate := now.Format("20060102")
return formattedDate
}

View File

@@ -107,25 +107,38 @@ func (d *Device) features() (features Features, err error) {
return features, nil return features, nil
} }
func (d Device) HasAttribute(key string) bool { func (d *Device) HasAttribute(key string) bool {
_, ok := d.attrs[key] _, ok := d.attrs[key]
return ok return ok
} }
func (d Device) Product() (string, error) { func (d *Device) Product() (string, error) {
if d.HasAttribute("product") { if d.HasAttribute("product") {
return d.attrs["product"], nil return d.attrs["product"], nil
} }
return "", errors.New("does not have attribute: product") return "", errors.New("does not have attribute: product")
} }
func (d Device) Model() (string, error) { func (d *Device) Model() (string, error) {
if d.HasAttribute("model") { if d.HasAttribute("model") {
return d.attrs["model"], nil return d.attrs["model"], nil
} }
return "", errors.New("does not have attribute: model") 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) { func (d *Device) Usb() (string, error) {
if d.HasAttribute("usb") { if d.HasAttribute("usb") {
return d.attrs["usb"], nil return d.attrs["usb"], nil
@@ -133,7 +146,7 @@ func (d *Device) Usb() (string, error) {
return "", errors.New("does not have attribute: usb") 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") { if d.HasAttribute("transport_id") {
return d.attrs["transport_id"], nil return d.attrs["transport_id"], nil
} }
@@ -524,7 +537,7 @@ func (d *Device) Pull(remotePath string, dest io.Writer) (err error) {
return 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 ( var (
tp transport tp transport
filesize int64 filesize int64
@@ -537,8 +550,11 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) {
return nil, err return nil, err
} }
defer func() { _ = tp.Close() }() defer func() { _ = tp.Close() }()
cmd := "abb_exec:package\x00install\x00-t"
cmd := fmt.Sprintf("abb_exec:package\x00install\x00-t\x00-S\x00%d", filesize) for _, arg := range args {
cmd += "\x00" + arg
}
cmd += fmt.Sprintf("\x00-S\x00%d", filesize)
if err = tp.SendWithCheck(cmd); err != nil { if err = tp.SendWithCheck(cmd); err != nil {
return nil, err return nil, err
} }
@@ -555,7 +571,7 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker) (raw []byte, err error) {
return 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 { haserr := func(ret string) bool {
return strings.Contains(ret, "Failure") return strings.Contains(ret, "Failure")
} }
@@ -575,8 +591,9 @@ func (d *Device) InstallAPK(apk io.ReadSeeker) (string, error) {
if err != nil { if err != nil {
return "", fmt.Errorf("error pushing: %v", err) return "", fmt.Errorf("error pushing: %v", err)
} }
args = append([]string{"install"}, args...)
res, err := d.RunShellCommand("pm", "install", "-f", remote) args = append(args, "-f", remote)
res, err := d.RunShellCommand("pm", args...)
if err != nil { if err != nil {
return "", errors.Wrap(err, "install apk failed") 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...) 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) { func (d *Device) ScreenCap() ([]byte, error) {
if d.HasFeature(FeatShellV2) { if d.HasFeature(FeatShellV2) {
return d.RunShellCommandV2WithBytes("screencap", "-p") return d.RunShellCommandV2WithBytes("screencap", "-p")

View File

@@ -60,7 +60,10 @@ func TestDevice_Product(t *testing.T) {
for i := range devices { for i := range devices {
dev := devices[i] dev := devices[i]
product := dev.Product() product, err := dev.Product()
if err != nil {
t.Fatal(err)
}
t.Log(dev.Serial(), product) t.Log(dev.Serial(), product)
} }
} }
@@ -70,7 +73,24 @@ func TestDevice_Model(t *testing.T) {
for i := range devices { for i := range devices {
dev := devices[i] 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 { for i := range devices {
dev := devices[i] 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) { func TestDevice_HasFeature(t *testing.T) {
setupDevices(t) setupDevices(t)

View File

@@ -320,6 +320,11 @@ func NewActionOptions(options ...ActionOption) *ActionOptions {
return actionOptions return actionOptions
} }
type TapTextAction struct {
Text string
Options []ActionOption
}
type ActionOption func(o *ActionOptions) type ActionOption func(o *ActionOptions)
func WithCustomOption(key string, value interface{}) ActionOption { func WithCustomOption(key string, value interface{}) ActionOption {

View File

@@ -2,27 +2,30 @@ package uixt
import ( import (
"bytes" "bytes"
"encoding/xml"
"fmt" "fmt"
"io/fs" "io/fs"
"io/ioutil" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/httprunner/funplugin/myexec"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "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/code"
"github.com/httprunner/httprunner/v4/hrp/internal/env" "github.com/httprunner/httprunner/v4/hrp/internal/env"
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb" "github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
"github.com/httprunner/httprunner/v4/hrp/pkg/utf7" "github.com/httprunner/httprunner/v4/hrp/pkg/utf7"
) )
const AdbKeyBoardPackageName = "com.android.adbkeyboard/.AdbIME" const (
const UnicodeImePackageName = "io.appium.settings/.UnicodeIME" AdbKeyBoardPackageName = "com.android.adbkeyboard/.AdbIME"
UnicodeImePackageName = "io.appium.settings/.UnicodeIME"
)
type adbDriver struct { type adbDriver struct {
Driver Driver
@@ -31,6 +34,56 @@ type adbDriver struct {
logcat *AdbLogcat 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 { func NewAdbDriver() *adbDriver {
log.Info().Msg("init adb driver") log.Info().Msg("init adb driver")
return &adbDriver{} 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) { 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 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) { func (ad *adbDriver) AccessibleSource() (source string, err error) {
err = errDriverNotImplemented err = errDriverNotImplemented
return return
@@ -517,7 +655,7 @@ func (ad *adbDriver) StartCaptureLog(identifier ...string) (err error) {
} }
// start logcat // start logcat
err = ad.logcat.CatchLogcat() err = ad.logcat.CatchLogcat("iesqaMonitor:V")
if err != nil { if err != nil {
err = errors.Wrap(code.AndroidCaptureLogError, err = errors.Wrap(code.AndroidCaptureLogError,
fmt.Sprintf("start adb log recording failed: %v", err)) 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) { func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
log.Info().Msg("stop adb log recording") defer func() {
err = ad.logcat.Stop() log.Info().Msg("stop adb log recording")
if err != nil { err = ad.logcat.Stop()
log.Error().Err(err).Msg("failed to get adb log recording") if err != nil {
err = errors.Wrap(code.AndroidCaptureLogError, log.Error().Err(err).Msg("failed to get adb log recording")
fmt.Sprintf("get adb log recording failed: %v", err)) }
return "", err }()
} pointRes := ConvertPoints(ad.logcat.reader)
content := ad.logcat.logBuffer.String()
log.Info().Str("logcat content", content).Msg("display logcat content")
pointRes := ConvertPoints(content)
// 没有解析到打点日志,走兜底逻辑 // 没有解析到打点日志,走兜底逻辑
if len(pointRes) == 0 { if len(pointRes) == 0 {
log.Info().Msg("action log is null, use action file >>>") 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 nil
}) })
// 先保持原有状态码不变这里不return error // 先保持原有状态码不变这里不return error
if err != nil { if err != nil {
log.Error().Err(err).Msg("read log file fail") log.Error().Err(err).Msg("read log file fail")
@@ -563,13 +698,13 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
return pointRes, nil return pointRes, nil
} }
data, err := ioutil.ReadFile(files[0]) reader, err := os.Open(files[0])
if err != nil { if err != nil {
log.Info().Msg("read File error") log.Info().Msg("open File error")
return pointRes, nil return pointRes, nil
} }
pointRes = ConvertPoints(string(data)) pointRes = ConvertPoints(reader)
} }
return pointRes, nil return pointRes, nil
} }

View File

@@ -1,18 +1,19 @@
package uixt package uixt
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"encoding/base64"
"fmt" "fmt"
"io"
"net" "net"
"os/exec" "os/exec"
"strings" "strings"
"github.com/httprunner/funplugin/myexec"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "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/code"
"github.com/httprunner/httprunner/v4/hrp/internal/json" "github.com/httprunner/httprunner/v4/hrp/internal/json"
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb" "github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
@@ -23,7 +24,6 @@ var (
AdbServerPort = gadb.AdbServerPort // 5037 AdbServerPort = gadb.AdbServerPort // 5037
UIA2ServerHost = "localhost" UIA2ServerHost = "localhost"
UIA2ServerPort = 6790 UIA2ServerPort = 6790
DeviceTempPath = "/data/local/tmp"
) )
const forwardToPrefix = "forward-to-" const forwardToPrefix = "forward-to-"
@@ -148,18 +148,6 @@ func GetAndroidDevices(serial ...string) (devices []*gadb.Device, err error) {
return deviceList, nil 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 { type AndroidDevice struct {
d *gadb.Device d *gadb.Device
logcat *AdbLogcat logcat *AdbLogcat
@@ -290,26 +278,27 @@ func getFreePort() (int, error) {
} }
type AdbLogcat struct { type AdbLogcat struct {
serial string serial string
logBuffer *bytes.Buffer // logBuffer *bytes.Buffer
errs []error errs []error
stopping chan struct{} stopping chan struct{}
done chan struct{} done chan struct{}
cmd *exec.Cmd cmd *exec.Cmd
reader io.Reader
} }
func NewAdbLogcat(serial string) *AdbLogcat { func NewAdbLogcat(serial string) *AdbLogcat {
return &AdbLogcat{ return &AdbLogcat{
serial: serial, serial: serial,
logBuffer: new(bytes.Buffer), // logBuffer: new(bytes.Buffer),
stopping: make(chan struct{}), stopping: make(chan struct{}),
done: make(chan struct{}), done: make(chan struct{}),
} }
} }
// CatchLogcatContext starts logcat with timeout context // CatchLogcatContext starts logcat with timeout context
func (l *AdbLogcat) CatchLogcatContext(timeoutCtx context.Context) (err error) { func (l *AdbLogcat) CatchLogcatContext(timeoutCtx context.Context) (err error) {
if err = l.CatchLogcat(); err != nil { if err = l.CatchLogcat(""); err != nil {
return return
} }
go func() { go func() {
@@ -344,7 +333,7 @@ func (l *AdbLogcat) Errors() (err error) {
return return
} }
func (l *AdbLogcat) CatchLogcat() (err error) { func (l *AdbLogcat) CatchLogcat(filter string) (err error) {
if l.cmd != nil { if l.cmd != nil {
log.Warn().Msg("logcat already start") log.Warn().Msg("logcat already start")
return nil 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 { if err = myexec.RunCommand("adb", "-s", l.serial, "shell", "logcat", "-c"); err != nil {
return return
} }
args := []string{"-s", l.serial, "logcat", "--format", "time"}
if filter != "" {
args = append(args, "-s", filter)
}
// start logcat // start logcat
l.cmd = myexec.Command("adb", "-s", l.serial, l.cmd = myexec.Command("adb", args...)
"logcat", "--format", "time", "-s", "iesqaMonitor:V") // l.cmd.Stderr = l.logBuffer
l.cmd.Stderr = l.logBuffer // l.cmd.Stdout = 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 { if err = l.cmd.Start(); err != nil {
return return
} }
@@ -370,17 +366,7 @@ func (l *AdbLogcat) CatchLogcat() (err error) {
} }
l.done <- struct{}{} 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 return
} }
@@ -394,9 +380,11 @@ type ExportPoint struct {
RunTime int `json:"run_time,omitempty" yaml:"run_time,omitempty"` RunTime int `json:"run_time,omitempty" yaml:"run_time,omitempty"`
} }
func ConvertPoints(data string) (eps []ExportPoint) { func ConvertPoints(reader io.Reader) (eps []ExportPoint) {
lines := strings.Split(data, "\n") scanner := bufio.NewScanner(reader)
for _, line := range lines { for scanner.Scan() {
line := scanner.Text()
log.Info().Str("logcat content", line)
if strings.Contains(line, "ext") { if strings.Contains(line, "ext") {
idx := strings.Index(line, "{") idx := strings.Index(line, "{")
if idx == -1 { if idx == -1 {

View File

@@ -3,8 +3,6 @@
package uixt package uixt
import ( import (
"encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
@@ -427,15 +425,15 @@ func TestDriver_AppTerminate(t *testing.T) {
} }
} }
func TestConvertPoints(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}}" // 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) // eps := ConvertPoints(data)
if len(eps) != 3 { // if len(eps) != 3 {
t.Fatal() // t.Fatal()
} // }
jsons, _ := json.Marshal(eps) // jsons, _ := json.Marshal(eps)
println(fmt.Sprintf("%v", string(jsons))) // println(fmt.Sprintf("%v", string(jsons)))
} //}
func TestDriver_ShellInputUnicode(t *testing.T) { func TestDriver_ShellInputUnicode(t *testing.T) {
device, _ := NewAndroidDevice() device, _ := NewAndroidDevice()
@@ -456,3 +454,16 @@ func TestDriver_ShellInputUnicode(t *testing.T) {
t.Log(os.WriteFile("s1.png", raw.Bytes(), 0o600)) 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)
}
}

View File

@@ -598,3 +598,11 @@ func (ud *uiaDriver) Source(srcOpt ...SourceOption) (source string, err error) {
source = reply.Value source = reply.Value
return 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...)
}

View File

@@ -589,6 +589,11 @@ type WebDriver interface {
// Source Return application elements tree // Source Return application elements tree
Source(srcOpt ...SourceOption) (string, error) Source(srcOpt ...SourceOption) (string, error)
TapByText(text string, options ...ActionOption) error
TapByTexts(actions ...TapTextAction) error
// AccessibleSource Return application elements accessibility tree // AccessibleSource Return application elements accessibility tree
AccessibleSource() (string, error) AccessibleSource() (string, error)

View File

@@ -759,6 +759,14 @@ func (wd *wdaDriver) Source(srcOpt ...SourceOption) (source string, err error) {
return 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) { func (wd *wdaDriver) AccessibleSource() (source string, err error) {
// [[FBRoute GET:@"/wda/accessibleSource"] respondWithTarget:self action:@selector(handleGetAccessibleSourceCommand:)] // [[FBRoute GET:@"/wda/accessibleSource"] respondWithTarget:self action:@selector(handleGetAccessibleSourceCommand:)]
// [[FBRoute GET:@"/wda/accessibleSource"].withoutSession // [[FBRoute GET:@"/wda/accessibleSource"].withoutSession