feat: merge install version

This commit is contained in:
余泓铮
2024-08-06 14:48:40 +08:00
10 changed files with 249 additions and 16 deletions

62
hrp/cmd/adb/install.go Normal file
View 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")
option := uixt.InstallOptions{Reinstall: replace, GrantPermission: grant, Downgrade: downgrade}
err = driverExt.Install(args[0], option)
if err != nil {
fmt.Println(err)
return err
}
fmt.Println("success")
return nil
},
}
func init() {
installCmd.Flags().StringVarP(&serial, "serial", "s", "", "filter by device's serial")
installCmd.Flags().BoolP("replace", "r", false, "replace existing application")
installCmd.Flags().BoolP("downgrade", "d", false, "allow version code downgrade (debuggable packages only)")
installCmd.Flags().BoolP("grant", "g", false, "grant all runtime permissions")
androidRootCmd.AddCommand(installCmd)
}

View File

@@ -1 +1 @@
v4.6.2
v4.6.2

View File

@@ -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) {

View File

@@ -446,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
}
@@ -586,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() }()

View File

@@ -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")

View File

@@ -4,9 +4,16 @@ import (
"bufio"
"bytes"
"context"
"crypto/md5"
"embed"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"net"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
@@ -14,6 +21,7 @@ import (
"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"
@@ -21,11 +29,13 @@ import (
)
var (
AdbServerHost = "localhost"
AdbServerPort = gadb.AdbServerPort // 5037
UIA2ServerHost = "localhost"
UIA2ServerPort = 6790
DouyinServerPort = 32316
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 eval_tool
@@ -318,6 +328,120 @@ func (dev *AndroidDevice) StopPcap() string {
return ""
}
func (dev *AndroidDevice) Install(app io.ReadSeeker, opts InstallOptions) error {
brand, err := dev.d.Brand()
if err != nil {
return err
}
args := []string{}
if opts.Reinstall {
args = append(args, "-r")
}
if opts.GrantPermission {
args = append(args, "-g")
}
if opts.Downgrade {
args = append(args, "-d")
}
switch strings.ToLower(brand) {
case "vivo":
return dev.installVivoSilent(app, args...)
case "oppo", "realme", "oneplus":
if dev.d.IsPackagesInstalled(EvalInstallerPackageName) {
return dev.installViaInstaller(app, args...)
}
log.Warn().Msg("oppo not install eval installer")
return dev.installCommon(app, args...)
default:
return dev.installCommon(app, args...)
}
}
func (dev *AndroidDevice) installVivoSilent(app io.ReadSeeker, args ...string) error {
currentTime := builtin.GetCurrentDay()
md5HashInBytes := md5.Sum([]byte(currentTime))
verifyCode := hex.EncodeToString(md5HashInBytes[:])
verifyCode = base64.StdEncoding.EncodeToString([]byte(verifyCode))
verifyCode = verifyCode[:8]
verifyCode = "-V" + verifyCode
args = append([]string{verifyCode}, args...)
_, err := dev.d.InstallAPK(app, args...)
return err
}
func (dev *AndroidDevice) installViaInstaller(app io.ReadSeeker, args ...string) error {
appRemotePath := "/data/local/tmp/" + strconv.FormatInt(time.Now().UnixMilli(), 10) + ".apk"
err := dev.d.Push(app, appRemotePath, time.Now())
if err != nil {
return err
}
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()
}()
// 需要监听是否完成安装
args = strings.Split(InstallViaInstallerCommand, " ")
args = append(args, 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
}
func getFreePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, errors.Wrap(err, "resolve tcp addr failed")
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, errors.Wrap(err, "listen tcp addr failed")
}
defer func() { _ = l.Close() }()
return l.Addr().(*net.TCPAddr).Port, nil
}
type LineCallback func(string)
type AdbLogcat struct {

View File

@@ -3,8 +3,6 @@
package uixt
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
@@ -445,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) {

View File

@@ -50,6 +50,18 @@ func WithThreshold(threshold float64) CVOption {
}
}
type InstallOptions struct {
Reinstall bool
GrantPermission bool
Downgrade bool
}
type InstallResult struct {
Result int `json:"result"`
ErrorCode int `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type ScreenResult struct {
bufSource *bytes.Buffer // raw image buffer bytes
imagePath string // image file path
@@ -201,6 +213,37 @@ func newDriverExt(device Device, driver WebDriver, options ...DriverOption) (dEx
return dExt, nil
}
func (dExt *DriverExt) Install(filePath string, opts InstallOptions) error {
app, err := os.Open(filePath)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("install %s open file failed", filePath))
}
stopChan := make(chan struct{})
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
actions := []TapTextAction{
{Text: "^.*无视风险安装$", Options: []ActionOption{WithTapOffset(100, 0), WithRegex(true), WithIgnoreNotFoundError(true)}},
{Text: "^已了解此应用未经检测.*", Options: []ActionOption{WithTapOffset(-450, 0), WithRegex(true), WithIgnoreNotFoundError(true)}},
}
_ = dExt.Driver.TapByTexts(actions...)
_ = dExt.TapByOCR("^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|安装|授权本次安装|继续安装|重新安装)$", WithRegex(true), WithIgnoreNotFoundError(true))
case <-stopChan:
fmt.Println("Ticker stopped")
return
}
}
}()
defer func() {
close(stopChan)
}()
return dExt.Device.Install(app, opts)
}
// takeScreenShot takes screenshot and saves image file to $CWD/screenshots/ folder
func (dExt *DriverExt) takeScreenShot(fileName string) (raw *bytes.Buffer, path string, err error) {
// iOS 优先使用 MJPEG 流进行截图,性能最优

View File

@@ -2,6 +2,7 @@ package uixt
import (
"bytes"
"io"
"math"
"strings"
"time"
@@ -504,6 +505,8 @@ type Device interface {
StartPcap() error
StopPcap() string
Install(app io.ReadSeeker, opts InstallOptions) error
}
type ForegroundApp struct {

View File

@@ -472,6 +472,10 @@ func (dev *IOSDevice) StopPcap() string {
return dev.pcapFile
}
func (dev *IOSDevice) Install(app io.ReadSeeker, opts InstallOptions) error {
return errors.New("install method not implemented")
}
func (dev *IOSDevice) forward(localPort, remotePort int) error {
log.Info().Int("localPort", localPort).Int("remotePort", remotePort).
Str("udid", dev.UDID).Msg("forward tcp port")