change: tap & swipe

This commit is contained in:
debugtalk
2022-08-28 00:41:45 +08:00
parent c4f687d051
commit 5c74db0087
4 changed files with 61 additions and 103 deletions

View File

@@ -33,9 +33,10 @@ const (
// UI handling // UI handling
uiHome MobileMethod = "home" uiHome MobileMethod = "home"
uiClick MobileMethod = "click" uiTapXY MobileMethod = "tap_xy"
uiDoubleClick MobileMethod = "double_click" uiTap MobileMethod = "tap"
uiLongClick MobileMethod = "long_click" uiDoubleTapXY MobileMethod = "double_tap_xy"
uiDoubleTap MobileMethod = "double_tap"
uiSwipe MobileMethod = "swipe" uiSwipe MobileMethod = "swipe"
uiInput MobileMethod = "input" uiInput MobileMethod = "input"

View File

@@ -66,25 +66,17 @@ func (s *StepAndroid) StopRecording() *StepAndroid {
return &StepAndroid{step: s.step} return &StepAndroid{step: s.step}
} }
func (s *StepAndroid) Click(params interface{}) *StepAndroid { func (s *StepAndroid) Tap(params interface{}) *StepAndroid {
s.step.Android.Actions = append(s.step.Android.Actions, MobileAction{ s.step.Android.Actions = append(s.step.Android.Actions, MobileAction{
Method: uiClick, Method: uiTap,
Params: params, Params: params,
}) })
return &StepAndroid{step: s.step} return &StepAndroid{step: s.step}
} }
func (s *StepAndroid) DoubleClick(params interface{}) *StepAndroid { func (s *StepAndroid) DoubleTap(params interface{}) *StepAndroid {
s.step.Android.Actions = append(s.step.Android.Actions, MobileAction{ s.step.Android.Actions = append(s.step.Android.Actions, MobileAction{
Method: uiDoubleClick, Method: uiDoubleTap,
Params: params,
})
return &StepAndroid{step: s.step}
}
func (s *StepAndroid) LongClick(params interface{}) *StepAndroid {
s.step.Android.Actions = append(s.step.Android.Actions, MobileAction{
Method: uiLongClick,
Params: params, Params: params,
}) })
return &StepAndroid{step: s.step} return &StepAndroid{step: s.step}

View File

@@ -2,12 +2,11 @@ package hrp
import ( import (
"fmt" "fmt"
"net/http"
"strings" "strings"
"time" "time"
gwdaExt "github.com/debugtalk/gwda-ext"
"github.com/electricbubble/gwda" "github.com/electricbubble/gwda"
"github.com/httprunner/uixt"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -93,25 +92,36 @@ func (s *StepIOS) Home() *StepIOS {
return &StepIOS{step: s.step} return &StepIOS{step: s.step}
} }
func (s *StepIOS) Click(params interface{}) *StepIOS { // TapXY taps the point {X,Y}, X & Y is percentage of coordinates
func (s *StepIOS) TapXY(x, y float64) *StepIOS {
s.step.IOS.Actions = append(s.step.IOS.Actions, MobileAction{ s.step.IOS.Actions = append(s.step.IOS.Actions, MobileAction{
Method: uiClick, Method: uiTapXY,
Params: []float64{x, y},
})
return &StepIOS{step: s.step}
}
// Tap taps on the target element
func (s *StepIOS) Tap(params string) *StepIOS {
s.step.IOS.Actions = append(s.step.IOS.Actions, MobileAction{
Method: uiTap,
Params: params, Params: params,
}) })
return &StepIOS{step: s.step} return &StepIOS{step: s.step}
} }
func (s *StepIOS) DoubleClick(params interface{}) *StepIOS { // DoubleTapXY double taps the point {X,Y}, X & Y is percentage of coordinates
func (s *StepIOS) DoubleTapXY(x, y float64) *StepIOS {
s.step.IOS.Actions = append(s.step.IOS.Actions, MobileAction{ s.step.IOS.Actions = append(s.step.IOS.Actions, MobileAction{
Method: uiDoubleClick, Method: uiDoubleTapXY,
Params: params, Params: []float64{x, y},
}) })
return &StepIOS{step: s.step} return &StepIOS{step: s.step}
} }
func (s *StepIOS) LongClick(params interface{}) *StepIOS { func (s *StepIOS) DoubleTap(params string) *StepIOS {
s.step.IOS.Actions = append(s.step.IOS.Actions, MobileAction{ s.step.IOS.Actions = append(s.step.IOS.Actions, MobileAction{
Method: uiLongClick, Method: uiDoubleTap,
Params: params, Params: params,
}) })
return &StepIOS{step: s.step} return &StepIOS{step: s.step}
@@ -385,7 +395,7 @@ func (r *HRPRunner) InitWDAClient(device WDADevice) (client *wdaClient, err erro
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to init WDA driver") return nil, errors.Wrap(err, "failed to init WDA driver")
} }
driverExt, err := gwdaExt.Extend(driver, 0.95) driverExt, err := uixt.Extend(driver, 0.95)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to extend gwda.WebDriver") return nil, errors.Wrap(err, "failed to extend gwda.WebDriver")
} }
@@ -407,13 +417,9 @@ func (r *HRPRunner) InitWDAClient(device WDADevice) (client *wdaClient, err erro
// cache wda client // cache wda client
r.wdaClients = make(map[string]*wdaClient) r.wdaClients = make(map[string]*wdaClient)
client = &wdaClient{ client = &wdaClient{
ID: time.Now().Unix(),
Device: targetDevice, Device: targetDevice,
DriverExt: driverExt, DriverExt: driverExt,
WindowSize: windowSize, WindowSize: windowSize,
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
} }
r.wdaClients[targetDevice.SerialNumber()] = client r.wdaClients[targetDevice.SerialNumber()] = client
@@ -476,11 +482,9 @@ func runStepIOS(s *SessionRunner, step *TStep) (stepResult *StepResult, err erro
var errActionNotImplemented = errors.New("UI action not implemented") var errActionNotImplemented = errors.New("UI action not implemented")
type wdaClient struct { type wdaClient struct {
ID int64
Device *gwda.Device Device *gwda.Device
DriverExt *gwdaExt.DriverExt DriverExt *uixt.DriverExt
WindowSize gwda.Size WindowSize gwda.Size
httpClient *http.Client
} }
func (w *wdaClient) doAction(action MobileAction) error { func (w *wdaClient) doAction(action MobileAction) error {
@@ -514,78 +518,39 @@ func (w *wdaClient) doAction(action MobileAction) error {
return fmt.Errorf("app_terminate params should be bundleId(string), got %v", action.Params) return fmt.Errorf("app_terminate params should be bundleId(string), got %v", action.Params)
case uiHome: case uiHome:
return w.DriverExt.Homescreen() return w.DriverExt.Homescreen()
case uiClick: case uiTapXY:
// click on coordinate
if location, ok := action.Params.([]int); ok {
// absolute x,y
if len(location) != 2 {
return fmt.Errorf("invalid click location params: %v", location)
}
return w.DriverExt.WebDriver.Tap(location[0], location[1])
}
if location, ok := action.Params.([]float64); ok { if location, ok := action.Params.([]float64); ok {
// relative x,y of window size // relative x,y of window size: [0.5, 0.5]
if len(location) != 2 { if len(location) != 2 {
return fmt.Errorf("invalid click location params: %v", location) return fmt.Errorf("invalid tap location params: %v", location)
} }
x := location[0] * float64(w.WindowSize.Width) return w.DriverExt.TapXY(location[0], location[1])
y := location[1] * float64(w.WindowSize.Height)
return w.DriverExt.TapFloat(x, y)
} }
// click on name or xpath return fmt.Errorf("invalid %s params: %v", uiTapXY, action.Params)
case uiTap:
if param, ok := action.Params.(string); ok { if param, ok := action.Params.(string); ok {
ele, err := w.findElement(param) return w.DriverExt.Tap(param)
if err != nil {
return errors.Wrap(err, "failed to find element")
}
return ele.Click()
} }
return fmt.Errorf("invalid click params: %v", action.Params) return fmt.Errorf("invalid %s params: %v", uiTap, action.Params)
case uiDoubleClick: case uiDoubleTapXY:
// double click on name or xpath if location, ok := action.Params.([]float64); ok {
// relative x,y of window size: [0.5, 0.5]
if len(location) != 2 {
return fmt.Errorf("invalid tap location params: %v", location)
}
return w.DriverExt.DoubleTapXY(location[0], location[1])
}
return fmt.Errorf("invalid %s params: %v", uiDoubleTapXY, action.Params)
case uiDoubleTap:
if param, ok := action.Params.(string); ok { if param, ok := action.Params.(string); ok {
ele, err := w.findElement(param) return w.DriverExt.DoubleTap(param)
if err != nil {
return errors.Wrap(err, "failed to find element")
}
return ele.DoubleTap()
} }
return fmt.Errorf("invalid click params: %v", action.Params) return fmt.Errorf("invalid %s params: %v", uiDoubleTap, action.Params)
case uiLongClick:
// long click 2s on name or xpath
if param, ok := action.Params.(string); ok {
ele, err := w.findElement(param)
if err != nil {
return errors.Wrap(err, "failed to find element")
}
return ele.TouchAndHold(2)
}
return fmt.Errorf("invalid click params: %v", action.Params)
case uiSwipe: case uiSwipe:
width := w.WindowSize.Width if param, ok := action.Params.(string); ok {
height := w.WindowSize.Height return w.DriverExt.SwipeTo(param)
var fromX, fromY, toX, toY int
if direction, ok := action.Params.(string); ok {
switch direction {
case "up":
fromX, fromY, toX, toY = width/2, height*3/4, width/2, height*1/4
case "down":
fromX, fromY, toX, toY = width/2, height*1/4, width/2, height*3/4
case "left":
fromX, fromY, toX, toY = width*3/4, height/2, width*1/4, height/2
case "right":
fromX, fromY, toX, toY = width*1/4, height/2, width*3/4, height/2
}
} else if params, ok := action.Params.([]int); ok {
if len(params) != 4 {
return fmt.Errorf("invalid swipe params: %v", params)
}
fromX, fromY, toX, toY = params[0], params[1], params[2], params[3]
} else {
return fmt.Errorf("invalid swipe params: %v", action.Params)
} }
return w.DriverExt.WebDriver.Swipe(fromX, fromY, toX, toY) return fmt.Errorf("invalid %s params: %v", uiSwipe, action.Params)
case uiInput: case uiInput:
// input text on current active element // input text on current active element
// append \n to send text with enter // append \n to send text with enter

View File

@@ -10,7 +10,7 @@ func TestIOSSettingsAction(t *testing.T) {
Config: NewConfig("ios ui action on Settings"), Config: NewConfig("ios ui action on Settings"),
TestSteps: []IStep{ TestSteps: []IStep{
NewStep("launch Settings"). NewStep("launch Settings").
IOS().Home().Click("//*[@label='设置']"). IOS().Home().Tap("//*[@label='设置']").
Validate(). Validate().
AssertNameExists("飞行模式"). AssertNameExists("飞行模式").
AssertNameNotExists("飞行模式2"), AssertNameNotExists("飞行模式2"),
@@ -31,7 +31,7 @@ func TestIOSSearchApp(t *testing.T) {
Config: NewConfig("ios ui action on Search App 资源库"), Config: NewConfig("ios ui action on Search App 资源库"),
TestSteps: []IStep{ TestSteps: []IStep{
NewStep("进入 App 资源库 搜索框"). NewStep("进入 App 资源库 搜索框").
IOS().Home().SwipeLeft().Times(2).Click("dewey-search-field"). IOS().Home().SwipeLeft().Times(2).Tap("dewey-search-field").
Validate(). Validate().
AssertNameExists("取消"), AssertNameExists("取消"),
NewStep("搜索抖音"). NewStep("搜索抖音").
@@ -78,13 +78,13 @@ func TestIOSWeixinLive(t *testing.T) {
IOS(). IOS().
Home(). Home().
AppTerminate("com.tencent.xin"). // 关闭已运行的微信,确保启动微信后在「微信」首页 AppTerminate("com.tencent.xin"). // 关闭已运行的微信,确保启动微信后在「微信」首页
Click("微信"). Tap("微信").
Validate(). Validate().
AssertNameExists("通讯录", "微信启动失败,「通讯录」不存在"), AssertNameExists("通讯录", "微信启动失败,「通讯录」不存在"),
NewStep("进入直播页"). NewStep("进入直播页").
IOS(). IOS().
Click("发现").Sleep(5). // 进入「发现页」;等待 5 秒确保加载完成 Tap("发现").Sleep(5). // 进入「发现页」;等待 5 秒确保加载完成
Click([]float64{0.5, 0.3}). // 基于坐标位置点击「直播」TODO通过 OCR 识别「直播」 TapXY(0.5, 0.3). // 基于坐标位置点击「直播」TODO通过 OCR 识别「直播」
Validate(). Validate().
AssertNameExists("直播"), AssertNameExists("直播"),
NewStep("向上滑动 5 次"). NewStep("向上滑动 5 次").
@@ -112,7 +112,7 @@ func TestIOSCameraPhotoCapture(t *testing.T) {
Validate(). Validate().
AssertNameExists("PhotoCapture", "拍照按钮不存在"), AssertNameExists("PhotoCapture", "拍照按钮不存在"),
NewStep("start recording"). NewStep("start recording").
IOS().Click("PhotoCapture"), IOS().Tap("PhotoCapture"),
}, },
} }
fmt.Println(testCase) fmt.Println(testCase)
@@ -140,9 +140,9 @@ func TestIOSCameraVideoCapture(t *testing.T) {
AssertNameExists("VideoCapture", "拍摄按钮不存在"), AssertNameExists("VideoCapture", "拍摄按钮不存在"),
NewStep("start recording"). NewStep("start recording").
IOS(). IOS().
Click("VideoCapture"). // 开始录像 Tap("VideoCapture"). // 开始录像
Sleep(5). Sleep(5).
Click("VideoCapture"), // 停止录像 Tap("VideoCapture"), // 停止录像
}, },
} }
fmt.Println(testCase) fmt.Println(testCase)
@@ -158,7 +158,7 @@ func TestIOSDouyinAction(t *testing.T) {
Config: NewConfig("ios ui action on 抖音"), Config: NewConfig("ios ui action on 抖音"),
TestSteps: []IStep{ TestSteps: []IStep{
NewStep("launch douyin"). NewStep("launch douyin").
IOS().Home().Click("//*[@label='抖音']"). IOS().Home().Tap("//*[@label='抖音']").
Validate(). Validate().
AssertNameExists("首页", "首页 tab 不存在"). AssertNameExists("首页", "首页 tab 不存在").
AssertNameExists("消息", "消息 tab 不存在"), AssertNameExists("消息", "消息 tab 不存在"),