mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-13 00:11:28 +08:00
feat: SwipeToTapApp, SwipeToTapText
This commit is contained in:
45
examples/uitest/demo_weixin_test.go
Normal file
45
examples/uitest/demo_weixin_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package uitest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIOSWeixinLive(t *testing.T) {
|
||||||
|
testCase := &hrp.TestCase{
|
||||||
|
Config: hrp.NewConfig("通过 feed 卡片进入微信直播间"),
|
||||||
|
TestSteps: []hrp.IStep{
|
||||||
|
hrp.NewStep("启动微信").
|
||||||
|
IOS().
|
||||||
|
Home().
|
||||||
|
AppTerminate("com.tencent.xin"). // 关闭已运行的微信,确保启动微信后在「微信」首页
|
||||||
|
SwipeToTapApp("微信", hrp.WithMaxRetryTimes(5)).
|
||||||
|
Validate().
|
||||||
|
AssertLabelExists("通讯录", "微信启动失败,「通讯录」不存在"),
|
||||||
|
hrp.NewStep("进入直播页").
|
||||||
|
IOS().
|
||||||
|
Tap("发现"). // 进入「发现页」
|
||||||
|
TapByOCR("视频号"). // 通过 OCR 识别「视频号」
|
||||||
|
Validate().
|
||||||
|
AssertLabelExists("视频号"),
|
||||||
|
hrp.NewStep("处理青少年弹窗").
|
||||||
|
IOS().
|
||||||
|
TapByOCR("我知道了", hrp.WithIgnoreNotFoundError(false)),
|
||||||
|
hrp.NewStep("在推荐页上划,直到出现「轻触进入直播间」").
|
||||||
|
IOS().
|
||||||
|
SwipeToTapText("轻触进入直播间", hrp.WithMaxRetryTimes(10)),
|
||||||
|
hrp.NewStep("向上滑动,等待 60s").
|
||||||
|
IOS().
|
||||||
|
SwipeUp().Sleep(60).ScreenShot(). // 上划 1 次,等待 60s,截图保存
|
||||||
|
SwipeUp().Times(60).ScreenShot(), // 再上划 1 次,等待 60s,截图保存
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fmt.Println(testCase)
|
||||||
|
|
||||||
|
err := hrp.NewRunner(t).Run(testCase)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
11
hrp/step.go
11
hrp/step.go
@@ -49,18 +49,29 @@ const (
|
|||||||
uiSelectorImage string = "ui_image"
|
uiSelectorImage string = "ui_image"
|
||||||
assertionExists string = "exists"
|
assertionExists string = "exists"
|
||||||
assertionNotExists string = "not_exists"
|
assertionNotExists string = "not_exists"
|
||||||
|
|
||||||
|
// custom actions
|
||||||
|
swipeToTapApp MobileMethod = "swipe_to_tap_app" // swipe left & right to find app and tap
|
||||||
|
swipeToTapText MobileMethod = "swipe_to_tap_text" // swipe up & down to find text and tap
|
||||||
)
|
)
|
||||||
|
|
||||||
type MobileAction struct {
|
type MobileAction struct {
|
||||||
Method MobileMethod `json:"method" yaml:"method"`
|
Method MobileMethod `json:"method" yaml:"method"`
|
||||||
Params interface{} `json:"params,omitempty" yaml:"params,omitempty"`
|
Params interface{} `json:"params,omitempty" yaml:"params,omitempty"`
|
||||||
|
|
||||||
|
maxRetryTimes int // max retry times
|
||||||
timeout int // TODO: wait timeout in seconds for mobile action
|
timeout int // TODO: wait timeout in seconds for mobile action
|
||||||
ignoreNotFoundError bool // ignore error if target element not found
|
ignoreNotFoundError bool // ignore error if target element not found
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionOption func(o *MobileAction)
|
type ActionOption func(o *MobileAction)
|
||||||
|
|
||||||
|
func WithMaxRetryTimes(maxRetryTimes int) ActionOption {
|
||||||
|
return func(o *MobileAction) {
|
||||||
|
o.maxRetryTimes = maxRetryTimes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func WithTimeout(timeout int) ActionOption {
|
func WithTimeout(timeout int) ActionOption {
|
||||||
return func(o *MobileAction) {
|
return func(o *MobileAction) {
|
||||||
o.timeout = timeout
|
o.timeout = timeout
|
||||||
|
|||||||
@@ -187,6 +187,38 @@ func (s *StepIOS) SwipeRight() *StepIOS {
|
|||||||
return &StepIOS{step: s.step}
|
return &StepIOS{step: s.step}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StepIOS) SwipeToTapApp(appName string, options ...ActionOption) *StepIOS {
|
||||||
|
action := MobileAction{
|
||||||
|
Method: swipeToTapApp,
|
||||||
|
Params: appName,
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
option(&action)
|
||||||
|
}
|
||||||
|
// default to retry 5 times
|
||||||
|
if action.maxRetryTimes == 0 {
|
||||||
|
action.maxRetryTimes = 5
|
||||||
|
}
|
||||||
|
s.step.IOS.Actions = append(s.step.IOS.Actions, action)
|
||||||
|
return &StepIOS{step: s.step}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepIOS) SwipeToTapText(text string, options ...ActionOption) *StepIOS {
|
||||||
|
action := MobileAction{
|
||||||
|
Method: swipeToTapText,
|
||||||
|
Params: text,
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
option(&action)
|
||||||
|
}
|
||||||
|
// default to retry 10 times
|
||||||
|
if action.maxRetryTimes == 0 {
|
||||||
|
action.maxRetryTimes = 10
|
||||||
|
}
|
||||||
|
s.step.IOS.Actions = append(s.step.IOS.Actions, action)
|
||||||
|
return &StepIOS{step: s.step}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StepIOS) Input(text string) *StepIOS {
|
func (s *StepIOS) Input(text string) *StepIOS {
|
||||||
s.step.IOS.Actions = append(s.step.IOS.Actions, MobileAction{
|
s.step.IOS.Actions = append(s.step.IOS.Actions, MobileAction{
|
||||||
Method: uiInput,
|
Method: uiInput,
|
||||||
@@ -519,12 +551,60 @@ func (ud *uiDriver) doAction(action MobileAction) error {
|
|||||||
if bundleId, ok := action.Params.(string); ok {
|
if bundleId, ok := action.Params.(string); ok {
|
||||||
return ud.AppLaunch(bundleId)
|
return ud.AppLaunch(bundleId)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("app_launch params should be bundleId(string), got %v", action.Params)
|
return fmt.Errorf("invalid %s params, should be bundleId(string), got %v",
|
||||||
|
appLaunch, action.Params)
|
||||||
case appLaunchUnattached:
|
case appLaunchUnattached:
|
||||||
if bundleId, ok := action.Params.(string); ok {
|
if bundleId, ok := action.Params.(string); ok {
|
||||||
return ud.AppLaunchUnattached(bundleId)
|
return ud.AppLaunchUnattached(bundleId)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("app_launch_unattached params should be bundleId(string), got %v", action.Params)
|
return fmt.Errorf("invalid %s params, should be bundleId(string), got %v",
|
||||||
|
appLaunchUnattached, action.Params)
|
||||||
|
case swipeToTapApp:
|
||||||
|
if appName, ok := action.Params.(string); ok {
|
||||||
|
var x, y, width, height float64
|
||||||
|
findApp := func(d *uixt.DriverExt) error {
|
||||||
|
var err error
|
||||||
|
x, y, width, height, err = d.FindTextByOCR(appName)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
foundAppAction := func(d *uixt.DriverExt) error {
|
||||||
|
// click app to launch
|
||||||
|
return d.TapFloat(x+width*0.5, y+height*0.5-20)
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to home screen
|
||||||
|
if err := ud.WebDriver.Homescreen(); err != nil {
|
||||||
|
return errors.Wrap(err, "go to home screen failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// swipe to first screen
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
ud.SwipeTo("right")
|
||||||
|
}
|
||||||
|
|
||||||
|
// swipe next screen until app found
|
||||||
|
return ud.SwipeUntil("left", findApp, foundAppAction, action.maxRetryTimes)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid %s params, should be app name(string), got %v",
|
||||||
|
swipeToTapApp, action.Params)
|
||||||
|
case swipeToTapText:
|
||||||
|
if text, ok := action.Params.(string); ok {
|
||||||
|
var x, y, width, height float64
|
||||||
|
findText := func(d *uixt.DriverExt) error {
|
||||||
|
var err error
|
||||||
|
x, y, width, height, err = d.FindTextByOCR(text)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
foundTextAction := func(d *uixt.DriverExt) error {
|
||||||
|
// tap text
|
||||||
|
return d.TapFloat(x+width*0.5, y+height*0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// swipe until live room found
|
||||||
|
return ud.SwipeUntil("up", findText, foundTextAction, 20)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid %s params, should be app text(string), got %v",
|
||||||
|
swipeToTapText, action.Params)
|
||||||
case appTerminate:
|
case appTerminate:
|
||||||
if bundleId, ok := action.Params.(string); ok {
|
if bundleId, ok := action.Params.(string); ok {
|
||||||
success, err := ud.AppTerminate(bundleId)
|
success, err := ud.AppTerminate(bundleId)
|
||||||
|
|||||||
Reference in New Issue
Block a user