mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-05 15:59:33 +08:00
merge master
This commit is contained in:
62
hrp/cmd/adb/install.go
Normal file
62
hrp/cmd/adb/install.go
Normal file
@@ -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")
|
||||
|
||||
err = driverExt.Install(args[0], uixt.NewInstallOptions(uixt.WithReinstall(replace), uixt.WithDowngrade(downgrade), uixt.WithGrantPermission(grant)))
|
||||
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)
|
||||
}
|
||||
52
hrp/cmd/ios/install.go
Normal file
52
hrp/cmd/ios/install.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package ios
|
||||
|
||||
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(udid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
device, err := uixt.NewIOSDevice(uixt.WithUDID(udid))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = device.Install(args[0], uixt.NewInstallOptions())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
fmt.Println("success")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
installCmd.Flags().StringVarP(&udid, "serial", "s", "", "filter by device's serial")
|
||||
|
||||
iosRootCmd.AddCommand(installCmd)
|
||||
}
|
||||
25
hrp/cmd/server.go
Normal file
25
hrp/cmd/server.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/server"
|
||||
)
|
||||
|
||||
// serverCmd represents the server command
|
||||
var serverCmd = &cobra.Command{
|
||||
Use: "server start",
|
||||
Short: "start hrp server",
|
||||
Long: `start hrp server. exec automation by http`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return server.NewServer(port)
|
||||
},
|
||||
}
|
||||
|
||||
var port int
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(serverCmd)
|
||||
serverCmd.Flags().IntVarP(&port, "port", "p", 8082, "Port to run the server on")
|
||||
}
|
||||
@@ -1,17 +1,23 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/csv"
|
||||
builtinJSON "encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -23,6 +29,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||
)
|
||||
|
||||
@@ -429,3 +436,138 @@ func GetCurrentDay() string {
|
||||
formattedDate := now.Format("20060102")
|
||||
return formattedDate
|
||||
}
|
||||
|
||||
func DownloadFile(filePath string, fileUrl string) error {
|
||||
log.Info().Str("filePath", filePath).Str("url", fileUrl).Msg("download file")
|
||||
parsedURL, err := url.Parse(fileUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// 创建一个新的 HTTP 请求
|
||||
req, err := http.NewRequest("GET", fileUrl, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if env.EAPI_TOKEN != "" {
|
||||
if parsedURL.Host != "gtf-eapi-cn.bytedance.com" && parsedURL.Host != "gtf-eapi-cn.bytedance.net" {
|
||||
return errors.New("invalid domain: must be gtf-eapi-cn.bytedance.com")
|
||||
}
|
||||
// 添加自定义头部
|
||||
req.Header.Add("accessKey", "ies.vedem.video")
|
||||
req.Header.Add("token", env.EAPI_TOKEN)
|
||||
}
|
||||
|
||||
// 创建一个 HTTP 客户端并发送请求
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("bad status: %s, download failed", resp.Status)
|
||||
}
|
||||
|
||||
// 将响应主体写入文件
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunCommand(cmdName string, args ...string) error {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
log.Info().Str("command", cmd.String()).Msg("exec command")
|
||||
|
||||
// print stderr output
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
stderrStr := stderr.String()
|
||||
log.Error().Err(err).Msg("failed to exec command. msg: " + stderrStr)
|
||||
if stderrStr != "" {
|
||||
err = errors.Wrap(err, stderrStr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
stderrStr := stderr.String()
|
||||
log.Error().Msg("failed to exec command. msg: " + stderrStr)
|
||||
log.Info().Msg("exec command output: " + stdout.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
type LineCallback func(line string) bool
|
||||
|
||||
// RunCommandWithCallback 运行命令并根据回调判断是否成功
|
||||
func RunCommandWithCallback(cmdName string, args []string, callback LineCallback) error {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
log.Info().Str("command", cmd.String()).Msg("exec command")
|
||||
|
||||
// 使用管道获取标准输出
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to get stdout pipe")
|
||||
return err
|
||||
}
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Error().Err(err).Msg("failed to start command")
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建一个用于标识成功的通道
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
// 逐行读取 stdout
|
||||
go func() {
|
||||
stdoutScanner := bufio.NewScanner(stdoutPipe)
|
||||
for stdoutScanner.Scan() {
|
||||
line := stdoutScanner.Text()
|
||||
log.Info().Msg("stdout: " + line)
|
||||
if callback(line) {
|
||||
done <- struct{}{}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待命令执行完成
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
log.Error().Msg("failed to exec command. msg: " + stderr.String())
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置一个1秒的超时上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
// 超时,判断失败
|
||||
log.Error().Msg("failed to exec command. msg: " + stderr.String())
|
||||
err = errors.New("command execution failed: callback failed while exec command")
|
||||
log.Error().Err(err).Msg("failed to find keyword in time")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
1
hrp/internal/env/env.go
vendored
1
hrp/internal/env/env.go
vendored
@@ -13,6 +13,7 @@ var (
|
||||
VEDEM_IMAGE_URL = os.Getenv("VEDEM_IMAGE_URL")
|
||||
VEDEM_IMAGE_AK = os.Getenv("VEDEM_IMAGE_AK")
|
||||
VEDEM_IMAGE_SK = os.Getenv("VEDEM_IMAGE_SK")
|
||||
EAPI_TOKEN = os.Getenv("EAPI_TOKEN")
|
||||
DISABLE_GA = os.Getenv("DISABLE_GA")
|
||||
DISABLE_SENTRY = os.Getenv("DISABLE_SENTRY")
|
||||
PYPI_INDEX_URL = os.Getenv("PYPI_INDEX_URL")
|
||||
|
||||
@@ -1 +1 @@
|
||||
v4.3.9
|
||||
v4.6.5
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -292,6 +292,33 @@ func (d *Device) ReverseForwardKill(remoteInterface interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) RunStubCommand(command []byte, processName string) (res string, err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
if err = tp.SendWithCheck(fmt.Sprintf("localabstract:%s", processName)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = tp.SendBytes(command); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
lenBuf, err := tp.ReadBytesN(4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
length := binary.LittleEndian.Uint32(lenBuf)
|
||||
result, err := tp.ReadBytesN(int(length) - 4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardKillAll() error {
|
||||
_, err := d.executeCommand("reverse:killforward-all")
|
||||
return err
|
||||
@@ -419,8 +446,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
|
||||
}
|
||||
|
||||
@@ -559,7 +586,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() }()
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
15
hrp/pkg/server/exception.go
Normal file
15
hrp/pkg/server/exception.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package server
|
||||
|
||||
// 常见的错误代码和消息
|
||||
const (
|
||||
InternalServerErrorCode = 100001
|
||||
InternalServerErrorMsg = "Internal Server Error"
|
||||
|
||||
InvalidParamErrorCode = 100002
|
||||
InvalidParamErrorMsg = "Invalid %s Param"
|
||||
)
|
||||
|
||||
const (
|
||||
DeviceNotFoundCode = 110001
|
||||
DeviceNotFoundMsg = "Device %s Not Found"
|
||||
)
|
||||
52
hrp/pkg/server/model.go
Normal file
52
hrp/pkg/server/model.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package server
|
||||
|
||||
type HttpResponse struct {
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
ErrorCode int `json:"errorCode"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
|
||||
type TapRequest struct {
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
|
||||
type DragRequest struct {
|
||||
FromX float64 `json:"from_x"`
|
||||
FromY float64 `json:"from_y"`
|
||||
ToX float64 `json:"to_x"`
|
||||
ToY float64 `json:"to_y"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
|
||||
type InputRequest struct {
|
||||
Text string `json:"text"`
|
||||
Frequency int `json:"frequency"` // only iOS
|
||||
}
|
||||
|
||||
type KeycodeRequest struct {
|
||||
Keycode int `json:"keycode"`
|
||||
}
|
||||
|
||||
type AppClearRequest struct {
|
||||
PackageName string `json:"packageName"`
|
||||
}
|
||||
|
||||
type AppLaunchRequest struct {
|
||||
PackageName string `json:"packageName"`
|
||||
}
|
||||
|
||||
type AppTerminalRequest struct {
|
||||
PackageName string `json:"packageName"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
PackageName string `json:"packageName"`
|
||||
PhoneNumber string `json:"phoneNumber"`
|
||||
Captcha string `json:"captcha"`
|
||||
}
|
||||
|
||||
type LogoutRequest struct {
|
||||
PackageName string `json:"packageName"`
|
||||
}
|
||||
558
hrp/pkg/server/server.go
Normal file
558
hrp/pkg/server/server.go
Normal file
@@ -0,0 +1,558 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||
)
|
||||
|
||||
func NewServer(port int) error {
|
||||
router := gin.Default()
|
||||
router.GET("/ping", pingHandler)
|
||||
router.GET("/api/v1/:platform/devices", listDeviceHandler)
|
||||
router.POST("/api/v1/:platform/:serial/ui/tap", parseDeviceInfo(), tapHandler)
|
||||
router.POST("/api/v1/:platform/:serial/ui/drag", parseDeviceInfo(), dragHandler)
|
||||
router.POST("/api/v1/:platform/:serial/ui/input", parseDeviceInfo(), inputHandler)
|
||||
router.POST("/api/v1/:platform/:serial/key/unlock", parseDeviceInfo(), unlockHandler)
|
||||
router.POST("/api/v1/:platform/:serial/key/home", parseDeviceInfo(), homeHandler)
|
||||
router.POST("/api/v1/:platform/:serial/key", parseDeviceInfo(), keycodeHandler)
|
||||
router.GET("/api/v1/:platform/:serial/app/foreground", parseDeviceInfo(), foregroundAppHandler)
|
||||
router.POST("/api/v1/:platform/:serial/app/clear", parseDeviceInfo(), clearAppHandler)
|
||||
router.POST("/api/v1/:platform/:serial/app/launch", parseDeviceInfo(), launchAppHandler)
|
||||
router.POST("/api/v1/:platform/:serial/app/terminal", parseDeviceInfo(), terminalAppHandler)
|
||||
router.GET("/api/v1/:platform/:serial/screenshot", parseDeviceInfo(), screenshotHandler)
|
||||
router.GET("/api/v1/:platform/:serial/stub/source", parseDeviceInfo(), sourceHandler)
|
||||
router.GET("/api/v1/:platform/:serial/adb/source", parseDeviceInfo(), adbSourceHandler)
|
||||
router.POST("/api/v1/:platform/:serial/stub/login", parseDeviceInfo(), loginHandler)
|
||||
router.POST("/api/v1/:platform/:serial/stub/logout", parseDeviceInfo(), logoutHandler)
|
||||
|
||||
err := router.Run(fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to start http server")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pingHandler(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func listDeviceHandler(c *gin.Context) {
|
||||
platform := c.Param("platform")
|
||||
switch strings.ToLower(platform) {
|
||||
case "android":
|
||||
{
|
||||
client, err := gadb.NewClient()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to init adb client")
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
devices, err := client.DeviceList()
|
||||
if err != nil && strings.Contains(err.Error(), "no android device found") {
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: nil})
|
||||
return
|
||||
} else if err != nil {
|
||||
log.Err(err).Msg("failed to list devices")
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
var deviceList []interface{}
|
||||
for _, device := range devices {
|
||||
brand, err := device.Brand()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get device brand")
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
model, err := device.Model()
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to get device model")
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
deviceInfo := map[string]interface{}{
|
||||
"serial": device.Serial(),
|
||||
"brand": brand,
|
||||
"model": model,
|
||||
"platform": "android",
|
||||
}
|
||||
deviceList = append(deviceList, deviceInfo)
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: deviceList})
|
||||
return
|
||||
}
|
||||
default:
|
||||
{
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{
|
||||
ErrorCode: InvalidParamErrorCode,
|
||||
ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "platform"),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tapHandler(c *gin.Context) {
|
||||
var tapReq TapRequest
|
||||
if err := c.ShouldBindJSON(&tapReq); err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
if tapReq.X < 1 && tapReq.Y < 1 {
|
||||
err := dExt.TapXY(tapReq.X, tapReq.Y, uixt.WithPressDuration(tapReq.Duration))
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap %f, %f", c.HandlerName(), tapReq.X, tapReq.Y))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := dExt.TapAbsXY(tapReq.X, tapReq.Y, uixt.WithPressDuration(tapReq.Duration))
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap %f, %f", c.HandlerName(), tapReq.X, tapReq.Y))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func dragHandler(c *gin.Context) {
|
||||
var dragReq DragRequest
|
||||
if err := c.ShouldBindJSON(&dragReq); err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
if dragReq.FromX < 1 && dragReq.FromY < 1 && dragReq.ToX < 1 && dragReq.ToY < 1 {
|
||||
err := dExt.SwipeRelative(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY, uixt.WithPressDuration(dragReq.Duration))
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to drag from %f, %f to %f, %f", c.HandlerName(), dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := dExt.Driver.SwipeFloat(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY, uixt.WithPressDuration(dragReq.Duration))
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to drag from %f, %f to %f, %f", c.HandlerName(), dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func inputHandler(c *gin.Context) {
|
||||
var inputReq InputRequest
|
||||
if err := c.ShouldBindJSON(&inputReq); err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
err := dExt.Driver.SendKeys(inputReq.Text, uixt.WithFrequency(inputReq.Frequency))
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input text %s", c.HandlerName(), inputReq.Text))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func unlockHandler(c *gin.Context) {
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
err := dExt.Driver.Unlock()
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func homeHandler(c *gin.Context) {
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
err := dExt.Driver.Homescreen()
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to enter homescreen", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func keycodeHandler(c *gin.Context) {
|
||||
var keycodeReq KeycodeRequest
|
||||
if err := c.ShouldBindJSON(&keycodeReq); err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
err := dExt.Driver.PressKeyCode(uixt.KeyCode(keycodeReq.Keycode))
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input keycode %d", c.HandlerName(), keycodeReq.Keycode))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func foregroundAppHandler(c *gin.Context) {
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
appInfo, err := dExt.Driver.GetForegroundApp()
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: appInfo})
|
||||
}
|
||||
|
||||
func clearAppHandler(c *gin.Context) {
|
||||
var appClearReq AppClearRequest
|
||||
if err := c.ShouldBindJSON(&appClearReq); err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
err := dExt.Driver.Clear(appClearReq.PackageName)
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func launchAppHandler(c *gin.Context) {
|
||||
var appLaunchReq AppLaunchRequest
|
||||
if err := c.ShouldBindJSON(&appLaunchReq); err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
err := dExt.Driver.AppLaunch(appLaunchReq.PackageName)
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to launch app %s", c.HandlerName(), appLaunchReq.PackageName))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func terminalAppHandler(c *gin.Context) {
|
||||
var appTerminalReq AppTerminalRequest
|
||||
if err := c.ShouldBindJSON(&appTerminalReq); err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
success, err := dExt.Driver.AppTerminate(appTerminalReq.PackageName)
|
||||
if !success {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to launch app %s", c.HandlerName(), appTerminalReq.PackageName))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func screenshotHandler(c *gin.Context) {
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
raw, err := dExt.Driver.Screenshot()
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get screenshot", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: base64.StdEncoding.EncodeToString(raw.Bytes())})
|
||||
}
|
||||
|
||||
func sourceHandler(c *gin.Context) {
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
app, err := dExt.Driver.GetForegroundApp()
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get foreground app", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
source, err := dExt.Driver.Source(uixt.NewSourceOption().WithProcessName(app.PackageName))
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get source %s", c.HandlerName(), app.PackageName))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: source})
|
||||
}
|
||||
|
||||
func adbSourceHandler(c *gin.Context) {
|
||||
deviceObj, exists := c.Get("device")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "device")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
device := deviceObj.(*uixt.AndroidDevice)
|
||||
driver, err := device.NewAdbDriver()
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to new adb driver", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
source, err := driver.Source()
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get adb source", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: source})
|
||||
}
|
||||
|
||||
func loginHandler(c *gin.Context) {
|
||||
var loginReq LoginRequest
|
||||
if err := c.ShouldBindJSON(&loginReq); err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
err := dExt.Driver.LoginNoneUI(loginReq.PackageName, loginReq.PhoneNumber, loginReq.Captcha)
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to login", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func logoutHandler(c *gin.Context) {
|
||||
var logoutReq LogoutRequest
|
||||
if err := c.ShouldBindJSON(&logoutReq); err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
driverObj, exists := c.Get("driver")
|
||||
if !exists {
|
||||
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
dExt := driverObj.(*uixt.DriverExt)
|
||||
err := dExt.Driver.LogoutNoneUI(logoutReq.PackageName)
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to login", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, HttpResponse{Result: true})
|
||||
}
|
||||
|
||||
func parseDeviceInfo() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
platform := c.Param("platform")
|
||||
switch strings.ToLower(platform) {
|
||||
case "android":
|
||||
serial := c.Param("serial")
|
||||
if serial == "" {
|
||||
log.Error().Str("platform", platform).Msg(fmt.Sprintf("[%s]: serial is empty", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{
|
||||
ErrorCode: InvalidParamErrorCode,
|
||||
ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "serial"),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
device, err := uixt.NewAndroidDevice(uixt.WithSerialNumber(serial), uixt.WithStub(true))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("platform", platform).Str("serial", serial).Msg(fmt.Sprintf("[%s]: Device Not Found", c.HandlerName()))
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{
|
||||
ErrorCode: DeviceNotFoundCode,
|
||||
ErrorMsg: fmt.Sprintf(DeviceNotFoundMsg, serial),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set("device", device)
|
||||
driver, err := device.NewDriver(uixt.WithDriverImageService(false), uixt.WithDriverResultFolder(false))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("platform", platform).Str("serial", serial).Msg(fmt.Sprintf("[%s]: Failed New Driver", c.HandlerName()))
|
||||
c.JSON(http.StatusInternalServerError, HttpResponse{
|
||||
ErrorCode: InternalServerErrorCode,
|
||||
ErrorMsg: err.Error(),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set("driver", driver)
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, HttpResponse{
|
||||
ErrorCode: InvalidParamErrorCode,
|
||||
ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "platform"),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ type ActionMethod string
|
||||
const (
|
||||
ACTION_AppInstall ActionMethod = "install"
|
||||
ACTION_AppUninstall ActionMethod = "uninstall"
|
||||
ACTION_AppClear ActionMethod = "app_clear"
|
||||
ACTION_AppStart ActionMethod = "app_start"
|
||||
ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
|
||||
ACTION_AppTerminate ActionMethod = "app_terminate"
|
||||
@@ -26,6 +27,10 @@ const (
|
||||
ACTION_SleepRandom ActionMethod = "sleep_random"
|
||||
ACTION_StartCamera ActionMethod = "camera_start" // alias for app_launch camera
|
||||
ACTION_StopCamera ActionMethod = "camera_stop" // alias for app_terminate camera
|
||||
ACTION_SetClipboard ActionMethod = "set_clipboard"
|
||||
ACTION_GetClipboard ActionMethod = "get_clipboard"
|
||||
ACTION_SetIme ActionMethod = "set_ime"
|
||||
ACTION_GetSource ActionMethod = "get_source"
|
||||
|
||||
// UI validation
|
||||
// selectors
|
||||
@@ -60,6 +65,9 @@ const (
|
||||
ACTION_VideoCrawler ActionMethod = "video_crawler"
|
||||
ACTION_ClosePopups ActionMethod = "close_popups"
|
||||
ACTION_EndToEndDelay ActionMethod = "live_e2e"
|
||||
ACTION_InstallApp ActionMethod = "install_app"
|
||||
ACTION_UninstallApp ActionMethod = "uninstall_app"
|
||||
ACTION_DownloadApp ActionMethod = "download_app"
|
||||
)
|
||||
|
||||
type MobileAction struct {
|
||||
@@ -554,8 +562,23 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
|
||||
|
||||
switch action.Method {
|
||||
case ACTION_AppInstall:
|
||||
// TODO
|
||||
return errActionNotImplemented
|
||||
if appUrl, ok := action.Params.(string); ok {
|
||||
if err = dExt.InstallByUrl(appUrl, NewInstallOptions(WithRetryTime(action.MaxRetryTimes))); err != nil {
|
||||
return errors.Wrap(err, "failed to install app")
|
||||
}
|
||||
}
|
||||
case ACTION_AppUninstall:
|
||||
if packageName, ok := action.Params.(string); ok {
|
||||
if err = dExt.Uninstall(packageName, action.GetOptions()...); err != nil {
|
||||
return errors.Wrap(err, "failed to uninstall app")
|
||||
}
|
||||
}
|
||||
case ACTION_AppClear:
|
||||
if packageName, ok := action.Params.(string); ok {
|
||||
if err = dExt.Driver.Clear(packageName); err != nil {
|
||||
return errors.Wrap(err, "failed to clear app")
|
||||
}
|
||||
}
|
||||
case ACTION_AppLaunch:
|
||||
if bundleId, ok := action.Params.(string); ok {
|
||||
return dExt.Driver.AppLaunch(bundleId)
|
||||
@@ -594,8 +617,34 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("app_terminate params should be bundleId(string), got %v", action.Params)
|
||||
case ACTION_SetClipboard:
|
||||
if text, ok := action.Params.(string); ok {
|
||||
err := dExt.Driver.SetPasteboard(PasteboardTypePlaintext, text)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to set clipboard")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("set_clioboard params should be text(string), got %v", action.Params)
|
||||
case ACTION_Home:
|
||||
return dExt.Driver.Homescreen()
|
||||
case ACTION_SetIme:
|
||||
if ime, ok := action.Params.(string); ok {
|
||||
err = dExt.Driver.SetIme(ime)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to set ime")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case ACTION_GetSource:
|
||||
if packageName, ok := action.Params.(string); ok {
|
||||
source := NewSourceOption().WithProcessName(packageName)
|
||||
_, err = dExt.Driver.Source(source)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to set ime")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case ACTION_TapXY:
|
||||
if location, ok := action.Params.([]interface{}); ok {
|
||||
// relative x,y of window size: [0.5, 0.5]
|
||||
|
||||
@@ -3,6 +3,7 @@ package uixt
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
@@ -218,10 +219,18 @@ func (ad *adbDriver) Orientation() (orientation Orientation, err error) {
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Homescreen() (err error) {
|
||||
return ad.PressKeyCode(KCHome, KMEmpty)
|
||||
return ad.PressKeyCodes(KCHome, KMEmpty)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta) (err error) {
|
||||
func (ad *adbDriver) Unlock() (err error) {
|
||||
return ad.PressKeyCodes(KCMenu, KMEmpty)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) PressKeyCode(keyCode KeyCode) (err error) {
|
||||
return ad.PressKeyCodes(keyCode, KMEmpty)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) PressKeyCodes(keyCode KeyCode, metaState KeyMeta) (err error) {
|
||||
// adb shell input keyevent <keyCode>
|
||||
_, err = ad.adbClient.RunShellCommand(
|
||||
"input", "keyevent", fmt.Sprintf("%d", keyCode))
|
||||
@@ -421,6 +430,14 @@ func (ad *adbDriver) IsUnicodeIMEInstalled() bool {
|
||||
return strings.Contains(output, UnicodeImePackageName)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) ListIme() []string {
|
||||
output, err := ad.adbClient.RunShellCommand("ime", "list", "-s")
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(output, "\n")
|
||||
}
|
||||
|
||||
func (ad *adbDriver) SendKeysByAdbKeyBoard(text string) (err error) {
|
||||
defer func() {
|
||||
// Reset to default, don't care which keyboard was chosen before switch:
|
||||
@@ -458,6 +475,15 @@ func (ad *adbDriver) Input(text string, options ...ActionOption) (err error) {
|
||||
return ad.SendKeys(text, options...)
|
||||
}
|
||||
|
||||
func (ad *adbDriver) Clear(packageName string) error {
|
||||
if _, err := ad.adbClient.RunShellCommand("pm", "clear", packageName); err != nil {
|
||||
log.Error().Str("packageName", packageName).Err(err).Msg("failed to clear package cache")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) PressButton(devBtn DeviceButton) (err error) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
@@ -499,6 +525,14 @@ func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (ad *adbDriver) LogoutNoneUI(packageName string) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (ad *adbDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy, err error) {
|
||||
source, err := ad.Source()
|
||||
if err != nil {
|
||||
@@ -680,46 +714,57 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
return pointRes, nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) GetDriverResults() []*DriverResult {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) {
|
||||
// adb shell dumpsys activity activities
|
||||
output, err := ad.adbClient.RunShellCommand("dumpsys", "activity", "activities")
|
||||
packageInfo, err := ad.adbClient.RunShellCommand("CLASSPATH=/data/local/tmp/evalite", "app_process", "/", "com.bytedance.iesqa.eval_process.PackageService")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to dumpsys activities")
|
||||
return AppInfo{}, errors.Wrap(err, "dumpsys activities failed")
|
||||
return app, err
|
||||
}
|
||||
log.Info().Msg(packageInfo)
|
||||
err = json.Unmarshal([]byte(strings.TrimSpace(packageInfo)), &app)
|
||||
return
|
||||
}
|
||||
|
||||
func (ad *adbDriver) SetIme(imeRegx string) error {
|
||||
imeList := ad.ListIme()
|
||||
ime := ""
|
||||
for _, imeName := range imeList {
|
||||
if regexp.MustCompile(imeRegx).MatchString(imeName) {
|
||||
ime = imeName
|
||||
break
|
||||
}
|
||||
}
|
||||
if ime == "" {
|
||||
return fmt.Errorf("failed to set ime by %s, ime list: %v", imeRegx, imeList)
|
||||
}
|
||||
brand, _ := ad.adbClient.Brand()
|
||||
packageName := strings.Split(ime, "/")[0]
|
||||
res, err := ad.adbClient.RunShellCommand("ime", "set", ime)
|
||||
log.Info().Str("funcName", "SetIme").Interface("ime", ime).
|
||||
Interface("output", res).Msg("set ime")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
if strings.ToLower(brand) == "oppo" {
|
||||
time.Sleep(1 * time.Second)
|
||||
pid, _ := ad.adbClient.RunShellCommand("pidof", packageName)
|
||||
if strings.TrimSpace(pid) == "" {
|
||||
appInfo, err := ad.GetForegroundApp()
|
||||
_ = ad.AppLaunch(packageName)
|
||||
if err == nil && packageName != UnicodeImePackageName {
|
||||
time.Sleep(10 * time.Second)
|
||||
nextAppInfo, err := ad.GetForegroundApp()
|
||||
log.Info().Str("beforeFocusedPackage", appInfo.PackageName).Str("afterFocusedPackage", nextAppInfo.PackageName).Msg("")
|
||||
if err == nil && nextAppInfo.PackageName != appInfo.PackageName {
|
||||
_ = ad.PressKeyCodes(KCBack, KMEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -4,26 +4,43 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"embed"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"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/env"
|
||||
"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
|
||||
DouyinServerPort = 32316
|
||||
AdbServerHost = "localhost"
|
||||
AdbServerPort = gadb.AdbServerPort // 5037
|
||||
UIA2ServerHost = "localhost"
|
||||
UIA2ServerPort = 6790
|
||||
EvalInstallerPackageName = "sogou.mobile.explorer"
|
||||
InstallViaInstallerCommand = "am start -S -n sogou.mobile.explorer/.PackageInstallerActivity -d"
|
||||
)
|
||||
|
||||
//go:embed evalite
|
||||
var evalite embed.FS
|
||||
|
||||
const forwardToPrefix = "forward-to-"
|
||||
|
||||
type AndroidDeviceOption func(*AndroidDevice)
|
||||
@@ -40,6 +57,12 @@ func WithUIA2(uia2On bool) AndroidDeviceOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithStub(stubOn bool) AndroidDeviceOption {
|
||||
return func(device *AndroidDevice) {
|
||||
device.STUB = stubOn
|
||||
}
|
||||
}
|
||||
|
||||
func WithUIA2IP(ip string) AndroidDeviceOption {
|
||||
return func(device *AndroidDevice) {
|
||||
device.UIA2IP = ip
|
||||
@@ -108,7 +131,14 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er
|
||||
|
||||
device.d = dev
|
||||
device.logcat = NewAdbLogcat(device.SerialNumber)
|
||||
|
||||
evalToolRaw, err := evalite.ReadFile("evalite")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(code.LoadFileError, err.Error())
|
||||
}
|
||||
err = dev.Push(bytes.NewReader(evalToolRaw), "/data/local/tmp/evalite", time.Now())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(code.AndroidShellExecError, err.Error())
|
||||
}
|
||||
log.Info().Str("serial", device.SerialNumber).Msg("init android device")
|
||||
return device, nil
|
||||
}
|
||||
@@ -151,9 +181,18 @@ type AndroidDevice struct {
|
||||
logcat *AdbLogcat
|
||||
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
|
||||
UIA2 bool `json:"uia2,omitempty" yaml:"uia2,omitempty"` // use uiautomator2
|
||||
STUB bool `json:"stub,omitempty" yaml:"stub,omitempty"` // use uiautomator2
|
||||
UIA2IP string `json:"uia2_ip,omitempty" yaml:"uia2_ip,omitempty"` // uiautomator2 server ip
|
||||
UIA2Port int `json:"uia2_port,omitempty" yaml:"uia2_port,omitempty"` // uiautomator2 server port
|
||||
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
|
||||
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) Init() error {
|
||||
myexec.RunCommand("adb", "-s", dev.SerialNumber, "shell",
|
||||
"ime", "enable", "io.appium.settings/.UnicodeIME")
|
||||
myexec.RunCommand("adb", "-s", dev.SerialNumber, "shell", "rm", "-r", env.DeviceActionLogFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) System() string {
|
||||
@@ -169,7 +208,7 @@ func (dev *AndroidDevice) LogEnabled() bool {
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) {
|
||||
driverOptions := &DriverOptions{}
|
||||
driverOptions := NewDriverOptions()
|
||||
for _, option := range options {
|
||||
option(driverOptions)
|
||||
}
|
||||
@@ -177,6 +216,8 @@ func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverE
|
||||
var driver WebDriver
|
||||
if dev.UIA2 || dev.LogOn {
|
||||
driver, err = dev.NewUSBDriver(driverOptions.capabilities)
|
||||
} else if dev.STUB {
|
||||
driver, err = dev.NewStubDriver(driverOptions.capabilities)
|
||||
} else {
|
||||
driver, err = dev.NewAdbDriver()
|
||||
}
|
||||
@@ -184,7 +225,7 @@ func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverE
|
||||
return nil, errors.Wrap(err, "failed to init UIA driver")
|
||||
}
|
||||
|
||||
driverExt, err = newDriverExt(dev, driver, driverOptions.plugin)
|
||||
driverExt, err = newDriverExt(dev, driver, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -221,6 +262,36 @@ func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver WebDri
|
||||
return uiaDriver, nil
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) NewStubDriver(capabilities Capabilities) (driver *stubAndroidDriver, err error) {
|
||||
socketLocalPort, err := dev.d.Forward(StubSocketName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
|
||||
fmt.Sprintf("forward port %d->%s failed: %v",
|
||||
socketLocalPort, StubSocketName, err))
|
||||
}
|
||||
|
||||
serverLocalPort, err := dev.d.Forward(DouyinServerPort)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
|
||||
fmt.Sprintf("forward port %d->%d failed: %v",
|
||||
serverLocalPort, DouyinServerPort, err))
|
||||
}
|
||||
|
||||
rawURL := fmt.Sprintf("http://%s%d:%d",
|
||||
forwardToPrefix, serverLocalPort, DouyinServerPort)
|
||||
|
||||
stubDriver, err := newStubAndroidDriver(fmt.Sprintf("127.0.0.1:%d", socketLocalPort), rawURL)
|
||||
if err != nil {
|
||||
_ = dev.d.ForwardKill(socketLocalPort)
|
||||
_ = dev.d.ForwardKill(serverLocalPort)
|
||||
return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
|
||||
}
|
||||
stubDriver.adbClient = dev.d
|
||||
stubDriver.logcat = dev.logcat
|
||||
|
||||
return stubDriver, nil
|
||||
}
|
||||
|
||||
// NewHTTPDriver creates new remote HTTP client, this will also start a new session.
|
||||
func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver, err error) {
|
||||
rawURL := fmt.Sprintf("http://%s:%d/wd/hub", dev.UIA2IP, dev.UIA2Port)
|
||||
@@ -261,6 +332,116 @@ func (dev *AndroidDevice) StopPcap() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) Uninstall(packageName string) error {
|
||||
return myexec.RunCommand("adb", "-s", dev.SerialNumber, "uninstall", packageName)
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) Install(appPath string, opts *InstallOptions) error {
|
||||
app, err := os.Open(appPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("install %s open file failed", appPath))
|
||||
}
|
||||
|
||||
defer app.Close()
|
||||
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
|
||||
}
|
||||
done := make(chan error)
|
||||
defer func() {
|
||||
close(done)
|
||||
}()
|
||||
logcat := NewAdbLogcatWithCallback(dev.d.Serial(), func(line string) {
|
||||
re := regexp.MustCompile(`\{.*?}`)
|
||||
match := re.FindString(line)
|
||||
if match == "" {
|
||||
return
|
||||
}
|
||||
var result InstallResult
|
||||
err := json.Unmarshal([]byte(match), &result)
|
||||
if err != nil {
|
||||
log.Warn().Msg("parse Install msg line error: " + match)
|
||||
return
|
||||
}
|
||||
if result.Result == 0 {
|
||||
// 安装成功
|
||||
done <- nil
|
||||
} else {
|
||||
done <- errors.New(match)
|
||||
}
|
||||
})
|
||||
err = logcat.CatchLogcat("PackageInstallerCallback")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = logcat.Stop()
|
||||
}()
|
||||
|
||||
// 需要监听是否完成安装
|
||||
command := strings.Split(InstallViaInstallerCommand, " ")
|
||||
args = append(command, appRemotePath)
|
||||
_, err = dev.d.RunShellCommand("am", args[1:]...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 等待安装完成或超时
|
||||
timeout := 3 * 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
|
||||
}
|
||||
|
||||
type LineCallback func(string)
|
||||
|
||||
type AdbLogcat struct {
|
||||
|
||||
273
hrp/pkg/uixt/android_stub_driver.go
Normal file
273
hrp/pkg/uixt/android_stub_driver.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package uixt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||
)
|
||||
|
||||
type stubAndroidDriver struct {
|
||||
socket net.Conn
|
||||
seq int
|
||||
timeout time.Duration
|
||||
adbDriver
|
||||
}
|
||||
|
||||
const StubSocketName = "com.bytest.device"
|
||||
|
||||
// newStubAndroidDriver
|
||||
// 创建stub Driver address为forward后的端口格式127.0.0.1:${port}
|
||||
func newStubAndroidDriver(address string, urlPrefix string, readTimeout ...time.Duration) (*stubAndroidDriver, error) {
|
||||
timeout := 10 * time.Second
|
||||
if len(readTimeout) > 0 {
|
||||
timeout = readTimeout[0]
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("failed to connect %s", address))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver := &stubAndroidDriver{
|
||||
socket: conn,
|
||||
timeout: timeout,
|
||||
}
|
||||
|
||||
if driver.urlPrefix, err = url.Parse(urlPrefix); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) {
|
||||
var localPort int
|
||||
{
|
||||
tmpURL, _ := url.Parse(sad.urlPrefix.String())
|
||||
hostname := tmpURL.Hostname()
|
||||
if strings.HasPrefix(hostname, forwardToPrefix) {
|
||||
localPort, _ = strconv.Atoi(strings.TrimPrefix(hostname, forwardToPrefix))
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adb forward: %w", err)
|
||||
}
|
||||
sad.client = convertToHTTPClient(conn)
|
||||
return sad.httpRequest(http.MethodGet, sad.concatURL(nil, pathElem...), nil)
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) {
|
||||
var localPort int
|
||||
{
|
||||
tmpURL, _ := url.Parse(sad.urlPrefix.String())
|
||||
hostname := tmpURL.Hostname()
|
||||
if strings.HasPrefix(hostname, forwardToPrefix) {
|
||||
localPort, _ = strconv.Atoi(strings.TrimPrefix(hostname, forwardToPrefix))
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adb forward: %w", err)
|
||||
}
|
||||
sad.client = convertToHTTPClient(conn)
|
||||
|
||||
var bsJSON []byte = nil
|
||||
if data != nil {
|
||||
if bsJSON, err = json.Marshal(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sad.httpRequest(http.MethodPost, sad.concatURL(nil, pathElem...), bsJSON)
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) NewSession(capabilities Capabilities) (SessionInfo, error) {
|
||||
return SessionInfo{}, errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) sendCommand(packageName string, cmdType string, params map[string]interface{}, readTimeout ...time.Duration) (interface{}, error) {
|
||||
sad.seq++
|
||||
packet := map[string]interface{}{
|
||||
"Seq": sad.seq,
|
||||
"Cmd": cmdType,
|
||||
"v": "",
|
||||
}
|
||||
for key, value := range params {
|
||||
if key == "Cmd" || key == "Seq" {
|
||||
return "", errors.New("params cannot be Cmd or Seq")
|
||||
}
|
||||
packet[key] = value
|
||||
}
|
||||
data, err := json.Marshal(packet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := sad.adbClient.RunStubCommand(append(data, '\n'), packageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resultMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(res), &resultMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resultMap["Error"] != nil {
|
||||
return nil, fmt.Errorf("failed to call stub command: %s", resultMap["Error"].(string))
|
||||
}
|
||||
|
||||
return resultMap["Result"], nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) DeleteSession() error {
|
||||
return sad.close()
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) close() error {
|
||||
if sad.socket != nil {
|
||||
return sad.socket.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) Status() (DeviceStatus, error) {
|
||||
app, err := sad.GetForegroundApp()
|
||||
if err != nil {
|
||||
return DeviceStatus{}, err
|
||||
}
|
||||
res, err := sad.sendCommand(app.PackageName, "Hello", nil)
|
||||
if err != nil {
|
||||
return DeviceStatus{}, err
|
||||
}
|
||||
log.Info().Msg(fmt.Sprintf("ping stub result :%v", res))
|
||||
return DeviceStatus{}, nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) Source(srcOpt ...SourceOption) (source string, err error) {
|
||||
app, err := sad.GetForegroundApp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
params := map[string]interface{}{
|
||||
"ClassName": "com.bytedance.byteinsight.MockOperator",
|
||||
"Method": "getLayout",
|
||||
"RetType": "",
|
||||
"Args": []string{},
|
||||
}
|
||||
res, err := sad.sendCommand(app.PackageName, "CallStaticMethod", params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.(string), nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) LoginNoneUIBak(packageName, phoneNumber, captcha string) error {
|
||||
_, err := sad.adbClient.RunShellCommand("am", "broadcast", "-a", fmt.Sprintf("%s.util.crony.action_login", packageName), "-e", "phone", phoneNumber, "-e", "code", captcha)
|
||||
time.Sleep(10 * time.Second)
|
||||
login, err := sad.isLogin(packageName)
|
||||
if err != nil || !login {
|
||||
log.Err(err).Msg("failed to login")
|
||||
return fmt.Errorf("failed to login")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) LoginNoneUI(packageName, phoneNumber, captcha string) error {
|
||||
params := map[string]interface{}{
|
||||
"phone": phoneNumber,
|
||||
"code": captcha,
|
||||
}
|
||||
resp, err := sad.httpPOST(params, "/host", "/login", "account")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := resp.valueConvertToJsonObject()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msgf("%v", res)
|
||||
if res["isSuccess"] != true {
|
||||
err = fmt.Errorf("falied to logout %s", res["data"])
|
||||
log.Err(err).Msgf("%v", res)
|
||||
return err
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
login, err := sad.isLogin(packageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !login {
|
||||
return fmt.Errorf("failed to login")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) LogoutNoneUI(packageName string) error {
|
||||
resp, err := sad.httpGET("/host", "/logout")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := resp.valueConvertToJsonObject()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msgf("%v", res)
|
||||
if res["isSuccess"] != true {
|
||||
err = fmt.Errorf("falied to logout %s", res["data"])
|
||||
log.Err(err).Msgf("%v", res)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%v", resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) LoginNoneUIDynamic(packageName, phoneNumber string, captcha string) error {
|
||||
params := map[string]interface{}{
|
||||
"ClassName": "qe.python.test.LoginUtil",
|
||||
"Method": "loginSync",
|
||||
"RetType": "",
|
||||
"Args": []string{phoneNumber, captcha},
|
||||
}
|
||||
res, err := sad.sendCommand(packageName, "CallStaticMethod", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msg(res.(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sad *stubAndroidDriver) isLogin(packageName string) (login bool, err error) {
|
||||
resp, err := sad.httpGET("/host", "/login", "/check")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
res, err := resp.valueConvertToJsonObject()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
log.Info().Msgf("%v", res)
|
||||
if res["isSuccess"] != true {
|
||||
err = fmt.Errorf("falied to get is login %s", res["data"])
|
||||
log.Err(err).Msgf("%v", res)
|
||||
return false, err
|
||||
}
|
||||
fmt.Printf("%v", resp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
56
hrp/pkg/uixt/android_stub_driver_test.go
Normal file
56
hrp/pkg/uixt/android_stub_driver_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package uixt
|
||||
|
||||
import "testing"
|
||||
|
||||
var androidStubDriver *stubAndroidDriver
|
||||
|
||||
func setupStubDriver(t *testing.T) {
|
||||
device, err := NewAndroidDevice()
|
||||
checkErr(t, err)
|
||||
device.STUB = true
|
||||
androidStubDriver, err = device.NewStubDriver(Capabilities{})
|
||||
checkErr(t, err)
|
||||
}
|
||||
|
||||
func TestHello(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
status, err := androidStubDriver.Status()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(status)
|
||||
}
|
||||
|
||||
func TestSource(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
source, err := androidStubDriver.Source()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(source)
|
||||
}
|
||||
|
||||
func TestIsLogin(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
res, err := androidStubDriver.isLogin("com.ss.android.ugc.aweme")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(res)
|
||||
}
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
err := androidStubDriver.LoginNoneUI("com.ss.android.ugc.aweme", "12342316231", "8517")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogout(t *testing.T) {
|
||||
setupStubDriver(t)
|
||||
err := androidStubDriver.LogoutNoneUI("com.ss.android.ugc.aweme")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
package uixt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -21,8 +20,8 @@ var (
|
||||
func setupAndroid(t *testing.T) {
|
||||
device, err := NewAndroidDevice()
|
||||
checkErr(t, err)
|
||||
device.UIA2 = false
|
||||
device.LogOn = true
|
||||
device.UIA2 = true
|
||||
device.LogOn = false
|
||||
driverExt, err = device.NewDriver()
|
||||
checkErr(t, err)
|
||||
}
|
||||
@@ -124,12 +123,9 @@ func TestDriver_DeviceSize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDriver_Source(t *testing.T) {
|
||||
driver, err := NewUIADriver(nil, uiaServerURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setupAndroid(t)
|
||||
|
||||
source, err := driver.Source()
|
||||
source, err := driverExt.Driver.Source()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -199,33 +195,24 @@ func TestDriver_DeviceInfo(t *testing.T) {
|
||||
func TestDriver_Tap(t *testing.T) {
|
||||
setupAndroid(t)
|
||||
driverExt.Driver.StartCaptureLog("")
|
||||
err := driverExt.Driver.Tap(150, 340, WithIdentifier("test"))
|
||||
err := driverExt.TapXY(0.5, 0.5, WithIdentifier("test"), WithPressDuration(4))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err = driverExt.Driver.TapFloat(60.5, 125.5, WithIdentifier("test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
result, _ := driverExt.Driver.StopCaptureLog()
|
||||
t.Log(result)
|
||||
//time.Sleep(time.Second)
|
||||
//
|
||||
//err = driverExt.Driver.TapFloat(60.5, 125.5, WithIdentifier("test"))
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//time.Sleep(time.Second)
|
||||
//result, _ := driverExt.Driver.StopCaptureLog()
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
func TestDriver_Swipe(t *testing.T) {
|
||||
driver, err := NewUIADriver(nil, uiaServerURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = driver.Swipe(400, 1000, 400, 500, WithPressDuration(2000))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = driver.SwipeFloat(400, 555.5, 400, 1255.5)
|
||||
setupAndroid(t)
|
||||
err := driverExt.Driver.Swipe(400, 1000, 400, 500, WithPressDuration(0.5))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -261,7 +248,7 @@ func TestDriver_Drag(t *testing.T) {
|
||||
func TestDriver_SendKeys(t *testing.T) {
|
||||
setupAndroid(t)
|
||||
|
||||
err := driverExt.Driver.SendKeys("Android\"输入速度测试", WithIdentifier("test"))
|
||||
err := driverExt.Driver.SendKeys("辽宁省沈阳市新民市民族街36-4", WithIdentifier("test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -306,6 +293,14 @@ func TestDriver_SetRotation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriver_GetOrientation(t *testing.T) {
|
||||
setupAndroid(t)
|
||||
_, _ = driverExt.Driver.AppTerminate("com.quark.browser")
|
||||
_ = driverExt.Driver.AppLaunch("com.quark.browser")
|
||||
time.Sleep(2 * time.Second)
|
||||
_ = driverExt.Driver.Homescreen()
|
||||
}
|
||||
|
||||
func TestUiSelectorHelper_NewUiSelectorHelper(t *testing.T) {
|
||||
uiSelector := NewUiSelectorHelper().Text("a").String()
|
||||
if uiSelector != `new UiSelector().text("a");` {
|
||||
@@ -448,8 +443,6 @@ func TestConvertPoints(t *testing.T) {
|
||||
if len(eps) != 3 {
|
||||
t.Fatal()
|
||||
}
|
||||
jsons, _ := json.Marshal(eps)
|
||||
println(fmt.Sprintf("%v", string(jsons)))
|
||||
}
|
||||
|
||||
func TestDriver_ShellInputUnicode(t *testing.T) {
|
||||
|
||||
@@ -29,6 +29,7 @@ func NewUIADriver(capabilities Capabilities, urlPrefix string) (driver *uiaDrive
|
||||
log.Info().Msg("init uiautomator2 driver")
|
||||
if capabilities == nil {
|
||||
capabilities = NewCapabilities()
|
||||
capabilities.WithWaitForIdleTimeout(0)
|
||||
}
|
||||
driver = new(uiaDriver)
|
||||
if driver.urlPrefix, err = url.Parse(urlPrefix); err != nil {
|
||||
@@ -141,7 +142,12 @@ func (ud *uiaDriver) httpDELETE(pathElem ...string) (rawResp rawResponse, err er
|
||||
func (ud *uiaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) {
|
||||
// register(postHandler, new NewSession("/wd/hub/session"))
|
||||
var rawResp rawResponse
|
||||
data := map[string]interface{}{"capabilities": capabilities}
|
||||
data := make(map[string]interface{})
|
||||
if len(capabilities) == 0 {
|
||||
data["capabilities"] = make(map[string]interface{})
|
||||
} else {
|
||||
data["capabilities"] = map[string]interface{}{"alwaysMatch": capabilities}
|
||||
}
|
||||
if rawResp, err = ud.Driver.httpPOST(data, "/session"); err != nil {
|
||||
return SessionInfo{SessionId: ""}, err
|
||||
}
|
||||
@@ -244,10 +250,14 @@ func (ud *uiaDriver) PressBack(options ...ActionOption) (err error) {
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) Homescreen() (err error) {
|
||||
return ud.PressKeyCode(KCHome, KMEmpty)
|
||||
return ud.PressKeyCodes(KCHome, KMEmpty)
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) {
|
||||
func (ud *uiaDriver) PressKeyCode(keyCode KeyCode) (err error) {
|
||||
return ud.PressKeyCodes(keyCode, KMEmpty)
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) PressKeyCodes(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) {
|
||||
// register(postHandler, new PressKeyCodeAsync("/wd/hub/session/:sessionId/appium/device/press_keycode"))
|
||||
data := map[string]interface{}{
|
||||
"keycode": keyCode,
|
||||
@@ -293,7 +303,7 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
|
||||
|
||||
duration := 100.0
|
||||
if actionOptions.PressDuration > 0 {
|
||||
duration = actionOptions.PressDuration
|
||||
duration = actionOptions.PressDuration * 1000
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"actions": []interface{}{
|
||||
@@ -399,7 +409,7 @@ func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...Actio
|
||||
|
||||
duration := 200.0
|
||||
if actionOptions.PressDuration > 0 {
|
||||
duration = actionOptions.PressDuration
|
||||
duration = actionOptions.PressDuration * 1000
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"actions": []interface{}{
|
||||
@@ -618,3 +628,10 @@ func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) GetDriverResults() []*DriverResult {
|
||||
defer func() {
|
||||
ud.Driver.driverResults = nil
|
||||
}()
|
||||
return ud.Driver.driverResults
|
||||
}
|
||||
|
||||
@@ -17,10 +17,18 @@ import (
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
urlPrefix *url.URL
|
||||
sessionId string
|
||||
client *http.Client
|
||||
scale float64
|
||||
urlPrefix *url.URL
|
||||
sessionId string
|
||||
client *http.Client
|
||||
scale float64
|
||||
driverResults []*DriverResult
|
||||
}
|
||||
|
||||
type DriverResult struct {
|
||||
RequestUrl string `json:"request_driver_url"`
|
||||
RequestBody string `json:"request_driver_body,omitempty"`
|
||||
RequestDuration time.Duration `json:"request_driver_duration"`
|
||||
RequestTime time.Time `json:"request_driver_time"`
|
||||
}
|
||||
|
||||
func (wd *Driver) concatURL(u *url.URL, elem ...string) string {
|
||||
@@ -73,7 +81,15 @@ func (wd *Driver) httpRequest(method string, rawURL string, rawBody []byte) (raw
|
||||
}()
|
||||
|
||||
rawResp, err = io.ReadAll(resp.Body)
|
||||
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", time.Since(start).String())
|
||||
duration := time.Since(start)
|
||||
driverResult := &DriverResult{
|
||||
RequestUrl: rawURL,
|
||||
RequestBody: string(rawBody),
|
||||
RequestDuration: duration,
|
||||
RequestTime: time.Now(),
|
||||
}
|
||||
wd.driverResults = append(wd.driverResults, driverResult)
|
||||
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", duration.String())
|
||||
if !strings.HasSuffix(rawURL, "screenshot") {
|
||||
// avoid printing screenshot data
|
||||
logger.Str("response", string(rawResp))
|
||||
|
||||
BIN
hrp/pkg/uixt/evalite
Normal file
BIN
hrp/pkg/uixt/evalite
Normal file
Binary file not shown.
@@ -126,11 +126,16 @@ type DriverExt struct {
|
||||
lastPopup *PopupInfo
|
||||
}
|
||||
|
||||
func newDriverExt(device Device, driver WebDriver, plugin funplugin.IPlugin) (dExt *DriverExt, err error) {
|
||||
func newDriverExt(device Device, driver WebDriver, options ...DriverOption) (dExt *DriverExt, err error) {
|
||||
driverOptions := NewDriverOptions()
|
||||
for _, option := range options {
|
||||
option(driverOptions)
|
||||
}
|
||||
|
||||
dExt = &DriverExt{
|
||||
Device: device,
|
||||
Driver: driver,
|
||||
plugin: plugin,
|
||||
plugin: driverOptions.plugin,
|
||||
cacheStepData: cacheStepData{},
|
||||
interruptSignal: make(chan os.Signal, 1),
|
||||
}
|
||||
@@ -150,21 +155,86 @@ func newDriverExt(device Device, driver WebDriver, plugin funplugin.IPlugin) (dE
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get screen resolution failed")
|
||||
}
|
||||
|
||||
if dExt.ImageService, err = newVEDEMImageService(); err != nil {
|
||||
return nil, err
|
||||
if driverOptions.withImageService {
|
||||
if dExt.ImageService, err = newVEDEMImageService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// create results directory
|
||||
if err = builtin.EnsureFolderExists(env.ResultsPath); err != nil {
|
||||
return nil, errors.Wrap(err, "create results directory failed")
|
||||
}
|
||||
if err = builtin.EnsureFolderExists(env.ScreenShotsPath); err != nil {
|
||||
return nil, errors.Wrap(err, "create screenshots directory failed")
|
||||
if driverOptions.withResultFolder {
|
||||
// create results directory
|
||||
if err = builtin.EnsureFolderExists(env.ResultsPath); err != nil {
|
||||
return nil, errors.Wrap(err, "create results directory failed")
|
||||
}
|
||||
if err = builtin.EnsureFolderExists(env.ScreenShotsPath); err != nil {
|
||||
return nil, errors.Wrap(err, "create screenshots directory failed")
|
||||
}
|
||||
}
|
||||
return dExt, nil
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) InstallByUrl(url string, opts *InstallOptions) error {
|
||||
// 获取当前目录
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 将文件保存到当前目录
|
||||
appPath := filepath.Join(cwd, fmt.Sprint(time.Now().UnixNano())) // 替换为你想保存的文件名
|
||||
err = builtin.DownloadFile(appPath, url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dExt.Install(appPath, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) Uninstall(packageName string, options ...ActionOption) error {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
err := dExt.Device.Uninstall(packageName)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("failed to uninstall")
|
||||
}
|
||||
if actionOptions.IgnoreNotFoundError {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (dExt *DriverExt) Install(filePath string, opts *InstallOptions) error {
|
||||
if _, ok := dExt.Device.(*AndroidDevice); ok {
|
||||
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(filePath, 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 流进行截图,性能最优
|
||||
@@ -267,7 +337,7 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
|
||||
cacheData["screenshots_urls"] = dExt.cacheStepData.screenResults.getScreenShotUrls()
|
||||
cacheData["screen_results"] = dExt.cacheStepData.screenResults
|
||||
cacheData["e2e_results"] = dExt.cacheStepData.e2eDelay
|
||||
|
||||
cacheData["driver_request_results"] = dExt.Driver.GetDriverResults()
|
||||
// clear cache
|
||||
dExt.cacheStepData.reset()
|
||||
return cacheData
|
||||
|
||||
48
hrp/pkg/uixt/install.go
Normal file
48
hrp/pkg/uixt/install.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package uixt
|
||||
|
||||
type InstallOptions struct {
|
||||
Reinstall bool
|
||||
GrantPermission bool
|
||||
Downgrade bool
|
||||
RetryTime int
|
||||
}
|
||||
|
||||
type InstallOption func(o *InstallOptions)
|
||||
|
||||
func NewInstallOptions(options ...InstallOption) *InstallOptions {
|
||||
installOptions := &InstallOptions{}
|
||||
for _, option := range options {
|
||||
option(installOptions)
|
||||
}
|
||||
return installOptions
|
||||
}
|
||||
|
||||
func WithReinstall(reinstall bool) InstallOption {
|
||||
return func(o *InstallOptions) {
|
||||
o.Reinstall = reinstall
|
||||
}
|
||||
}
|
||||
|
||||
func WithGrantPermission(grantPermission bool) InstallOption {
|
||||
return func(o *InstallOptions) {
|
||||
o.GrantPermission = grantPermission
|
||||
}
|
||||
}
|
||||
|
||||
func WithDowngrade(downgrade bool) InstallOption {
|
||||
return func(o *InstallOptions) {
|
||||
o.Downgrade = downgrade
|
||||
}
|
||||
}
|
||||
|
||||
func WithRetryTime(retryTime int) InstallOption {
|
||||
return func(o *InstallOptions) {
|
||||
o.RetryTime = retryTime
|
||||
}
|
||||
}
|
||||
|
||||
type InstallResult struct {
|
||||
Result int `json:"result"`
|
||||
ErrorCode int `json:"errorCode"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
@@ -250,11 +250,7 @@ type Screen struct {
|
||||
}
|
||||
|
||||
type AppInfo struct {
|
||||
ProcessArguments struct {
|
||||
Env interface{} `json:"env"`
|
||||
Args []interface{} `json:"args"`
|
||||
} `json:"processArguments"`
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name,omitempty"`
|
||||
AppBaseInfo
|
||||
}
|
||||
|
||||
@@ -264,6 +260,10 @@ type AppBaseInfo struct {
|
||||
ViewController string `json:"viewController,omitempty"` // ios view controller
|
||||
PackageName string `json:"packageName,omitempty"` // android package name
|
||||
Activity string `json:"activity,omitempty"` // android activity
|
||||
VersionName string `json:"versionName,omitempty"`
|
||||
VersionCode int `json:"versionCode,omitempty"`
|
||||
AppName string `json:"appName,omitempty"`
|
||||
// AppIcon string `json:"appIcon,omitempty"`
|
||||
}
|
||||
|
||||
type AppState int
|
||||
@@ -374,6 +374,11 @@ func (opt SourceOption) WithFormatAsJson() SourceOption {
|
||||
return opt
|
||||
}
|
||||
|
||||
func (opt SourceOption) WithProcessName(processName string) SourceOption {
|
||||
opt["processName"] = processName
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithFormatAsXml Application elements tree in form of xml string
|
||||
func (opt SourceOption) WithFormatAsXml() SourceOption {
|
||||
opt["format"] = "xml"
|
||||
@@ -446,8 +451,17 @@ type Rect struct {
|
||||
}
|
||||
|
||||
type DriverOptions struct {
|
||||
capabilities Capabilities
|
||||
plugin funplugin.IPlugin
|
||||
capabilities Capabilities
|
||||
plugin funplugin.IPlugin
|
||||
withImageService bool
|
||||
withResultFolder bool
|
||||
}
|
||||
|
||||
func NewDriverOptions() *DriverOptions {
|
||||
return &DriverOptions{
|
||||
withImageService: true,
|
||||
withResultFolder: true,
|
||||
}
|
||||
}
|
||||
|
||||
type DriverOption func(*DriverOptions)
|
||||
@@ -458,6 +472,18 @@ func WithDriverCapabilities(capabilities Capabilities) DriverOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithDriverImageService(withImageService bool) DriverOption {
|
||||
return func(options *DriverOptions) {
|
||||
options.withImageService = withImageService
|
||||
}
|
||||
}
|
||||
|
||||
func WithDriverResultFolder(withResultFolder bool) DriverOption {
|
||||
return func(options *DriverOptions) {
|
||||
options.withResultFolder = withResultFolder
|
||||
}
|
||||
}
|
||||
|
||||
func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
|
||||
return func(options *DriverOptions) {
|
||||
options.plugin = plugin
|
||||
@@ -466,8 +492,8 @@ func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
|
||||
|
||||
// current implemeted device: IOSDevice, AndroidDevice
|
||||
type Device interface {
|
||||
System() string // ios or android
|
||||
UUID() string // ios udid or android serial
|
||||
Init() error // init android device
|
||||
UUID() string // ios udid or android serial
|
||||
LogEnabled() bool
|
||||
NewDriver(...DriverOption) (driverExt *DriverExt, err error)
|
||||
|
||||
@@ -476,6 +502,10 @@ type Device interface {
|
||||
|
||||
StartPcap() error
|
||||
StopPcap() string
|
||||
|
||||
Uninstall(packageName string) error
|
||||
|
||||
Install(appPath string, opts *InstallOptions) error
|
||||
}
|
||||
|
||||
type ForegroundApp struct {
|
||||
@@ -524,6 +554,8 @@ type WebDriver interface {
|
||||
// Homescreen Forces the device under test to switch to the home screen
|
||||
Homescreen() error
|
||||
|
||||
Unlock() (err error)
|
||||
|
||||
// AppLaunch Launch an application with given bundle identifier in scope of current session.
|
||||
// !This method is only available since Xcode9 SDK
|
||||
AppLaunch(packageName string) error
|
||||
@@ -570,6 +602,8 @@ type WebDriver interface {
|
||||
// It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330
|
||||
GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error)
|
||||
|
||||
SetIme(ime string) error
|
||||
|
||||
// SendKeys Types a string into active element. There must be element with keyboard focus,
|
||||
// otherwise an error is raised.
|
||||
// WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60
|
||||
@@ -578,17 +612,25 @@ type WebDriver interface {
|
||||
// Input works like SendKeys
|
||||
Input(text string, options ...ActionOption) error
|
||||
|
||||
Clear(packageName string) error
|
||||
|
||||
// PressButton Presses the corresponding hardware button on the device
|
||||
PressButton(devBtn DeviceButton) error
|
||||
|
||||
// PressBack Presses the back button
|
||||
PressBack(options ...ActionOption) error
|
||||
|
||||
PressKeyCode(keyCode KeyCode) (err error)
|
||||
|
||||
Screenshot() (*bytes.Buffer, error)
|
||||
|
||||
// Source Return application elements tree
|
||||
Source(srcOpt ...SourceOption) (string, error)
|
||||
|
||||
LoginNoneUI(packageName, phoneNumber string, captcha string) error
|
||||
|
||||
LogoutNoneUI(packageName string) error
|
||||
|
||||
TapByText(text string, options ...ActionOption) error
|
||||
|
||||
TapByTexts(actions ...TapTextAction) error
|
||||
@@ -609,4 +651,5 @@ type WebDriver interface {
|
||||
// triggers the log capture and returns the log entries
|
||||
StartCaptureLog(identifier ...string) (err error)
|
||||
StopCaptureLog() (result interface{}, err error)
|
||||
GetDriverResults() []*DriverResult
|
||||
}
|
||||
|
||||
@@ -276,6 +276,7 @@ type IOSDevice struct {
|
||||
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
|
||||
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
|
||||
XCTestBundleID string `json:"xctest_bundle_id,omitempty" yaml:"xctest_bundle_id,omitempty"`
|
||||
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
|
||||
|
||||
// switch to iOS springboard before init WDA session
|
||||
ResetHomeOnStartup bool `json:"reset_home_on_startup,omitempty" yaml:"reset_home_on_startup,omitempty"`
|
||||
@@ -294,8 +295,8 @@ type IOSDevice struct {
|
||||
pcapFile string // saved pcap file path
|
||||
}
|
||||
|
||||
func (dev *IOSDevice) System() string {
|
||||
return "ios"
|
||||
func (dev *IOSDevice) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dev *IOSDevice) UUID() string {
|
||||
@@ -307,7 +308,7 @@ func (dev *IOSDevice) LogEnabled() bool {
|
||||
}
|
||||
|
||||
func (dev *IOSDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) {
|
||||
driverOptions := &DriverOptions{}
|
||||
driverOptions := NewDriverOptions()
|
||||
for _, option := range options {
|
||||
option(driverOptions)
|
||||
}
|
||||
@@ -338,7 +339,7 @@ func (dev *IOSDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt,
|
||||
}
|
||||
}
|
||||
|
||||
driverExt, err = newDriverExt(dev, driver, driverOptions.plugin)
|
||||
driverExt, err = newDriverExt(dev, driver, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -471,6 +472,20 @@ func (dev *IOSDevice) StopPcap() string {
|
||||
return dev.pcapFile
|
||||
}
|
||||
|
||||
func (dev *IOSDevice) Install(appPath string, opts *InstallOptions) (err error) {
|
||||
for i := 0; i <= opts.RetryTime; i++ {
|
||||
err = builtin.RunCommand("go-ios", "install", "--path="+appPath, "--udid="+dev.UDID)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (dev *IOSDevice) Uninstall(bundleId string) error {
|
||||
return builtin.RunCommand("go-ios", "uninstall", bundleId, "--udid="+dev.UDID)
|
||||
}
|
||||
|
||||
func (dev *IOSDevice) forward(localPort, remotePort int) error {
|
||||
log.Info().Int("localPort", localPort).Int("remotePort", remotePort).
|
||||
Str("udid", dev.UDID).Msg("forward tcp port")
|
||||
@@ -635,7 +650,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver
|
||||
wd := new(wdaDriver)
|
||||
wd.client = http.DefaultClient
|
||||
|
||||
host := "127.0.0.1"
|
||||
host := "localhost"
|
||||
if wd.urlPrefix, err = url.Parse(fmt.Sprintf("http://%s:%d", host, localPort)); err != nil {
|
||||
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, err.Error())
|
||||
}
|
||||
|
||||
@@ -591,6 +591,14 @@ func (wd *wdaDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) SetIme(ime string) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) PressKeyCode(keyCode KeyCode) (err error) {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) SendKeys(text string, options ...ActionOption) (err error) {
|
||||
// [[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)]
|
||||
actionOptions := NewActionOptions(options...)
|
||||
@@ -607,6 +615,10 @@ func (wd *wdaDriver) Input(text string, options ...ActionOption) (err error) {
|
||||
return wd.SendKeys(text, options...)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) Clear(packageName string) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
// PressBack simulates a short press on the BACK button.
|
||||
func (wd *wdaDriver) PressBack(options ...ActionOption) (err error) {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
@@ -651,6 +663,14 @@ func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) LogoutNoneUI(packageName string) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) StartCamera() (err error) {
|
||||
// start camera, alias for app_launch com.apple.camera
|
||||
return wd.AppLaunch("com.apple.camera")
|
||||
@@ -877,6 +897,13 @@ func (wd *wdaDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
return reply.Value, nil
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) GetDriverResults() []*DriverResult {
|
||||
defer func() {
|
||||
wd.Driver.driverResults = nil
|
||||
}()
|
||||
return wd.Driver.driverResults
|
||||
}
|
||||
|
||||
type rawResponse []byte
|
||||
|
||||
func (r rawResponse) checkErr() (err error) {
|
||||
@@ -939,6 +966,13 @@ func (r rawResponse) valueConvertToJsonRawMessage() (raw builtinJSON.RawMessage,
|
||||
return
|
||||
}
|
||||
|
||||
func (r rawResponse) valueConvertToJsonObject() (obj map[string]interface{}, err error) {
|
||||
if err = json.Unmarshal(r, &obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r rawResponse) valueDecodeAsBase64() (raw *bytes.Buffer, err error) {
|
||||
str, err := r.valueConvertToString()
|
||||
if err != nil {
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -26,7 +28,7 @@ func setup(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
iOSDriverExt, err = newDriverExt(device, driver, nil)
|
||||
iOSDriverExt, err = newDriverExt(device, driver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -37,6 +39,15 @@ func TestViaUSB(t *testing.T) {
|
||||
t.Log(driver.Status())
|
||||
}
|
||||
|
||||
func TestInstall(t *testing.T) {
|
||||
setup(t)
|
||||
err := iOSDriverExt.Install("/Users/bytedance/Downloads/com.yueyou.cyreader_1387717110_7.54.20.ipa", NewInstallOptions(WithRetryTime(5)))
|
||||
log.Error().Err(err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIOSDevice(t *testing.T) {
|
||||
device, _ := NewIOSDevice()
|
||||
if device != nil {
|
||||
|
||||
@@ -62,11 +62,11 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error {
|
||||
// filter ocr texts with time format
|
||||
var liveTimeTexts []string
|
||||
for _, ocrText := range ocrTexts {
|
||||
if len(ocrText.Text) < 10 || strings.Contains(ocrText.Text, ":") {
|
||||
if len(ocrText.Text) < 13 || strings.Contains(ocrText.Text, ":") {
|
||||
continue
|
||||
}
|
||||
// exclude digit(s) recognized as letter(s)
|
||||
_, errParseInt := strconv.ParseInt(ocrText.Text[:10], 10, 64)
|
||||
_, errParseInt := strconv.ParseInt(ocrText.Text[:13], 10, 64)
|
||||
if errParseInt != nil {
|
||||
continue
|
||||
}
|
||||
@@ -81,11 +81,6 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(liveTimeText) < 13 {
|
||||
for (13 - len(liveTimeText)) > 0 {
|
||||
liveTimeText += "0"
|
||||
}
|
||||
}
|
||||
liveTimeInt, err := strconv.Atoi(liveTimeText)
|
||||
if err != nil {
|
||||
liveTimeInt = 0
|
||||
|
||||
11
hrp/pkg/utf7/utf7_test.go
Normal file
11
hrp/pkg/utf7/utf7_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package utf7
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_Decode(t *testing.T) {
|
||||
str, err := Encoding.NewDecoder().String("&j71bgXcBbIiWM14CZbBsEV4CbBFlz4hX-36-4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(str)
|
||||
}
|
||||
@@ -648,6 +648,16 @@ func (r *SessionRunner) initWithParameters(parameters map[string]interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SessionRunner) IgnorePopup() bool {
|
||||
if r.caseRunner.testCase.Config.Android != nil {
|
||||
return r.caseRunner.testCase.Config.Android[0].IgnorePopup
|
||||
}
|
||||
if r.caseRunner.testCase.Config.IOS != nil {
|
||||
return r.caseRunner.testCase.Config.IOS[0].IgnorePopup
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// updateSummary updates summary of StepResult.
|
||||
func (r *SessionRunner) updateSummary(stepResult *StepResult) {
|
||||
switch stepResult.StepType {
|
||||
|
||||
@@ -53,6 +53,7 @@ type TStep struct {
|
||||
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
|
||||
Export []string `json:"export,omitempty" yaml:"export,omitempty"`
|
||||
Loops int `json:"loops,omitempty" yaml:"loops,omitempty"`
|
||||
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
|
||||
}
|
||||
|
||||
// IStep represents interface for all types for teststeps, includes:
|
||||
|
||||
@@ -605,18 +605,18 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
||||
}
|
||||
|
||||
// report GA event
|
||||
sdk.SendGA4Event("hrp_run_ui", map[string]interface{}{
|
||||
go sdk.SendGA4Event("hrp_run_ui", map[string]interface{}{
|
||||
"osType": osType,
|
||||
})
|
||||
|
||||
identifer := mobileStep.Identifier
|
||||
if mobileStep.Options != nil && identifer == "" {
|
||||
identifer = mobileStep.Options.Identifier
|
||||
identifier := mobileStep.Identifier
|
||||
if mobileStep.Options != nil && identifier == "" {
|
||||
identifier = mobileStep.Options.Identifier
|
||||
}
|
||||
if len(mobileStep.Actions) != 0 && identifer == "" {
|
||||
if len(mobileStep.Actions) != 0 && identifier == "" {
|
||||
for _, action := range mobileStep.Actions {
|
||||
if action.Identifier != "" {
|
||||
identifer = action.Identifier
|
||||
identifier = action.Identifier
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -624,7 +624,7 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
||||
|
||||
stepResult = &StepResult{
|
||||
Name: step.Name,
|
||||
Identifier: identifer,
|
||||
Identifier: identifier,
|
||||
StepType: StepType(osType),
|
||||
Success: false,
|
||||
ContentSize: 0,
|
||||
@@ -650,8 +650,10 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
||||
}
|
||||
|
||||
// automatic handling of pop-up windows on each step finished
|
||||
if err2 := uiDriver.ClosePopupsHandler(); err2 != nil {
|
||||
log.Error().Err(err2).Str("step", step.Name).Msg("handle popup failed on step finished")
|
||||
if !step.IgnorePopup && !s.IgnorePopup() {
|
||||
if err2 := uiDriver.ClosePopups(); err2 != nil {
|
||||
log.Error().Err(err2).Str("step", step.Name).Msg("auto handle popup failed")
|
||||
}
|
||||
}
|
||||
|
||||
// save attachments
|
||||
|
||||
Reference in New Issue
Block a user