mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
feat: 新增按控件点击,获取设备应用,修改日志获取
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user