diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bc3daa39..2116f3f4 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## v4.3.6 (2023-07-24) + +- feat: support to reset driver (or session only) automatically when UIA2 / WDA crashed or WebDriver request failed + ## v4.3.5 (2023-07-23) - refactor: send events to Google Analytics 4, replace GA v1 diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index 81fab8ee..3a09e36d 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -6,9 +6,11 @@ import ( "encoding/json" "fmt" "net" + "net/http" "net/url" "strconv" "strings" + "time" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -46,12 +48,10 @@ func NewUIADriver(capabilities Capabilities, urlPrefix string) (driver *uiaDrive driver.client = convertToHTTPClient(conn) session, err := driver.NewSession(capabilities) - if err == nil { - driver.sessionId = session.SessionId - } else { - log.Warn().Msg( - "create UIAutomator session failed, use adb driver instead") + if err != nil { + return nil, errors.Wrap(err, "create UIAutomator session failed") } + driver.sessionId = session.SessionId return driver, nil } @@ -83,11 +83,65 @@ func (bs BatteryStatus) String() string { } } +func (ud *uiaDriver) resetDriver() error { + newUIADriver, err := NewUIADriver(NewCapabilities(), ud.urlPrefix.String()) + if err != nil { + return err + } + ud.client = newUIADriver.client + ud.sessionId = newUIADriver.sessionId + return nil +} + +func (ud *uiaDriver) httpRequest(method string, rawURL string, rawBody []byte, disableRetry ...bool) (rawResp rawResponse, err error) { + disableRetryBool := len(disableRetry) > 0 && disableRetry[0] + for retryCount := 1; retryCount <= 5; retryCount++ { + rawResp, err = ud.Driver.httpRequest(method, rawURL, rawBody) + if err == nil || disableRetryBool { + return + } + // wait for UIA2 server to resume automatically + time.Sleep(3 * time.Second) + oldSessionID := ud.sessionId + if err = ud.resetDriver(); err != nil { + log.Err(err).Msgf("failed to reset uia2 driver, retry count: %v", retryCount) + continue + } + log.Debug().Str("new session", ud.sessionId).Str("old session", oldSessionID).Msgf("successful to reset uia2 driver, retry count: %v", retryCount) + if oldSessionID != "" { + rawURL = strings.Replace(rawURL, oldSessionID, ud.sessionId, 1) + } + } + return +} + +func (ud *uiaDriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) { + return ud.httpRequest(http.MethodGet, ud.concatURL(nil, pathElem...), nil) +} + +func (ud *uiaDriver) httpGETWithRetry(pathElem ...string) (rawResp rawResponse, err error) { + return ud.httpRequest(http.MethodGet, ud.concatURL(nil, pathElem...), nil, true) +} + +func (ud *uiaDriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, 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.concatURL(nil, pathElem...), bsJSON) +} + +func (ud *uiaDriver) httpDELETE(pathElem ...string) (rawResp rawResponse, err error) { + return ud.httpRequest(http.MethodDelete, ud.concatURL(nil, pathElem...), nil) +} + func (ud *uiaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) { // register(postHandler, new NewSession("/wd/hub/session")) var rawResp rawResponse data := map[string]interface{}{"capabilities": capabilities} - if rawResp, err = ud.httpPOST(data, "/session"); err != nil { + if rawResp, err = ud.Driver.httpPOST(data, "/session"); err != nil { return SessionInfo{SessionId: ""}, err } reply := new(struct{ Value struct{ SessionId string } }) diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index cac6bfce..21e259f7 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -484,11 +484,15 @@ func (dev *IOSDevice) forward(localPort, remotePort int) error { go func(lConn, rConn net.Conn) { if _, err := io.Copy(lConn, rConn); err != nil { log.Error().Err(err).Msg("copy local -> remote") + rConn.Close() + accept.Close() } }(lConn, rConn) go func(lConn, rConn net.Conn) { if _, err := io.Copy(rConn, lConn); err != nil { log.Error().Err(err).Msg("copy local <- remote") + rConn.Close() + accept.Close() } }(lConn, rConn) }(accept) @@ -660,9 +664,11 @@ func (dev *IOSDevice) NewUSBDriver(capabilities Capabilities) (driver WebDriver, if wd.urlPrefix, err = url.Parse("http://" + dev.UDID); err != nil { return nil, errors.Wrap(code.IOSDeviceUSBDriverError, err.Error()) } - if _, err = wd.NewSession(capabilities); err != nil { + var sessionInfo SessionInfo + if sessionInfo, err = wd.NewSession(capabilities); err != nil { return nil, errors.Wrap(code.IOSDeviceUSBDriverError, err.Error()) } + wd.sessionId = sessionInfo.SessionId // init WDA scale if wd.scale, err = wd.Scale(); err != nil { diff --git a/hrp/pkg/uixt/ios_driver.go b/hrp/pkg/uixt/ios_driver.go index c45f6867..ca444f2f 100644 --- a/hrp/pkg/uixt/ios_driver.go +++ b/hrp/pkg/uixt/ios_driver.go @@ -10,6 +10,7 @@ import ( "net/url" "regexp" "strings" + "time" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -31,6 +32,62 @@ type wdaDriver struct { mjpegClient *http.Client } +func (wd *wdaDriver) resetSession() error { + capabilities := NewCapabilities() + capabilities.WithDefaultAlertAction(AlertActionAccept) + + sessionInfo, err := wd.NewSession(capabilities) + if err != nil { + return err + } + wd.sessionId = sessionInfo.SessionId + return nil +} + +func (wd *wdaDriver) httpRequest(method string, rawURL string, rawBody []byte, disableRetry ...bool) (rawResp rawResponse, err error) { + disableRetryBool := len(disableRetry) > 0 && disableRetry[0] + for retryCount := 1; retryCount <= 5; retryCount++ { + rawResp, err = wd.Driver.httpRequest(method, rawURL, rawBody) + if err == nil || disableRetryBool { + return + } + // TODO: polling WDA to check if resumed automatically + time.Sleep(5 * time.Second) + oldSessionID := wd.sessionId + if err = wd.resetSession(); err != nil { + log.Err(err).Msgf("failed to reset wda driver, retry count: %v", retryCount) + continue + } + log.Debug().Str("new session", wd.sessionId).Str("old session", oldSessionID).Msgf("successful to reset wda driver, retry count: %v", retryCount) + if oldSessionID != "" { + rawURL = strings.Replace(rawURL, oldSessionID, wd.sessionId, 1) + } + } + return +} + +func (wd *wdaDriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) { + return wd.httpRequest(http.MethodGet, wd.concatURL(nil, pathElem...), nil) +} + +func (wd *wdaDriver) httpGETWithRetry(pathElem ...string) (rawResp rawResponse, err error) { + return wd.httpRequest(http.MethodGet, wd.concatURL(nil, pathElem...), nil, true) +} + +func (wd *wdaDriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, 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.concatURL(nil, pathElem...), bsJSON) +} + +func (wd *wdaDriver) httpDELETE(pathElem ...string) (rawResp rawResponse, err error) { + return wd.httpRequest(http.MethodDelete, wd.concatURL(nil, pathElem...), nil) +} + func (wd *wdaDriver) GetMjpegClient() *http.Client { return wd.mjpegClient } @@ -45,13 +102,12 @@ func (wd *wdaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionI } var rawResp rawResponse - if rawResp, err = wd.httpPOST(data, "/session"); err != nil { + if rawResp, err = wd.Driver.httpPOST(data, "/session"); err != nil { return SessionInfo{}, err } if sessionInfo, err = rawResp.valueConvertToSessionInfo(); err != nil { return SessionInfo{}, err } - wd.sessionId = sessionInfo.SessionId return }