From c8a42e951a4353f204b87623a190a5ab3653231b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Tue, 6 Aug 2024 20:30:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=8F=8C=E7=AB=AF?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E5=8D=B8=E8=BD=BD=20=E5=AE=89=E5=8D=93?= =?UTF-8?q?=E6=B8=85=E6=A5=9A=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hrp/cmd/adb/install.go | 4 +- hrp/cmd/ios/install.go | 52 +++++++++++++++++++++ hrp/internal/builtin/utils.go | 28 ++++++++++++ hrp/internal/version/VERSION | 2 +- hrp/pkg/uixt/action.go | 23 +++++++++- hrp/pkg/uixt/android_device.go | 12 ++++- hrp/pkg/uixt/ext.go | 84 +++++++++++++++++++--------------- hrp/pkg/uixt/install.go | 48 +++++++++++++++++++ hrp/pkg/uixt/interface.go | 5 +- hrp/pkg/uixt/ios_device.go | 15 +++++- hrp/pkg/uixt/ios_test.go | 10 +++- 11 files changed, 236 insertions(+), 47 deletions(-) create mode 100644 hrp/cmd/ios/install.go create mode 100644 hrp/pkg/uixt/install.go diff --git a/hrp/cmd/adb/install.go b/hrp/cmd/adb/install.go index b7245c81..cce100de 100644 --- a/hrp/cmd/adb/install.go +++ b/hrp/cmd/adb/install.go @@ -42,8 +42,8 @@ var installCmd = &cobra.Command{ 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) + + err = driverExt.Install(args[0], uixt.NewInstallOptions(uixt.WithReinstall(replace), uixt.WithDowngrade(downgrade), uixt.WithGrantPermission(grant))) if err != nil { fmt.Println(err) return err diff --git a/hrp/cmd/ios/install.go b/hrp/cmd/ios/install.go new file mode 100644 index 00000000..7fdcd3eb --- /dev/null +++ b/hrp/cmd/ios/install.go @@ -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) +} diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 5c32d5f7..dcfb8bf1 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -8,9 +8,11 @@ import ( "encoding/csv" builtinJSON "encoding/json" "fmt" + "io" "math" "math/rand" "net" + "net/http" "os" "path/filepath" "reflect" @@ -509,3 +511,29 @@ func GetCurrentDay() string { formattedDate := now.Format("20060102") return formattedDate } + +func DownloadFile(filePath string, url string) error { + log.Info().Str("filePath", filePath).Str("url", url).Msg("download file") + out, err := os.Create(filePath) + if err != nil { + return err + } + defer out.Close() + + resp, err := http.Get(url) + 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 +} diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index a61f96e3..1db9ff27 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.6.2 +v4.6.3 diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index 06e9deaf..67ddac24 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -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" @@ -64,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 { @@ -558,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); 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) diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 44fbb679..f83dba74 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "net" + "os" "os/exec" "regexp" "strconv" @@ -328,7 +329,16 @@ func (dev *AndroidDevice) StopPcap() string { return "" } -func (dev *AndroidDevice) Install(app io.ReadSeeker, opts InstallOptions) error { +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)) + } + brand, err := dev.d.Brand() if err != nil { return err diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 7fe57571..94508df2 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -50,18 +50,6 @@ 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 @@ -213,35 +201,59 @@ 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) +func (dExt *DriverExt) InstallByUrl(url string, opts *InstallOptions) error { + // 获取当前目录 + cwd, err := os.Getwd() if err != nil { - return errors.Wrap(err, fmt.Sprintf("install %s open file failed", filePath)) + return err } - 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)}}, + // 将文件保存到当前目录 + 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) error { + return dExt.Device.Uninstall(packageName) +} + +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 } - _ = 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) + }() + defer func() { + close(stopChan) + }() + } + + return dExt.Device.Install(filePath, opts) } // takeScreenShot takes screenshot and saves image file to $CWD/screenshots/ folder diff --git a/hrp/pkg/uixt/install.go b/hrp/pkg/uixt/install.go new file mode 100644 index 00000000..819d1746 --- /dev/null +++ b/hrp/pkg/uixt/install.go @@ -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"` +} diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index 017e52f9..ca067c78 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -2,7 +2,6 @@ package uixt import ( "bytes" - "io" "math" "strings" "time" @@ -506,7 +505,9 @@ type Device interface { StartPcap() error StopPcap() string - Install(app io.ReadSeeker, opts InstallOptions) error + Uninstall(packageName string) error + + Install(appPath string, opts *InstallOptions) error } type ForegroundApp struct { diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index 910bcba7..576bfa1c 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -13,6 +13,7 @@ import ( "strconv" "time" + "github.com/httprunner/funplugin/myexec" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -472,8 +473,18 @@ 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) Install(appPath string, opts *InstallOptions) (err error) { + for i := 0; i < opts.RetryTime; i++ { + err = myexec.RunCommand("ideviceinstaller", "-u", dev.UDID, "-i", appPath) + if err == nil { + return nil + } + } + return err +} + +func (dev *IOSDevice) Uninstall(bundleId string) error { + return myexec.RunCommand("ideviceinstaller", "-u", dev.UDID, "-U", bundleId) } func (dev *IOSDevice) forward(localPort, remotePort int) error { diff --git a/hrp/pkg/uixt/ios_test.go b/hrp/pkg/uixt/ios_test.go index fdfdce70..18029ca9 100644 --- a/hrp/pkg/uixt/ios_test.go +++ b/hrp/pkg/uixt/ios_test.go @@ -26,7 +26,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 +37,14 @@ func TestViaUSB(t *testing.T) { t.Log(driver.Status()) } +func TestInstall(t *testing.T) { + setup(t) + err := iOSDriverExt.Install("/Users/bytedance/workcode/httprunner/hrp/pkg/uixt/1722942152176906000", NewInstallOptions(WithRetryTime(5))) + if err != nil { + t.Fatal(err) + } +} + func TestNewIOSDevice(t *testing.T) { device, _ := NewIOSDevice() if device != nil {