From 9f7795f703f54a5d22a762f0d41ad87c40e4bed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 19 Mar 2024 14:44:22 +0800 Subject: [PATCH 01/14] fix: fix rotate tap swipe error --- hrp/pkg/uixt/android_adb_driver.go | 5 +++++ hrp/pkg/uixt/android_test.go | 11 +++++++++- hrp/pkg/uixt/android_uia2_driver.go | 14 +++++++++++++ hrp/pkg/uixt/interface.go | 2 ++ hrp/pkg/uixt/swipe.go | 23 ++++++++++++++++----- hrp/pkg/uixt/tap.go | 31 +++++++++++++++++++++++++---- 6 files changed, 76 insertions(+), 10 deletions(-) diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index 2d552a36..7910e839 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -185,6 +185,11 @@ func (ad *adbDriver) StopCamera() (err error) { return } +func (ad *adbDriver) Orientation() (orientation Orientation, err error) { + err = errDriverNotImplemented + return +} + func (ad *adbDriver) Homescreen() (err error) { return ad.PressKeyCode(KCHome, KMEmpty) } diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index ffed7d4e..cd15736b 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -11,13 +11,14 @@ import ( ) var ( - uiaServerURL = "http://localhost:6790/wd/hub" + uiaServerURL = "http://forward-to-6790:6790/wd/hub" driverExt *DriverExt ) func setupAndroid(t *testing.T) { device, err := NewAndroidDevice() checkErr(t, err) + device.UIA2 = true driverExt, err = device.NewDriver() checkErr(t, err) } @@ -215,6 +216,14 @@ func TestDriver_Swipe(t *testing.T) { } } +func TestDriver_Swipe_Relative(t *testing.T) { + setupAndroid(t) + err := driverExt.SwipeRelative(0.5, 0.7, 0.5, 0.5) + if err != nil { + t.Fatal(err) + } +} + func TestDriver_Drag(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 5f38556d..819ec4ef 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -253,6 +253,20 @@ func (ud *uiaDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...K return } +func (ud *uiaDriver) Orientation() (orientation Orientation, err error) { + // [[FBRoute GET:@"/orientation"] respondWithTarget:self action:@selector(handleGetOrientation:)] + var rawResp rawResponse + if rawResp, err = ud.httpGET("/session", ud.sessionId, "/orientation"); err != nil { + return "", err + } + reply := new(struct{ Value Orientation }) + if err = json.Unmarshal(rawResp, reply); err != nil { + return "", err + } + orientation = reply.Value + return +} + func (ud *uiaDriver) Tap(x, y int, options ...ActionOption) error { return ud.TapFloat(float64(x), float64(y), options...) } diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index e292d93c..a447d3a3 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -537,6 +537,8 @@ type WebDriver interface { // StopCamera Stops the camera for recording StopCamera() error + Orientation() (orientation Orientation, err error) + // Tap Sends a tap event at the coordinate. Tap(x, y int, options ...ActionOption) error TapFloat(x, y float64, options ...ActionOption) error diff --git a/hrp/pkg/uixt/swipe.go b/hrp/pkg/uixt/swipe.go index 4c97d212..23d3490e 100644 --- a/hrp/pkg/uixt/swipe.go +++ b/hrp/pkg/uixt/swipe.go @@ -19,17 +19,30 @@ func assertRelative(p float64) bool { func (dExt *DriverExt) SwipeRelative(fromX, fromY, toX, toY float64, options ...ActionOption) error { width := dExt.windowSize.Width height := dExt.windowSize.Height + orientation, err := dExt.Driver.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("swipe from (%v, %v) to (%v, %v) get orientation failed, use default orientation", + fromX, fromY, toX, toY) + orientation = OrientationPortrait + } if !assertRelative(fromX) || !assertRelative(fromY) || !assertRelative(toX) || !assertRelative(toY) { return fmt.Errorf("fromX(%f), fromY(%f), toX(%f), toY(%f) must be less than 1", fromX, fromY, toX, toY) } - - fromX = float64(width) * fromX - fromY = float64(height) * fromY - toX = float64(width) * toX - toY = float64(height) * toY + // 左转和右转都是"LANDSCAPE" + if orientation == OrientationPortrait { + fromX = float64(width) * fromX + fromY = float64(height) * fromY + toX = float64(width) * toX + toY = float64(height) * toY + } else { + fromX = float64(height) * fromX + fromY = float64(width) * fromY + toX = float64(height) * toX + toY = float64(width) * toY + } return dExt.Driver.SwipeFloat(fromX, fromY, toX, toY, options...) } diff --git a/hrp/pkg/uixt/tap.go b/hrp/pkg/uixt/tap.go index a992c94e..f52b5785 100644 --- a/hrp/pkg/uixt/tap.go +++ b/hrp/pkg/uixt/tap.go @@ -2,6 +2,7 @@ package uixt import ( "fmt" + "github.com/rs/zerolog/log" ) func (dExt *DriverExt) TapAbsXY(x, y float64, options ...ActionOption) error { @@ -15,9 +16,19 @@ func (dExt *DriverExt) TapXY(x, y float64, options ...ActionOption) error { return fmt.Errorf("x, y percentage should be <= 1, got x=%v, y=%v", x, y) } - x = x * float64(dExt.windowSize.Width) - y = y * float64(dExt.windowSize.Height) - + orientation, err := dExt.Driver.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("tap (%v, %v) get orientation failed, use default orientation", + x, y) + orientation = OrientationPortrait + } + if orientation == OrientationPortrait { + x = x * float64(dExt.windowSize.Width) + y = y * float64(dExt.windowSize.Height) + } else { + x = x * float64(dExt.windowSize.Height) + y = y * float64(dExt.windowSize.Width) + } return dExt.TapAbsXY(x, y, options...) } @@ -86,7 +97,19 @@ func (dExt *DriverExt) DoubleTapXY(x, y float64) error { if x > 1 || y > 1 { return fmt.Errorf("x, y percentage should be < 1, got x=%v, y=%v", x, y) } - + orientation, err := dExt.Driver.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("tap (%v, %v) get orientation failed, use default orientation", + x, y) + orientation = OrientationPortrait + } + if orientation == OrientationPortrait { + x = x * float64(dExt.windowSize.Width) + y = y * float64(dExt.windowSize.Height) + } else { + x = x * float64(dExt.windowSize.Height) + y = y * float64(dExt.windowSize.Width) + } x = x * float64(dExt.windowSize.Width) y = y * float64(dExt.windowSize.Height) return dExt.Driver.DoubleTapFloat(x, y) From 80c50707f3881214cecc6d2fff5d46bd8776cbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Mon, 18 Mar 2024 14:47:18 +0800 Subject: [PATCH 02/14] fix: default input frequency from 60 to 10 feat: swipe and tap by /action without duration --- hrp/pkg/uixt/action.go | 2 +- hrp/pkg/uixt/android_uia2_driver.go | 49 ++++++++++++++++++----------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index c15b4406..2dbe889f 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -297,7 +297,7 @@ func (o *ActionOptions) updateData(data map[string]interface{}) { data["frequency"] = o.Frequency } if _, ok := data["frequency"]; !ok { - data["frequency"] = 60 // default frequency + data["frequency"] = 10 // default frequency } if _, ok := data["replace"]; !ok { diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index 819ec4ef..b68d09e9 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -283,14 +283,26 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error) y += actionOptions.getRandomOffset() data := map[string]interface{}{ - "x": x, - "y": y, + "actions": []interface{}{ + map[string]interface{}{ + "type": "pointer", + "parameters": map[string]string{"pointerType": "touch"}, + "id": "touch", + "actions": []interface{}{ + map[string]interface{}{"type": "pointerMove", "duration": 0, "x": x, "y": y, "origin": "viewport"}, + map[string]interface{}{"type": "pointerDown", "duration": 0, "button": 0}, + map[string]interface{}{"type": "pause", "duration": 100}, + map[string]interface{}{"type": "pointerUp", "duration": 0, "button": 0}, + }, + }, + }, } + // update data options in post data for extra uiautomator configurations actionOptions.updateData(data) - _, err = ud.httpPOST(data, "/session", ud.sessionId, "appium/tap") - return + _, err = ud.httpPOST(data, "/session", ud.sessionId, "actions/tap") + return err } func (ud *uiaDriver) TouchAndHold(x, y int, second ...float64) (err error) { @@ -361,28 +373,27 @@ func (ud *uiaDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption) func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error { // register(postHandler, new Swipe("/wd/hub/session/:sessionId/touch/perform")) actionOptions := NewActionOptions(options...) - if len(actionOptions.Offset) == 4 { - fromX += float64(actionOptions.Offset[0]) - fromY += float64(actionOptions.Offset[1]) - toX += float64(actionOptions.Offset[2]) - toY += float64(actionOptions.Offset[3]) - } - fromX += actionOptions.getRandomOffset() - fromY += actionOptions.getRandomOffset() - toX += actionOptions.getRandomOffset() - toY += actionOptions.getRandomOffset() data := map[string]interface{}{ - "startX": fromX, - "startY": fromY, - "endX": toX, - "endY": toY, + "actions": []interface{}{ + map[string]interface{}{ + "type": "pointer", + "parameters": map[string]string{"pointerType": "touch"}, + "id": "touch", + "actions": []interface{}{ + map[string]interface{}{"type": "pointerMove", "duration": 0, "x": fromX, "y": fromY, "origin": "viewport"}, + map[string]interface{}{"type": "pointerDown", "duration": 0, "button": 0}, + map[string]interface{}{"type": "pointerMove", "duration": 200, "x": toX, "y": toY, "origin": "viewport"}, + map[string]interface{}{"type": "pointerUp", "duration": 0, "button": 0}, + }, + }, + }, } // update data options in post data for extra uiautomator configurations actionOptions.updateData(data) - _, err := ud.httpPOST(data, "/session", ud.sessionId, "touch/perform") + _, err := ud.httpPOST(data, "/session", ud.sessionId, "actions/swipe") return err } From 60580256c0c5826f18a65e272826dee63b7adfea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 19 Mar 2024 16:27:06 +0800 Subject: [PATCH 03/14] fix: error getting window size during screen rotation feat: add log --- hrp/pkg/uixt/android_adb_driver.go | 25 +++++++++++++++++++++++-- hrp/pkg/uixt/android_test.go | 2 +- hrp/pkg/uixt/android_uia2_driver.go | 8 ++++++++ hrp/pkg/uixt/interface.go | 4 ++++ hrp/pkg/uixt/ios_driver.go | 8 ++++++++ 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index 7910e839..f3161a56 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -6,6 +6,7 @@ import ( "io/fs" "io/ioutil" "path/filepath" + "regexp" "strconv" "strings" "time" @@ -91,7 +92,14 @@ func (ad *adbDriver) WindowSize() (size Size, err error) { return Size{Width: width, Height: height}, nil } } - + orientation, err := ad.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("window size get orientation failed, use default orientation") + orientation = OrientationPortrait + } + if orientation != OrientationPortrait { + size.Width, size.Height = size.Height, size.Width + } err = errors.New("physical window size not found by adb") return } @@ -186,7 +194,20 @@ func (ad *adbDriver) StopCamera() (err error) { } func (ad *adbDriver) Orientation() (orientation Orientation, err error) { - err = errDriverNotImplemented + output, err := ad.adbClient.RunShellCommand("dumpsys", "input", "|", "grep", "'SurfaceOrientation'") + if err != nil { + return + } + re := regexp.MustCompile(`SurfaceOrientation: (\d)`) + matches := re.FindStringSubmatch(output) + if len(matches) > 1 { // 确保找到了匹配项 + if matches[1] == "0" || matches[1] == "2" { + return OrientationPortrait, nil + } else if matches[1] == "1" || matches[1] == "3" { + return OrientationLandscapeLeft, nil + } + } + err = fmt.Errorf("not found SurfaceOrientation value") return } diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index cd15736b..9b2d13db 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -18,7 +18,7 @@ var ( func setupAndroid(t *testing.T) { device, err := NewAndroidDevice() checkErr(t, err) - device.UIA2 = true + //device.UIA2 = true driverExt, err = device.NewDriver() checkErr(t, err) } diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index b68d09e9..ecd2611d 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -224,6 +224,14 @@ func (ud *uiaDriver) WindowSize() (size Size, err error) { return Size{}, err } size = reply.Value.Size + orientation, err := ud.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("window size get orientation failed, use default orientation") + orientation = OrientationPortrait + } + if orientation != OrientationPortrait { + size.Width, size.Height = size.Height, size.Width + } return } diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index a447d3a3..07885f56 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -511,6 +511,10 @@ type WebDriver interface { // since the location service needs some time to update the location data. Location() (Location, error) BatteryInfo() (BatteryInfo, error) + + // WindowSize Return the width and height in portrait mode. + // when getting the window size in wda/ui2/adb, if the device is in landscape mode, + // the width and height will be reversed. WindowSize() (Size, error) Screen() (Screen, error) Scale() (float64, error) diff --git a/hrp/pkg/uixt/ios_driver.go b/hrp/pkg/uixt/ios_driver.go index ea2d305f..2c69e984 100644 --- a/hrp/pkg/uixt/ios_driver.go +++ b/hrp/pkg/uixt/ios_driver.go @@ -207,6 +207,14 @@ func (wd *wdaDriver) WindowSize() (size Size, err error) { } size.Height = size.Height * int(scale) size.Width = size.Width * int(scale) + orientation, err := wd.Orientation() + if err != nil { + log.Warn().Err(err).Msgf("window size get orientation failed, use default orientation") + orientation = OrientationPortrait + } + if orientation != OrientationPortrait { + size.Width, size.Height = size.Height, size.Width + } return } From df745842d5a040d85e81d7006d2e0f17beb2d4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Wed, 28 Feb 2024 17:34:03 +0800 Subject: [PATCH 04/14] fix: kuake input unicode error --- hrp/pkg/uixt/android_adb_driver.go | 31 +++++++++++++++++++++++- hrp/pkg/uixt/android_test.go | 38 +++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index f3161a56..cec08193 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -2,6 +2,7 @@ package uixt import ( "bytes" + "encoding/base64" "fmt" "io/fs" "io/ioutil" @@ -353,7 +354,7 @@ func (ad *adbDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe func (ad *adbDriver) SendKeys(text string, options ...ActionOption) (err error) { // adb shell input text - _, err = ad.adbClient.RunShellCommand("input", "text", text) + _, err = ad.adbClient.RunShellCommand("input", "text", encodeUnicodeText(text)) if err != nil { return errors.Wrap(err, "send keys failed") } @@ -620,6 +621,34 @@ func (ad *adbDriver) AssertForegroundApp(packageName string, activityType ...str "assert foreground activity failed") } +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, "=") +} + +func encodeUnicodeText(text string) string { + text = strings.ReplaceAll(text, "&", "&-") + var sb strings.Builder + sb.WriteRune('"') + for _, c := range text { + if c <= 127 { + sb.WriteRune(c) + } else { + // Encode non-ASCII character and append it + sb.WriteString("&" + encodeUnicode(c) + "-") + } + } + sb.WriteRune('"') + return sb.String() +} + var androidActivities = map[string]map[string][]string{ // DY "com.ss.android.ugc.aweme": { diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index 9b2d13db..1da77b00 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -255,17 +255,17 @@ func TestDriver_SendKeys(t *testing.T) { } time.Sleep(time.Second * 2) - err = driver.SendKeys("def") - if err != nil { - t.Fatal(err) - } - time.Sleep(time.Second * 2) + //err = driver.SendKeys("def") + //if err != nil { + // t.Fatal(err) + //} + //time.Sleep(time.Second * 2) - err = driver.SendKeys("\\n") + //err = driver.SendKeys("\\n") // err = driver.SendKeys(`\n`, false) - if err != nil { - t.Fatal(err) - } + //if err != nil { + // t.Fatal(err) + //} } func TestDriver_PressBack(t *testing.T) { @@ -437,3 +437,23 @@ func TestConvertPoints(t *testing.T) { jsons, _ := json.Marshal(eps) println(fmt.Sprintf("%v", string(jsons))) } + +func TestDriver_ShellInputUnicode(t *testing.T) { + device, _ := NewAndroidDevice() + driver, err := device.NewAdbDriver() + if err != nil { + t.Fatal(err) + } + + err = driver.SendKeys("test中文输入&") + if err != nil { + t.Fatal(err) + } + + raw, err := driver.Screenshot() + if err != nil { + t.Fatal(err) + } + + t.Log(os.WriteFile("s1.png", raw.Bytes(), 0o600)) +} From 58a905328b4488f39dc41a08c226472bcb3b3818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Mon, 8 Apr 2024 15:03:40 +0800 Subject: [PATCH 05/14] feat: android input by appium ime --- hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/android_adb_driver.go | 104 +++++++++++++------ hrp/pkg/uixt/android_device.go | 19 +++- hrp/pkg/uixt/android_test.go | 11 +- hrp/pkg/uixt/android_uia2_driver.go | 78 ++++++++++++--- hrp/pkg/uixt/ios_driver.go | 4 +- hrp/pkg/uixt/ios_test.go | 26 ++++- hrp/pkg/utf7/decoder.go | 149 ++++++++++++++++++++++++++++ hrp/pkg/utf7/encoder.go | 91 +++++++++++++++++ hrp/pkg/utf7/utf7.go | 34 +++++++ 10 files changed, 459 insertions(+), 59 deletions(-) create mode 100644 hrp/pkg/utf7/decoder.go create mode 100644 hrp/pkg/utf7/encoder.go create mode 100644 hrp/pkg/utf7/utf7.go diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index f59ab889..686bba9a 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.3.7 +v4.4.0 \ No newline at end of file diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index cec08193..a2281cd3 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -2,8 +2,8 @@ package uixt import ( "bytes" - "encoding/base64" "fmt" + "github.com/httprunner/httprunner/v4/hrp/pkg/utf7" "io/fs" "io/ioutil" "path/filepath" @@ -22,6 +22,7 @@ import ( ) const AdbKeyBoardPackageName = "com.android.adbkeyboard/.AdbIME" +const UnicodeImePackageName = "io.appium.settings/.UnicodeIME" type adbDriver struct { Driver @@ -353,14 +354,53 @@ func (ad *adbDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe } func (ad *adbDriver) SendKeys(text string, options ...ActionOption) (err error) { + err = ad.SendUnicodeKeys(text, options...) + if err == nil { + return + } + err = ad.InputText(text, options...) + return +} + +func (ad *adbDriver) InputText(text string, options ...ActionOption) (err error) { // adb shell input text - _, err = ad.adbClient.RunShellCommand("input", "text", encodeUnicodeText(text)) + _, err = ad.adbClient.RunShellCommand("input", "text", text) if err != nil { return errors.Wrap(err, "send keys failed") } return nil } +func (ad *adbDriver) SendUnicodeKeys(text string, options ...ActionOption) (err error) { + // If the Unicode IME is not installed, fall back to the old interface. + // There might be differences in the tracking schemes across different phones, and it is pending further verification. + // In release version: without the Unicode IME installed, the test cannot execute. + if !ad.IsUnicodeIMEInstalled() { + return fmt.Errorf("appium unicode ime not installed") + } + currentIme, err := ad.GetIme() + if err != nil { + return + } + if currentIme != UnicodeImePackageName { + defer func() { + _ = ad.SetIme(currentIme) + }() + err = ad.SetIme(UnicodeImePackageName) + if err != nil { + log.Warn().Err(err).Msgf("set Unicode Ime failed") + return + } + } + encodedStr, err := utf7.Encoding.NewEncoder().String(text) + if err != nil { + log.Warn().Err(err).Msgf("encode text with modified utf7 failed") + return + } + err = ad.InputText("\""+strings.ReplaceAll(encodedStr, "\"", "\\\"")+"\"", options...) + return +} + func (ad *adbDriver) IsAdbKeyBoardInstalled() bool { output, err := ad.adbClient.RunShellCommand("ime", "list", "-a") if err != nil { @@ -369,6 +409,14 @@ func (ad *adbDriver) IsAdbKeyBoardInstalled() bool { return strings.Contains(output, AdbKeyBoardPackageName) } +func (ad *adbDriver) IsUnicodeIMEInstalled() bool { + output, err := ad.adbClient.RunShellCommand("ime", "list", "-s") + if err != nil { + return false + } + return strings.Contains(output, UnicodeImePackageName) +} + func (ad *adbDriver) SendKeysByAdbKeyBoard(text string) (err error) { defer func() { // Reset to default, don't care which keyboard was chosen before switch: @@ -561,6 +609,30 @@ func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) { return AppInfo{}, errors.Wrap(code.MobileUIAssertForegroundAppError, "get foreground app failed") } +func (ad *adbDriver) SetIme(ime string) error { + _, err := ad.adbClient.RunShellCommand("ime", "set", ime) + if err != nil { + return err + } + // even if the shell command has returned, + // as there might be a situation where the input method has not been completely switched yet + // Listen to the following message. + // InputMethodManagerService: onServiceConnected, name:ComponentInfo{io.appium.settings/io.appium.settings.UnicodeIME}, token:android.os.Binder@44f825 + // But there is no such log on Vivo. + time.Sleep(3 * time.Second) + return nil +} + +func (ad *adbDriver) GetIme() (ime string, err error) { + currentIme, err := ad.adbClient.RunShellCommand("settings", "get", "secure", "default_input_method") + if err != nil { + log.Warn().Err(err).Msgf("get default ime failed") + return + } + currentIme = strings.TrimSpace(currentIme) + return currentIme, nil +} + func (ad *adbDriver) AssertForegroundApp(packageName string, activityType ...string) error { log.Debug().Str("package_name", packageName). Strs("activity_type", activityType). @@ -621,34 +693,6 @@ func (ad *adbDriver) AssertForegroundApp(packageName string, activityType ...str "assert foreground activity failed") } -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, "=") -} - -func encodeUnicodeText(text string) string { - text = strings.ReplaceAll(text, "&", "&-") - var sb strings.Builder - sb.WriteRune('"') - for _, c := range text { - if c <= 127 { - sb.WriteRune(c) - } else { - // Encode non-ASCII character and append it - sb.WriteString("&" + encodeUnicode(c) + "-") - } - } - sb.WriteRune('"') - return sb.String() -} - var androidActivities = map[string]map[string][]string{ // DY "com.ss.android.ugc.aweme": { diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 84edf057..acb13539 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -3,17 +3,18 @@ package uixt import ( "bytes" "context" + "encoding/base64" "fmt" + "github.com/httprunner/funplugin/myexec" + "github.com/httprunner/httprunner/v4/hrp/internal/json" "net" "os/exec" "strings" - "github.com/httprunner/funplugin/myexec" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp/internal/code" - "github.com/httprunner/httprunner/v4/hrp/internal/json" "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" ) @@ -147,6 +148,18 @@ 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 @@ -562,7 +575,7 @@ func (s UiSelectorHelper) Index(index int) UiSelectorHelper { // // For example, to simulate a user click on // the third image that is enabled in a UI screen, you -// could specify a a search criteria where the instance is +// could specify a search criteria where the instance is // 2, the `className(String)` matches the image // widget class, and `enabled(boolean)` is true. // The code would look like this: diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index 1da77b00..50220107 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "os" "testing" "time" ) @@ -18,7 +19,7 @@ var ( func setupAndroid(t *testing.T) { device, err := NewAndroidDevice() checkErr(t, err) - //device.UIA2 = true + device.UIA2 = false driverExt, err = device.NewDriver() checkErr(t, err) } @@ -244,15 +245,13 @@ func TestDriver_Drag(t *testing.T) { } func TestDriver_SendKeys(t *testing.T) { - driver, err := NewUIADriver(nil, uiaServerURL) + setupAndroid(t) + + err := driverExt.Driver.SendKeys("Android\"输入速度测试", WithIdentifier("test")) if err != nil { t.Fatal(err) } - err = driver.SendKeys("abc") - if err != nil { - t.Fatal(err) - } time.Sleep(time.Second * 2) //err = driver.SendKeys("def") diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index ecd2611d..58f6b733 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "github.com/httprunner/httprunner/v4/hrp/pkg/utf7" "net" "net/http" "net/url" @@ -452,21 +453,74 @@ func (ud *uiaDriver) SendKeys(text string, options ...ActionOption) (err error) // register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/keys")) // https://github.com/appium/appium-uiautomator2-server/blob/master/app/src/main/java/io/appium/uiautomator2/handler/SendKeysToElement.java#L76-L85 actionOptions := NewActionOptions(options...) - data := map[string]interface{}{ - "text": text, - } - // new data options in post data for extra uiautomator configurations - actionOptions.updateData(data) - - _, err = ud.httpPOST(data, "/session", ud.sessionId, "keys") + err = ud.SendUnicodeKeys(text, options...) if err != nil { - // use com.android.adbkeyboard if existed - if ud.IsAdbKeyBoardInstalled() { - err = ud.SendKeysByAdbKeyBoard(text) - } else { - _, err = ud.adbClient.RunShellCommand("input", "text", text) + data := map[string]interface{}{ + "text": text, + } + + // new data options in post data for extra uiautomator configurations + actionOptions.updateData(data) + + _, err = ud.httpPOST(data, "/session", ud.sessionId, "/keys") + } + return +} + +func (ud *uiaDriver) SendUnicodeKeys(text string, options ...ActionOption) (err error) { + // If the Unicode IME is not installed, fall back to the old interface. + // There might be differences in the tracking schemes across different phones, and it is pending further verification. + // In release version: without the Unicode IME installed, the test cannot execute. + if !ud.IsUnicodeIMEInstalled() { + return fmt.Errorf("appium unicode ime not installed") + } + currentIme, err := ud.adbDriver.GetIme() + if err != nil { + return + } + if currentIme != UnicodeImePackageName { + defer func() { + _ = ud.adbDriver.SetIme(currentIme) + }() + err = ud.adbDriver.SetIme(UnicodeImePackageName) + if err != nil { + log.Warn().Err(err).Msgf("set Unicode Ime failed") + return } } + encodedStr, err := utf7.Encoding.NewEncoder().String(text) + if err != nil { + log.Warn().Err(err).Msgf("encode text with modified utf7 failed") + return + } + err = ud.SendActionKey(encodedStr, options...) + return +} + +func (ud *uiaDriver) SendActionKey(text string, options ...ActionOption) (err error) { + actionOptions := NewActionOptions(options...) + var actions []interface{} + for i, c := range text { + actions = append(actions, map[string]interface{}{"type": "keyDown", "value": string(c)}, + map[string]interface{}{"type": "keyUp", "value": string(c)}) + if i != len(text)-1 { + actions = append(actions, map[string]interface{}{"type": "pause", "duration": 40}) + } + } + + data := map[string]interface{}{ + "actions": []interface{}{ + map[string]interface{}{ + "type": "key", + "id": "key", + "actions": actions, + }, + }, + } + + // new data options in post data for extra uiautomator configurations + actionOptions.updateData(data) + _, err = ud.httpPOST(data, "/session", ud.sessionId, "/actions/keys") return } diff --git a/hrp/pkg/uixt/ios_driver.go b/hrp/pkg/uixt/ios_driver.go index 2c69e984..e274044a 100644 --- a/hrp/pkg/uixt/ios_driver.go +++ b/hrp/pkg/uixt/ios_driver.go @@ -555,8 +555,8 @@ func (wd *wdaDriver) DragFloat(fromX, fromY, toX, toY float64, options ...Action // update data options in post data for extra WDA configurations actionOptions.updateData(data) - - _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/dragfromtoforduration") + // wda 43 version + _, err = wd.httpPOST(data, "/session", wd.sessionId, "/wda/drag") return } diff --git a/hrp/pkg/uixt/ios_test.go b/hrp/pkg/uixt/ios_test.go index 0e6a16f5..3ab9785f 100644 --- a/hrp/pkg/uixt/ios_test.go +++ b/hrp/pkg/uixt/ios_test.go @@ -10,17 +10,23 @@ import ( ) var ( - bundleId = "com.apple.Preferences" - driver WebDriver + bundleId = "com.apple.Preferences" + driver WebDriver + iOSDriverExt *DriverExt ) func setup(t *testing.T) { - device, err := NewIOSDevice() + device, err := NewIOSDevice(WithWDAPort(8700), WithWDAMjpegPort(8800)) if err != nil { t.Fatal(err) } - - driver, err = device.NewUSBDriver(nil) + capabilities := NewCapabilities() + capabilities.WithDefaultAlertAction(AlertActionAccept) + driver, err = device.NewUSBDriver(capabilities) + if err != nil { + t.Fatal(err) + } + iOSDriverExt, err = newDriverExt(device, driver, nil) if err != nil { t.Fatal(err) } @@ -267,6 +273,16 @@ func Test_remoteWD_Drag(t *testing.T) { } } +func Test_Relative_Drag(t *testing.T) { + setup(t) + + // err := driver.Drag(200, 300, 200, 500, WithDataPressDuration(0.5)) + err := iOSDriverExt.SwipeRelative(0.5, 0.7, 0.5, 0.5) + if err != nil { + t.Fatal(err) + } +} + func Test_remoteWD_SetPasteboard(t *testing.T) { setup(t) diff --git a/hrp/pkg/utf7/decoder.go b/hrp/pkg/utf7/decoder.go new file mode 100644 index 00000000..cfcba8c0 --- /dev/null +++ b/hrp/pkg/utf7/decoder.go @@ -0,0 +1,149 @@ +package utf7 + +import ( + "errors" + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/text/transform" +) + +// ErrInvalidUTF7 means that a transformer encountered invalid UTF-7. +var ErrInvalidUTF7 = errors.New("utf7: invalid UTF-7") + +type decoder struct { + ascii bool +} + +func (d *decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + for i := 0; i < len(src); i++ { + ch := src[i] + + if ch < min || ch > max { // Illegal code point in ASCII mode + err = ErrInvalidUTF7 + return + } + + if ch != '&' { + if nDst+1 > len(dst) { + err = transform.ErrShortDst + return + } + + nSrc++ + + dst[nDst] = ch + nDst++ + + d.ascii = true + continue + } + + // Find the end of the Base64 or "&-" segment + start := i + 1 + for i++; i < len(src) && src[i] != '-'; i++ { + if src[i] == '\r' || src[i] == '\n' { // base64 package ignores CR and LF + err = ErrInvalidUTF7 + return + } + } + + if i == len(src) { // Implicit shift ("&...") + if atEOF { + err = ErrInvalidUTF7 + } else { + err = transform.ErrShortSrc + } + return + } + + var b []byte + if i == start { // Escape sequence "&-" + b = []byte{'&'} + d.ascii = true + } else { // Control or non-ASCII code points in base64 + if !d.ascii { // Null shift ("&...-&...-") + err = ErrInvalidUTF7 + return + } + + b = decode(src[start:i]) + d.ascii = false + } + + if len(b) == 0 { // Bad encoding + err = ErrInvalidUTF7 + return + } + + if nDst+len(b) > len(dst) { + d.ascii = true + err = transform.ErrShortDst + return + } + + nSrc = i + 1 + + for _, ch := range b { + dst[nDst] = ch + nDst++ + } + } + + if atEOF { + d.ascii = true + } + + return +} + +func (d *decoder) Reset() { + d.ascii = true +} + +// Extracts UTF-16-BE bytes from base64 data and converts them to UTF-8. +// A nil slice is returned if the encoding is invalid. +func decode(b64 []byte) []byte { + var b []byte + + // Allocate a single block of memory large enough to store the Base64 data + // (if padding is required), UTF-16-BE bytes, and decoded UTF-8 bytes. + // Since a 2-byte UTF-16 sequence may expand into a 3-byte UTF-8 sequence, + // double the space allocation for UTF-8. + if n := len(b64); b64[n-1] == '=' { + return nil + } else if n&3 == 0 { + b = make([]byte, b64Enc.DecodedLen(n)*3) + } else { + n += 4 - n&3 + b = make([]byte, n+b64Enc.DecodedLen(n)*3) + copy(b[copy(b, b64):n], []byte("==")) + b64, b = b[:n], b[n:] + } + + // Decode Base64 into the first 1/3rd of b + n, err := b64Enc.Decode(b, b64) + if err != nil || n&1 == 1 { + return nil + } + + // Decode UTF-16-BE into the remaining 2/3rds of b + b, s := b[:n], b[n:] + j := 0 + for i := 0; i < n; i += 2 { + r := rune(b[i])<<8 | rune(b[i+1]) + if utf16.IsSurrogate(r) { + if i += 2; i == n { + return nil + } + r2 := rune(b[i])<<8 | rune(b[i+1]) + if r = utf16.DecodeRune(r, r2); r == repl { + return nil + } + } else if min <= r && r <= max { + return nil + } + j += utf8.EncodeRune(s[j:], r) + } + return s[:j] +} diff --git a/hrp/pkg/utf7/encoder.go b/hrp/pkg/utf7/encoder.go new file mode 100644 index 00000000..8414d109 --- /dev/null +++ b/hrp/pkg/utf7/encoder.go @@ -0,0 +1,91 @@ +package utf7 + +import ( + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/text/transform" +) + +type encoder struct{} + +func (e *encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + for i := 0; i < len(src); { + ch := src[i] + + var b []byte + if min <= ch && ch <= max { + b = []byte{ch} + if ch == '&' { + b = append(b, '-') + } + + i++ + } else { + start := i + + // Find the next printable ASCII code point + i++ + for i < len(src) && (src[i] < min || src[i] > max) { + i++ + } + + if !atEOF && i == len(src) { + err = transform.ErrShortSrc + return + } + + b = encode(src[start:i]) + } + + if nDst+len(b) > len(dst) { + err = transform.ErrShortDst + return + } + + nSrc = i + + for _, ch := range b { + dst[nDst] = ch + nDst++ + } + } + + return +} + +func (e *encoder) Reset() {} + +// Converts string s from UTF-8 to UTF-16-BE, encodes the result as base64, +// removes the padding, and adds UTF-7 shifts. +func encode(s []byte) []byte { + // len(s) is sufficient for UTF-8 to UTF-16 conversion if there are no + // control code points (see table below). + b := make([]byte, 0, len(s)+4) + for len(s) > 0 { + r, size := utf8.DecodeRune(s) + if r > utf8.MaxRune { + r, size = utf8.RuneError, 1 // Bug fix (issue 3785) + } + s = s[size:] + if r1, r2 := utf16.EncodeRune(r); r1 != repl { + b = append(b, byte(r1>>8), byte(r1)) + r = r2 + } + b = append(b, byte(r>>8), byte(r)) + } + + // Encode as base64 + n := b64Enc.EncodedLen(len(b)) + 2 + b64 := make([]byte, n) + b64Enc.Encode(b64[1:], b) + + // Strip padding + n -= 2 - (len(b)+2)%3 + b64 = b64[:n] + + // Add UTF-7 shifts + b64[0] = '&' + b64[n-1] = '-' + return b64 +} diff --git a/hrp/pkg/utf7/utf7.go b/hrp/pkg/utf7/utf7.go new file mode 100644 index 00000000..b9dd9623 --- /dev/null +++ b/hrp/pkg/utf7/utf7.go @@ -0,0 +1,34 @@ +// Package utf7 implements modified UTF-7 encoding defined in RFC 3501 section 5.1.3 +package utf7 + +import ( + "encoding/base64" + + "golang.org/x/text/encoding" +) + +const ( + min = 0x20 // Minimum self-representing UTF-7 value + max = 0x7E // Maximum self-representing UTF-7 value + + repl = '\uFFFD' // Unicode replacement code point +) + +var b64Enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,") + +type enc struct{} + +func (e enc) NewDecoder() *encoding.Decoder { + return &encoding.Decoder{ + Transformer: &decoder{true}, + } +} + +func (e enc) NewEncoder() *encoding.Encoder { + return &encoding.Encoder{ + Transformer: &encoder{}, + } +} + +// Encoding is the modified UTF-7 encoding. +var Encoding encoding.Encoding = enc{} From e011741d6bea65a6d8ed511c94eaf0c901eff4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 9 Apr 2024 17:03:50 +0800 Subject: [PATCH 06/14] feat: android swipe and tap with duration --- hrp/pkg/uixt/android_test.go | 2 +- hrp/pkg/uixt/android_uia2_driver.go | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/hrp/pkg/uixt/android_test.go b/hrp/pkg/uixt/android_test.go index 50220107..ac177bf6 100644 --- a/hrp/pkg/uixt/android_test.go +++ b/hrp/pkg/uixt/android_test.go @@ -206,7 +206,7 @@ func TestDriver_Swipe(t *testing.T) { t.Fatal(err) } - err = driver.Swipe(400, 1000, 400, 500) + err = driver.Swipe(400, 1000, 400, 500, WithPressDuration(2000)) 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 58f6b733..dcfda888 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -291,6 +291,10 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error) x += actionOptions.getRandomOffset() y += actionOptions.getRandomOffset() + duration := 100.0 + if actionOptions.PressDuration > 0 { + duration = actionOptions.PressDuration + } data := map[string]interface{}{ "actions": []interface{}{ map[string]interface{}{ @@ -300,7 +304,7 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error) "actions": []interface{}{ map[string]interface{}{"type": "pointerMove", "duration": 0, "x": x, "y": y, "origin": "viewport"}, map[string]interface{}{"type": "pointerDown", "duration": 0, "button": 0}, - map[string]interface{}{"type": "pause", "duration": 100}, + map[string]interface{}{"type": "pause", "duration": duration}, map[string]interface{}{"type": "pointerUp", "duration": 0, "button": 0}, }, }, @@ -382,7 +386,21 @@ func (ud *uiaDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption) func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error { // register(postHandler, new Swipe("/wd/hub/session/:sessionId/touch/perform")) actionOptions := NewActionOptions(options...) + if len(actionOptions.Offset) == 4 { + fromX += float64(actionOptions.Offset[0]) + fromY += float64(actionOptions.Offset[1]) + toX += float64(actionOptions.Offset[2]) + toY += float64(actionOptions.Offset[3]) + } + fromX += actionOptions.getRandomOffset() + fromY += actionOptions.getRandomOffset() + toX += actionOptions.getRandomOffset() + toY += actionOptions.getRandomOffset() + duration := 200.0 + if actionOptions.PressDuration > 0 { + duration = actionOptions.PressDuration + } data := map[string]interface{}{ "actions": []interface{}{ map[string]interface{}{ @@ -392,7 +410,7 @@ func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...Actio "actions": []interface{}{ map[string]interface{}{"type": "pointerMove", "duration": 0, "x": fromX, "y": fromY, "origin": "viewport"}, map[string]interface{}{"type": "pointerDown", "duration": 0, "button": 0}, - map[string]interface{}{"type": "pointerMove", "duration": 200, "x": toX, "y": toY, "origin": "viewport"}, + map[string]interface{}{"type": "pointerMove", "duration": duration, "x": toX, "y": toY, "origin": "viewport"}, map[string]interface{}{"type": "pointerUp", "duration": 0, "button": 0}, }, }, @@ -449,6 +467,7 @@ func (ud *uiaDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe return } +// SendKeys Android input does not support setting frequency. func (ud *uiaDriver) SendKeys(text string, options ...ActionOption) (err error) { // register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/keys")) // https://github.com/appium/appium-uiautomator2-server/blob/master/app/src/main/java/io/appium/uiautomator2/handler/SendKeysToElement.java#L76-L85 From d129455f118ea88652d9eea2f81a91a256769543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 9 Apr 2024 19:20:29 +0800 Subject: [PATCH 07/14] fix: format import file --- go.mod | 14 ++++++++++---- go.sum | 17 +++++++++++++++++ hrp/pkg/uixt/android_adb_driver.go | 2 +- hrp/pkg/uixt/android_device.go | 4 ++-- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 23cc3089..bb57aacc 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.4 gocv.io/x/gocv v0.32.1 - golang.org/x/net v0.14.0 + golang.org/x/net v0.20.0 golang.org/x/oauth2 v0.8.0 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 @@ -47,10 +47,12 @@ require ( github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-plugin v1.4.10 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/incu6us/goimports-reviser/v2 v2.5.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -67,17 +69,21 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.5.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.17.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + mvdan.cc/gofumpt v0.6.0 // indirect ) // replace github.com/httprunner/funplugin => ../funplugin diff --git a/go.sum b/go.sum index 19e9da71..7ea4a7f1 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -178,6 +180,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/incu6us/goimports-reviser/v2 v2.5.3 h1:DzvFl1+qOIDukqN8vMM/10MQswFQywUdwXxsjuowxlc= +github.com/incu6us/goimports-reviser/v2 v2.5.3/go.mod h1:P18aXhQaED7izHIP9IPI9PqEs7Y7D9okq71Q8Y8yHN4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= @@ -290,6 +294,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= @@ -369,6 +375,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -402,6 +410,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -420,6 +429,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -468,6 +479,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -479,6 +491,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -522,6 +535,8 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -638,6 +653,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= +mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index a2281cd3..8bf2d066 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -3,7 +3,6 @@ package uixt import ( "bytes" "fmt" - "github.com/httprunner/httprunner/v4/hrp/pkg/utf7" "io/fs" "io/ioutil" "path/filepath" @@ -19,6 +18,7 @@ import ( "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" diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index acb13539..f8d5ef11 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -5,8 +5,6 @@ import ( "context" "encoding/base64" "fmt" - "github.com/httprunner/funplugin/myexec" - "github.com/httprunner/httprunner/v4/hrp/internal/json" "net" "os/exec" "strings" @@ -14,7 +12,9 @@ import ( "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" ) From d3aa72a713a3932a7807d6c159f9e4118bd5a716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 9 Apr 2024 19:38:01 +0800 Subject: [PATCH 08/14] fix: format import file --- go.mod | 2 +- go.sum | 3 +++ hrp/pkg/uixt/android_uia2_driver.go | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index bb57aacc..1cc8d8c4 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( gocv.io/x/gocv v0.32.1 golang.org/x/net v0.20.0 golang.org/x/oauth2 v0.8.0 + golang.org/x/text v0.14.0 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 @@ -78,7 +79,6 @@ require ( golang.org/x/mod v0.14.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.17.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect diff --git a/go.sum b/go.sum index 7ea4a7f1..40bce52f 100644 --- a/go.sum +++ b/go.sum @@ -410,6 +410,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -479,6 +480,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -491,6 +493,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index dcfda888..b182b6ed 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/httprunner/httprunner/v4/hrp/pkg/utf7" "net" "net/http" "net/url" @@ -17,6 +16,7 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp/internal/code" + "github.com/httprunner/httprunner/v4/hrp/pkg/utf7" ) var errDriverNotImplemented = errors.New("driver method not implemented") 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 09/14] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=8C=89?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E7=82=B9=E5=87=BB=EF=BC=8C=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=BA=94=E7=94=A8=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=97=A5=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 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 10/14] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Eui2=E6=8E=A7?= =?UTF-8?q?=E4=BB=B6=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 } From 5ccc8d00aac18fbe7bb87a4435bdb1b92f7f8c5c 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 20:03:39 +0800 Subject: [PATCH 11/14] =?UTF-8?q?feat:=20=E5=85=8D=E5=AF=86=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E5=AE=89=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hrp/cmd/adb/install.go | 62 ++++++++++++++++ hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/android_device.go | 130 ++++++++++++++++++++++++++++++++- hrp/pkg/uixt/ext.go | 43 +++++++++++ hrp/pkg/uixt/interface.go | 3 + hrp/pkg/uixt/ios_device.go | 4 + 6 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 hrp/cmd/adb/install.go diff --git a/hrp/cmd/adb/install.go b/hrp/cmd/adb/install.go new file mode 100644 index 00000000..b7245c81 --- /dev/null +++ b/hrp/cmd/adb/install.go @@ -0,0 +1,62 @@ +package adb + +import ( + "fmt" + "strings" + "time" + + "github.com/spf13/cobra" + + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" +) + +var installCmd = &cobra.Command{ + Use: "install [flags] PACKAGE", + Short: "Push package to the device and install them atomically", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + startTime := time.Now() + defer func() { + sdk.SendGA4Event("hrp_adb_devices", map[string]interface{}{ + "args": strings.Join(args, "-"), + "success": err == nil, + "engagement_time_msec": time.Since(startTime).Milliseconds(), + }) + }() + _, err = getDevice(serial) + if err != nil { + return err + } + + device, err := uixt.NewAndroidDevice(uixt.WithSerialNumber(serial)) + if err != nil { + fmt.Println(err) + return err + } + driverExt, err := device.NewDriver() + if err != nil { + fmt.Println(err) + return err + } + replace, _ := cmd.Flags().GetBool("replace") + downgrade, _ := cmd.Flags().GetBool("downgrade") + grant, _ := cmd.Flags().GetBool("grant") + option := uixt.InstallOptions{Reinstall: replace, GrantPermission: grant, Downgrade: downgrade} + err = driverExt.Install(args[0], option) + if err != nil { + fmt.Println(err) + return err + } + fmt.Println("success") + return nil + }, +} + +func init() { + installCmd.Flags().StringVarP(&serial, "serial", "s", "", "filter by device's serial") + installCmd.Flags().BoolP("replace", "r", false, "replace existing application") + installCmd.Flags().BoolP("downgrade", "d", false, "allow version code downgrade (debuggable packages only)") + installCmd.Flags().BoolP("grant", "g", false, "grant all runtime permissions") + androidRootCmd.AddCommand(installCmd) +} diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 686bba9a..17fed9e6 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.4.0 \ No newline at end of file +v4.5.0.20240425 \ No newline at end of file diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index b154a405..eef59e79 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -4,26 +4,36 @@ import ( "bufio" "bytes" "context" + "crypto/md5" + "encoding/base64" + "encoding/hex" "fmt" "io" "net" "os/exec" + "regexp" + "strconv" "strings" + "time" "github.com/httprunner/funplugin/myexec" "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/json" "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" ) var ( - AdbServerHost = "localhost" - AdbServerPort = gadb.AdbServerPort // 5037 - UIA2ServerHost = "localhost" - UIA2ServerPort = 6790 + AdbServerHost = "localhost" + AdbServerPort = gadb.AdbServerPort // 5037 + UIA2ServerHost = "localhost" + UIA2ServerPort = 6790 + DeviceTempPath = "/data/local/tmp" + EvalInstallerPackageName = "sogou.mobile.explorer" + InstallViaInstallerCommand = "am start -S -n sogou.mobile.explorer/.PackageInstallerActivity -d" ) const forwardToPrefix = "forward-to-" @@ -263,6 +273,118 @@ func (dev *AndroidDevice) StopPcap() string { return "" } +func (dev *AndroidDevice) Install(app io.ReadSeeker, opts InstallOptions) error { + brand, err := dev.d.Brand() + if err != nil { + return err + } + args := []string{} + if opts.Reinstall { + args = append(args, "-r") + } + if opts.GrantPermission { + args = append(args, "-g") + } + if opts.Downgrade { + args = append(args, "-d") + } + switch strings.ToLower(brand) { + case "vivo": + return dev.installVivoSilent(app, args...) + case "oppo", "realme", "oneplus": + if dev.d.IsPackagesInstalled(EvalInstallerPackageName) { + return dev.installViaInstaller(app, args...) + } + log.Warn().Msg("oppo not install eval installer") + return dev.installCommon(app, args...) + default: + return dev.installCommon(app, args...) + } +} + +func (dev *AndroidDevice) installVivoSilent(app io.ReadSeeker, args ...string) error { + currentTime := builtin.GetCurrentDay() + md5HashInBytes := md5.Sum([]byte(currentTime)) + verifyCode := hex.EncodeToString(md5HashInBytes[:]) + verifyCode = base64.StdEncoding.EncodeToString([]byte(verifyCode)) + verifyCode = verifyCode[:8] + verifyCode = "-V" + verifyCode + args = append([]string{verifyCode}, args...) + _, err := dev.d.InstallAPK(app, args...) + return err +} + +func (dev *AndroidDevice) installViaInstaller(app io.ReadSeeker, args ...string) error { + appRemotePath := "/data/local/tmp/" + strconv.FormatInt(time.Now().UnixMilli(), 10) + ".apk" + err := dev.d.Push(app, appRemotePath, time.Now()) + if err != nil { + return err + } + quit := make(chan struct{}) + done := make(chan error) + defer func() { close(quit) }() + // 需要监听是否完成安装 + go func() { + logcat := NewAdbLogcat(dev.d.Serial()) + err = logcat.CatchLogcat("PackageInstallerCallback") + if err != nil { + done <- err + return + } + scanner := bufio.NewScanner(logcat.reader) + defer func() { + close(done) + _ = logcat.Stop() + }() + for scanner.Scan() { + select { + case <-quit: + break + default: + line := scanner.Text() + re := regexp.MustCompile(`\{.*?}`) + match := re.FindString(line) + if match == "" { + continue + } + var result InstallResult + err := json.Unmarshal([]byte(match), &result) + if err != nil { + log.Warn().Msg("parse Install msg line error: " + match) + continue + } + if result.Result == 0 { + // 安装成功 + done <- nil + return + } else { + done <- errors.New(match) + } + } + } + done <- errors.New("install failed by installer") + }() + args = strings.Split(InstallViaInstallerCommand, " ") + args = append(args, appRemotePath) + _, err = dev.d.RunShellCommand("am", args[1:]...) + if err != nil { + return err + } + // 等待安装完成或超时 + timeout := 1 * time.Minute + select { + case err := <-done: + return err // 返回安装结果或错误 + case <-time.After(timeout): + return fmt.Errorf("installation timed out after %v", timeout) + } +} + +func (dev *AndroidDevice) installCommon(app io.ReadSeeker, args ...string) error { + _, err := dev.d.InstallAPK(app, args...) + return err +} + func getFreePort() (int, error) { addr, err := net.ResolveTCPAddr("tcp", "localhost:0") if err != nil { diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index b9ad0041..2a8e6d97 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -50,6 +50,18 @@ func WithThreshold(threshold float64) CVOption { } } +type InstallOptions struct { + Reinstall bool + GrantPermission bool + Downgrade bool +} + +type InstallResult struct { + Result int `json:"result"` + ErrorCode int `json:"errorCode"` + ErrorMsg string `json:"errorMsg"` +} + type ScreenResult struct { bufSource *bytes.Buffer // raw image buffer bytes imagePath string // image file path @@ -194,6 +206,37 @@ func newDriverExt(device Device, driver WebDriver, plugin funplugin.IPlugin) (dE return dExt, nil } +func (dExt *DriverExt) Install(filePath string, opts InstallOptions) error { + app, err := os.Open(filePath) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("install %s open file failed", filePath)) + } + stopChan := make(chan struct{}) + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + actions := []TapTextAction{ + {Text: "^.*无视风险安装$", Options: []ActionOption{WithTapOffset(100, 0), WithRegex(true), WithIgnoreNotFoundError(true)}}, + {Text: "^已了解此应用未经检测.*", Options: []ActionOption{WithTapOffset(-450, 0), WithRegex(true), WithIgnoreNotFoundError(true)}}, + } + _ = dExt.Driver.TapByTexts(actions...) + _ = dExt.TapByOCR("^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|安装|授权本次安装|继续安装|重新安装)$", WithRegex(true), WithIgnoreNotFoundError(true)) + case <-stopChan: + fmt.Println("Ticker stopped") + return + } + } + }() + defer func() { + close(stopChan) + }() + return dExt.Device.Install(app, opts) +} + // takeScreenShot takes screenshot and saves image file to $CWD/screenshots/ folder func (dExt *DriverExt) takeScreenShot(fileName string) (raw *bytes.Buffer, path string, err error) { // iOS 优先使用 MJPEG 流进行截图,性能最优 diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index 3fccf5ba..cf0a0c6f 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -2,6 +2,7 @@ package uixt import ( "bytes" + "io" "math" "strings" "time" @@ -477,6 +478,8 @@ type Device interface { StartPcap() error StopPcap() string + + Install(app io.ReadSeeker, opts InstallOptions) error } type ForegroundApp struct { diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index b5283667..6cfc4b57 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -466,6 +466,10 @@ func (dev *IOSDevice) StopPcap() string { return dev.pcapFile } +func (dev *IOSDevice) Install(app io.ReadSeeker, opts InstallOptions) error { + return errors.New("install method not implemented") +} + func (dev *IOSDevice) forward(localPort, remotePort int) error { log.Info().Int("localPort", localPort).Int("remotePort", remotePort). Str("udid", dev.UDID).Msg("forward tcp port") From e903d2be8ebcaf753a829af03ab610cee37f3e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Mon, 6 May 2024 19:44:59 +0800 Subject: [PATCH 12/14] fix: catch logcat by callback --- hrp/internal/version/VERSION | 2 +- hrp/pkg/gadb/device_test.go | 2 +- hrp/pkg/uixt/android_adb_driver.go | 26 +++++++++++++------- hrp/pkg/uixt/android_device.go | 38 ++++++++++++++++++++++++------ 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 17fed9e6..6c124601 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.5.0.20240425 \ No newline at end of file +v4.5.0.202405061137 \ No newline at end of file diff --git a/hrp/pkg/gadb/device_test.go b/hrp/pkg/gadb/device_test.go index 5f3ba45c..e1279051 100644 --- a/hrp/pkg/gadb/device_test.go +++ b/hrp/pkg/gadb/device_test.go @@ -351,7 +351,7 @@ func TestDevice_ListPackages(t *testing.T) { t.Fatal(err) } t.Log(res) - installed, err := dev.IsPackagesInstalled("io.appium.uiautomator2.server") + installed := dev.IsPackagesInstalled("io.appium.uiautomator2.server") if err != nil { t.Fatal(err) } diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index c1d6c2cc..fbbc32fd 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -1,6 +1,7 @@ package uixt import ( + "bufio" "bytes" "encoding/xml" "fmt" @@ -603,12 +604,6 @@ func (ad *adbDriver) IsHealthy() (healthy bool, err error) { func (ad *adbDriver) StartCaptureLog(identifier ...string) (err error) { log.Info().Msg("start adb log recording") - - // clear logcat - if _, err = ad.adbClient.RunShellCommand("logcat", "-c"); err != nil { - return err - } - // start logcat err = ad.logcat.CatchLogcat("iesqaMonitor:V") if err != nil { @@ -627,7 +622,10 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { log.Error().Err(err).Msg("failed to get adb log recording") } }() - pointRes := ConvertPoints(ad.logcat.reader) + if err != nil { + log.Error().Err(err).Msg("failed to close adb log writer") + } + pointRes := ConvertPoints(ad.logcat.logs) // 没有解析到打点日志,走兜底逻辑 if len(pointRes) == 0 { @@ -659,7 +657,19 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) { return pointRes, nil } - pointRes = ConvertPoints(reader) + var lines []string // 创建一个空的字符串数组来存储文件的每一行 + + // 使用 bufio.NewScanner 读取文件 + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + lines = append(lines, scanner.Text()) // 将每行文本添加到字符串数组 + } + + if err := scanner.Err(); err != nil { + return pointRes, nil + } + + pointRes = ConvertPoints(lines) } return pointRes, nil } diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index eef59e79..c3858ef6 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -399,6 +399,8 @@ func getFreePort() (int, error) { return l.Addr().(*net.TCPAddr).Port, nil } +type LineCallback func(string) + type AdbLogcat struct { serial string // logBuffer *bytes.Buffer @@ -406,7 +408,19 @@ type AdbLogcat struct { stopping chan struct{} done chan struct{} cmd *exec.Cmd - reader io.Reader + callback LineCallback + logs []string +} + +func NewAdbLogcatWithCallback(serial string, callback LineCallback) *AdbLogcat { + return &AdbLogcat{ + serial: serial, + // logBuffer: new(bytes.Buffer), + stopping: make(chan struct{}), + done: make(chan struct{}), + callback: callback, + logs: make([]string, 0), + } } func NewAdbLogcat(serial string) *AdbLogcat { @@ -415,6 +429,7 @@ func NewAdbLogcat(serial string) *AdbLogcat { // logBuffer: new(bytes.Buffer), stopping: make(chan struct{}), done: make(chan struct{}), + logs: make([]string, 0), } } @@ -477,10 +492,20 @@ func (l *AdbLogcat) CatchLogcat(filter string) (err error) { if err != nil { return err } - l.reader = reader if err = l.cmd.Start(); err != nil { return } + go func() { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Text() + if l.callback != nil { + l.callback(line) // Process each line with callback + } else { + l.logs = append(l.logs, line) // Store line if no callback + } + } + }() go func() { <-l.stopping if e := myexec.KillProcessesByGpid(l.cmd); e != nil { @@ -502,11 +527,9 @@ type ExportPoint struct { RunTime int `json:"run_time,omitempty" yaml:"run_time,omitempty"` } -func ConvertPoints(reader io.Reader) (eps []ExportPoint) { - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - line := scanner.Text() - log.Info().Str("logcat content", line) +func ConvertPoints(lines []string) (eps []ExportPoint) { + log.Info().Msg("ConvertPoints") + for _, line := range lines { if strings.Contains(line, "ext") { idx := strings.Index(line, "{") if idx == -1 { @@ -519,6 +542,7 @@ func ConvertPoints(reader io.Reader) (eps []ExportPoint) { log.Error().Msg("failed to parse point data") continue } + log.Info().Msg(line) eps = append(eps, p) } } From 996bcab70588b74b91a59ced94b2059995e30f3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Mon, 6 May 2024 19:45:27 +0800 Subject: [PATCH 13/14] fix: listen install result by callback --- hrp/pkg/uixt/android_device.go | 68 ++++++++++++++-------------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index c3858ef6..27d73283 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -320,50 +320,38 @@ func (dev *AndroidDevice) installViaInstaller(app io.ReadSeeker, args ...string) if err != nil { return err } - quit := make(chan struct{}) done := make(chan error) - defer func() { close(quit) }() - // 需要监听是否完成安装 - go func() { - logcat := NewAdbLogcat(dev.d.Serial()) - err = logcat.CatchLogcat("PackageInstallerCallback") - if err != nil { - done <- err + defer func() { + close(done) + }() + logcat := NewAdbLogcatWithCallback(dev.d.Serial(), func(line string) { + re := regexp.MustCompile(`\{.*?}`) + match := re.FindString(line) + if match == "" { return } - scanner := bufio.NewScanner(logcat.reader) - defer func() { - close(done) - _ = logcat.Stop() - }() - for scanner.Scan() { - select { - case <-quit: - break - default: - line := scanner.Text() - re := regexp.MustCompile(`\{.*?}`) - match := re.FindString(line) - if match == "" { - continue - } - var result InstallResult - err := json.Unmarshal([]byte(match), &result) - if err != nil { - log.Warn().Msg("parse Install msg line error: " + match) - continue - } - if result.Result == 0 { - // 安装成功 - done <- nil - return - } else { - done <- errors.New(match) - } - } + var result InstallResult + err := json.Unmarshal([]byte(match), &result) + if err != nil { + log.Warn().Msg("parse Install msg line error: " + match) + return } - done <- errors.New("install failed by installer") + if result.Result == 0 { + // 安装成功 + done <- nil + } else { + done <- errors.New(match) + } + }) + err = logcat.CatchLogcat("PackageInstallerCallback") + if err != nil { + return err + } + defer func() { + _ = logcat.Stop() }() + + // 需要监听是否完成安装 args = strings.Split(InstallViaInstallerCommand, " ") args = append(args, appRemotePath) _, err = dev.d.RunShellCommand("am", args[1:]...) @@ -374,7 +362,7 @@ func (dev *AndroidDevice) installViaInstaller(app io.ReadSeeker, args ...string) timeout := 1 * time.Minute select { case err := <-done: - return err // 返回安装结果或错误 + return err case <-time.After(timeout): return fmt.Errorf("installation timed out after %v", timeout) } From 918550b46e0b6abb9df21ee7b67dba6d20ba8323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 21 May 2024 14:55:01 +0800 Subject: [PATCH 14/14] feat: update adb shell timeout --- hrp/internal/version/VERSION | 2 +- hrp/pkg/gadb/client.go | 5 +++-- hrp/pkg/gadb/device.go | 6 +++--- hrp/pkg/gadb/transport.go | 2 +- hrp/pkg/uixt/android_device.go | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 6c124601..30f59f16 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.5.0.202405061137 \ No newline at end of file +v4.5.1.install \ No newline at end of file diff --git a/hrp/pkg/gadb/client.go b/hrp/pkg/gadb/client.go index eb6b9d8c..a81ac116 100644 --- a/hrp/pkg/gadb/client.go +++ b/hrp/pkg/gadb/client.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" "strings" + "time" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -209,8 +210,8 @@ func (c Client) KillServer() (err error) { return } -func (c Client) createTransport() (tp transport, err error) { - return newTransport(fmt.Sprintf("%s:%d", c.host, c.port)) +func (c Client) createTransport(readTimeout ...time.Duration) (tp transport, err error) { + return newTransport(fmt.Sprintf("%s:%d", c.host, c.port), readTimeout...) } func (c Client) executeCommand(command string, onlyVerifyResponse ...bool) (resp string, err error) { diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index 37092e84..73df6061 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -406,8 +406,8 @@ func (d *Device) EnableAdbOverTCP(port ...int) (err error) { return } -func (d *Device) createDeviceTransport() (tp transport, err error) { - if tp, err = newTransport(fmt.Sprintf("%s:%d", d.adbClient.host, d.adbClient.port)); err != nil { +func (d *Device) createDeviceTransport(readTimeout ...time.Duration) (tp transport, err error) { + if tp, err = newTransport(fmt.Sprintf("%s:%d", d.adbClient.host, d.adbClient.port), readTimeout...); err != nil { return transport{}, err } @@ -546,7 +546,7 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker, args ...string) (raw []byt if err != nil { return nil, err } - if tp, err = d.createDeviceTransport(); err != nil { + if tp, err = d.createDeviceTransport(5 * time.Minute); err != nil { return nil, err } defer func() { _ = tp.Close() }() diff --git a/hrp/pkg/gadb/transport.go b/hrp/pkg/gadb/transport.go index c55b32b7..d09b5c72 100644 --- a/hrp/pkg/gadb/transport.go +++ b/hrp/pkg/gadb/transport.go @@ -17,7 +17,7 @@ import ( var ErrConnBroken = errors.New("socket connection broken") -var DefaultAdbReadTimeout time.Duration = 60 +var DefaultAdbReadTimeout time.Duration = 300 var regexDeviceOffline = regexp.MustCompile("device .* not found") diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 27d73283..9a5eb768 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -359,7 +359,7 @@ func (dev *AndroidDevice) installViaInstaller(app io.ReadSeeker, args ...string) return err } // 等待安装完成或超时 - timeout := 1 * time.Minute + timeout := 3 * time.Minute select { case err := <-done: return err