mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-12 16:01:27 +08:00
change: tap & swipe
This commit is contained in:
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 不存在"),
|
||||||
|
|||||||
Reference in New Issue
Block a user