From e7d78badcd72420bf4b89a00a578655918bafd7f Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Fri, 7 Feb 2025 16:20:25 +0800 Subject: [PATCH] refactor: NewAndroidDevice --- go.mod | 2 - internal/version/VERSION | 2 +- pkg/uixt/android_device.go | 457 ++++------------------------------- pkg/uixt/android_test.go | 37 --- pkg/uixt/ext/android_stub.go | 318 ------------------------ 5 files changed, 50 insertions(+), 766 deletions(-) delete mode 100644 pkg/uixt/ext/android_stub.go diff --git a/go.mod b/go.mod index 7e089d3d..8c6306fb 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/httprunner/httprunner/v5 go 1.22.0 -toolchain go1.22.7 - require ( code.byted.org/iesqa/ghdc v0.0.0-20241009025217-ecb76cf5bd27 github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 diff --git a/internal/version/VERSION b/internal/version/VERSION index e61253a7..52dafb47 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0+2502071620 +v5.0.0+2502071622 diff --git a/pkg/uixt/android_device.go b/pkg/uixt/android_device.go index f6eaf353..4cee7380 100644 --- a/pkg/uixt/android_device.go +++ b/pkg/uixt/android_device.go @@ -28,10 +28,6 @@ import ( ) const ( - // adb server - AdbServerHost = "localhost" - AdbServerPort = gadb.AdbServerPort // 5037 - EvalInstallerPackageName = "sogou.mobile.explorer" InstallViaInstallerCommand = "am start -S -n sogou.mobile.explorer/.PackageInstallerActivity -d" ) @@ -42,76 +38,54 @@ var evalite embed.FS func NewAndroidDevice(opts ...option.AndroidDeviceOption) (device *AndroidDevice, err error) { androidOptions := option.NewAndroidDeviceOptions(opts...) - deviceList, err := GetAndroidDevices(androidOptions.SerialNumber) + // get all attached android devices + adbClient, err := gadb.NewClientWith( + androidOptions.AdbServerHost, androidOptions.AdbServerPort) if err != nil { - return nil, errors.Wrap(code.DeviceConnectionError, err.Error()) + return nil, err + } + devices, err := adbClient.DeviceList() + if err != nil { + return nil, err + } + if len(devices) == 0 { + return nil, errors.Wrapf(code.DeviceConnectionError, + "no attached android devices") } - if androidOptions.SerialNumber == "" && len(deviceList) > 1 { - return nil, errors.Wrap(code.DeviceConnectionError, "more than one device connected, please specify the serial") - } - - dev := deviceList[0] - + // filter device by serial + var gadbDevice *gadb.Device if androidOptions.SerialNumber == "" { - selectSerial := dev.Serial() - androidOptions.SerialNumber = selectSerial - log.Warn(). - Str("serial", androidOptions.SerialNumber). - Msg("android SerialNumber is not specified, select the first one") + if len(devices) > 1 { + return nil, errors.Wrap(code.DeviceConnectionError, + "more than one device connected, please specify the serial") + } + gadbDevice = devices[0] + androidOptions.SerialNumber = gadbDevice.Serial() + log.Warn().Str("serial", androidOptions.SerialNumber). + Msg("android SerialNumber is not specified, select the attached one") + } else { + for _, d := range devices { + if d.Serial() == androidOptions.SerialNumber { + gadbDevice = d + break + } + } + if gadbDevice == nil { + return nil, errors.Wrapf(code.DeviceConnectionError, + "android device %s not attached", androidOptions.SerialNumber) + } } device = &AndroidDevice{ - Device: dev, + Device: gadbDevice, AndroidDeviceOptions: androidOptions, Logcat: NewAdbLogcat(androidOptions.SerialNumber), } - - evalToolRaw, err := evalite.ReadFile("evalite") - if err != nil { - return nil, errors.Wrap(code.LoadFileError, err.Error()) - } - err = dev.Push(bytes.NewReader(evalToolRaw), "/data/local/tmp/evalite", time.Now()) - if err != nil { - return nil, errors.Wrap(code.DeviceShellExecError, err.Error()) - } log.Info().Str("serial", device.SerialNumber).Msg("init android device") return device, nil } -func GetAndroidDevices(serial ...string) (devices []*gadb.Device, err error) { - var adbClient gadb.Client - if adbClient, err = gadb.NewClientWith(AdbServerHost, AdbServerPort); err != nil { - return nil, err - } - - if devices, err = adbClient.DeviceList(); err != nil { - return nil, err - } - - var deviceList []*gadb.Device - // filter by serial - for _, d := range devices { - for _, s := range serial { - if s != "" && s != d.Serial() { - continue - } - deviceList = append(deviceList, d) - } - } - - if len(deviceList) == 0 { - var err error - if serial == nil || (len(serial) == 1 && serial[0] == "") { - err = fmt.Errorf("no android device found") - } else { - err = fmt.Errorf("no android device found for serial %v", serial) - } - return nil, err - } - return deviceList, nil -} - type AndroidDevice struct { *gadb.Device *option.AndroidDeviceOptions @@ -122,6 +96,16 @@ func (dev *AndroidDevice) Setup() error { dev.RunShellCommand("ime", "enable", UnicodeImePackageName) dev.RunShellCommand("rm", "-r", config.DeviceActionLogFilePath) + // setup evalite + evalToolRaw, err := evalite.ReadFile("evalite") + if err != nil { + return errors.Wrap(code.LoadFileError, err.Error()) + } + err = dev.Push(bytes.NewReader(evalToolRaw), "/data/local/tmp/evalite", time.Now()) + if err != nil { + return errors.Wrap(code.DeviceShellExecError, err.Error()) + } + if dev.UIA2 { // uiautomator2 server must be started before @@ -211,11 +195,6 @@ func (dev *AndroidDevice) StopPcap() string { return "" } -func (dev *AndroidDevice) Uninstall(packageName string) error { - _, err := dev.RunShellCommand("uninstall", packageName) - return err -} - func (dev *AndroidDevice) Install(apkPath string, opts ...option.InstallOption) error { installOpts := option.NewInstallOptions(opts...) brand, err := dev.Brand() @@ -317,6 +296,11 @@ func (dev *AndroidDevice) installCommon(apkPath string, args ...string) error { return err } +func (dev *AndroidDevice) Uninstall(packageName string) error { + _, err := dev.RunShellCommand("uninstall", packageName) + return err +} + func (dev *AndroidDevice) GetCurrentWindow() (windowInfo WindowInfo, err error) { // adb shell dumpsys window | grep -E 'mCurrentFocus|mFocusedApp' output, err := dev.RunShellCommand("dumpsys", "window", "|", "grep", "-E", "'mCurrentFocus|mFocusedApp'") @@ -618,346 +602,3 @@ func ConvertPoints(lines []string) (eps []ExportPoint) { } return } - -type UiSelectorHelper struct { - value *bytes.Buffer -} - -func NewUiSelectorHelper() UiSelectorHelper { - return UiSelectorHelper{value: bytes.NewBufferString("new UiSelector()")} -} - -func (s UiSelectorHelper) String() string { - return s.value.String() + ";" -} - -// Text Set the search criteria to match the visible text displayed -// in a widget (for example, the text label to launch an app). -// -// The text for the element must match exactly with the string in your input -// argument. Matching is case-sensitive. -func (s UiSelectorHelper) Text(text string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.text("%s")`, text)) - return s -} - -// TextMatches Set the search criteria to match the visible text displayed in a layout -// element, using a regular expression. -// -// The text in the widget must match exactly with the string in your -// input argument. -func (s UiSelectorHelper) TextMatches(regex string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.textMatches("%s")`, regex)) - return s -} - -// TextStartsWith Set the search criteria to match visible text in a widget that is -// prefixed by the text parameter. -// -// The matching is case-insensitive. -func (s UiSelectorHelper) TextStartsWith(text string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.textStartsWith("%s")`, text)) - return s -} - -// TextContains Set the search criteria to match the visible text in a widget -// where the visible text must contain the string in your input argument. -// -// The matching is case-sensitive. -func (s UiSelectorHelper) TextContains(text string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.textContains("%s")`, text)) - return s -} - -// ClassName Set the search criteria to match the class property -// for a widget (for example, "android.widget.Button"). -func (s UiSelectorHelper) ClassName(className string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.className("%s")`, className)) - return s -} - -// ClassNameMatches Set the search criteria to match the class property -// for a widget, using a regular expression. -func (s UiSelectorHelper) ClassNameMatches(regex string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.classNameMatches("%s")`, regex)) - return s -} - -// Description Set the search criteria to match the content-description -// property for a widget. -// -// The content-description is typically used -// by the Android Accessibility framework to -// provide an audio prompt for the widget when -// the widget is selected. The content-description -// for the widget must match exactly -// with the string in your input argument. -// -// Matching is case-sensitive. -func (s UiSelectorHelper) Description(desc string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.description("%s")`, desc)) - return s -} - -// DescriptionMatches Set the search criteria to match the content-description -// property for a widget. -// -// The content-description is typically used -// by the Android Accessibility framework to -// provide an audio prompt for the widget when -// the widget is selected. The content-description -// for the widget must match exactly -// with the string in your input argument. -func (s UiSelectorHelper) DescriptionMatches(regex string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.descriptionMatches("%s")`, regex)) - return s -} - -// DescriptionStartsWith Set the search criteria to match the content-description -// property for a widget. -// -// The content-description is typically used -// by the Android Accessibility framework to -// provide an audio prompt for the widget when -// the widget is selected. The content-description -// for the widget must start -// with the string in your input argument. -// -// Matching is case-insensitive. -func (s UiSelectorHelper) DescriptionStartsWith(desc string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.descriptionStartsWith("%s")`, desc)) - return s -} - -// DescriptionContains Set the search criteria to match the content-description -// property for a widget. -// -// The content-description is typically used -// by the Android Accessibility framework to -// provide an audio prompt for the widget when -// the widget is selected. The content-description -// for the widget must contain -// the string in your input argument. -// -// Matching is case-insensitive. -func (s UiSelectorHelper) DescriptionContains(desc string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.descriptionContains("%s")`, desc)) - return s -} - -// ResourceId Set the search criteria to match the given resource ID. -func (s UiSelectorHelper) ResourceId(id string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.resourceId("%s")`, id)) - return s -} - -// ResourceIdMatches Set the search criteria to match the resource ID -// of the widget, using a regular expression. -func (s UiSelectorHelper) ResourceIdMatches(regex string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.resourceIdMatches("%s")`, regex)) - return s -} - -// Index Set the search criteria to match the widget by its node -// index in the layout hierarchy. -// -// The index value must be 0 or greater. -// -// Using the index can be unreliable and should only -// be used as a last resort for matching. Instead, -// consider using the `Instance(int)` method. -func (s UiSelectorHelper) Index(index int) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.index(%d)`, index)) - return s -} - -// Instance Set the search criteria to match the -// widget by its instance number. -// -// The instance value must be 0 or greater, where -// the first instance is 0. -// -// For example, to simulate a user click on -// the third image that is enabled in a UI screen, you -// could specify a search criteria where the instance is -// 2, the `className(String)` matches the image -// widget class, and `enabled(boolean)` is true. -// The code would look like this: -// -// `new UiSelector().className("android.widget.ImageView") -// .enabled(true).instance(2);` -func (s UiSelectorHelper) Instance(instance int) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.instance(%d)`, instance)) - return s -} - -// Enabled Set the search criteria to match widgets that are enabled. -// -// Typically, using this search criteria alone is not useful. -// You should also include additional criteria, such as text, -// content-description, or the class name for a widget. -// -// If no other search criteria is specified, and there is more -// than one matching widget, the first widget in the tree -// is selected. -func (s UiSelectorHelper) Enabled(b bool) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.enabled(%t)`, b)) - return s -} - -// Focused Set the search criteria to match widgets that have focus. -// -// Typically, using this search criteria alone is not useful. -// You should also include additional criteria, such as text, -// content-description, or the class name for a widget. -// -// If no other search criteria is specified, and there is more -// than one matching widget, the first widget in the tree -// is selected. -func (s UiSelectorHelper) Focused(b bool) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.focused(%t)`, b)) - return s -} - -// Focusable Set the search criteria to match widgets that are focusable. -// -// Typically, using this search criteria alone is not useful. -// You should also include additional criteria, such as text, -// content-description, or the class name for a widget. -// -// If no other search criteria is specified, and there is more -// than one matching widget, the first widget in the tree -// is selected. -func (s UiSelectorHelper) Focusable(b bool) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.focusable(%t)`, b)) - return s -} - -// Scrollable Set the search criteria to match widgets that are scrollable. -// -// Typically, using this search criteria alone is not useful. -// You should also include additional criteria, such as text, -// content-description, or the class name for a widget. -// -// If no other search criteria is specified, and there is more -// than one matching widget, the first widget in the tree -// is selected. -func (s UiSelectorHelper) Scrollable(b bool) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.scrollable(%t)`, b)) - return s -} - -// Selected Set the search criteria to match widgets that -// are currently selected. -// -// Typically, using this search criteria alone is not useful. -// You should also include additional criteria, such as text, -// content-description, or the class name for a widget. -// -// If no other search criteria is specified, and there is more -// than one matching widget, the first widget in the tree -// is selected. -func (s UiSelectorHelper) Selected(b bool) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.selected(%t)`, b)) - return s -} - -// Checked Set the search criteria to match widgets that -// are currently checked (usually for checkboxes). -// -// Typically, using this search criteria alone is not useful. -// You should also include additional criteria, such as text, -// content-description, or the class name for a widget. -// -// If no other search criteria is specified, and there is more -// than one matching widget, the first widget in the tree -// is selected. -func (s UiSelectorHelper) Checked(b bool) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.checked(%t)`, b)) - return s -} - -// Checkable Set the search criteria to match widgets that are checkable. -// -// Typically, using this search criteria alone is not useful. -// You should also include additional criteria, such as text, -// content-description, or the class name for a widget. -// -// If no other search criteria is specified, and there is more -// than one matching widget, the first widget in the tree -// is selected. -func (s UiSelectorHelper) Checkable(b bool) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.checkable(%t)`, b)) - return s -} - -// Clickable Set the search criteria to match widgets that are clickable. -// -// Typically, using this search criteria alone is not useful. -// You should also include additional criteria, such as text, -// content-description, or the class name for a widget. -// -// If no other search criteria is specified, and there is more -// than one matching widget, the first widget in the tree -// is selected. -func (s UiSelectorHelper) Clickable(b bool) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.clickable(%t)`, b)) - return s -} - -// LongClickable Set the search criteria to match widgets that are long-clickable. -// -// Typically, using this search criteria alone is not useful. -// You should also include additional criteria, such as text, -// content-description, or the class name for a widget. -// -// If no other search criteria is specified, and there is more -// than one matching widget, the first widget in the tree -// is selected. -func (s UiSelectorHelper) LongClickable(b bool) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.longClickable(%t)`, b)) - return s -} - -// packageName Set the search criteria to match the package name -// of the application that contains the widget. -func (s UiSelectorHelper) packageName(name string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.packageName(%s)`, name)) - return s -} - -// PackageNameMatches Set the search criteria to match the package name -// of the application that contains the widget. -func (s UiSelectorHelper) PackageNameMatches(regex string) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.packageNameMatches(%s)`, regex)) - return s -} - -// ChildSelector Adds a child UiSelector criteria to this selector. -// -// Use this selector to narrow the search scope to -// child widgets under a specific parent widget. -func (s UiSelectorHelper) ChildSelector(selector UiSelectorHelper) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.childSelector(%s)`, selector.value.String())) - return s -} - -func (s UiSelectorHelper) PatternSelector(selector UiSelectorHelper) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.patternSelector(%s)`, selector.value.String())) - return s -} - -func (s UiSelectorHelper) ContainerSelector(selector UiSelectorHelper) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.containerSelector(%s)`, selector.value.String())) - return s -} - -// FromParent Adds a child UiSelector criteria to this selector which is used to -// start search from the parent widget. -// -// Use this selector to narrow the search scope to -// sibling widgets as well all child widgets under a parent. -func (s UiSelectorHelper) FromParent(selector UiSelectorHelper) UiSelectorHelper { - s.value.WriteString(fmt.Sprintf(`.fromParent(%s)`, selector.value.String())) - return s -} diff --git a/pkg/uixt/android_test.go b/pkg/uixt/android_test.go index cddb8b03..862f01a0 100644 --- a/pkg/uixt/android_test.go +++ b/pkg/uixt/android_test.go @@ -236,33 +236,6 @@ func TestDriver_GetOrientation(t *testing.T) { _ = driverExt.Driver.Homescreen() } -func TestUiSelectorHelper_NewUiSelectorHelper(t *testing.T) { - uiSelector := NewUiSelectorHelper().Text("a").String() - if uiSelector != `new UiSelector().text("a");` { - t.Fatal("[ERROR]", uiSelector) - } - - uiSelector = NewUiSelectorHelper().Text("a").TextStartsWith("b").String() - if uiSelector != `new UiSelector().text("a").textStartsWith("b");` { - t.Fatal("[ERROR]", uiSelector) - } - - uiSelector = NewUiSelectorHelper().ClassName("android.widget.LinearLayout").Index(6).String() - if uiSelector != `new UiSelector().className("android.widget.LinearLayout").index(6);` { - t.Fatal("[ERROR]", uiSelector) - } - - uiSelector = NewUiSelectorHelper().Focused(false).Instance(6).String() - if uiSelector != `new UiSelector().focused(false).instance(6);` { - t.Fatal("[ERROR]", uiSelector) - } - - uiSelector = NewUiSelectorHelper().ChildSelector(NewUiSelectorHelper().Enabled(true)).String() - if uiSelector != `new UiSelector().childSelector(new UiSelector().enabled(true));` { - t.Fatal("[ERROR]", uiSelector) - } -} - func Test_getFreePort(t *testing.T) { freePort, err := builtin.GetFreePort() if err != nil { @@ -271,16 +244,6 @@ func Test_getFreePort(t *testing.T) { t.Log(freePort) } -func TestDeviceList(t *testing.T) { - devices, err := GetAndroidDevices() - if err != nil { - t.Fatal(err) - } - for i := range devices { - t.Log(devices[i].Serial()) - } -} - func TestDriver_AppLaunch(t *testing.T) { device, _ := NewAndroidDevice() driver, err := device.NewDriver() diff --git a/pkg/uixt/ext/android_stub.go b/pkg/uixt/ext/android_stub.go deleted file mode 100644 index 6546c91b..00000000 --- a/pkg/uixt/ext/android_stub.go +++ /dev/null @@ -1,318 +0,0 @@ -package uixt_ext - -import ( - "context" - "fmt" - "net" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/v5/code" - "github.com/httprunner/httprunner/v5/internal/json" - "github.com/httprunner/httprunner/v5/pkg/uixt" - "github.com/httprunner/httprunner/v5/pkg/uixt/option" -) - -const ( - StubSocketName = "com.bytest.device" - DouyinServerPort = 32316 -) - -func NewStubDriver(device *uixt.AndroidDevice, opts ...option.DriverOption) (driver *StubAndroidDriver, err error) { - socketLocalPort, err := device.Forward(StubSocketName) - if err != nil { - return nil, errors.Wrap(code.DeviceConnectionError, - fmt.Sprintf("forward port %d->%s failed: %v", - socketLocalPort, StubSocketName, err)) - } - - serverLocalPort, err := device.Forward(DouyinServerPort) - if err != nil { - return nil, errors.Wrap(code.DeviceConnectionError, - fmt.Sprintf("forward port %d->%d failed: %v", - serverLocalPort, DouyinServerPort, err)) - } - - address := fmt.Sprintf("127.0.0.1:%d", socketLocalPort) - conn, err := net.Dial("tcp", address) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("failed to connect %s", address)) - return nil, err - } - - driver = &StubAndroidDriver{ - socket: conn, - timeout: 10 * time.Second, - } - - rawURL := fmt.Sprintf("http://forward-to-%d:%d", - serverLocalPort, DouyinServerPort) - if driver.urlPrefix, err = url.Parse(rawURL); err != nil { - return nil, err - } - - driver.Device = device.Device - driver.Logcat = device.Logcat - return driver, nil -} - -type StubAndroidDriver struct { - uixt.ADBDriver - socket net.Conn - seq int - timeout time.Duration -} - -type AppLoginInfo struct { - Did string `json:"did,omitempty" yaml:"did,omitempty"` - Uid string `json:"uid,omitempty" yaml:"uid,omitempty"` - IsLogin bool `json:"is_login,omitempty" yaml:"is_login,omitempty"` -} - -func (sad *StubAndroidDriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) { - var localPort int - { - tmpURL, _ := url.Parse(sad.urlPrefix.String()) - hostname := tmpURL.Hostname() - if strings.HasPrefix(hostname, forwardToPrefix) { - localPort, _ = strconv.Atoi(strings.TrimPrefix(hostname, forwardToPrefix)) - } - } - - conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort)) - if err != nil { - return nil, fmt.Errorf("adb forward: %w", err) - } - sad.Client = convertToHTTPClient(conn) - return sad.Request(http.MethodGet, sad.concatURL(nil, pathElem...), nil) -} - -func (sad *StubAndroidDriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) { - var localPort int - { - tmpURL, _ := url.Parse(sad.urlPrefix.String()) - hostname := tmpURL.Hostname() - if strings.HasPrefix(hostname, forwardToPrefix) { - localPort, _ = strconv.Atoi(strings.TrimPrefix(hostname, forwardToPrefix)) - } - } - - conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort)) - if err != nil { - return nil, fmt.Errorf("adb forward: %w", err) - } - sad.Client = convertToHTTPClient(conn) - - var bsJSON []byte = nil - if data != nil { - if bsJSON, err = json.Marshal(data); err != nil { - return nil, err - } - } - return sad.Request(http.MethodPost, sad.concatURL(nil, pathElem...), bsJSON) -} - -func (sad *StubAndroidDriver) sendCommand(packageName string, cmdType string, params map[string]interface{}, readTimeout ...time.Duration) (interface{}, error) { - sad.seq++ - packet := map[string]interface{}{ - "Seq": sad.seq, - "Cmd": cmdType, - "v": "", - } - for key, value := range params { - if key == "Cmd" || key == "Seq" { - return "", errors.New("params cannot be Cmd or Seq") - } - packet[key] = value - } - data, err := json.Marshal(packet) - if err != nil { - return nil, err - } - - res, err := sad.Device.RunStubCommand(append(data, '\n'), packageName) - if err != nil { - return nil, err - } - var resultMap map[string]interface{} - if err := json.Unmarshal([]byte(res), &resultMap); err != nil { - return nil, err - } - if resultMap["Error"] != nil { - return nil, fmt.Errorf("failed to call stub command: %s", resultMap["Error"].(string)) - } - - return resultMap["Result"], nil -} - -func (sad *StubAndroidDriver) DeleteSession() error { - return sad.close() -} - -func (sad *StubAndroidDriver) close() error { - if sad.socket != nil { - return sad.socket.Close() - } - return nil -} - -func (sad *StubAndroidDriver) Status() (uixt.DeviceStatus, error) { - app, err := sad.GetForegroundApp() - if err != nil { - return uixt.DeviceStatus{}, err - } - res, err := sad.sendCommand(app.PackageName, "Hello", nil) - if err != nil { - return uixt.DeviceStatus{}, err - } - log.Info().Msg(fmt.Sprintf("ping stub result :%v", res)) - return uixt.DeviceStatus{}, nil -} - -func (sad *StubAndroidDriver) Source(srcOpt ...option.SourceOption) (source string, err error) { - app, err := sad.GetForegroundApp() - if err != nil { - return "", err - } - params := map[string]interface{}{ - "ClassName": "com.bytedance.byteinsight.MockOperator", - "Method": "getLayout", - "RetType": "", - "Args": []string{}, - } - res, err := sad.sendCommand(app.PackageName, "CallStaticMethod", params) - if err != nil { - return "", err - } - return res.(string), nil -} - -func (sad *StubAndroidDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) { - params := map[string]interface{}{ - "phone": phoneNumber, - } - if captcha != "" { - params["captcha"] = captcha - } else if password != "" { - params["password"] = password - } else { - return info, fmt.Errorf("password and capcha is empty") - } - resp, err := sad.httpPOST(params, "/host", "/login", "account") - if err != nil { - return info, err - } - res, err := resp.valueConvertToJsonObject() - if err != nil { - return info, err - } - log.Info().Msgf("%v", res) - if res["isSuccess"] != true { - err = fmt.Errorf("falied to login %s", res["data"]) - log.Err(err).Msgf("%v", res) - return info, err - } - time.Sleep(20 * time.Second) - info, err = sad.getLoginAppInfo(packageName) - if err != nil || !info.IsLogin { - return info, fmt.Errorf("falied to login %v", info) - } - return info, nil -} - -func (sad *StubAndroidDriver) LogoutNoneUI(packageName string) error { - resp, err := sad.httpGET("/host", "/logout") - if err != nil { - return err - } - res, err := resp.valueConvertToJsonObject() - if err != nil { - return err - } - log.Info().Msgf("%v", res) - if res["isSuccess"] != true { - err = fmt.Errorf("falied to logout %s", res["data"]) - log.Err(err).Msgf("%v", res) - return err - } - fmt.Printf("%v", resp) - if err != nil { - return err - } - time.Sleep(3 * time.Second) - return nil -} - -func (sad *StubAndroidDriver) LoginNoneUIDynamic(packageName, phoneNumber string, captcha string) error { - params := map[string]interface{}{ - "ClassName": "qe.python.test.LoginUtil", - "Method": "loginSync", - "RetType": "", - "Args": []string{phoneNumber, captcha}, - } - res, err := sad.sendCommand(packageName, "CallStaticMethod", params) - if err != nil { - return err - } - log.Info().Msg(res.(string)) - return nil -} - -func (sad *StubAndroidDriver) SetHDTStatus(status bool) error { - _, err := sad.Device.RunShellCommand("settings", "put", "global", "feedbacker_sso_bypass_token", "default_sso_bypass_token") - if err != nil { - log.Warn().Msg(fmt.Sprintf("failed to disable sso, error: %v", err)) - } - params := map[string]interface{}{ - "ClassName": "com.bytedance.ies.stark.framework.HybridDevTool", - "Method": "setEnabled", - "RetType": "", - "Args": []bool{status}, - } - res, err := sad.sendCommand("com.ss.android.ugc.aweme", "CallStaticMethod", params) - if err != nil { - return fmt.Errorf("failed to set hds status %v, error: %v", status, err) - } - log.Info().Msg(fmt.Sprintf("set hdt status result: %s", res)) - return nil -} - -func (sad *StubAndroidDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) { - resp, err := sad.httpGET("/host", "/app", "/info") - if err != nil { - return info, err - } - res, err := resp.valueConvertToJsonObject() - if err != nil { - return info, err - } - if res["isSuccess"] != true { - err = fmt.Errorf("falied to get app info %s", res["data"]) - log.Err(err).Msgf("%v", res) - return info, err - } - - err = json.Unmarshal([]byte(res["data"].(string)), &info) - if err != nil { - err = fmt.Errorf("falied to parse app info %s", res["data"]) - return - } - return info, nil -} - -func convertToHTTPClient(conn net.Conn) *http.Client { - return &http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return conn, nil - }, - }, - Timeout: 30 * time.Second, - } -}