From 440d873eebb12aabdf00ae00ed9ef484f20cf3f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Wed, 19 Feb 2025 15:50:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9ios=5Fstub=5Fdriver?= =?UTF-8?q?=E3=80=82wda=E7=BB=84=E5=90=88=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/uixt/driver_ext/android_stub_driver.go | 4 - pkg/uixt/driver_ext/ios_stub_driver.go | 435 ++++++++++++++++++-- pkg/uixt/driver_ext/ios_stub_driver_test.go | 35 ++ pkg/uixt/ios_device.go | 17 + 4 files changed, 462 insertions(+), 29 deletions(-) create mode 100644 pkg/uixt/driver_ext/ios_stub_driver_test.go diff --git a/pkg/uixt/driver_ext/android_stub_driver.go b/pkg/uixt/driver_ext/android_stub_driver.go index dde8f912..dfc2dd4f 100644 --- a/pkg/uixt/driver_ext/android_stub_driver.go +++ b/pkg/uixt/driver_ext/android_stub_driver.go @@ -55,10 +55,6 @@ func (sad *StubAndroidDriver) Setup() error { fmt.Sprintf("forward port %d->%s failed: %v", socketLocalPort, StubSocketName, err)) } - err = sad.Session.SetupPortForward(socketLocalPort) - if err != nil { - return err - } douyinLocalPort, err := sad.Device.Forward(AndroidDouyinPort) if err != nil { diff --git a/pkg/uixt/driver_ext/ios_stub_driver.go b/pkg/uixt/driver_ext/ios_stub_driver.go index c4d27e57..f3cfb393 100644 --- a/pkg/uixt/driver_ext/ios_stub_driver.go +++ b/pkg/uixt/driver_ext/ios_stub_driver.go @@ -1,8 +1,10 @@ package driver_ext import ( + "bytes" "encoding/json" "fmt" + "github.com/httprunner/httprunner/v5/pkg/uixt/types" "net/url" "time" @@ -16,8 +18,9 @@ import ( ) type StubIOSDriver struct { - *uixt.WDADriver - + Device *uixt.IOSDevice + Session *uixt.DriverSession + wdaDriver *uixt.WDADriver timeout time.Duration douyinUrlPrefix string douyinLiteUrlPrefix string @@ -30,13 +33,10 @@ const ( ) func NewStubIOSDriver(dev *uixt.IOSDevice) (*StubIOSDriver, error) { - wdaDriver, err := uixt.NewWDADriver(dev) - if err != nil { - return nil, err - } driver := &StubIOSDriver{ - WDADriver: wdaDriver, - timeout: 10 * time.Second, + Device: dev, + timeout: 10 * time.Second, + Session: uixt.NewDriverSession(), } // setup driver @@ -52,10 +52,6 @@ func (s *StubIOSDriver) Setup() error { if err != nil { return err } - err = s.Session.SetupPortForward(localPort) - if err != nil { - return err - } s.Session.SetBaseURL(fmt.Sprintf("http://127.0.0.1:%d", localPort)) localDouyinPort, err := builtin.GetFreePort() @@ -82,6 +78,18 @@ func (s *StubIOSDriver) Setup() error { return nil } +func (s *StubIOSDriver) setUpWda() (err error) { + if s.wdaDriver == nil { + driver, err := uixt.NewWDADriver(s.Device) + if err != nil { + log.Error().Err(err).Msg("stub driver failed to init wda driver") + return err + } + s.wdaDriver = driver + } + return nil +} + func (s *StubIOSDriver) getLocalPort() (int, error) { localStubPort, err := builtin.GetFreePort() if err != nil { @@ -104,7 +112,7 @@ func (s *StubIOSDriver) Source(srcOpt ...option.SourceOption) (string, error) { return string(resp), nil } -func (s *StubIOSDriver) OpenUrl(urlStr string, options ...option.ActionOption) (err error) { +func (s *StubIOSDriver) OpenUrl(urlStr string, opts ...option.ActionOption) (err error) { targetUrl := fmt.Sprintf("/openURL?url=%s", url.QueryEscape(urlStr)) fmt.Sprintln(targetUrl) resp, err := s.Session.GET(targetUrl) @@ -154,17 +162,12 @@ func (s *StubIOSDriver) LoginDouyin(packageName, phoneNumber string, captcha, pa } else { return info, fmt.Errorf("password and capcha is empty") } - bsJSON, err := json.Marshal(params) - if err != nil { - return info, err - } - urlPrefix, err := s.getUrlPrefix(packageName) if err != nil { return info, err } - fullUrl := urlPrefix + "/host/login/account/" + urlPrefix - resp, err := s.Session.POST(bsJSON, fullUrl) + fullUrl := urlPrefix + "/host/login/account/" + resp, err := s.Session.POST(params, fullUrl) if err != nil { return info, err } @@ -221,11 +224,7 @@ func (s *StubIOSDriver) EnableDevtool(packageName string, enable bool) (err erro params := map[string]interface{}{ "enable": enable, } - bsJSON, err := json.Marshal(params) - if err != nil { - return err - } - resp, err := s.Session.POST(bsJSON, fullUrl) + resp, err := s.Session.POST(params, fullUrl) if err != nil { return err } @@ -280,3 +279,389 @@ func (s *StubIOSDriver) getUrlPrefix(packageName string) (urlPrefix string, err } return urlPrefix, nil } + +func (s *StubIOSDriver) TearDown() error { + if s.wdaDriver != nil { + _ = s.wdaDriver.TearDown() + } + return nil +} + +// NewSession starts a new session and returns the SessionInfo. +func (s *StubIOSDriver) InitSession(capabilities option.Capabilities) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.InitSession(capabilities) +} + +// DeleteSession Kills application associated with that session and removes session +// 1. alertsMonitor disable +// 2. testedApplicationBundleId terminate +func (s *StubIOSDriver) DeleteSession() error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.DeleteSession() +} + +func (s *StubIOSDriver) Status() (types.DeviceStatus, error) { + err := s.setUpWda() + if err != nil { + return types.DeviceStatus{}, err + } + return s.wdaDriver.Status() +} + +func (s *StubIOSDriver) GetDevice() uixt.IDevice { + return s.Device +} + +func (s *StubIOSDriver) DeviceInfo() (types.DeviceInfo, error) { + err := s.setUpWda() + if err != nil { + return types.DeviceInfo{}, err + } + return s.wdaDriver.DeviceInfo() +} + +func (s *StubIOSDriver) Location() (types.Location, error) { + err := s.setUpWda() + if err != nil { + return types.Location{}, err + } + return s.wdaDriver.Location() +} + +func (s *StubIOSDriver) BatteryInfo() (types.BatteryInfo, error) { + err := s.setUpWda() + if err != nil { + return types.BatteryInfo{}, err + } + return s.wdaDriver.BatteryInfo() +} + +// WindowSize Return the width and height in portrait mode. +// when getting the window size in wda/ui2/adb, if the device is in landscape mode, +// the width and height will be reversed. +func (s *StubIOSDriver) WindowSize() (types.Size, error) { + err := s.setUpWda() + if err != nil { + return types.Size{}, err + } + return s.wdaDriver.WindowSize() +} + +func (s *StubIOSDriver) Screen() (uixt.Screen, error) { + err := s.setUpWda() + if err != nil { + return uixt.Screen{}, err + } + return s.wdaDriver.Screen() +} + +func (s *StubIOSDriver) Scale() (float64, error) { + err := s.setUpWda() + if err != nil { + return 0, err + } + return s.wdaDriver.Scale() +} + +// Homescreen Forces the device under test to switch to the home screen +func (s *StubIOSDriver) Home() error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.Home() +} + +func (s *StubIOSDriver) Unlock() (err error) { + err = s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.Unlock() +} + +// AppLaunch Launch an application with given bundle identifier in scope of current session. +// !This method is only available since Xcode9 SDK +func (s *StubIOSDriver) AppLaunch(packageName string) error { + _ = s.EnableDevtool(packageName, true) + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.AppLaunch(packageName) +} + +// AppTerminate Terminate an application with the given package name. +// Either `true` if the app has been successfully terminated or `false` if it was not running +func (s *StubIOSDriver) AppTerminate(packageName string) (bool, error) { + err := s.setUpWda() + if err != nil { + return false, err + } + return s.wdaDriver.AppTerminate(packageName) +} + +// GetForegroundApp returns current foreground app package name and activity name +func (s *StubIOSDriver) ForegroundInfo() (appInfo types.AppInfo, err error) { + err = s.setUpWda() + if err != nil { + return types.AppInfo{}, err + } + return s.wdaDriver.ForegroundInfo() +} + +func (s *StubIOSDriver) Orientation() (orientation types.Orientation, err error) { + err = s.setUpWda() + if err != nil { + return types.OrientationPortrait, err + } + return s.wdaDriver.Orientation() +} + +func (s *StubIOSDriver) Rotation() (rotation types.Rotation, err error) { + err = s.setUpWda() + if err != nil { + return types.Rotation{}, err + } + return s.wdaDriver.Rotation() +} + +func (s *StubIOSDriver) SetRotation(rotation types.Rotation) (err error) { + err = s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.SetRotation(rotation) +} + +// Tap Sends a tap event at the coordinate. +func (s *StubIOSDriver) TapXY(x, y float64, opts ...option.ActionOption) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.TapXY(x, y, opts...) +} + +func (s *StubIOSDriver) TapAbsXY(x, y float64, opts ...option.ActionOption) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.TapAbsXY(x, y, opts...) +} + +// DoubleTap Sends a double tap event at the coordinate. +func (s *StubIOSDriver) DoubleTapXY(x, y float64, opts ...option.ActionOption) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.DoubleTapXY(x, y, opts...) +} + +// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration. +// +// second: The default value is 1 +func (s *StubIOSDriver) TouchAndHold(x, y float64, opts ...option.ActionOption) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.TouchAndHold(x, y, opts...) +} + +// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate. +// WithPressDurationOption option can be used to set pressForDuration (default to 1 second). +func (s *StubIOSDriver) Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.Drag(fromX, fromY, toX, toY, opts...) +} + +// Swipe works like Drag, but `pressForDuration` value is 0 +func (s *StubIOSDriver) Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.Swipe(fromX, fromY, toX, toY, opts...) +} + +// SetPasteboard Sets data to the general pasteboard +func (s *StubIOSDriver) SetPasteboard(contentType types.PasteboardType, content string) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.SetPasteboard(contentType, content) +} + +// GetPasteboard Gets the data contained in the general pasteboard. +// +// It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330 +func (s *StubIOSDriver) GetPasteboard(contentType types.PasteboardType) (raw *bytes.Buffer, err error) { + err = s.setUpWda() + if err != nil { + return nil, err + } + return s.wdaDriver.GetPasteboard(contentType) +} + +func (s *StubIOSDriver) SetIme(ime string) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.SetIme(ime) +} + +// Input works like SendKeys +func (s *StubIOSDriver) Input(text string, opts ...option.ActionOption) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.Input(text, opts...) +} + +func (s *StubIOSDriver) Backspace(count int, opts ...option.ActionOption) (err error) { + err = s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.Backspace(count, opts...) +} + +func (s *StubIOSDriver) AppClear(packageName string) error { + return types.ErrDriverNotImplemented +} + +func (s *StubIOSDriver) Back() (err error) { + err = s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.Back() +} + +// PressButton Presses the corresponding hardware button on the device +func (s *StubIOSDriver) PressButton(devBtn types.DeviceButton) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.PressButton(devBtn) +} + +func (s *StubIOSDriver) ScreenShot(opts ...option.ActionOption) (*bytes.Buffer, error) { + err := s.setUpWda() + if err != nil { + return s.Device.ScreenShot() + } + return s.wdaDriver.ScreenShot() +} + +// AccessibleSource Return application elements accessibility tree +func (s *StubIOSDriver) AccessibleSource() (string, error) { + err := s.setUpWda() + if err != nil { + return "", err + } + return s.wdaDriver.AccessibleSource() +} + +// HealthCheck Health check might modify simulator state so it should only be called in-between testing sessions +// +// Checks health of XCTest by: +// 1) Querying application for some elements, +// 2) Triggering some device events. +func (s *StubIOSDriver) HealthCheck() error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.HealthCheck() +} + +func (s *StubIOSDriver) GetAppiumSettings() (map[string]interface{}, error) { + err := s.setUpWda() + if err != nil { + return nil, err + } + return s.wdaDriver.GetAppiumSettings() +} + +func (s *StubIOSDriver) SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error) { + err := s.setUpWda() + if err != nil { + return nil, err + } + return s.wdaDriver.SetAppiumSettings(settings) +} + +func (s *StubIOSDriver) IsHealthy() (bool, error) { + err := s.setUpWda() + if err != nil { + return false, err + } + return s.wdaDriver.IsHealthy() +} + +func (s *StubIOSDriver) ScreenRecord(duration time.Duration) (videoPath string, err error) { + err = s.setUpWda() + if err != nil { + return "", err + } + return s.wdaDriver.ScreenRecord(duration) +} + +func (s *StubIOSDriver) PushImage(localPath string) error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.PushImage(localPath) +} + +func (s *StubIOSDriver) ClearImages() error { + err := s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.ClearImages() +} + +// triggers the log capture and returns the log entries +func (s *StubIOSDriver) StartCaptureLog(identifier ...string) (err error) { + err = s.setUpWda() + if err != nil { + return err + } + return s.wdaDriver.StartCaptureLog(identifier...) +} + +func (s *StubIOSDriver) StopCaptureLog() (result interface{}, err error) { + err = s.setUpWda() + if err != nil { + return nil, err + } + return s.wdaDriver.StopCaptureLog() +} + +func (s *StubIOSDriver) GetSession() *uixt.DriverSession { + err := s.setUpWda() + if err != nil { + return nil + } + return s.wdaDriver.Session +} diff --git a/pkg/uixt/driver_ext/ios_stub_driver_test.go b/pkg/uixt/driver_ext/ios_stub_driver_test.go new file mode 100644 index 00000000..d730e277 --- /dev/null +++ b/pkg/uixt/driver_ext/ios_stub_driver_test.go @@ -0,0 +1,35 @@ +package driver_ext + +import ( + "github.com/httprunner/httprunner/v5/pkg/uixt" + "github.com/httprunner/httprunner/v5/pkg/uixt/option" + "testing" +) + +var ( + iOSStubDriver *StubIOSDriver +) + +func checkErr(t *testing.T, err error, msg ...string) { + if err != nil { + if len(msg) == 0 { + t.Fatal(err) + } else { + t.Fatal(msg, err) + } + } +} + +func setupIOSStubDriver(t *testing.T) { + iOSDevice, err := uixt.NewIOSDevice(option.WithWDAPort(8700), option.WithWDAMjpegPort(8800), option.WithResetHomeOnStartup(false)) + checkErr(t, err) + iOSStubDriver, err = NewStubIOSDriver(iOSDevice) + checkErr(t, err) +} + +func TestIOSStubDriver_LoginNoneUI(t *testing.T) { + setupIOSStubDriver(t) + info, err := iOSStubDriver.LoginNoneUI("com.ss.iphone.ugc.AwemeInhouse", "12343418541", "", "im112233") + checkErr(t, err) + t.Logf("login info: %+v", info) +} diff --git a/pkg/uixt/ios_device.go b/pkg/uixt/ios_device.go index 0cb7adbc..ec452b04 100644 --- a/pkg/uixt/ios_device.go +++ b/pkg/uixt/ios_device.go @@ -1,6 +1,7 @@ package uixt import ( + "bytes" "context" "encoding/json" "fmt" @@ -326,6 +327,22 @@ func (dev *IOSDevice) ListApps(appType ApplicationType) (apps []installationprox return apps, nil } +func (dev *IOSDevice) ScreenShot() (*bytes.Buffer, error) { + screenshotService, err := instruments.NewScreenshotService(dev.DeviceEntry) + if err != nil { + log.Error().Err(err).Msg("Starting screenshot service failed") + return nil, err + } + defer screenshotService.Close() + + imageBytes, err := screenshotService.TakeScreenshot() + if err != nil { + log.Error().Err(err).Msg("failed to task screenshot") + return nil, err + } + return bytes.NewBuffer(imageBytes), nil +} + func (dev *IOSDevice) GetAppInfo(packageName string) (appInfo installationproxy.AppInfo, err error) { svc, _ := installationproxy.New(dev.DeviceEntry) defer svc.Close()