Files
httprunner/hrp/pkg/uixt/android_adb_driver.go

866 lines
24 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package uixt
import (
"bytes"
"encoding/xml"
"fmt"
"io/fs"
"os"
"path/filepath"
"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/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"
UnicodeImePackageName = "io.appium.settings/.UnicodeIME"
)
type adbDriver struct {
Driver
adbClient *gadb.Device
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{}
}
func (ad *adbDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) DeleteSession() (err error) {
return errDriverNotImplemented
}
func (ad *adbDriver) Status() (deviceStatus DeviceStatus, err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) DeviceInfo() (deviceInfo DeviceInfo, err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) Location() (location Location, err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) BatteryInfo() (batteryInfo BatteryInfo, err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) WindowSize() (size Size, err error) {
// adb shell wm size
output, err := ad.adbClient.RunShellCommand("wm", "size")
if err != nil {
return size, errors.Wrap(err, "get window size failed with adb")
}
// output may contain both Physical and Override size, use Override if existed
// Physical size: 1080x2340
// Override size: 1080x2220
matchedSizeType := "Physical"
if strings.Contains(output, "Override") {
matchedSizeType = "Override"
}
var resolution string
sizeList := strings.Split(output, "\n")
log.Trace().Msgf("window size: %v", sizeList)
for _, size := range sizeList {
if strings.Contains(size, matchedSizeType) {
resolution = strings.Split(size, ": ")[1]
// 1080x2340
ss := strings.Split(resolution, "x")
width, _ := strconv.Atoi(ss[0])
height, _ := strconv.Atoi(ss[1])
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
}
func (ad *adbDriver) Screen() (screen Screen, err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) Scale() (scale float64, err error) {
return 1, nil
}
func (ad *adbDriver) GetTimestamp() (timestamp int64, err error) {
// adb shell date +%s
output, err := ad.adbClient.RunShellCommand("date", "+%s")
if err != nil {
return 0, errors.Wrap(err, "failed to get timestamp by adb")
}
timestamp, err = strconv.ParseInt(strings.TrimSpace(output), 10, 64)
if err != nil {
return 0, errors.Wrap(err, "convert timestamp failed")
}
return timestamp, nil
}
// PressBack simulates a short press on the BACK button.
func (ad *adbDriver) PressBack(options ...ActionOption) (err error) {
// adb shell input keyevent 4
_, err = ad.adbClient.RunShellCommand("input", "keyevent", fmt.Sprintf("%d", KCBack))
if err != nil {
return errors.Wrap(err, "press back failed")
}
return nil
}
func (ad *adbDriver) StartCamera() (err error) {
if _, err = ad.adbClient.RunShellCommand("rm", "-r", "/sdcard/DCIM/Camera"); err != nil {
return errors.Wrap(err, "remove /sdcard/DCIM/Camera failed")
}
time.Sleep(5 * time.Second)
var version string
if version, err = ad.adbClient.RunShellCommand("getprop", "ro.build.version.release"); err != nil {
return err
}
if version == "11" || version == "12" {
if _, err = ad.adbClient.RunShellCommand("am", "start", "-a", "android.media.action.STILL_IMAGE_CAMERA"); err != nil {
return err
}
time.Sleep(5 * time.Second)
if _, err = ad.adbClient.RunShellCommand("input", "swipe", "750", "1000", "250", "1000"); err != nil {
return err
}
time.Sleep(5 * time.Second)
if _, err = ad.adbClient.RunShellCommand("input", "keyevent", fmt.Sprintf("%d", KCCamera)); err != nil {
return err
}
return
} else {
if _, err = ad.adbClient.RunShellCommand("am", "start", "-a", "android.media.action.VIDEO_CAPTURE"); err != nil {
return err
}
time.Sleep(5 * time.Second)
if _, err = ad.adbClient.RunShellCommand("input", "keyevent", fmt.Sprintf("%d", KCCamera)); err != nil {
return err
}
return
}
}
func (ad *adbDriver) StopCamera() (err error) {
err = ad.PressBack()
if err != nil {
return err
}
err = ad.Homescreen()
if err != nil {
return err
}
// kill samsung shell command
if _, err = ad.AppTerminate("com.sec.android.app.camera"); err != nil {
return err
}
// kill other camera (huawei mi)
if _, err = ad.AppTerminate("com.android.camera2"); err != nil {
return err
}
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)
}
func (ad *adbDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta) (err error) {
// adb shell input keyevent <keyCode>
_, err = ad.adbClient.RunShellCommand(
"input", "keyevent", fmt.Sprintf("%d", keyCode))
return
}
func (ad *adbDriver) AppLaunch(packageName string) (err error) {
// 不指定 Activity 名称启动(启动主 Activity
// adb shell monkey -p <packagename> -c android.intent.category.LAUNCHER 1
sOutput, err := ad.adbClient.RunShellCommand(
"monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1",
)
if err != nil {
return errors.Wrap(code.MobileUILaunchAppError,
fmt.Sprintf("monkey launch failed: %v", err))
}
if strings.Contains(sOutput, "monkey aborted") {
return errors.Wrap(code.MobileUILaunchAppError,
fmt.Sprintf("monkey aborted: %s", strings.TrimSpace(sOutput)))
}
return nil
}
func (ad *adbDriver) AppTerminate(packageName string) (successful bool, err error) {
// 强制停止应用,停止 <packagename> 相关的进程
// adb shell am force-stop <packagename>
_, err = ad.adbClient.RunShellCommand("am", "force-stop", packageName)
if err != nil {
return false, errors.Wrap(err, "force-stop app failed")
}
return true, nil
}
func (ad *adbDriver) Tap(x, y int, options ...ActionOption) error {
return ad.TapFloat(float64(x), float64(y), options...)
}
func (ad *adbDriver) TapFloat(x, y float64, options ...ActionOption) (err error) {
actionOptions := NewActionOptions(options...)
if len(actionOptions.Offset) == 2 {
x += float64(actionOptions.Offset[0])
y += float64(actionOptions.Offset[1])
}
x += actionOptions.getRandomOffset()
y += actionOptions.getRandomOffset()
// adb shell input tap x y
xStr := fmt.Sprintf("%.1f", x)
yStr := fmt.Sprintf("%.1f", y)
_, err = ad.adbClient.RunShellCommand(
"input", "tap", xStr, yStr)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("tap <%s, %s> failed", xStr, yStr))
}
return nil
}
func (ad *adbDriver) DoubleTap(x, y int) error {
return ad.DoubleTapFloat(float64(x), float64(y))
}
func (ad *adbDriver) DoubleTapFloat(x, y float64) (err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) TouchAndHold(x, y int, second ...float64) (err error) {
return ad.TouchAndHoldFloat(float64(x), float64(y), second...)
}
func (ad *adbDriver) TouchAndHoldFloat(x, y float64, second ...float64) (err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) Drag(fromX, fromY, toX, toY int, options ...ActionOption) error {
return ad.DragFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
}
func (ad *adbDriver) DragFloat(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) Swipe(fromX, fromY, toX, toY int, options ...ActionOption) error {
return ad.SwipeFloat(float64(fromX), float64(fromY), float64(toX), float64(toY), options...)
}
func (ad *adbDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...ActionOption) error {
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()
// adb shell input swipe fromX fromY toX toY
_, err := ad.adbClient.RunShellCommand(
"input", "swipe",
fmt.Sprintf("%.1f", fromX), fmt.Sprintf("%.1f", fromY),
fmt.Sprintf("%.1f", toX), fmt.Sprintf("%.1f", toY),
)
if err != nil {
return errors.Wrap(err, "swipe failed")
}
return nil
}
func (ad *adbDriver) ForceTouch(x, y int, pressure float64, second ...float64) error {
return ad.ForceTouchFloat(float64(x), float64(y), pressure, second...)
}
func (ad *adbDriver) ForceTouchFloat(x, y, pressure float64, second ...float64) (err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) SetPasteboard(contentType PasteboardType, content string) (err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error) {
err = errDriverNotImplemented
return
}
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 {
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 {
return false
}
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:
if _, resetErr := ad.adbClient.RunShellCommand("ime", "reset"); resetErr != nil {
log.Error().Err(err).Msg("failed to reset ime")
}
}()
// Enable ADBKeyBoard from adb
if _, err = ad.adbClient.RunShellCommand("ime", "enable", AdbKeyBoardPackageName); err != nil {
log.Error().Err(err).Msg("failed to enable adbKeyBoard")
return
}
// Switch to ADBKeyBoard from adb
if _, err = ad.adbClient.RunShellCommand("ime", "set", AdbKeyBoardPackageName); err != nil {
log.Error().Err(err).Msg("failed to set adbKeyBoard")
return
}
time.Sleep(time.Second)
// input Quoted text
text = strings.ReplaceAll(text, " ", "\\ ")
if _, err = ad.adbClient.RunShellCommand("am", "broadcast", "-a", "ADB_INPUT_TEXT", "--es", "msg", text); err != nil {
log.Error().Err(err).Msg("failed to input by adbKeyBoard")
return
}
if _, err = ad.adbClient.RunShellCommand("input", "keyevent", fmt.Sprintf("%d", KCEnter)); err != nil {
log.Error().Err(err).Msg("failed to input keyevent enter")
return
}
time.Sleep(time.Second)
return
}
func (ad *adbDriver) Input(text string, options ...ActionOption) (err error) {
return ad.SendKeys(text, options...)
}
func (ad *adbDriver) PressButton(devBtn DeviceButton) (err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) Rotation() (rotation Rotation, err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) SetRotation(rotation Rotation) (err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) Screenshot() (raw *bytes.Buffer, err error) {
// adb shell screencap -p
resp, err := ad.adbClient.ScreenCap()
if err == nil {
return bytes.NewBuffer(resp), nil
}
return nil, err
}
func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) {
_, 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
}
func (ad *adbDriver) HealthCheck() (err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) GetAppiumSettings() (settings map[string]interface{}, err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) SetAppiumSettings(settings map[string]interface{}) (ret map[string]interface{}, err error) {
err = errDriverNotImplemented
return
}
func (ad *adbDriver) IsHealthy() (healthy bool, err error) {
err = errDriverNotImplemented
return
}
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 {
err = errors.Wrap(code.AndroidCaptureLogError,
fmt.Sprintf("start adb log recording failed: %v", err))
return err
}
return nil
}
func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
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 >>>")
logFilePathPrefix := fmt.Sprintf("%v/data", env.ActionLogFilePath)
files := []string{}
myexec.RunCommand("adb", "-s", ad.adbClient.Serial(), "pull", env.DeviceActionLogFilePath, env.ActionLogFilePath)
err = filepath.Walk(env.ActionLogFilePath, func(path string, info fs.FileInfo, err error) error {
// 只是需要日志文件
if ok := strings.Contains(path, logFilePathPrefix); ok {
files = append(files, path)
}
return nil
})
// 先保持原有状态码不变这里不return error
if err != nil {
log.Error().Err(err).Msg("read log file fail")
return pointRes, nil
}
if len(files) != 1 {
log.Error().Err(err).Msg("log file count error")
return pointRes, nil
}
reader, err := os.Open(files[0])
if err != nil {
log.Info().Msg("open File error")
return pointRes, nil
}
pointRes = ConvertPoints(reader)
}
return pointRes, nil
}
func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) {
// adb shell dumpsys activity activities
output, err := ad.adbClient.RunShellCommand("dumpsys", "activity", "activities")
if err != nil {
log.Error().Err(err).Msg("failed to dumpsys activities")
return AppInfo{}, errors.Wrap(err, "dumpsys activities failed")
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
trimmedLine := strings.TrimSpace(line)
// grep mResumedActivity|ResumedActivity
if strings.HasPrefix(trimmedLine, "mResumedActivity:") || strings.HasPrefix(trimmedLine, "ResumedActivity:") {
// mResumedActivity: ActivityRecord{9656d74 u0 com.android.settings/.Settings t407}
// ResumedActivity: ActivityRecord{8265c25 u0 com.android.settings/.Settings t73}
strs := strings.Split(trimmedLine, " ")
for _, str := range strs {
if strings.Contains(str, "/") {
// com.android.settings/.Settings
s := strings.Split(str, "/")
app := AppInfo{
AppBaseInfo: AppBaseInfo{
PackageName: s[0],
Activity: s[1],
},
}
return app, nil
}
}
}
}
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).
Msg("assert android foreground package and activity")
app, err := ad.GetForegroundApp()
if err != nil {
log.Warn().Err(err).Msg("get foreground app failed, skip app/activity assertion")
return nil // Notice: ignore error when get foreground app failed
}
// assert package
if app.PackageName != packageName {
log.Error().
Interface("foreground_app", app.AppBaseInfo).
Str("expected_package", packageName).
Msg("assert package failed")
return errors.Wrap(code.MobileUIAssertForegroundAppError,
"assert foreground package failed")
}
if len(activityType) == 0 {
return nil
}
// assert activity
expectActivityType := activityType[0]
activities, ok := androidActivities[packageName]
if !ok {
msg := fmt.Sprintf("activities not configured for package %s", packageName)
log.Error().Msg(msg)
return errors.Wrap(code.MobileUIAssertForegroundActivityError, msg)
}
expectActivities, ok := activities[expectActivityType]
if !ok {
msg := fmt.Sprintf("activity type %s not configured for package %s",
expectActivityType, packageName)
log.Error().Msg(msg)
return errors.Wrap(code.MobileUIAssertForegroundActivityError, msg)
}
// assertion
for _, expectActivity := range expectActivities {
if strings.HasSuffix(app.Activity, expectActivity) {
// assert activity success
return nil
}
}
// assert activity failed
log.Error().
Interface("foreground_app", app.AppBaseInfo).
Str("expected_activity_type", expectActivityType).
Strs("expected_activities", expectActivities).
Msg("assert activity failed")
return errors.Wrap(code.MobileUIAssertForegroundActivityError,
"assert foreground activity failed")
}
var androidActivities = map[string]map[string][]string{
// DY
"com.ss.android.ugc.aweme": {
"feed": []string{".splash.SplashActivity"},
"live": []string{".live.LivePlayActivity"},
},
// DY lite
"com.ss.android.ugc.aweme.lite": {
"feed": []string{".splash.SplashActivity"},
"live": []string{".live.LivePlayActivity"},
},
// KS
"com.smile.gifmaker": {
"feed": []string{
"com.yxcorp.gifshow.HomeActivity",
"com.yxcorp.gifshow.detail.PhotoDetailActivity",
},
"live": []string{
"com.kuaishou.live.core.basic.activity.LiveSlideActivity",
"com.yxcorp.gifshow.detail.PhotoDetailActivity",
},
},
// KS lite
"com.kuaishou.nebula": {
"feed": []string{
"com.yxcorp.gifshow.HomeActivity",
"com.yxcorp.gifshow.detail.PhotoDetailActivity",
},
"live": []string{
"com.kuaishou.live.core.basic.activity.LiveSlideActivity",
"com.yxcorp.gifshow.detail.PhotoDetailActivity",
},
},
// TODO: SPH, XHS
}