From b2cac25bd493cc4b9c5749359de446ccb2a3b82c Mon Sep 17 00:00:00 2001 From: xucong053 Date: Thu, 29 Sep 2022 16:15:56 +0800 Subject: [PATCH] change: update adb logs --- examples/uitest/demo_android_douyin_test.go | 4 +- hrp/internal/uixt/android_device.go | 168 +++++++++++++++++++- hrp/internal/uixt/android_device_test.go | 18 +++ hrp/internal/uixt/android_driver.go | 1 + hrp/internal/uixt/ext.go | 39 +++++ hrp/step_ios_ui.go | 2 +- 6 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 hrp/internal/uixt/android_device_test.go diff --git a/examples/uitest/demo_android_douyin_test.go b/examples/uitest/demo_android_douyin_test.go index b43f8b9f..d07558ce 100644 --- a/examples/uitest/demo_android_douyin_test.go +++ b/examples/uitest/demo_android_douyin_test.go @@ -11,12 +11,12 @@ import ( func TestAndroidDouYinLive(t *testing.T) { testCase := &hrp.TestCase{ Config: hrp.NewConfig("通过 feed 头像进入抖音直播间"). - SetAndroid(hrp.WithAdbLogOn(true)), + SetAndroid(hrp.WithAdbLogOn(true), hrp.WithSerialNumber("2d06bf70")), TestSteps: []hrp.IStep{ hrp.NewStep("打开网页"). Android(). Home(). - AppTerminate("com.google.android.apps.chrome.Main").Sleep(1). // 关闭已运行的抖音,确保启动抖音后在「抖音」首页 + AppTerminate("com.google.android.apps.chrome.Main").Sleep(1). SwipeToTapApp("Chrome", hrp.WithMaxRetryTimes(5)).TapByOCR("搜索").Input("https://gtftask.bytedance.com/local-time").TapByOCR("前往").Sleep(5). Validate(). AssertOCRExists("1664", "网页打开失败"), diff --git a/hrp/internal/uixt/android_device.go b/hrp/internal/uixt/android_device.go index 568adcdf..a2f170d9 100644 --- a/hrp/internal/uixt/android_device.go +++ b/hrp/internal/uixt/android_device.go @@ -2,12 +2,19 @@ package uixt import ( "bytes" + "context" "fmt" "net" + "os/exec" "reflect" + "regexp" + "strconv" + "strings" + "syscall" "github.com/electricbubble/gadb" "github.com/pkg/errors" + "github.com/rs/zerolog/log" ) var ( @@ -19,6 +26,15 @@ var ( const forwardToPrefix = "forward-to-" +const ( + regexFloat = `[0-9\.]*` +) + +var ( + regexCompileSwipe = regexp.MustCompile(fmt.Sprintf(`timesec=(%s)\s*startX=(%s)\s*startY=(%s)\s*endX=(%s)\s*endY=(%s)`, regexFloat, regexFloat, regexFloat, regexFloat, regexFloat)) // parse ${var} or $var + regexCompileTap = regexp.MustCompile(fmt.Sprintf(`timesec=(%s)\s*x=(%s)\s*y=(%s)`, regexFloat, regexFloat, regexFloat)) // parse ${func1($a, $b)} // parse number +) + func InitUIAClient(device *AndroidDevice) (*DriverExt, error) { var deviceOptions []AndroidDeviceOption if device.SerialNumber != "" { @@ -51,10 +67,10 @@ func InitUIAClient(device *AndroidDevice) (*DriverExt, error) { } if device.LogOn { - // TODO + err = driverExt.StartLogRecording("hrp_adb_log") } - return driverExt, nil + return driverExt, err } type AndroidDeviceOption func(*AndroidDevice) @@ -106,6 +122,7 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er device.SerialNumber = dev.Serial() device.d = dev + device.logcat = NewAdbLogcat(serialNumber) return device, nil } @@ -114,6 +131,7 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er type AndroidDevice struct { d gadb.Device + logcat *DeviceLogcat SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"` IP string `json:"ip,omitempty" yaml:"ip,omitempty"` Port int `json:"port,omitempty" yaml:"port,omitempty"` @@ -152,6 +170,7 @@ func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver *uiaDr return nil, err } driver.adbDevice = dev.d + driver.logcat = dev.logcat driver.localPort = localPort return driver, nil @@ -182,6 +201,151 @@ func getFreePort() (int, error) { return l.Addr().(*net.TCPAddr).Port, nil } +type DeviceLogcat struct { + serial string + logBuffer *bytes.Buffer + errs []error + stopping chan struct{} + done chan struct{} + cmd *exec.Cmd +} + +func NewAdbLogcat(serial string) *DeviceLogcat { + return &DeviceLogcat{ + serial: serial, + logBuffer: new(bytes.Buffer), + stopping: make(chan struct{}), + done: make(chan struct{}), + } +} + +// CatchLogcatContext starts logcat with timeout context +func (l *DeviceLogcat) CatchLogcatContext(timeoutCtx context.Context) (err error) { + if err = l.CatchLogcat(); err != nil { + return + } + go func() { + select { + case <-timeoutCtx.Done(): + _ = l.Stop() + case <-l.stopping: + } + }() + return +} + +func (l *DeviceLogcat) Stop() error { + select { + case <-l.stopping: + default: + close(l.stopping) + <-l.done + close(l.done) + } + return l.Errors() +} + +func (l *DeviceLogcat) Errors() (err error) { + for _, e := range l.errs { + if err != nil { + err = fmt.Errorf("%v |[DeviceLogcatErr] %v", err, e) + } else { + err = fmt.Errorf("[DeviceLogcatErr] %v", e) + } + } + return +} + +func (l *DeviceLogcat) CatchLogcat() (err error) { + if l.cmd != nil { + err = fmt.Errorf("logcat already start") + } + command := fmt.Sprintf("adb -s %s logcat -c && adb -s %s logcat -v time -s iesqaMonitor:V", l.serial, l.serial) + l.cmd = exec.Command("bash", "-c", command) + l.cmd.Stderr = l.logBuffer + l.cmd.Stdout = l.logBuffer + l.cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + if err = l.cmd.Start(); err != nil { + return + } + go func() { + <-l.stopping + if e := syscall.Kill(-l.cmd.Process.Pid, syscall.SIGKILL); e != nil { + l.errs = append(l.errs, fmt.Errorf("kill logcat process err:%v", e)) + } + l.done <- struct{}{} + }() + return +} + +func (l *DeviceLogcat) BufferedLogcat() (err error) { + // -d: dump the current buffered logcat result and exits + command := fmt.Sprintf("adb -s %s logcat -d", l.serial) + cmd := exec.Command("bash", "-c", command) + cmd.Stdout = l.logBuffer + cmd.Stderr = l.logBuffer + if err = cmd.Run(); err != nil { + return + } + return +} + +type ExportPoint struct { + Start int `json:"start" yaml:"start"` + End int `json:"end" yaml:"end"` + From interface{} `json:"from" yaml:"from"` + To interface{} `json:"to" yaml:"to"` + Operation string `json:"operation" yaml:"operation"` + Ext string `json:"ext" yaml:"ext"` + 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 { + if strings.Contains(line, "startX") { + matched := regexCompileSwipe.FindStringSubmatch(line) + if len(matched) != 6 { + log.Error().Msg("failed to parse point data") + continue + } + start, _ := strconv.Atoi(matched[1]) + fromX, _ := strconv.ParseFloat(matched[2], 64) + fromY, _ := strconv.ParseFloat(matched[3], 64) + toX, _ := strconv.ParseFloat(matched[4], 64) + toY, _ := strconv.ParseFloat(matched[5], 64) + p := ExportPoint{ + Start: start, + End: start, + From: []float64{fromX, fromY}, + To: []float64{toX, toY}, + Operation: "Gtf-Drag", + Ext: "", + } + eps = append(eps, p) + } else if strings.Contains(line, "x=") { + matched := regexCompileTap.FindStringSubmatch(line) + if len(matched) != 4 { + log.Error().Msg("failed to parse point data") + continue + } + start, _ := strconv.Atoi(matched[1]) + x, _ := strconv.ParseFloat(matched[2], 64) + y, _ := strconv.ParseFloat(matched[3], 64) + p := ExportPoint{ + Start: start, + End: start, + From: []float64{x, y}, + To: []float64{x, y}, + Operation: "Gtf-Tap", + Ext: "", + } + eps = append(eps, p) + } + } + return +} + type UiSelectorHelper struct { value *bytes.Buffer } diff --git a/hrp/internal/uixt/android_device_test.go b/hrp/internal/uixt/android_device_test.go new file mode 100644 index 00000000..4195a5ef --- /dev/null +++ b/hrp/internal/uixt/android_device_test.go @@ -0,0 +1,18 @@ +package uixt + +import ( + "fmt" + "testing" + + "github.com/httprunner/httprunner/v4/hrp/internal/json" +) + +func TestConvertPoints(t *testing.T) { + data := "09-29 15:02:08.379 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434928378\tstartX=720.000000\tstartY=1462.000000\tendX=1296.000000\tendY=1462.000000\n09-29 15:02:09.433 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434929432\tstartX=720.000000\tstartY=1462.000000\tendX=1296.000000\tendY=1462.000000\n09-29 15:02:10.452 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434930452\tstartX=720.000000\tstartY=1462.000000\tendX=1296.000000\tendY=1462.000000\n09-29 15:02:11.451 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434931450\tstartX=720.000000\tstartY=1462.000000\tendX=1296.000000\tendY=1462.000000\n09-29 15:02:12.491 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434932489\tstartX=720.000000\tstartY=1462.000000\tendX=1296.000000\tendY=1462.000000\n09-29 15:02:16.028 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434936027\tstartX=720.000000\tstartY=1462.000000\tendX=144.000000\tendY=1462.000000\n09-29 15:02:21.424 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434941423\tstartX=720.000000\tstartY=1462.000000\tendX=144.000000\tendY=1462.000000\n09-29 15:02:27.923 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434947922\tstartX=720.000000\tstartY=1462.000000\tendX=144.000000\tendY=1462.000000\n09-29 15:02:33.628 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434953628\tstartX=720.000000\tstartY=1462.000000\tendX=144.000000\tendY=1462.000000\n09-29 15:02:39.347 I/iesqaMonitor( 9938): [tap]\ttimesec=1664434959347\tx=1259.5y=1868.5" + eps := ConvertPoints(data) + if len(eps) != 10 { + t.Fatal() + } + jsons, _ := json.Marshal(eps) + println(fmt.Sprintf("%v", string(jsons))) +} diff --git a/hrp/internal/uixt/android_driver.go b/hrp/internal/uixt/android_driver.go index 858cb2d7..c5c0782b 100644 --- a/hrp/internal/uixt/android_driver.go +++ b/hrp/internal/uixt/android_driver.go @@ -22,6 +22,7 @@ type uiaDriver struct { Driver adbDevice gadb.Device + logcat *DeviceLogcat localPort int } diff --git a/hrp/internal/uixt/ext.go b/hrp/internal/uixt/ext.go index 93155a76..29642168 100644 --- a/hrp/internal/uixt/ext.go +++ b/hrp/internal/uixt/ext.go @@ -301,6 +301,45 @@ func (dExt *DriverExt) IsImageExist(text string) bool { return err == nil } +func (dExt *DriverExt) StartLogRecording(identifier string) error { + if _, ok := dExt.Driver.(*wdaDriver); ok { + log.Info().Msg("start WDA log recording") + data := map[string]interface{}{"action": "start", "type": 2, "identifier": identifier} + _, err := dExt.triggerWDALog(data) + if err != nil { + return errors.Wrap(err, "failed to start WDA log recording") + } + } else { + log.Info().Msg("start adb log recording") + err := dExt.Driver.(*uiaDriver).logcat.CatchLogcat() + if err != nil { + return errors.Wrap(err, "failed to start adb log recording") + } + } + return nil +} + +func (dExt *DriverExt) GetLogs() (interface{}, error) { + if _, ok := dExt.Driver.(*wdaDriver); ok { + log.Info().Msg("stop WDA log recording") + data := map[string]interface{}{"action": "stop"} + reply, err := dExt.triggerWDALog(data) + if err != nil { + return "", errors.Wrap(err, "failed to get WDA logs") + } + return reply.Value, nil + } else { + log.Info().Msg("stop adb log recording") + err := dExt.Driver.(*uiaDriver).logcat.Stop() + if err != nil { + println("failed to get adb log recording") + //return "", errors.Wrap(err, "failed to get adb log recording") + } + content := dExt.Driver.(*uiaDriver).logcat.logBuffer.String() + return ConvertPoints(content), err + } +} + var errActionNotImplemented = errors.New("UI action not implemented") func (dExt *DriverExt) DoAction(action MobileAction) error { diff --git a/hrp/step_ios_ui.go b/hrp/step_ios_ui.go index e0a1a985..7b16b405 100644 --- a/hrp/step_ios_ui.go +++ b/hrp/step_ios_ui.go @@ -477,7 +477,7 @@ func (r *HRPRunner) initUIClient(device uixt.Device) (client *uixt.DriverExt, er uuid := device.UUID() // avoid duplicate init - if uuid == "" && len(r.uiClients) == 1 { + if uuid == "" && len(r.uiClients) > 0 { for _, v := range r.uiClients { return v, nil }