mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-05 07:49:37 +08:00
Feat/yuhongzheng/pre auto install
* fix: fix rotate tap swipe error * fix: default input frequency from 60 to 10 * fix: error getting window size during screen rotation * fix: kuake input unicode error * feat: android input by appium ime * feat: android swipe and tap with duration * fix: format import file * fix: format import file * feat: 新增按控件点击,获取设备应用,修改日志获取 * feat: 新增ui2控件点击 * fix: format file * fix: format file * Merge branch 'video-release' into 'feat/yuhongzheng/pre_auto_install' * merge * Merge branch 'feat/yuhongzheng/pre_auto_install' of… * fix: close reader * Merge branch 'video-release' into 'feat/yuhongzheng/pre_auto_install' * fix: format code * Merge branch 'feat/yuhongzheng/pre_auto_install' of… * fix: test send key https://code.byted.org/iesqa/httprunner/merge_requests/34
This commit is contained in:
@@ -1,25 +1,32 @@
|
||||
package uixt
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"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 (
|
||||
AdbKeyBoardPackageName = "com.android.adbkeyboard/.AdbIME"
|
||||
UnicodeImePackageName = "io.appium.settings/.UnicodeIME"
|
||||
)
|
||||
|
||||
type adbDriver struct {
|
||||
Driver
|
||||
@@ -91,7 +98,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
|
||||
}
|
||||
@@ -185,6 +199,24 @@ func (ad *adbDriver) StopCamera() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Orientation() (orientation Orientation, err error) {
|
||||
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
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Homescreen() (err error) {
|
||||
return ad.PressKeyCode(KCHome, KMEmpty)
|
||||
}
|
||||
@@ -326,6 +358,15 @@ 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 <text>
|
||||
_, err = ad.adbClient.RunShellCommand("input", "text", text)
|
||||
if err != nil {
|
||||
@@ -334,6 +375,36 @@ func (ad *adbDriver) SendKeys(text string, options ...ActionOption) (err error)
|
||||
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 {
|
||||
@@ -342,6 +413,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:
|
||||
@@ -404,10 +483,103 @@ 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("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
|
||||
}
|
||||
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()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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.Layout, 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 []Layout, text string, options ...ActionOption) []Bounds {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
var results []Bounds
|
||||
for _, node := range nodes {
|
||||
result := ad.searchNodes(node.Layout, 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.Layout, 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
|
||||
@@ -435,14 +607,8 @@ 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()
|
||||
err = ad.logcat.CatchLogcat("iesqaMonitor:V")
|
||||
if err != nil {
|
||||
err = errors.Wrap(code.AndroidCaptureLogError,
|
||||
fmt.Sprintf("start adb log recording failed: %v", err))
|
||||
@@ -452,17 +618,18 @@ 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()
|
||||
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")
|
||||
}
|
||||
}()
|
||||
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
|
||||
log.Error().Err(err).Msg("failed to close adb log writer")
|
||||
}
|
||||
content := ad.logcat.logBuffer.String()
|
||||
log.Info().Str("logcat content", content).Msg("display logcat content")
|
||||
pointRes := ConvertPoints(content)
|
||||
pointRes := ConvertPoints(ad.logcat.logs)
|
||||
|
||||
// 没有解析到打点日志,走兜底逻辑
|
||||
if len(pointRes) == 0 {
|
||||
log.Info().Msg("action log is null, use action file >>>")
|
||||
@@ -476,7 +643,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")
|
||||
@@ -488,13 +654,28 @@ 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
|
||||
}
|
||||
defer func() {
|
||||
_ = reader.Close()
|
||||
}()
|
||||
|
||||
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(string(data))
|
||||
pointRes = ConvertPoints(lines)
|
||||
}
|
||||
return pointRes, nil
|
||||
}
|
||||
@@ -534,6 +715,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).
|
||||
|
||||
Reference in New Issue
Block a user