mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 10:29:39 +08:00
refactor: NewAndroidDevice
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user