mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 15:37:35 +08:00
change: add stub android/ios driver
This commit is contained in:
+2
-2
@@ -3,7 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/server"
|
server_ext "github.com/httprunner/httprunner/v5/server/ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// serverCmd represents the server command
|
// serverCmd represents the server command
|
||||||
@@ -13,7 +13,7 @@ var serverCmd = &cobra.Command{
|
|||||||
Long: `start hrp server, call httprunner by HTTP`,
|
Long: `start hrp server, call httprunner by HTTP`,
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return server.NewRouter().Run(port)
|
return server_ext.NewExtRouter().Run(port)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v5.0.0+2502182046
|
v5.0.0+2502182234
|
||||||
|
|||||||
@@ -0,0 +1,341 @@
|
|||||||
|
package driver_ext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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"
|
||||||
|
"github.com/httprunner/httprunner/v5/pkg/uixt/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StubAndroidDriver struct {
|
||||||
|
*uixt.ADBDriver
|
||||||
|
|
||||||
|
seq int
|
||||||
|
timeout time.Duration
|
||||||
|
douyinUrlPrefix string
|
||||||
|
douyinLiteUrlPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
StubSocketName = "com.bytest.device"
|
||||||
|
AndroidDouyinPort = 32316
|
||||||
|
AndroidDouyinLitePort = 32792
|
||||||
|
)
|
||||||
|
|
||||||
|
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 NewStubAndroidDriver(dev *uixt.AndroidDevice) (*StubAndroidDriver, error) {
|
||||||
|
adbDriver, err := uixt.NewADBDriver(dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
driver := &StubAndroidDriver{
|
||||||
|
timeout: 10 * time.Second,
|
||||||
|
ADBDriver: adbDriver,
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sad *StubAndroidDriver) Setup() error {
|
||||||
|
socketLocalPort, err := sad.Device.Forward(StubSocketName)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(code.DeviceConnectionError,
|
||||||
|
fmt.Sprintf("forward port %d->%s failed: %v",
|
||||||
|
socketLocalPort, StubSocketName, err))
|
||||||
|
}
|
||||||
|
err = sad.Session.SetupPortForward(socketLocalPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
douyinLocalPort, err := sad.Device.Forward(AndroidDouyinPort)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(code.DeviceConnectionError,
|
||||||
|
fmt.Sprintf("forward port %d->%d failed: %v",
|
||||||
|
douyinLocalPort, AndroidDouyinPort, err))
|
||||||
|
}
|
||||||
|
sad.douyinUrlPrefix = fmt.Sprintf("http://127.0.0.1:%d", douyinLocalPort)
|
||||||
|
|
||||||
|
douyinLiteLocalPort, err := sad.Device.Forward(AndroidDouyinLitePort)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(code.DeviceConnectionError,
|
||||||
|
fmt.Sprintf("forward port %d->%d failed: %v",
|
||||||
|
douyinLiteLocalPort, AndroidDouyinLitePort, err))
|
||||||
|
}
|
||||||
|
sad.douyinLiteUrlPrefix = fmt.Sprintf("http://127.0.0.1:%d", douyinLiteLocalPort)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sad *StubAndroidDriver) sendCommand(packageName string, cmdType string, params map[string]interface{}) (
|
||||||
|
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) AppLaunch(packageName string) (err error) {
|
||||||
|
_ = sad.EnableDevtool(packageName, true)
|
||||||
|
err = sad.ADBDriver.AppLaunch(packageName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sad *StubAndroidDriver) Status() (types.DeviceStatus, error) {
|
||||||
|
app, err := sad.ForegroundInfo()
|
||||||
|
if err != nil {
|
||||||
|
return types.DeviceStatus{}, err
|
||||||
|
}
|
||||||
|
res, err := sad.sendCommand(app.PackageName, "Hello", nil)
|
||||||
|
if err != nil {
|
||||||
|
return types.DeviceStatus{}, err
|
||||||
|
}
|
||||||
|
log.Info().Msg(fmt.Sprintf("ping stub result :%v", res))
|
||||||
|
return types.DeviceStatus{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sad *StubAndroidDriver) Source(srcOpt ...option.SourceOption) (source string, err error) {
|
||||||
|
app, err := sad.ForegroundInfo()
|
||||||
|
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 {
|
||||||
|
if app.PackageName == "com.ss.android.ugc.aweme" {
|
||||||
|
log.Error().Err(err).Msg("failed to get source")
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if res.(string) == "{}" {
|
||||||
|
res, err = sad.sendCommand(app.PackageName, "CallStaticMethod", params)
|
||||||
|
if err != nil {
|
||||||
|
if app.PackageName == "com.ss.android.ugc.aweme" {
|
||||||
|
log.Error().Err(err).Msg("failed to get source")
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.(string), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sad *StubAndroidDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (
|
||||||
|
info AppLoginInfo, err error) {
|
||||||
|
app, err := sad.ForegroundInfo()
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
// app.PackageName in ["com.ss.android.ugc.aweme", "com.ss.android.ugc.aweme.lite"]
|
||||||
|
if app.PackageName == "com.ss.android.ugc.aweme" || app.PackageName == "com.ss.android.ugc.aweme.lite" {
|
||||||
|
return sad.LoginDouyin(app.PackageName, phoneNumber, captcha, password)
|
||||||
|
} else if app.PackageName == "com.ss.android.article.video" {
|
||||||
|
return sad.LoginXigua(app.PackageName, phoneNumber, captcha, password)
|
||||||
|
} else {
|
||||||
|
return info, fmt.Errorf("not support app %s", app.PackageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sad *StubAndroidDriver) LoginXigua(packageName, phoneNumber string, captcha, password string) (
|
||||||
|
info AppLoginInfo, err error) {
|
||||||
|
loginSchema := ""
|
||||||
|
if captcha != "" {
|
||||||
|
loginSchema = fmt.Sprintf("snssdk32://local_channel_autologin?login_type=1&account=%s&smscode=%s",
|
||||||
|
phoneNumber, captcha)
|
||||||
|
} else if password != "" {
|
||||||
|
loginSchema = fmt.Sprintf("snssdk32://local_channel_autologin?login_type=2&account=%s&password=%s",
|
||||||
|
phoneNumber, password)
|
||||||
|
} else {
|
||||||
|
return info, fmt.Errorf("password and capcha is empty")
|
||||||
|
}
|
||||||
|
info.IsLogin = true
|
||||||
|
return info, sad.OpenUrl(loginSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sad *StubAndroidDriver) LoginDouyin(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")
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err = sad.getLoginAppInfo(packageName)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("failed to get login info")
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
if info.Did == "" {
|
||||||
|
_ = sad.Home()
|
||||||
|
_ = sad.AppLaunch(packageName)
|
||||||
|
time.Sleep(20 * time.Second)
|
||||||
|
}
|
||||||
|
if info.IsLogin {
|
||||||
|
_ = sad.LogoutNoneUI(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlPrefix, err := sad.getUrlPrefix(packageName)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
fullUrl := urlPrefix + "/host/login/account/"
|
||||||
|
resp, err := sad.Session.POST(params, fullUrl)
|
||||||
|
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 {
|
||||||
|
urlPrefix, err := sad.getUrlPrefix(packageName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fullUrl := urlPrefix + "/host/logout"
|
||||||
|
resp, err := sad.Session.GET(fullUrl)
|
||||||
|
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) EnableDevtool(packageName string, enable bool) error {
|
||||||
|
urlPrefix, err := sad.getUrlPrefix(packageName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fullUrl := urlPrefix + "/host/devtool/enable"
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"enable": enable,
|
||||||
|
}
|
||||||
|
resp, err := sad.Session.POST(params, fullUrl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res, err := resp.ValueConvertToJsonObject()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info().Msgf("%v", res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sad *StubAndroidDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) {
|
||||||
|
urlPrefix, err := sad.getUrlPrefix(packageName)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
fullUrl := urlPrefix + "/host/app/info"
|
||||||
|
resp, err := sad.Session.GET(fullUrl)
|
||||||
|
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 (sad *StubAndroidDriver) getUrlPrefix(packageName string) (urlPrefix string, err error) {
|
||||||
|
if packageName == "com.ss.android.ugc.aweme" {
|
||||||
|
urlPrefix = sad.douyinUrlPrefix
|
||||||
|
} else if packageName == "com.ss.android.ugc.aweme.lite" {
|
||||||
|
urlPrefix = sad.douyinLiteUrlPrefix
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("not support app %s", packageName)
|
||||||
|
}
|
||||||
|
return urlPrefix, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package driver_ext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
|
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
||||||
|
"github.com/httprunner/httprunner/v5/pkg/uixt/ai"
|
||||||
|
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IStubDriver interface {
|
||||||
|
uixt.IDriver
|
||||||
|
|
||||||
|
LoginNoneUI(packageName, phoneNumber, captcha, password string) (info AppLoginInfo, err error)
|
||||||
|
LogoutNoneUI(packageName string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXTDriver(driver uixt.IDriver, opts ...ai.AIServiceOption) *XTDriver {
|
||||||
|
services := ai.NewAIService(opts...)
|
||||||
|
driverExt := &XTDriver{
|
||||||
|
XTDriver: &uixt.XTDriver{
|
||||||
|
IDriver: driver,
|
||||||
|
CVService: services.ICVService,
|
||||||
|
LLMService: services.ILLMService,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return driverExt
|
||||||
|
}
|
||||||
|
|
||||||
|
type XTDriver struct {
|
||||||
|
*uixt.XTDriver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dExt *XTDriver) InstallByUrl(url string, opts ...option.InstallOption) error {
|
||||||
|
appPath, err := builtin.DownloadFileByUrl(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dExt.Install(appPath, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dExt *XTDriver) Install(filePath string, opts ...option.InstallOption) error {
|
||||||
|
if _, ok := dExt.GetDevice().(*uixt.AndroidDevice); ok {
|
||||||
|
stopChan := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(8 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
_ = dExt.TapByOCR("^(.*无视风险安装|正在扫描.*|我知道了|稍后继续|稍后提醒|继续安装|知道了|确定|继续|完成|点击继续安装|继续安装旧版本|替换|.*正在安装|安装|授权本次安装|重新安装|仍要安装|更多详情|我知道了|已了解此应用未经检测.)$", option.WithRegex(true), option.WithIgnoreNotFoundError(true))
|
||||||
|
case <-stopChan:
|
||||||
|
fmt.Println("Ticker stopped")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer func() {
|
||||||
|
close(stopChan)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dExt.GetDevice().Install(filePath, opts...)
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
package driver_ext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
|
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
||||||
|
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StubIOSDriver struct {
|
||||||
|
*uixt.WDADriver
|
||||||
|
|
||||||
|
timeout time.Duration
|
||||||
|
douyinUrlPrefix string
|
||||||
|
douyinLiteUrlPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
IOSDouyinPort = 32921
|
||||||
|
IOSDouyinLitePort = 33461
|
||||||
|
defaultBightInsightPort = 8000
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewStubIOSDriver(dev *uixt.IOSDevice) (*StubIOSDriver, error) {
|
||||||
|
wdaDriver, err := uixt.NewWDADriver(dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
driver := &StubIOSDriver{
|
||||||
|
WDADriver: wdaDriver,
|
||||||
|
timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup driver
|
||||||
|
if err := driver.Setup(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) Setup() error {
|
||||||
|
localPort, err := s.getLocalPort()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.Session.SetupPortForward(localPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Session.SetBaseURL(fmt.Sprintf("http://127.0.0.1:%d", localPort))
|
||||||
|
|
||||||
|
localDouyinPort, err := builtin.GetFreePort()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(code.DeviceHTTPDriverError,
|
||||||
|
fmt.Sprintf("get free port failed: %v", err))
|
||||||
|
}
|
||||||
|
if err = s.Device.Forward(localDouyinPort, IOSDouyinPort); err != nil {
|
||||||
|
return errors.Wrap(code.DeviceHTTPDriverError,
|
||||||
|
fmt.Sprintf("forward tcp port failed: %v", err))
|
||||||
|
}
|
||||||
|
s.douyinUrlPrefix = fmt.Sprintf("http://127.0.0.1:%d", localDouyinPort)
|
||||||
|
|
||||||
|
localDouyinLitePort, err := builtin.GetFreePort()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(code.DeviceHTTPDriverError,
|
||||||
|
fmt.Sprintf("get free port failed: %v", err))
|
||||||
|
}
|
||||||
|
if err = s.Device.Forward(localDouyinLitePort, IOSDouyinLitePort); err != nil {
|
||||||
|
return errors.Wrap(code.DeviceHTTPDriverError,
|
||||||
|
fmt.Sprintf("forward tcp port failed: %v", err))
|
||||||
|
}
|
||||||
|
s.douyinLiteUrlPrefix = fmt.Sprintf("http://127.0.0.1:%d", localDouyinLitePort)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) getLocalPort() (int, error) {
|
||||||
|
localStubPort, err := builtin.GetFreePort()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(code.DeviceHTTPDriverError,
|
||||||
|
fmt.Sprintf("get free port failed: %v", err))
|
||||||
|
}
|
||||||
|
if err = s.Device.Forward(localStubPort, defaultBightInsightPort); err != nil {
|
||||||
|
return 0, errors.Wrap(code.DeviceHTTPDriverError,
|
||||||
|
fmt.Sprintf("forward tcp port failed: %v", err))
|
||||||
|
}
|
||||||
|
return localStubPort, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) Source(srcOpt ...option.SourceOption) (string, error) {
|
||||||
|
resp, err := s.Session.GET("/source?format=json&onlyWeb=false")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("get source err")
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return string(resp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) OpenUrl(urlStr string, options ...option.ActionOption) (err error) {
|
||||||
|
targetUrl := fmt.Sprintf("/openURL?url=%s", url.QueryEscape(urlStr))
|
||||||
|
fmt.Sprintln(targetUrl)
|
||||||
|
resp, err := s.Session.GET(targetUrl)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("get source err")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Sprintln(resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
||||||
|
appInfo, err := s.ForegroundInfo()
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
if appInfo.BundleId == "com.ss.iphone.ugc.AwemeInhouse" || appInfo.BundleId == "com.ss.iphone.ugc.awemeinhouse.lite" {
|
||||||
|
return s.LoginDouyin(appInfo.BundleId, phoneNumber, captcha, password)
|
||||||
|
} else if appInfo.BundleId == "com.ss.iphone.InHouse.article.Video" {
|
||||||
|
return s.LoginXigua(appInfo.BundleId, phoneNumber, captcha, password)
|
||||||
|
} else {
|
||||||
|
return info, fmt.Errorf("not support app")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) LoginXigua(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
||||||
|
loginSchema := ""
|
||||||
|
if captcha != "" {
|
||||||
|
loginSchema = fmt.Sprintf("snssdk32://local_channel_autologin?login_type=1&account=%s&smscode=%s", phoneNumber, captcha)
|
||||||
|
} else if password != "" {
|
||||||
|
loginSchema = fmt.Sprintf("snssdk32://local_channel_autologin?login_type=2&account=%s&password=%s", phoneNumber, password)
|
||||||
|
} else {
|
||||||
|
return info, fmt.Errorf("password and capcha is empty")
|
||||||
|
}
|
||||||
|
info.IsLogin = true
|
||||||
|
return info, s.OpenUrl(loginSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) LoginDouyin(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")
|
||||||
|
}
|
||||||
|
bsJSON, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
urlPrefix, err := s.getUrlPrefix(packageName)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
fullUrl := urlPrefix + "/host/login/account/" + urlPrefix
|
||||||
|
resp, err := s.Session.POST(bsJSON, fullUrl)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
res, err := resp.ValueConvertToJsonObject()
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
log.Info().Msgf("%v", res)
|
||||||
|
// {'isSuccess': True, 'data': '登录成功', 'code': 0}
|
||||||
|
if res["isSuccess"] != true {
|
||||||
|
err = fmt.Errorf("falied to logout %s", res["data"])
|
||||||
|
log.Err(err).Msgf("%v", res)
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
time.Sleep(20 * time.Second)
|
||||||
|
info, err = s.getLoginAppInfo(packageName)
|
||||||
|
if err != nil || !info.IsLogin {
|
||||||
|
return info, fmt.Errorf("falied to login %v", info)
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) LogoutNoneUI(packageName string) error {
|
||||||
|
urlPrefix, err := s.getUrlPrefix(packageName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fullUrl := urlPrefix + "/host/loginout/"
|
||||||
|
resp, err := s.Session.GET(fullUrl)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) EnableDevtool(packageName string, enable bool) (err error) {
|
||||||
|
urlPrefix, err := s.getUrlPrefix(packageName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fullUrl := urlPrefix + "/host/devtool/enable"
|
||||||
|
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"enable": enable,
|
||||||
|
}
|
||||||
|
bsJSON, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := s.Session.POST(bsJSON, fullUrl)
|
||||||
|
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 enable devtool %s", res["data"])
|
||||||
|
log.Err(err).Msgf("%v", res)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) {
|
||||||
|
urlPrefix, err := s.getUrlPrefix(packageName)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
fullUrl := urlPrefix + "/host/app/info/"
|
||||||
|
|
||||||
|
resp, err := s.Session.GET(fullUrl)
|
||||||
|
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 get is login %s", res["data"])
|
||||||
|
log.Err(err).Msgf("%v", res)
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(res["data"].(string)), &info)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StubIOSDriver) getUrlPrefix(packageName string) (urlPrefix string, err error) {
|
||||||
|
if packageName == "com.ss.iphone.ugc.AwemeInhouse" {
|
||||||
|
urlPrefix = s.douyinUrlPrefix
|
||||||
|
} else if packageName == "com.ss.iphone.ugc.awemeinhouse.lite" {
|
||||||
|
urlPrefix = s.douyinLiteUrlPrefix
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("not support app %s", packageName)
|
||||||
|
}
|
||||||
|
return urlPrefix, nil
|
||||||
|
}
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
package driver_ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"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"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
shootsSocketName = "com.bytest.device"
|
|
||||||
douyinServerPort = 32316
|
|
||||||
forwardToPrefix = "forward-to-"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewShootsAndroidDriver(device *uixt.AndroidDevice) (driver *ShootsAndroidDriver, err error) {
|
|
||||||
socketLocalPort, err := device.Device.Forward(shootsSocketName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(code.DeviceConnectionError,
|
|
||||||
fmt.Sprintf("forward port %d->%s failed: %v",
|
|
||||||
socketLocalPort, shootsSocketName, 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
|
|
||||||
}
|
|
||||||
adbDriver, err := uixt.NewADBDriver(device)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
driver = &ShootsAndroidDriver{
|
|
||||||
ADBDriver: adbDriver,
|
|
||||||
socket: conn,
|
|
||||||
timeout: 10 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
serverLocalPort, err := device.Device.Forward(douyinServerPort)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(code.DeviceConnectionError,
|
|
||||||
fmt.Sprintf("forward port %d->%d failed: %v",
|
|
||||||
serverLocalPort, douyinServerPort, err))
|
|
||||||
}
|
|
||||||
rawURL := fmt.Sprintf("http://forward-to-%d:%d",
|
|
||||||
serverLocalPort, douyinServerPort)
|
|
||||||
driver.Session.SetBaseURL(rawURL)
|
|
||||||
|
|
||||||
return driver, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShootsAndroidDriver struct {
|
|
||||||
*uixt.ADBDriver
|
|
||||||
socket net.Conn
|
|
||||||
seq int
|
|
||||||
timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sad *ShootsAndroidDriver) 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 shoots command: %s", resultMap["Error"].(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultMap["Result"], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sad *ShootsAndroidDriver) DeleteSession() error {
|
|
||||||
return sad.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sad *ShootsAndroidDriver) close() error {
|
|
||||||
if sad.socket != nil {
|
|
||||||
return sad.socket.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sad *ShootsAndroidDriver) Status() (types.DeviceStatus, error) {
|
|
||||||
app, err := sad.ForegroundInfo()
|
|
||||||
if err != nil {
|
|
||||||
return types.DeviceStatus{}, err
|
|
||||||
}
|
|
||||||
res, err := sad.sendCommand(app.PackageName, "Hello", nil)
|
|
||||||
if err != nil {
|
|
||||||
return types.DeviceStatus{}, err
|
|
||||||
}
|
|
||||||
log.Info().Msg(fmt.Sprintf("ping shoots result :%v", res))
|
|
||||||
return types.DeviceStatus{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sad *ShootsAndroidDriver) Source(srcOpt ...option.SourceOption) (source string, err error) {
|
|
||||||
app, err := sad.ForegroundInfo()
|
|
||||||
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 *ShootsAndroidDriver) 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.Session.POST(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 *ShootsAndroidDriver) LogoutNoneUI(packageName string) error {
|
|
||||||
resp, err := sad.Session.GET("/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 *ShootsAndroidDriver) 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 *ShootsAndroidDriver) 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 *ShootsAndroidDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) {
|
|
||||||
resp, err := sad.Session.GET("/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
|
|
||||||
}
|
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
package driver_ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
var shootsAndroidDriver *ShootsAndroidDriver
|
|
||||||
|
|
||||||
func setupShootsAndroidDriver(t *testing.T) {
|
|
||||||
device, err := uixt.NewAndroidDevice()
|
|
||||||
checkErr(t, err)
|
|
||||||
shootsAndroidDriver, err = NewShootsAndroidDriver(device)
|
|
||||||
checkErr(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkErr(t *testing.T, err error, msg ...string) {
|
|
||||||
if err != nil {
|
|
||||||
if len(msg) == 0 {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else {
|
|
||||||
t.Fatal(msg, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHello(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
status, err := shootsAndroidDriver.Status()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSource(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
source, err := shootsAndroidDriver.Source()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogin(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
info, err := shootsAndroidDriver.LoginNoneUI("com.ss.android.ugc.aweme", "12342316231", "8517", "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogout(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
err := shootsAndroidDriver.LogoutNoneUI("com.ss.android.ugc.aweme")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwipe(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
err := shootsAndroidDriver.Swipe(878, 2375, 672, 2375)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTap(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
err := shootsAndroidDriver.TapXY(900, 400)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDoubleTap(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
err := shootsAndroidDriver.DoubleTapXY(500, 500)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLongPress(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
err := shootsAndroidDriver.Swipe(1036, 1076, 1036, 1076,
|
|
||||||
option.WithDuration(3))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInput(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
err := shootsAndroidDriver.Input("\"哈哈\"")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSave(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
raw, err := shootsAndroidDriver.ScreenShot()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
source, err := shootsAndroidDriver.Source()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
step := 14
|
|
||||||
file, err := os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/0/%d.jpg", step))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
file.Write(raw.Bytes())
|
|
||||||
|
|
||||||
file, err = os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/0/%d.json", step))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
file.Write([]byte(source))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppLaunch(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
err := shootsAndroidDriver.AppLaunch("com.ss.android.ugc.aweme")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppTerminal(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
_, err := shootsAndroidDriver.AppTerminate("com.ss.android.ugc.aweme")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppInfo(t *testing.T) {
|
|
||||||
setupShootsAndroidDriver(t)
|
|
||||||
info, err := shootsAndroidDriver.getLoginAppInfo("com.ss.android.ugc.aweme")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(info)
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
package driver_ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"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"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultBightInsightPort = 8000
|
|
||||||
defaultDouyinServerPort = 32921
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewShootsIOSDriver(device *uixt.IOSDevice) (driver *ShootsIOSDriver, err error) {
|
|
||||||
localShootsPort, err := builtin.GetFreePort()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("get free port failed: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = device.Forward(localShootsPort, defaultBightInsightPort); err != nil {
|
|
||||||
return nil, errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("forward tcp port failed: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
localServerPort, err := builtin.GetFreePort()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("get free port failed: %v", err))
|
|
||||||
}
|
|
||||||
if err = device.Forward(localServerPort, defaultDouyinServerPort); err != nil {
|
|
||||||
return nil, errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("forward tcp port failed: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
wdaDriver, err := uixt.NewWDADriver(device)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to init WDA driver for shoots IOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := 10 * time.Second
|
|
||||||
driver = &ShootsIOSDriver{
|
|
||||||
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
|
|
||||||
|
|
||||||
return driver, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShootsIOSDriver struct {
|
|
||||||
*uixt.WDADriver
|
|
||||||
|
|
||||||
bightInsightPrefix string
|
|
||||||
serverPrefix string
|
|
||||||
timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ShootsIOSDriver) Source(srcOpt ...option.SourceOption) (string, error) {
|
|
||||||
resp, err := s.Session.GET(fmt.Sprintf("%s/source?format=json&onlyWeb=false", s.bightInsightPrefix))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(resp), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ShootsIOSDriver) 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 := s.Session.POST(params, fmt.Sprintf("%s/host/login/account/", s.serverPrefix))
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
res, err := resp.ValueConvertToJsonObject()
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
log.Info().Msgf("%v", res)
|
|
||||||
// {'isSuccess': True, 'data': '登录成功', 'code': 0}
|
|
||||||
if res["isSuccess"] != true {
|
|
||||||
err = fmt.Errorf("falied to logout %s", res["data"])
|
|
||||||
log.Err(err).Msgf("%v", res)
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
time.Sleep(20 * time.Second)
|
|
||||||
info, err = s.getLoginAppInfo(packageName)
|
|
||||||
if err != nil || !info.IsLogin {
|
|
||||||
return info, fmt.Errorf("falied to login %v", info)
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ShootsIOSDriver) LogoutNoneUI(packageName string) error {
|
|
||||||
resp, err := s.Session.GET(fmt.Sprintf("%s/host/loginout/", s.serverPrefix))
|
|
||||||
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
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ShootsIOSDriver) TearDown() error {
|
|
||||||
s.WDADriver.TearDown()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ShootsIOSDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) {
|
|
||||||
resp, err := s.Session.GET(fmt.Sprintf("%s/host/app/info/", s.serverPrefix))
|
|
||||||
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 get is login %s", res["data"])
|
|
||||||
log.Err(err).Msgf("%v", res)
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal([]byte(res["data"].(string)), &info)
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package driver_ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
shootsIOSDriver uixt.IDriver
|
|
||||||
iOSDevice *uixt.IOSDevice
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupShootsIOSDriver(t *testing.T) {
|
|
||||||
var err error
|
|
||||||
iOSDevice, err = uixt.NewIOSDevice(
|
|
||||||
option.WithWDAPort(8700),
|
|
||||||
option.WithWDAMjpegPort(8800))
|
|
||||||
checkErr(t, err)
|
|
||||||
shootsIOSDriver, err = NewShootsIOSDriver(iOSDevice)
|
|
||||||
checkErr(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIOSLogin(t *testing.T) {
|
|
||||||
setupShootsIOSDriver(t)
|
|
||||||
info, err := shootsIOSDriver.(*ShootsIOSDriver).LoginNoneUI("", "12342316231", "8517", "")
|
|
||||||
checkErr(t, err)
|
|
||||||
t.Log(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIOSLogout(t *testing.T) {
|
|
||||||
setupShootsIOSDriver(t)
|
|
||||||
err := shootsIOSDriver.(*ShootsIOSDriver).LogoutNoneUI("")
|
|
||||||
checkErr(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIOSIsLogin(t *testing.T) {
|
|
||||||
setupShootsIOSDriver(t)
|
|
||||||
err := shootsIOSDriver.(*ShootsIOSDriver).LogoutNoneUI("")
|
|
||||||
checkErr(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIOSSource(t *testing.T) {
|
|
||||||
setupShootsIOSDriver(t)
|
|
||||||
source, err := shootsIOSDriver.Source()
|
|
||||||
checkErr(t, err)
|
|
||||||
t.Log(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIOSForeground(t *testing.T) {
|
|
||||||
setupShootsIOSDriver(t)
|
|
||||||
app, err := shootsIOSDriver.ForegroundInfo()
|
|
||||||
checkErr(t, err)
|
|
||||||
t.Log(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIOSSwipe(t *testing.T) {
|
|
||||||
setupShootsIOSDriver(t)
|
|
||||||
shootsIOSDriver.Swipe(540, 0, 540, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIOSSave(t *testing.T) {
|
|
||||||
setupShootsIOSDriver(t)
|
|
||||||
raw, err := shootsIOSDriver.ScreenShot()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
source, err := shootsIOSDriver.Source()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
step := 7
|
|
||||||
file, err := os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/ios/4159417_cvcn02okg4g0/%d.jpg", step))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
file.Write(raw.Bytes())
|
|
||||||
|
|
||||||
file, err = os.Create(fmt.Sprintf("/Users/bytedance/workcode/wings_algorithm/testcases/data/cases/ios/4159417_cvcn02okg4g0/%d.json", step))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
file.Write([]byte(source))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListen(t *testing.T) {
|
|
||||||
setupShootsIOSDriver(t)
|
|
||||||
localPort, err := builtin.GetFreePort()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = iOSDevice.Forward(localPort, 8800)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
addr := fmt.Sprintf("0.0.0.0:%d", localPort)
|
|
||||||
l, err := net.Listen("tcp", addr)
|
|
||||||
if err == nil {
|
|
||||||
l.Close() // 端口成功绑定后立即释放,返回该端口号
|
|
||||||
} else {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user