From 29c0e1cbef1ca2eb813145dd75fadc10e09f3397 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Mon, 17 Feb 2025 17:08:24 +0800 Subject: [PATCH] refactor: DriverSession --- internal/version/VERSION | 2 +- pkg/uixt/android_driver_adb.go | 9 +- pkg/uixt/android_driver_uia2.go | 103 +++----- pkg/uixt/driver.go | 7 +- pkg/uixt/driver_ext/shoots_android_driver.go | 2 +- pkg/uixt/driver_ext/shoots_ios_driver.go | 21 +- pkg/uixt/driver_ext_screenshot.go | 2 +- pkg/uixt/driver_session.go | 213 ++++++++++----- pkg/uixt/driver_session_test.go | 34 +++ pkg/uixt/driver_test.go | 12 +- pkg/uixt/driver_utils.go | 13 + pkg/uixt/harmony_driver_hdc.go | 7 +- pkg/uixt/ios_device.go | 72 ----- pkg/uixt/ios_driver_wda.go | 264 +++++++++---------- pkg/uixt/ios_test.go | 9 - step_ui.go | 3 +- 16 files changed, 377 insertions(+), 396 deletions(-) create mode 100644 pkg/uixt/driver_session_test.go diff --git a/internal/version/VERSION b/internal/version/VERSION index 18de5b29..9f5d8478 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0+2502141035 +v5.0.0+2502171711 diff --git a/pkg/uixt/android_driver_adb.go b/pkg/uixt/android_driver_adb.go index f3b26307..848d9fa6 100644 --- a/pkg/uixt/android_driver_adb.go +++ b/pkg/uixt/android_driver_adb.go @@ -31,15 +31,14 @@ func NewADBDriver(device *AndroidDevice) (*ADBDriver, error) { log.Info().Interface("device", device).Msg("init android adb driver") driver := &ADBDriver{ Device: device, - Session: &Session{}, + Session: NewDriverSession(), } - driver.InitSession(nil) return driver, nil } type ADBDriver struct { Device *AndroidDevice - Session *Session + Session *DriverSession // cache to avoid repeated query windowSize types.Size @@ -79,8 +78,6 @@ func (ad *ADBDriver) runShellCommand(cmd string, args ...string) (output string, } func (ad *ADBDriver) InitSession(capabilities option.Capabilities) error { - ad.Session = &Session{} - ad.Session.Reset() return nil } @@ -713,7 +710,7 @@ func (ad *ADBDriver) StopCaptureLog() (result interface{}, err error) { return pointRes, nil } -func (ad *ADBDriver) GetSession() *Session { +func (ad *ADBDriver) GetSession() *DriverSession { return ad.Session } diff --git a/pkg/uixt/android_driver_uia2.go b/pkg/uixt/android_driver_uia2.go index b1ef0a1e..0c006e6f 100644 --- a/pkg/uixt/android_driver_uia2.go +++ b/pkg/uixt/android_driver_uia2.go @@ -5,9 +5,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "net/http" - "strings" - "time" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -20,12 +17,6 @@ import ( func NewUIA2Driver(device *AndroidDevice) (*UIA2Driver, error) { log.Info().Interface("device", device).Msg("init android UIA2 driver") - localPort, err := device.Device.Forward(device.Options.UIA2Port) - if err != nil { - return nil, errors.Wrap(code.DeviceConnectionError, - fmt.Sprintf("forward port %d->%d failed: %v", - localPort, device.Options.UIA2Port, err)) - } adbDriver, err := NewADBDriver(device) if err != nil { return nil, err @@ -33,6 +24,18 @@ func NewUIA2Driver(device *AndroidDevice) (*UIA2Driver, error) { driver := &UIA2Driver{ ADBDriver: adbDriver, } + err = driver.InitSession(nil) + if err != nil { + return nil, err + } + + // forward port + localPort, err := device.Device.Forward(device.Options.UIA2Port) + if err != nil { + return nil, errors.Wrap(code.DeviceConnectionError, + fmt.Sprintf("forward port %d->%d failed: %v", + localPort, device.Options.UIA2Port, err)) + } err = driver.Session.InitConnection(localPort) if err != nil { return nil, err @@ -47,49 +50,6 @@ type UIA2Driver struct { windowSize types.Size } -func (ud *UIA2Driver) resetDriver() error { - return ud.InitSession(option.NewCapabilities()) -} - -func (ud *UIA2Driver) httpRequest(method string, rawURL string, rawBody []byte) (rawResp DriverRawResponse, err error) { - for retryCount := 1; retryCount <= 5; retryCount++ { - rawResp, err = ud.Session.Request(method, rawURL, rawBody) - if err == nil { - return - } - // wait for UIA2 server to resume automatically - time.Sleep(3 * time.Second) - oldSessionID := ud.Session.ID - if err2 := ud.resetDriver(); err2 != nil { - log.Err(err2).Msgf("failed to reset uia2 driver, retry count: %v", retryCount) - continue - } - log.Debug().Str("new session", ud.Session.ID).Str("old session", oldSessionID).Msgf("successful to reset uia2 driver, retry count: %v", retryCount) - if oldSessionID != "" { - rawURL = strings.Replace(rawURL, oldSessionID, ud.Session.ID, 1) - } - } - return -} - -func (ud *UIA2Driver) httpGET(pathElem ...string) (rawResp DriverRawResponse, err error) { - return ud.httpRequest(http.MethodGet, ud.Session.concatURL(nil, pathElem...), nil) -} - -func (ud *UIA2Driver) httpPOST(data interface{}, pathElem ...string) (rawResp DriverRawResponse, err error) { - var bsJSON []byte = nil - if data != nil { - if bsJSON, err = json.Marshal(data); err != nil { - return nil, err - } - } - return ud.httpRequest(http.MethodPost, ud.Session.concatURL(nil, pathElem...), bsJSON) -} - -func (ud *UIA2Driver) httpDELETE(pathElem ...string) (rawResp DriverRawResponse, err error) { - return ud.httpRequest(http.MethodDelete, ud.Session.concatURL(nil, pathElem...), nil) -} - func (ud *UIA2Driver) InitSession(capabilities option.Capabilities) (err error) { // register(postHandler, new InitSession("/wd/hub/session")) var rawResp DriverRawResponse @@ -106,8 +66,7 @@ func (ud *UIA2Driver) InitSession(capabilities option.Capabilities) (err error) if err = json.Unmarshal(rawResp, reply); err != nil { return err } - ud.Session = &Session{ID: reply.Value.SessionId} - ud.Session.Reset() + ud.Session.ID = reply.Value.SessionId return nil } @@ -115,7 +74,7 @@ func (ud *UIA2Driver) DeleteSession() (err error) { if ud.Session.ID == "" { return nil } - if _, err = ud.httpDELETE("/session", ud.Session.ID); err == nil { + if _, err = ud.Session.DELETE("/session", ud.Session.ID); err == nil { ud.Session.ID = "" } @@ -144,7 +103,7 @@ func (ud *UIA2Driver) Status() (deviceStatus types.DeviceStatus, err error) { func (ud *UIA2Driver) DeviceInfo() (deviceInfo types.DeviceInfo, err error) { // register(getHandler, new GetDeviceInfo("/wd/hub/session/:sessionId/appium/device/info")) var rawResp DriverRawResponse - if rawResp, err = ud.httpGET("/session", ud.Session.ID, "appium/device/info"); err != nil { + if rawResp, err = ud.Session.GET("/session", ud.Session.ID, "appium/device/info"); err != nil { return types.DeviceInfo{}, err } reply := new(struct{ Value struct{ types.DeviceInfo } }) @@ -158,7 +117,7 @@ func (ud *UIA2Driver) DeviceInfo() (deviceInfo types.DeviceInfo, err error) { func (ud *UIA2Driver) BatteryInfo() (batteryInfo types.BatteryInfo, err error) { // register(getHandler, new GetBatteryInfo("/wd/hub/session/:sessionId/appium/device/battery_info")) var rawResp DriverRawResponse - if rawResp, err = ud.httpGET("/session", ud.Session.ID, "appium/device/battery_info"); err != nil { + if rawResp, err = ud.Session.GET("/session", ud.Session.ID, "appium/device/battery_info"); err != nil { return types.BatteryInfo{}, err } reply := new(struct{ Value struct{ types.BatteryInfo } }) @@ -180,7 +139,7 @@ func (ud *UIA2Driver) WindowSize() (size types.Size, err error) { } var rawResp DriverRawResponse - if rawResp, err = ud.httpGET("/session", ud.Session.ID, "window/:windowHandle/size"); err != nil { + if rawResp, err = ud.Session.GET("/session", ud.Session.ID, "window/:windowHandle/size"); err != nil { return types.Size{}, errors.Wrap(err, "get window size failed by UIA2 request") } reply := new(struct{ Value struct{ types.Size } }) @@ -206,7 +165,7 @@ func (ud *UIA2Driver) WindowSize() (size types.Size, err error) { // Back simulates a short press on the BACK button. func (ud *UIA2Driver) Back() (err error) { // register(postHandler, new PressBack("/wd/hub/session/:sessionId/back")) - _, err = ud.httpPOST(nil, "/session", ud.Session.ID, "back") + _, err = ud.Session.POST(nil, "/session", ud.Session.ID, "back") return } @@ -221,14 +180,14 @@ func (ud *UIA2Driver) PressKeyCodes(keyCode KeyCode, metaState KeyMeta, flags .. if len(flags) != 0 { data["flags"] = flags[0] } - _, err = ud.httpPOST(data, "/session", ud.Session.ID, "appium/device/press_keycode") + _, err = ud.Session.POST(data, "/session", ud.Session.ID, "appium/device/press_keycode") return } func (ud *UIA2Driver) Orientation() (orientation types.Orientation, err error) { // [[FBRoute GET:@"/orientation"] respondWithTarget:self action:@selector(handleGetOrientation:)] var rawResp DriverRawResponse - if rawResp, err = ud.httpGET("/session", ud.Session.ID, "/orientation"); err != nil { + if rawResp, err = ud.Session.GET("/session", ud.Session.ID, "/orientation"); err != nil { return "", err } reply := new(struct{ Value types.Orientation }) @@ -261,7 +220,7 @@ func (ud *UIA2Driver) DoubleTapXY(x, y float64, opts ...option.ActionOption) err }, } - _, err = ud.httpPOST(data, "/session", ud.Session.ID, "actions/tap") + _, err = ud.Session.POST(data, "/session", ud.Session.ID, "actions/tap") return err } @@ -301,7 +260,7 @@ func (ud *UIA2Driver) TapAbsXY(x, y float64, opts ...option.ActionOption) error // update data options in post data for extra uiautomator configurations actionOptions.UpdateData(data) - _, err := ud.httpPOST(data, "/session", ud.Session.ID, "actions/tap") + _, err := ud.Session.POST(data, "/session", ud.Session.ID, "actions/tap") return err } @@ -319,7 +278,7 @@ func (ud *UIA2Driver) TouchAndHold(x, y float64, opts ...option.ActionOption) (e "duration": int(duration * 1000), }, } - _, err = ud.httpPOST(data, "/session", ud.Session.ID, "touch/longclick") + _, err = ud.Session.POST(data, "/session", ud.Session.ID, "touch/longclick") return } @@ -347,7 +306,7 @@ func (ud *UIA2Driver) Drag(fromX, fromY, toX, toY float64, opts ...option.Action actionOptions.UpdateData(data) // register(postHandler, new Drag("/wd/hub/session/:sessionId/touch/drag")) - _, err = ud.httpPOST(data, "/session", ud.Session.ID, "touch/drag") + _, err = ud.Session.POST(data, "/session", ud.Session.ID, "touch/drag") return err } @@ -390,7 +349,7 @@ func (ud *UIA2Driver) Swipe(fromX, fromY, toX, toY float64, opts ...option.Actio // update data options in post data for extra uiautomator configurations actionOptions.UpdateData(data) - _, err = ud.httpPOST(data, "/session", ud.Session.ID, "actions/swipe") + _, err = ud.Session.POST(data, "/session", ud.Session.ID, "actions/swipe") return err } @@ -408,7 +367,7 @@ func (ud *UIA2Driver) SetPasteboard(contentType types.PasteboardType, content st "content": base64.StdEncoding.EncodeToString([]byte(content)), } // register(postHandler, new SetClipboard("/wd/hub/session/:sessionId/appium/device/set_clipboard")) - _, err = ud.httpPOST(data, "/session", ud.Session.ID, "appium/device/set_clipboard") + _, err = ud.Session.POST(data, "/session", ud.Session.ID, "appium/device/set_clipboard") return } @@ -421,7 +380,7 @@ func (ud *UIA2Driver) GetPasteboard(contentType types.PasteboardType) (raw *byte "contentType": contentType[0], } var rawResp DriverRawResponse - if rawResp, err = ud.httpPOST(data, "/session", ud.Session.ID, "appium/device/get_clipboard"); err != nil { + if rawResp, err = ud.Session.POST(data, "/session", ud.Session.ID, "appium/device/get_clipboard"); err != nil { return } reply := new(struct{ Value string }) @@ -451,7 +410,7 @@ func (ud *UIA2Driver) Input(text string, opts ...option.ActionOption) (err error // new data options in post data for extra uiautomator configurations actionOptions.UpdateData(data) - _, err = ud.httpPOST(data, "/session", ud.Session.ID, "/keys") + _, err = ud.Session.POST(data, "/session", ud.Session.ID, "/keys") } return } @@ -509,14 +468,14 @@ func (ud *UIA2Driver) SendActionKey(text string, opts ...option.ActionOption) (e // new data options in post data for extra uiautomator configurations actionOptions.UpdateData(data) - _, err = ud.httpPOST(data, "/session", ud.Session.ID, "/actions/keys") + _, err = ud.Session.POST(data, "/session", ud.Session.ID, "/actions/keys") return } func (ud *UIA2Driver) Rotation() (rotation types.Rotation, err error) { // register(getHandler, new GetRotation("/wd/hub/session/:sessionId/rotation")) var rawResp DriverRawResponse - if rawResp, err = ud.httpGET("/session", ud.Session.ID, "rotation"); err != nil { + if rawResp, err = ud.Session.GET("/session", ud.Session.ID, "rotation"); err != nil { return types.Rotation{}, err } reply := new(struct{ Value types.Rotation }) @@ -537,7 +496,7 @@ func (ud *UIA2Driver) ScreenShot(opts ...option.ActionOption) (raw *bytes.Buffer func (ud *UIA2Driver) Source(srcOpt ...option.SourceOption) (source string, err error) { // register(getHandler, new Source("/wd/hub/session/:sessionId/source")) var rawResp DriverRawResponse - if rawResp, err = ud.httpGET("/session", ud.Session.ID, "source"); err != nil { + if rawResp, err = ud.Session.GET("/session", ud.Session.ID, "source"); err != nil { return "", err } reply := new(struct{ Value string }) diff --git a/pkg/uixt/driver.go b/pkg/uixt/driver.go index e5af7e26..78fd5dd7 100644 --- a/pkg/uixt/driver.go +++ b/pkg/uixt/driver.go @@ -26,7 +26,7 @@ type IDriver interface { // session InitSession(capabilities option.Capabilities) error - GetSession() *Session + GetSession() *DriverSession DeleteSession() error // device info and status @@ -77,6 +77,8 @@ func NewXTDriver(driver IDriver, opts ...ai.AIServiceOption) *XTDriver { IDriver: driver, CVService: services.ICVService, LLMService: services.ILLMService, + + screenResults: make([]*ScreenResult, 0), } return driverExt } @@ -86,4 +88,7 @@ type XTDriver struct { IDriver CVService ai.ICVService // OCR/CV LLMService ai.ILLMService // LLM + + // cache screenshot results + screenResults []*ScreenResult } diff --git a/pkg/uixt/driver_ext/shoots_android_driver.go b/pkg/uixt/driver_ext/shoots_android_driver.go index 9078a012..11be065c 100644 --- a/pkg/uixt/driver_ext/shoots_android_driver.go +++ b/pkg/uixt/driver_ext/shoots_android_driver.go @@ -52,7 +52,7 @@ func NewShootsAndroidDriver(device *uixt.AndroidDevice) (driver *ShootsAndroidDr } rawURL := fmt.Sprintf("http://forward-to-%d:%d", serverLocalPort, douyinServerPort) - driver.Session.Init(rawURL) + driver.Session.SetBaseURL(rawURL) return driver, nil } diff --git a/pkg/uixt/driver_ext/shoots_ios_driver.go b/pkg/uixt/driver_ext/shoots_ios_driver.go index 30b879b6..7b944279 100644 --- a/pkg/uixt/driver_ext/shoots_ios_driver.go +++ b/pkg/uixt/driver_ext/shoots_ios_driver.go @@ -3,7 +3,6 @@ package driver_ext import ( "encoding/json" "fmt" - "net/http" "time" "github.com/pkg/errors" @@ -42,18 +41,16 @@ func NewShootsIOSDriver(device *uixt.IOSDevice) (driver *ShootsIOSDriver, err er fmt.Sprintf("forward tcp port failed: %v", err)) } - capabilities := option.NewCapabilities() - capabilities.WithDefaultAlertAction(option.AlertActionAccept) - wdaDriver, err := device.NewHTTPDriver(capabilities) + wdaDriver, err := uixt.NewWDADriver(device) if err != nil { return nil, errors.Wrap(err, "failed to init WDA driver for shoots IOS") } - host := "localhost" timeout := 10 * time.Second driver = &ShootsIOSDriver{ - WDADriver: wdaDriver.(*uixt.WDADriver), + WDADriver: wdaDriver, } + host := "localhost" driver.bightInsightPrefix = fmt.Sprintf("http://%s:%d", host, localShootsPort) driver.serverPrefix = fmt.Sprintf("http://%s:%d", host, localServerPort) driver.timeout = timeout @@ -70,7 +67,7 @@ type ShootsIOSDriver struct { } func (s *ShootsIOSDriver) Source(srcOpt ...option.SourceOption) (string, error) { - resp, err := s.Session.Request(http.MethodGet, fmt.Sprintf("%s/source?format=json&onlyWeb=false", s.bightInsightPrefix), []byte{}) + resp, err := s.Session.GET(fmt.Sprintf("%s/source?format=json&onlyWeb=false", s.bightInsightPrefix)) if err != nil { return "", err } @@ -88,11 +85,7 @@ func (s *ShootsIOSDriver) LoginNoneUI(packageName, phoneNumber string, captcha, } else { return info, fmt.Errorf("password and capcha is empty") } - bsJSON, err := json.Marshal(params) - if err != nil { - return info, err - } - resp, err := s.Session.Request(http.MethodPost, fmt.Sprintf("%s/host/login/account/", s.serverPrefix), bsJSON) + resp, err := s.Session.POST(params, fmt.Sprintf("%s/host/login/account/", s.serverPrefix)) if err != nil { return info, err } @@ -116,7 +109,7 @@ func (s *ShootsIOSDriver) LoginNoneUI(packageName, phoneNumber string, captcha, } func (s *ShootsIOSDriver) LogoutNoneUI(packageName string) error { - resp, err := s.Session.Request(http.MethodGet, fmt.Sprintf("%s/host/loginout/", s.serverPrefix), []byte{}) + resp, err := s.Session.GET(fmt.Sprintf("%s/host/loginout/", s.serverPrefix)) if err != nil { return err } @@ -140,7 +133,7 @@ func (s *ShootsIOSDriver) TearDown() error { } func (s *ShootsIOSDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) { - resp, err := s.Session.Request(http.MethodGet, fmt.Sprintf("%s/host/app/info/", s.serverPrefix), []byte{}) + resp, err := s.Session.GET(fmt.Sprintf("%s/host/app/info/", s.serverPrefix)) if err != nil { return info, err } diff --git a/pkg/uixt/driver_ext_screenshot.go b/pkg/uixt/driver_ext_screenshot.go index d1ec9894..613071e8 100644 --- a/pkg/uixt/driver_ext_screenshot.go +++ b/pkg/uixt/driver_ext_screenshot.go @@ -102,7 +102,7 @@ func (dExt *XTDriver) GetScreenResult(opts ...option.ActionOption) (screenResult } // cache screen result - dExt.GetSession().addScreenResult(screenResult) + dExt.screenResults = append(dExt.screenResults, screenResult) if imageResult != nil { screenResult.Texts = imageResult.OCRResult.ToOCRTexts() diff --git a/pkg/uixt/driver_session.go b/pkg/uixt/driver_session.go index 8990016d..69e21a35 100644 --- a/pkg/uixt/driver_session.go +++ b/pkg/uixt/driver_session.go @@ -20,51 +20,9 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v5/internal/json" + "github.com/httprunner/httprunner/v5/pkg/uixt/option" ) -type Session struct { - ID string - baseURL *url.URL - client *http.Client - - // cache uia2/wda request and response - requests []*DriverRequests - // cache screenshot ocr results - screenResults []*ScreenResult -} - -func (d *Session) addScreenResult(screenResult *ScreenResult) { - d.screenResults = append(d.screenResults, screenResult) -} - -func (d *Session) addRequestResult(driverResult *DriverRequests) { - d.requests = append(d.requests, driverResult) -} - -func (d *Session) Init(baseUrl string) error { - var err error - d.baseURL, err = url.Parse(baseUrl) - return err -} - -func (d *Session) Reset() { - d.screenResults = make([]*ScreenResult, 0) - d.requests = make([]*DriverRequests, 0) -} - -func (d *Session) GetData(withReset bool) Attachments { - data := Attachments{ - "screen_results": d.screenResults, - } - if len(d.requests) != 0 { - data["requests"] = d.requests - } - if withReset { - d.Reset() - } - return data -} - type Attachments map[string]interface{} type DriverRequests struct { @@ -81,35 +39,134 @@ type DriverRequests struct { Error string `json:"error,omitempty"` } -func (wd *Session) concatURL(u *url.URL, elem ...string) string { - var tmp *url.URL - if u == nil { - u = wd.baseURL +func NewDriverSession() *DriverSession { + timeout := 30 * time.Second + session := &DriverSession{ + ctx: context.Background(), + timeout: timeout, + client: &http.Client{ + Timeout: timeout, + }, + requests: make([]*DriverRequests, 0), + maxRetry: 5, } - tmp, _ = url.Parse(u.String()) - tmp.Path = path.Join(append([]string{u.Path}, elem...)...) - return tmp.String() + return session } -func (wd *Session) GET(pathElem ...string) (rawResp DriverRawResponse, err error) { - return wd.Request(http.MethodGet, wd.concatURL(nil, pathElem...), nil) +type DriverSession struct { + ctx context.Context + ID string + baseUrl string + client *http.Client + timeout time.Duration + maxRetry int + + // cache driver request and response + requests []*DriverRequests } -func (wd *Session) POST(data interface{}, pathElem ...string) (rawResp DriverRawResponse, err error) { +func (s *DriverSession) Init(capabilities option.Capabilities) (err error) { + data := make(map[string]interface{}) + if len(capabilities) == 0 { + data["capabilities"] = make(map[string]interface{}) + } else { + data["capabilities"] = map[string]interface{}{"alwaysMatch": capabilities} + } + var rawResp DriverRawResponse + if rawResp, err = s.POST(data, "/session"); err != nil { + return err + } + reply := new(struct{ Value struct{ SessionId string } }) + if err = json.Unmarshal(rawResp, reply); err != nil { + return err + } + s.ID = reply.Value.SessionId + + // WDA + // sessionInfo, err := rawResp.ValueConvertToSessionInfo() + // if err != nil { + // return err + // } + // // update session ID + // wd.Session.ID = sessionInfo.ID + + return nil +} + +func (s *DriverSession) Reset() { + s.requests = make([]*DriverRequests, 0) +} + +func (s *DriverSession) SetBaseURL(baseUrl string) { + s.baseUrl = baseUrl +} + +func (s *DriverSession) addRequestResult(driverResult *DriverRequests) { + s.requests = append(s.requests, driverResult) +} + +func (s *DriverSession) History() []*DriverRequests { + return s.requests +} + +func (s *DriverSession) concatURL(elem ...string) (string, error) { + if s.baseUrl == "" { + return "", fmt.Errorf("base URL is empty") + } + + u, err := url.Parse(s.baseUrl) + if err != nil { + return "", fmt.Errorf("failed to parse base URL: %w", err) + } + + // 分离路径和查询参数 + lastElem := elem[len(elem)-1] + parts := strings.SplitN(lastElem, "?", 2) + elem[len(elem)-1] = parts[0] + + // 合并基础路径 + u.Path = path.Join(append([]string{u.Path}, elem...)...) + + // 如果有查询参数,添加到 URL + if len(parts) > 1 { + u.RawQuery = parts[1] + } + + return u.String(), nil +} + +func (s *DriverSession) GET(pathElem ...string) (rawResp DriverRawResponse, err error) { + urlStr, err := s.concatURL(pathElem...) + if err != nil { + return nil, err + } + return s.Request(http.MethodGet, urlStr, nil) +} + +func (s *DriverSession) POST(data interface{}, pathElem ...string) (rawResp DriverRawResponse, err error) { + urlStr, err := s.concatURL(pathElem...) + if err != nil { + return nil, err + } var bsJSON []byte = nil if data != nil { if bsJSON, err = json.Marshal(data); err != nil { return nil, err } } - return wd.Request(http.MethodPost, wd.concatURL(nil, pathElem...), bsJSON) + return s.Request(http.MethodPost, urlStr, bsJSON) } -func (wd *Session) DELETE(pathElem ...string) (rawResp DriverRawResponse, err error) { - return wd.Request(http.MethodDelete, wd.concatURL(nil, pathElem...), nil) +func (s *DriverSession) DELETE(pathElem ...string) (rawResp DriverRawResponse, err error) { + urlStr, err := s.concatURL(pathElem...) + if err != nil { + return nil, err + } + return s.Request(http.MethodDelete, urlStr, nil) } -func (wd *Session) Request(method string, rawURL string, rawBody []byte) (rawResp DriverRawResponse, err error) { +func (s *DriverSession) Request(method string, rawURL string, rawBody []byte) ( + rawResp DriverRawResponse, err error) { driverResult := &DriverRequests{ RequestMethod: method, RequestUrl: rawURL, @@ -117,7 +174,7 @@ func (wd *Session) Request(method string, rawURL string, rawBody []byte) (rawRes } defer func() { - wd.addRequestResult(driverResult) + s.addRequestResult(driverResult) var logger *zerolog.Event if err != nil { @@ -143,7 +200,7 @@ func (wd *Session) Request(method string, rawURL string, rawBody []byte) (rawRes logger.Msg("request uixt driver") }() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(s.ctx, s.timeout) defer cancel() req, err := http.NewRequestWithContext(ctx, method, rawURL, bytes.NewBuffer(rawBody)) @@ -155,7 +212,7 @@ func (wd *Session) Request(method string, rawURL string, rawBody []byte) (rawRes driverResult.RequestTime = time.Now() var resp *http.Response - if resp, err = wd.client.Do(req); err != nil { + if resp, err = s.client.Do(req); err != nil { return nil, err } defer func() { @@ -189,12 +246,36 @@ func (wd *Session) Request(method string, rawURL string, rawBody []byte) (rawRes return } -func (wd *Session) InitConnection(localPort int) error { +// TODO: FIXME +func (s *DriverSession) RequestWithRetry(method string, rawURL string, rawBody []byte) ( + rawResp DriverRawResponse, err error) { + for count := 1; count <= s.maxRetry; count++ { + rawResp, err = s.Request(method, rawURL, rawBody) + if err == nil { + return + } + time.Sleep(3 * time.Second) + oldSessionID := s.ID + if err2 := s.Init(nil); err2 != nil { + log.Error().Err(err2).Msgf( + "failed to reset session, try count %v", count) + continue + } + log.Debug().Str("new session", s.ID).Str("old session", oldSessionID).Msgf( + "reset session successfully, try count %v", count) + if oldSessionID != "" { + rawURL = strings.Replace(rawURL, oldSessionID, s.ID, 1) + } + } + return +} + +func (s *DriverSession) InitConnection(localPort int) error { conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", localPort)) if err != nil { return fmt.Errorf("create tcp connection error %v", err) } - wd.client = NewHTTPClientWithConnection(conn, 30*time.Second) + s.client = NewHTTPClientWithConnection(conn, s.timeout) return nil } @@ -253,12 +334,12 @@ func (r DriverRawResponse) ValueConvertToBool() (b bool, err error) { return } -func (r DriverRawResponse) ValueConvertToSessionInfo() (sessionInfo Session, err error) { - reply := new(struct{ Value struct{ Session } }) +func (r DriverRawResponse) ValueConvertToSessionInfo() (sessionInfo DriverSession, err error) { + reply := new(struct{ Value struct{ DriverSession } }) if err = json.Unmarshal(r, reply); err != nil { - return Session{}, err + return DriverSession{}, err } - sessionInfo = reply.Value.Session + sessionInfo = reply.Value.DriverSession return } diff --git a/pkg/uixt/driver_session_test.go b/pkg/uixt/driver_session_test.go new file mode 100644 index 00000000..b2e4362b --- /dev/null +++ b/pkg/uixt/driver_session_test.go @@ -0,0 +1,34 @@ +package uixt + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDriverSession(t *testing.T) { + session := NewDriverSession() + session.SetBaseURL("https://postman-echo.com") + resp, err := session.GET("/get") + if err != nil { + t.Fatal(err) + } + t.Log(resp) + + resp, err = session.GET("/get?a=1&b=2") + if err != nil { + t.Fatal(err) + } + t.Log(resp) + + driverRequests := session.History() + if !assert.Equal(t, 2, len(driverRequests)) { + t.FailNow() + } + + session.Reset() + driverRequests = session.History() + if !assert.Equal(t, 0, len(driverRequests)) { + t.FailNow() + } +} diff --git a/pkg/uixt/driver_test.go b/pkg/uixt/driver_test.go index 57225bd0..017ae4ca 100644 --- a/pkg/uixt/driver_test.go +++ b/pkg/uixt/driver_test.go @@ -68,12 +68,12 @@ var ( iosDriverExt *XTDriver ) -func init() { - iosDevice, _ = NewIOSDevice() - driver, _ := iosDevice.NewDriver() - iosDriverExt = NewXTDriver(driver, - ai.WithCVService(ai.CVServiceTypeVEDEM)) -} +// func init() { +// iosDevice, _ = NewIOSDevice() +// driver, _ := iosDevice.NewDriver() +// iosDriverExt = NewXTDriver(driver, +// ai.WithCVService(ai.CVServiceTypeVEDEM)) +// } func TestDriverExt_TapXY(t *testing.T) { err := iosDriverExt.TapXY(0.4, 0.5) diff --git a/pkg/uixt/driver_utils.go b/pkg/uixt/driver_utils.go index 617449de..fb808db7 100644 --- a/pkg/uixt/driver_utils.go +++ b/pkg/uixt/driver_utils.go @@ -90,6 +90,19 @@ func (dExt *XTDriver) Setup() error { return nil } +func (dExt *XTDriver) GetData(withReset bool) map[string]interface{} { + session := dExt.GetSession() + data := map[string]interface{}{ + "requests": session.History(), + "screen_results": dExt.screenResults, + } + if withReset { + session.Reset() + dExt.screenResults = make([]*ScreenResult, 0) + } + return data +} + func (dExt *XTDriver) assertOCR(text, assert string) error { var opts []option.ActionOption opts = append(opts, option.WithScreenShotFileName(fmt.Sprintf("assert_ocr_%s", text))) diff --git a/pkg/uixt/harmony_driver_hdc.go b/pkg/uixt/harmony_driver_hdc.go index ce6e278b..56311023 100644 --- a/pkg/uixt/harmony_driver_hdc.go +++ b/pkg/uixt/harmony_driver_hdc.go @@ -34,16 +34,13 @@ func NewHDCDriver(device *HarmonyDevice) (*HDCDriver, error) { type HDCDriver struct { Device *HarmonyDevice - Session *Session + Session *DriverSession points []ExportPoint uiDriver *ghdc.UIDriver } func (hd *HDCDriver) InitSession(capabilities option.Capabilities) error { - hd.Session = &Session{} - hd.Session.Reset() - hd.Unlock() return nil } @@ -51,7 +48,7 @@ func (hd *HDCDriver) DeleteSession() error { return types.ErrDriverNotImplemented } -func (hd *HDCDriver) GetSession() *Session { +func (hd *HDCDriver) GetSession() *DriverSession { return hd.Session } diff --git a/pkg/uixt/ios_device.go b/pkg/uixt/ios_device.go index daf463e3..0d651943 100644 --- a/pkg/uixt/ios_device.go +++ b/pkg/uixt/ios_device.go @@ -5,11 +5,7 @@ import ( "encoding/json" "fmt" "io" - "net" - "net/http" - "net/url" "os" - "strconv" "time" "github.com/Masterminds/semver" @@ -27,7 +23,6 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v5/code" - "github.com/httprunner/httprunner/v5/internal/builtin" "github.com/httprunner/httprunner/v5/pkg/uixt/option" "github.com/httprunner/httprunner/v5/pkg/uixt/types" ) @@ -461,73 +456,6 @@ func (dev *IOSDevice) Reboot() error { return nil } -// NewHTTPDriver creates new remote HTTP client, this will also start a new session. -func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver IDriver, err error) { - var localPort int - localPort, err = strconv.Atoi(os.Getenv("WDA_LOCAL_PORT")) - if err != nil { - localPort, err = builtin.GetFreePort() - if err != nil { - return nil, errors.Wrap(code.DeviceHTTPDriverError, - fmt.Sprintf("get free port failed: %v", err)) - } - if err = dev.Forward(localPort, dev.Options.WDAPort); err != nil { - return nil, errors.Wrap(code.DeviceHTTPDriverError, - fmt.Sprintf("forward tcp port failed: %v", err)) - } - } else { - log.Info().Int("WDA_LOCAL_PORT", localPort).Msg("reuse WDA local port") - } - - var localMjpegPort int - localMjpegPort, err = strconv.Atoi(os.Getenv("WDA_LOCAL_MJPEG_PORT")) - if err != nil { - localMjpegPort, err = builtin.GetFreePort() - if err != nil { - return nil, errors.Wrap(code.DeviceHTTPDriverError, - fmt.Sprintf("get free port failed: %v", err)) - } - if err = dev.Forward(localMjpegPort, dev.Options.WDAMjpegPort); err != nil { - return nil, errors.Wrap(code.DeviceHTTPDriverError, - fmt.Sprintf("forward tcp port failed: %v", err)) - } - } else { - log.Info().Int("WDA_LOCAL_MJPEG_PORT", localMjpegPort). - Msg("reuse WDA local mjpeg port") - } - - log.Info().Interface("capabilities", capabilities). - Int("localPort", localPort).Int("localMjpegPort", localMjpegPort). - Msg("init WDA HTTP driver") - - wd := new(WDADriver) - wd.Device = dev - wd.Session.client = &http.Client{ - Timeout: time.Second * 10, // 设置超时时间为 10 秒 - } - - host := "localhost" - if wd.Session.baseURL, err = url.Parse(fmt.Sprintf("http://%s:%d", host, localPort)); err != nil { - return nil, errors.Wrap(code.DeviceHTTPDriverError, err.Error()) - } - - // create new session - if err = wd.InitSession(capabilities); err != nil { - return nil, errors.Wrap(code.DeviceHTTPDriverError, err.Error()) - } - - if wd.mjpegHTTPConn, err = net.Dial( - "tcp", - fmt.Sprintf("%s:%d", host, localMjpegPort), - ); err != nil { - return nil, errors.Wrap(code.DeviceHTTPDriverError, err.Error()) - } - wd.mjpegClient = NewHTTPClientWithConnection(wd.mjpegHTTPConn, 30*time.Second) - wd.mjpegUrl = fmt.Sprintf("%s:%d", host, localMjpegPort) - - return wd, nil -} - func (dev *IOSDevice) GetPackageInfo(packageName string) (types.AppInfo, error) { svc, err := installationproxy.New(dev.DeviceEntry) if err != nil { diff --git a/pkg/uixt/ios_driver_wda.go b/pkg/uixt/ios_driver_wda.go index 534fbb6c..1ccf2925 100644 --- a/pkg/uixt/ios_driver_wda.go +++ b/pkg/uixt/ios_driver_wda.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "syscall" "time" @@ -33,22 +34,75 @@ func NewWDADriver(device *IOSDevice) (*WDADriver, error) { log.Info().Interface("device", device).Msg("init ios WDA driver") driver := &WDADriver{ Device: device, - Session: &Session{}, + Session: NewDriverSession(), + } + err := driver.InitSession(nil) + if err != nil { + return nil, err } - driver.InitSession(nil) // init WDA scale - var err error if driver.scale, err = driver.Scale(); err != nil { return nil, err } + // forward local port to device + var localPort int + localPort, err = strconv.Atoi(os.Getenv("WDA_LOCAL_PORT")) + if err != nil { + localPort, err = builtin.GetFreePort() + if err != nil { + return nil, errors.Wrap(code.DeviceHTTPDriverError, + fmt.Sprintf("get free port failed: %v", err)) + } + if err = device.Forward(localPort, device.Options.WDAPort); err != nil { + return nil, errors.Wrap(code.DeviceHTTPDriverError, + fmt.Sprintf("forward tcp port failed: %v", err)) + } + } else { + log.Info().Int("WDA_LOCAL_PORT", localPort).Msg("reuse WDA local port") + } + + var localMjpegPort int + localMjpegPort, err = strconv.Atoi(os.Getenv("WDA_LOCAL_MJPEG_PORT")) + if err != nil { + localMjpegPort, err = builtin.GetFreePort() + if err != nil { + return nil, errors.Wrap(code.DeviceHTTPDriverError, + fmt.Sprintf("get free port failed: %v", err)) + } + if err = device.Forward(localMjpegPort, device.Options.WDAMjpegPort); err != nil { + return nil, errors.Wrap(code.DeviceHTTPDriverError, + fmt.Sprintf("forward tcp port failed: %v", err)) + } + } else { + log.Info().Int("WDA_LOCAL_MJPEG_PORT", localMjpegPort). + Msg("reuse WDA local mjpeg port") + } + + host := "localhost" + driver.Session.SetBaseURL(fmt.Sprintf("http://%s:%d", host, localPort)) + + // create new session + if err = driver.InitSession(nil); err != nil { + return nil, errors.Wrap(code.DeviceHTTPDriverError, err.Error()) + } + + if driver.mjpegHTTPConn, err = net.Dial( + "tcp", + fmt.Sprintf("%s:%d", host, localMjpegPort), + ); err != nil { + return nil, errors.Wrap(code.DeviceHTTPDriverError, err.Error()) + } + driver.mjpegClient = NewHTTPClientWithConnection(driver.mjpegHTTPConn, 30*time.Second) + driver.mjpegUrl = fmt.Sprintf("%s:%d", host, localMjpegPort) + return driver, nil } type WDADriver struct { Device *IOSDevice - Session *Session + Session *DriverSession // cache to avoid repeated query windowSize types.Size @@ -59,17 +113,18 @@ type WDADriver struct { mjpegUrl string } -func (wd *WDADriver) resetSession() error { - capabilities := option.NewCapabilities() - capabilities.WithDefaultAlertAction(option.AlertActionAccept) +func (wd *WDADriver) GetMjpegClient() *http.Client { + return wd.mjpegClient +} - data := map[string]interface{}{ - "capabilities": map[string]interface{}{ - "alwaysMatch": capabilities, - }, +func (wd *WDADriver) InitSession(capabilities option.Capabilities) error { + // [[FBRoute POST:@"/session"].withoutSession respondWithTarget:self action:@selector(handleCreateSession:)] + data := make(map[string]interface{}) + if len(capabilities) == 0 { + data["capabilities"] = make(map[string]interface{}) + } else { + data["capabilities"] = map[string]interface{}{"alwaysMatch": capabilities} } - - // Notice: use Driver.POST instead of httpPOST to avoid loop calling rawResp, err := wd.Session.POST(data, "/session") if err != nil { return err @@ -83,85 +138,6 @@ func (wd *WDADriver) resetSession() error { return nil } -func (wd *WDADriver) httpRequest(method string, rawURL string, rawBody []byte) (rawResp DriverRawResponse, err error) { - retryInterval := 3 * time.Second - for retryCount := 1; retryCount <= 3; retryCount++ { - rawResp, err = wd.Session.Request(method, rawURL, rawBody) - if err == nil { - return - } - - // check WDA server status - status, err := wd.Status() - if err != nil { - log.Err(err).Msg("get WDA server status failed") - } else if status.State != "success" { - log.Warn().Interface("status", status).Msg("WDA server status is not success") - } else { - log.Info().Interface("status", status).Msg("get WDA server status") - } - - retryInterval = retryInterval * 2 - time.Sleep(retryInterval) - oldSessionID := wd.Session.ID - if err2 := wd.resetSession(); err2 != nil { - log.Err(err2).Msgf("failed to reset wda driver session, retry count: %v", retryCount) - continue - } - log.Debug().Str("new session", wd.Session.ID).Str("old session", oldSessionID). - Msgf("reset wda driver session successfully, retry count: %v", retryCount) - if oldSessionID != "" { - rawURL = strings.Replace(rawURL, oldSessionID, wd.Session.ID, 1) - } - } - return -} - -func (wd *WDADriver) httpGET(pathElem ...string) (rawResp DriverRawResponse, err error) { - return wd.httpRequest(http.MethodGet, wd.Session.concatURL(nil, pathElem...), nil) -} - -func (wd *WDADriver) httpPOST(data interface{}, pathElem ...string) (rawResp DriverRawResponse, err error) { - var bsJSON []byte = nil - if data != nil { - if bsJSON, err = json.Marshal(data); err != nil { - return nil, err - } - } - return wd.httpRequest(http.MethodPost, wd.Session.concatURL(nil, pathElem...), bsJSON) -} - -func (wd *WDADriver) httpDELETE(pathElem ...string) (rawResp DriverRawResponse, err error) { - return wd.httpRequest(http.MethodDelete, wd.Session.concatURL(nil, pathElem...), nil) -} - -func (wd *WDADriver) GetMjpegClient() *http.Client { - return wd.mjpegClient -} - -func (wd *WDADriver) InitSession(capabilities option.Capabilities) error { - // [[FBRoute POST:@"/session"].withoutSession respondWithTarget:self action:@selector(handleCreateSession:)] - data := make(map[string]interface{}) - if len(capabilities) == 0 { - data["capabilities"] = make(map[string]interface{}) - } else { - data["capabilities"] = map[string]interface{}{ - "alwaysMatch": capabilities, - } - } - - rawResp, err := wd.httpPOST(data, "/session") - if err != nil { - return err - } - sessionInfo, err := rawResp.ValueConvertToSessionInfo() - if err != nil { - return err - } - wd.Session = &sessionInfo - return nil -} - func (wd *WDADriver) DeleteSession() (err error) { if wd.mjpegClient != nil { wd.mjpegClient.CloseIdleConnections() @@ -171,7 +147,7 @@ func (wd *WDADriver) DeleteSession() (err error) { } // [[FBRoute DELETE:@""] respondWithTarget:self action:@selector(handleDeleteSession:)] - _, err = wd.httpDELETE("/session", wd.Session.ID) + _, err = wd.Session.DELETE("/session", wd.Session.ID) return } @@ -198,7 +174,7 @@ func (wd *WDADriver) DeviceInfo() (deviceInfo types.DeviceInfo, err error) { // [[FBRoute GET:@"/wda/device/info"] respondWithTarget:self action:@selector(handleGetDeviceInfo:)] // [[FBRoute GET:@"/wda/device/info"].withoutSession var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/wda/device/info"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/wda/device/info"); err != nil { return types.DeviceInfo{}, err } reply := new(struct{ Value struct{ types.DeviceInfo } }) @@ -213,7 +189,7 @@ func (wd *WDADriver) Location() (location types.Location, err error) { // [[FBRoute GET:@"/wda/device/location"] respondWithTarget:self action:@selector(handleGetLocation:)] // [[FBRoute GET:@"/wda/device/location"].withoutSession var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/wda/device/location"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/wda/device/location"); err != nil { return types.Location{}, err } reply := new(struct{ Value struct{ types.Location } }) @@ -227,7 +203,7 @@ func (wd *WDADriver) Location() (location types.Location, err error) { func (wd *WDADriver) BatteryInfo() (batteryInfo types.BatteryInfo, err error) { // [[FBRoute GET:@"/wda/batteryInfo"] respondWithTarget:self action:@selector(handleGetBatteryInfo:)] var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/wda/batteryInfo"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/wda/batteryInfo"); err != nil { return types.BatteryInfo{}, err } reply := new(struct{ Value struct{ types.BatteryInfo } }) @@ -246,7 +222,7 @@ func (wd *WDADriver) WindowSize() (size types.Size, err error) { } var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/window/size"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/window/size"); err != nil { return types.Size{}, errors.Wrap(err, "get window size failed by WDA request") } reply := new(struct{ Value struct{ types.Size } }) @@ -280,7 +256,7 @@ func (wd *WDADriver) Scale() (float64, error) { func (wd *WDADriver) Screen() (screen ai.Screen, err error) { // [[FBRoute GET:@"/wda/screen"] respondWithTarget:self action:@selector(handleGetScreen:)] var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/wda/screen"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/wda/screen"); err != nil { return ai.Screen{}, err } reply := new(struct{ Value struct{ ai.Screen } }) @@ -294,7 +270,7 @@ func (wd *WDADriver) Screen() (screen ai.Screen, err error) { func (wd *WDADriver) ScreenShot(opts ...option.ActionOption) (raw *bytes.Buffer, err error) { // [[FBRoute GET:@"/screenshot"] respondWithTarget:self action:@selector(handleGetScreenshot:)] // [[FBRoute GET:@"/screenshot"].withoutSession respondWithTarget:self action:@selector(handleGetScreenshot:)] - rawResp, err := wd.httpGET("/session", wd.Session.ID, "/screenshot") + rawResp, err := wd.Session.GET("/session", wd.Session.ID, "/screenshot") if err != nil { return nil, errors.Wrap(code.DeviceScreenShotError, fmt.Sprintf("WDA screenshot failed %v", err)) @@ -327,7 +303,7 @@ func (wd *WDADriver) ActiveAppInfo() (info types.AppInfo, err error) { // [[FBRoute GET:@"/wda/activeAppInfo"] respondWithTarget:self action:@selector(handleActiveAppInfo:)] // [[FBRoute GET:@"/wda/activeAppInfo"].withoutSession var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/wda/activeAppInfo"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/wda/activeAppInfo"); err != nil { return types.AppInfo{}, err } reply := new(struct{ Value struct{ types.AppInfo } }) @@ -341,7 +317,7 @@ func (wd *WDADriver) ActiveAppInfo() (info types.AppInfo, err error) { func (wd *WDADriver) ActiveAppsList() (appsList []types.AppBaseInfo, err error) { // [[FBRoute GET:@"/wda/apps/list"] respondWithTarget:self action:@selector(handleGetActiveAppsList:)] var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/wda/apps/list"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/wda/apps/list"); err != nil { return nil, err } reply := new(struct{ Value []types.AppBaseInfo }) @@ -356,7 +332,7 @@ func (wd *WDADriver) AppState(bundleId string) (runState types.AppState, err err // [[FBRoute POST:@"/wda/apps/state"] respondWithTarget:self action:@selector(handleSessionAppState:)] data := map[string]interface{}{"bundleId": bundleId} var rawResp DriverRawResponse - if rawResp, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/apps/state"); err != nil { + if rawResp, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/apps/state"); err != nil { return 0, err } reply := new(struct{ Value types.AppState }) @@ -372,7 +348,7 @@ func (wd *WDADriver) IsLocked() (locked bool, err error) { // [[FBRoute GET:@"/wda/locked"] respondWithTarget:self action:@selector(handleIsLocked:)] // [[FBRoute GET:@"/wda/locked"].withoutSession var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/wda/locked"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/wda/locked"); err != nil { return false, err } if locked, err = rawResp.ValueConvertToBool(); err != nil { @@ -384,20 +360,20 @@ func (wd *WDADriver) IsLocked() (locked bool, err error) { func (wd *WDADriver) Unlock() (err error) { // [[FBRoute POST:@"/wda/unlock"] respondWithTarget:self action:@selector(handleUnlock:)] // [[FBRoute POST:@"/wda/unlock"].withoutSession - _, err = wd.httpPOST(nil, "/session", wd.Session.ID, "/wda/unlock") + _, err = wd.Session.POST(nil, "/session", wd.Session.ID, "/wda/unlock") return } func (wd *WDADriver) Lock() (err error) { // [[FBRoute POST:@"/wda/lock"] respondWithTarget:self action:@selector(handleLock:)] // [[FBRoute POST:@"/wda/lock"].withoutSession - _, err = wd.httpPOST(nil, "/session", wd.Session.ID, "/wda/lock") + _, err = wd.Session.POST(nil, "/session", wd.Session.ID, "/wda/lock") return } func (wd *WDADriver) Home() (err error) { // [[FBRoute POST:@"/wda/homescreen"].withoutSession respondWithTarget:self action:@selector(handleHomescreenCommand:)] - _, err = wd.httpPOST(nil, "/wda/homescreen") + _, err = wd.Session.POST(nil, "/wda/homescreen") return } @@ -405,7 +381,7 @@ func (wd *WDADriver) AlertText() (text string, err error) { // [[FBRoute GET:@"/alert/text"] respondWithTarget:self action:@selector(handleAlertGetTextCommand:)] // [[FBRoute GET:@"/alert/text"].withoutSession var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/alert/text"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/alert/text"); err != nil { return "", err } if text, err = rawResp.ValueConvertToString(); err != nil { @@ -417,7 +393,7 @@ func (wd *WDADriver) AlertText() (text string, err error) { func (wd *WDADriver) AlertButtons() (btnLabels []string, err error) { // [[FBRoute GET:@"/wda/alert/buttons"] respondWithTarget:self action:@selector(handleGetAlertButtonsCommand:)] var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/wda/alert/buttons"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/wda/alert/buttons"); err != nil { return nil, err } reply := new(struct{ Value []string }) @@ -435,7 +411,7 @@ func (wd *WDADriver) AlertAccept(label ...string) (err error) { if len(label) != 0 && label[0] != "" { data["name"] = label[0] } - _, err = wd.httpPOST(data, "/alert/accept") + _, err = wd.Session.POST(data, "/alert/accept") return } @@ -446,14 +422,14 @@ func (wd *WDADriver) AlertDismiss(label ...string) (err error) { if len(label) != 0 && label[0] != "" { data["name"] = label[0] } - _, err = wd.httpPOST(data, "/alert/dismiss") + _, err = wd.Session.POST(data, "/alert/dismiss") return } func (wd *WDADriver) AlertSendKeys(text string) (err error) { // [[FBRoute POST:@"/alert/text"] respondWithTarget:self action:@selector(handleAlertSetTextCommand:)] data := map[string]interface{}{"value": strings.Split(text, "")} - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/alert/text") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/alert/text") return } @@ -464,7 +440,7 @@ func (wd *WDADriver) AppLaunch(bundleId string) (err error) { data["environment"] = map[string]interface{}{ "SHOW_EXPLORER": "NO", } - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/apps/launch") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/apps/launch") if err != nil { return errors.Wrap(code.MobileUILaunchAppError, fmt.Sprintf("wda launch failed: %v", err)) @@ -475,7 +451,7 @@ func (wd *WDADriver) AppLaunch(bundleId string) (err error) { func (wd *WDADriver) AppLaunchUnattached(bundleId string) (err error) { // [[FBRoute POST:@"/wda/apps/launchUnattached"].withoutSession respondWithTarget:self action:@selector(handleLaunchUnattachedApp:)] data := map[string]interface{}{"bundleId": bundleId} - _, err = wd.httpPOST(data, "/wda/apps/launchUnattached") + _, err = wd.Session.POST(data, "/wda/apps/launchUnattached") if err != nil { return errors.Wrap(code.MobileUILaunchAppError, fmt.Sprintf("wda launchUnattached failed: %v", err)) @@ -487,7 +463,7 @@ func (wd *WDADriver) AppTerminate(bundleId string) (successful bool, err error) // [[FBRoute POST:@"/wda/apps/terminate"] respondWithTarget:self action:@selector(handleSessionAppTerminate:)] data := map[string]interface{}{"bundleId": bundleId} var rawResp DriverRawResponse - if rawResp, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/apps/terminate"); err != nil { + if rawResp, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/apps/terminate"); err != nil { return false, err } if successful, err = rawResp.ValueConvertToBool(); err != nil { @@ -499,7 +475,7 @@ func (wd *WDADriver) AppTerminate(bundleId string) (successful bool, err error) func (wd *WDADriver) AppActivate(bundleId string) (err error) { // [[FBRoute POST:@"/wda/apps/activate"] respondWithTarget:self action:@selector(handleSessionAppActivate:)] data := map[string]interface{}{"bundleId": bundleId} - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/apps/activate") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/apps/activate") return } @@ -509,7 +485,7 @@ func (wd *WDADriver) AppDeactivate(second float64) (err error) { second = 3.0 } data := map[string]interface{}{"duration": second} - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/deactivateApp") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/deactivateApp") return } @@ -552,7 +528,7 @@ func (wd *WDADriver) TapAbsXY(x, y float64, opts ...option.ActionOption) error { "x": wd.toScale(x), "y": wd.toScale(y), } - _, err := wd.httpPOST(data, "/session", wd.Session.ID, "/wda/tap/0") + _, err := wd.Session.POST(data, "/session", wd.Session.ID, "/wda/tap/0") return err } @@ -569,7 +545,7 @@ func (wd *WDADriver) DoubleTapXY(x, y float64, opts ...option.ActionOption) erro "x": x, "y": y, } - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/doubleTap") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/doubleTap") return err } @@ -608,8 +584,8 @@ func (wd *WDADriver) Drag(fromX, fromY, toX, toY float64, opts ...option.ActionO // update data options in post data for extra WDA configurations actionOptions.UpdateData(data) // wda 43 version - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/dragfromtoforduration") - // _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/drag") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/dragfromtoforduration") + // _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/drag") return err } @@ -623,7 +599,7 @@ func (wd *WDADriver) SetPasteboard(contentType types.PasteboardType, content str "contentType": contentType, "content": base64.StdEncoding.EncodeToString([]byte(content)), } - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/setPasteboard") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/setPasteboard") return } @@ -631,7 +607,7 @@ func (wd *WDADriver) GetPasteboard(contentType types.PasteboardType) (raw *bytes // [[FBRoute POST:@"/wda/getPasteboard"] respondWithTarget:self action:@selector(handleGetPasteboard:)] data := map[string]interface{}{"contentType": contentType} var rawResp DriverRawResponse - if rawResp, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/getPasteboard"); err != nil { + if rawResp, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/getPasteboard"); err != nil { return nil, err } if raw, err = rawResp.ValueDecodeAsBase64(); err != nil { @@ -652,7 +628,7 @@ func (wd *WDADriver) Input(text string, opts ...option.ActionOption) (err error) // new data options in post data for extra WDA configurations actionOptions.UpdateData(data) - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/keys") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/keys") return } @@ -666,7 +642,7 @@ func (wd *WDADriver) Backspace(count int, opts ...option.ActionOption) (err erro // new data options in post data for extra WDA configurations actionOptions.UpdateData(data) - _, err = wd.httpPOST(data, "/gtf/interaction/input/backspace") + _, err = wd.Session.POST(data, "/gtf/interaction/input/backspace") return } @@ -692,21 +668,21 @@ func (wd *WDADriver) Back() (err error) { "toY": toY, } - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/dragfromtoforduration") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/dragfromtoforduration") return } func (wd *WDADriver) PressButton(devBtn types.DeviceButton) (err error) { // [[FBRoute POST:@"/wda/pressButton"] respondWithTarget:self action:@selector(handlePressButtonCommand:)] data := map[string]interface{}{"name": devBtn} - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/wda/pressButton") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/wda/pressButton") return } func (wd *WDADriver) Orientation() (orientation types.Orientation, err error) { // [[FBRoute GET:@"/orientation"] respondWithTarget:self action:@selector(handleGetOrientation:)] var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/orientation"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/orientation"); err != nil { return "", err } reply := new(struct{ Value types.Orientation }) @@ -720,14 +696,14 @@ func (wd *WDADriver) Orientation() (orientation types.Orientation, err error) { func (wd *WDADriver) SetOrientation(orientation types.Orientation) (err error) { // [[FBRoute POST:@"/orientation"] respondWithTarget:self action:@selector(handleSetOrientation:)] data := map[string]interface{}{"orientation": orientation} - _, err = wd.httpPOST(data, "/session", wd.Session.ID, "/orientation") + _, err = wd.Session.POST(data, "/session", wd.Session.ID, "/orientation") return } func (wd *WDADriver) Rotation() (rotation types.Rotation, err error) { // [[FBRoute GET:@"/rotation"] respondWithTarget:self action:@selector(handleGetRotation:)] var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/rotation"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/rotation"); err != nil { return types.Rotation{}, err } reply := new(struct{ Value types.Rotation }) @@ -740,14 +716,18 @@ func (wd *WDADriver) Rotation() (rotation types.Rotation, err error) { func (wd *WDADriver) SetRotation(rotation types.Rotation) (err error) { // [[FBRoute POST:@"/rotation"] respondWithTarget:self action:@selector(handleSetRotation:)] - _, err = wd.httpPOST(rotation, "/session", wd.Session.ID, "/rotation") + _, err = wd.Session.POST(rotation, "/session", wd.Session.ID, "/rotation") return } func (wd *WDADriver) Source(srcOpt ...option.SourceOption) (source string, err error) { // [[FBRoute GET:@"/source"] respondWithTarget:self action:@selector(handleGetSourceCommand:)] // [[FBRoute GET:@"/source"].withoutSession - tmp, _ := url.Parse(wd.Session.concatURL(nil, "/session", wd.Session.ID)) + urlStr, err := wd.Session.concatURL("/session", wd.Session.ID) + if err != nil { + return "", err + } + tmp, _ := url.Parse(urlStr) toJsonRaw := false if len(srcOpt) != 0 { q := tmp.Query() @@ -761,8 +741,12 @@ func (wd *WDADriver) Source(srcOpt ...option.SourceOption) (source string, err e tmp.RawQuery = q.Encode() } + urlStr, err = wd.Session.concatURL(tmp.Path, "/source") + if err != nil { + return "", err + } var rawResp DriverRawResponse - if rawResp, err = wd.httpRequest(http.MethodGet, wd.Session.concatURL(tmp, "/source"), nil); err != nil { + if rawResp, err = wd.Session.GET(http.MethodGet, urlStr); err != nil { return "", nil } if toJsonRaw { @@ -782,7 +766,7 @@ func (wd *WDADriver) AccessibleSource() (source string, err error) { // [[FBRoute GET:@"/wda/accessibleSource"] respondWithTarget:self action:@selector(handleGetAccessibleSourceCommand:)] // [[FBRoute GET:@"/wda/accessibleSource"].withoutSession var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/wda/accessibleSource"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/wda/accessibleSource"); err != nil { return "", err } var jr builtinJSON.RawMessage @@ -795,13 +779,13 @@ func (wd *WDADriver) AccessibleSource() (source string, err error) { func (wd *WDADriver) HealthCheck() (err error) { // [[FBRoute GET:@"/wda/healthcheck"].withoutSession respondWithTarget:self action:@selector(handleGetHealthCheck:)] - _, err = wd.httpGET("/wda/healthcheck") + _, err = wd.Session.GET("/wda/healthcheck") return } func (wd *WDADriver) IsHealthy() (healthy bool, err error) { var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/health"); err != nil { + if rawResp, err = wd.Session.GET("/health"); err != nil { return false, err } if string(rawResp) != "I-AM-ALIVE" { @@ -813,7 +797,7 @@ func (wd *WDADriver) IsHealthy() (healthy bool, err error) { func (wd *WDADriver) GetAppiumSettings() (settings map[string]interface{}, err error) { // [[FBRoute GET:@"/appium/settings"] respondWithTarget:self action:@selector(handleGetSettings:)] var rawResp DriverRawResponse - if rawResp, err = wd.httpGET("/session", wd.Session.ID, "/appium/settings"); err != nil { + if rawResp, err = wd.Session.GET("/session", wd.Session.ID, "/appium/settings"); err != nil { return nil, err } reply := new(struct{ Value map[string]interface{} }) @@ -828,7 +812,7 @@ func (wd *WDADriver) SetAppiumSettings(settings map[string]interface{}) (ret map // [[FBRoute POST:@"/appium/settings"] respondWithTarget:self action:@selector(handleSetSettings:)] data := map[string]interface{}{"settings": settings} var rawResp DriverRawResponse - if rawResp, err = wd.httpPOST(data, "/session", wd.Session.ID, "/appium/settings"); err != nil { + if rawResp, err = wd.Session.POST(data, "/session", wd.Session.ID, "/appium/settings"); err != nil { return nil, err } reply := new(struct{ Value map[string]interface{} }) @@ -840,13 +824,13 @@ func (wd *WDADriver) SetAppiumSettings(settings map[string]interface{}) (ret map } func (wd *WDADriver) WdaShutdown() (err error) { - _, err = wd.httpGET("/wda/shutdown") + _, err = wd.Session.GET("/wda/shutdown") return } func (wd *WDADriver) triggerWDALog(data map[string]interface{}) (rawResp []byte, err error) { // [[FBRoute POST:@"/gtf/automation/log"].withoutSession respondWithTarget:self action:@selector(handleAutomationLog:)] - return wd.httpPOST(data, "/gtf/automation/log") + return wd.Session.POST(data, "/gtf/automation/log") } func (wd *WDADriver) ScreenRecord(duration time.Duration) (videoPath string, err error) { @@ -945,7 +929,7 @@ func (wd *WDADriver) StopCaptureLog() (result interface{}, err error) { return reply.Value, nil } -func (wd *WDADriver) GetSession() *Session { +func (wd *WDADriver) GetSession() *DriverSession { return wd.Session } diff --git a/pkg/uixt/ios_test.go b/pkg/uixt/ios_test.go index 9351bbe8..65bfa023 100644 --- a/pkg/uixt/ios_test.go +++ b/pkg/uixt/ios_test.go @@ -89,15 +89,6 @@ func TestIOSDevice_GetPackageInfo(t *testing.T) { t.Log(appInfo) } -func TestNewWDAHTTPDriver(t *testing.T) { - device, _ := NewIOSDevice() - var err error - _, err = device.NewHTTPDriver(nil) - if err != nil { - t.Fatal(err) - } -} - func TestNewUSBDriver(t *testing.T) { setup(t) diff --git a/step_ui.go b/step_ui.go index f8c6f0fb..5bcefd91 100644 --- a/step_ui.go +++ b/step_ui.go @@ -670,8 +670,7 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err } // save attachments - session := uiDriver.GetSession() - for key, value := range session.GetData(true) { + for key, value := range uiDriver.GetData(true) { attachments[key] = value } stepResult.Attachments = attachments