diff --git a/examples/worldcup/main_test.go b/examples/worldcup/main_test.go index 0bb65a14..cf684e93 100644 --- a/examples/worldcup/main_test.go +++ b/examples/worldcup/main_test.go @@ -3,9 +3,12 @@ package main import ( + "os" "testing" "github.com/stretchr/testify/assert" + + "github.com/httprunner/httprunner/v4/hrp" ) func TestConvertTimeToSeconds(t *testing.T) { @@ -44,3 +47,70 @@ func TestMainAndroid(t *testing.T) { wc.EnterLive(bundleID) wc.Start() } + +func init() { + os.Setenv("UDID", "00008030-00194DA421C1802E") +} + +func TestIOSDouyinWorldCupLive(t *testing.T) { + testCase := &hrp.TestCase{ + Config: hrp.NewConfig("直播_抖音_世界杯_ios"). + WithVariables(map[string]interface{}{ + "device": "${ENV(UDID)}", + }). + SetIOS( + hrp.WithUDID("$device"), + hrp.WithLogOn(true), + hrp.WithWDAPort(8700), + hrp.WithWDAMjpegPort(8800), + hrp.WithXCTest("com.gtf.wda.runner.xctrunner"), + ), + TestSteps: []hrp.IStep{ + hrp.NewStep("启动抖音"). + IOS(). + Home(). + AppTerminate("com.ss.iphone.ugc.Aweme"). // 关闭已运行的抖音 + AppLaunch("com.ss.iphone.ugc.Aweme"). + Validate(). + AssertOCRExists("首页", "抖音启动失败,「首页」不存在"), + hrp.NewStep("处理青少年弹窗"). + IOS(). + TapByOCR("我知道了", hrp.WithIgnoreNotFoundError(true)), + hrp.NewStep("点击首页"). + IOS(). + TapByOCR("首页", hrp.WithIndex(-1)).Sleep(5), + hrp.NewStep("点击世界杯页"). + IOS(). + SwipeToTapText("世界杯", + hrp.WithMaxRetryTimes(5), + hrp.WithCustomDirection(0.4, 0.07, 0.6, 0.07), // 滑动 tab,从左到右,解决「世界杯」被遮挡的问题 + hrp.WithScope(0, 0, 1, 0.15), // 限定 tab 区域 + hrp.WithWaitTime(1), + ). + Swipe(0.5, 0.3, 0.5, 0.2), // 少量上划,解决「直播中」未展示的问题 + hrp.NewStep("点击进入直播间"). + IOS(). + LoopTimes(30). // 重复执行 30 次 + TapByOCR("直播中", hrp.WithIdentifier("click_live"), hrp.WithIndex(-1)). + Sleep(30).Back().Sleep(30), + hrp.NewStep("关闭抖音"). + IOS(). + AppTerminate("com.ss.iphone.ugc.Aweme"), + hrp.NewStep("返回主界面,并打开本地时间戳"). + IOS(). + Home().SwipeToTapApp("local", hrp.WithMaxRetryTimes(5)).Sleep(10). + Validate(). + AssertOCRExists("16", "打开本地时间戳失败"), + }, + } + + if err := testCase.Dump2JSON("ios_worldcup_live_douyin_test.json"); err != nil { + t.Fatal(err) + } + + runner := hrp.NewRunner(t).SetSaveTests(true) + err := runner.Run(testCase) + if err != nil { + t.Fatal(err) + } +} diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index cd837ff1..59c0b643 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -872,7 +872,9 @@ func WithScreenShot(fileName ...string) DataOption { } func NewDataOptions(options ...DataOption) *DataOptions { - dataOptions := &DataOptions{} + dataOptions := &DataOptions{ + Data: make(map[string]interface{}), + } for _, option := range options { option(dataOptions) } diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index 7dc10523..d5a19a60 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -159,6 +159,9 @@ func GetIOSDeviceOptions(dev *IOSDevice) (deviceOptions []IOSDeviceOption) { if dev.PerfOptions != nil { deviceOptions = append(deviceOptions, WithPerfOptions(dev.perfOpitons()...)) } + if dev.XCTestBundleID != "" { + deviceOptions = append(deviceOptions, WithXCTest(dev.XCTestBundleID)) + } if dev.ResetHomeOnStartup { deviceOptions = append(deviceOptions, WithResetHomeOnStartup(true)) } diff --git a/hrp/step_mobile_ui.go b/hrp/step_mobile_ui.go index 4dcf2be8..ebd65728 100644 --- a/hrp/step_mobile_ui.go +++ b/hrp/step_mobile_ui.go @@ -22,6 +22,7 @@ var ( WithAcceptAlertButtonSelector = uixt.WithAcceptAlertButtonSelector WithDismissAlertButtonSelector = uixt.WithDismissAlertButtonSelector WithPerfOptions = uixt.WithPerfOptions + WithXCTest = uixt.WithXCTest ) // android setting options @@ -34,6 +35,7 @@ var ( type MobileStep struct { Serial string `json:"serial,omitempty" yaml:"serial,omitempty"` + Times int `json:"times,omitempty" yaml:"times,omitempty"` uixt.MobileAction `yaml:",inline"` Actions []uixt.MobileAction `json:"actions,omitempty" yaml:"actions,omitempty"` } @@ -301,25 +303,9 @@ func (s *StepMobile) Input(text string, options ...uixt.ActionOption) *StepMobil return &StepMobile{step: s.step} } -// Times specify running times for run last action -func (s *StepMobile) Times(n int) *StepMobile { - if n <= 0 { - log.Warn().Int("n", n).Msg("times should be positive, set to 1") - n = 1 - } - - mobileStep := s.mobileStep() - actionsTotal := len(mobileStep.Actions) - if actionsTotal == 0 { - return s - } - - // actionsTotal >=1 && n >= 1 - lastAction := mobileStep.Actions[actionsTotal-1 : actionsTotal][0] - for i := 0; i < n-1; i++ { - // duplicate last action n-1 times - mobileStep.Actions = append(mobileStep.Actions, lastAction) - } +// LoopTimes specify running times for the current step +func (s *StepMobile) LoopTimes(n int) *StepMobile { + s.mobileStep().Times = n return &StepMobile{step: s.step} } @@ -622,20 +608,33 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err actions = mobileStep.Actions } - // run actions - for _, action := range actions { - if action.Params, err = s.caseRunner.parser.Parse(action.Params, stepVariables); err != nil { - if !code.IsErrorPredefined(err) { - err = errors.Wrap(code.ParseError, - fmt.Sprintf("parse action params failed: %v", err)) + // run times + times := mobileStep.Times + if times < 0 { + log.Warn().Int("times", times).Msg("times should be positive, set to 1") + times = 1 + } else if times == 0 { + times = 1 + } else if times > 1 { + log.Info().Int("times", times).Msg("run actions with specified times") + } + + // run actions with specified times + for i := 0; i < times; i++ { + for _, action := range actions { + if action.Params, err = s.caseRunner.parser.Parse(action.Params, stepVariables); err != nil { + if !code.IsErrorPredefined(err) { + err = errors.Wrap(code.ParseError, + fmt.Sprintf("parse action params failed: %v", err)) + } + return stepResult, err } - return stepResult, err - } - if err := uiDriver.DoAction(action); err != nil { - if !code.IsErrorPredefined(err) { - err = errors.Wrap(code.MobileUIDriverError, err.Error()) + if err := uiDriver.DoAction(action); err != nil { + if !code.IsErrorPredefined(err) { + err = errors.Wrap(code.MobileUIDriverError, err.Error()) + } + return stepResult, err } - return stepResult, err } } diff --git a/hrp/step_mobile_ui_test.go b/hrp/step_mobile_ui_test.go index c9c7dae3..75f4cfe7 100644 --- a/hrp/step_mobile_ui_test.go +++ b/hrp/step_mobile_ui_test.go @@ -32,7 +32,7 @@ func TestIOSSearchApp(t *testing.T) { Config: NewConfig("ios ui action on Search App 资源库"), TestSteps: []IStep{ NewStep("进入 App 资源库 搜索框"). - IOS().Home().SwipeLeft().Times(2).Tap("dewey-search-field"). + IOS().Home().SwipeLeft().SwipeLeft().Tap("dewey-search-field"). Validate(). AssertLabelExists("取消"), NewStep("搜索抖音"). @@ -84,10 +84,10 @@ func TestIOSWeixinLive(t *testing.T) { TapByOCR("直播"). // 通过 OCR 识别「直播」 Validate(). AssertLabelExists("直播"), - NewStep("向上滑动 5 次"). + NewStep("向上滑动 3 次,截图保存"). IOS(). - SwipeUp().Times(3).ScreenShot(). // 上划 3 次,截图保存 - SwipeUp().Times(2).ScreenShot(), // 再上划 2 次,截图保存 + LoopTimes(3). // 整体循环 3 次 + SwipeUp().SwipeUp().ScreenShot(), // 上划 2 次,截图保存 }, } err := NewRunner(t).Run(testCase) @@ -154,7 +154,9 @@ func TestIOSDouyinAction(t *testing.T) { AssertLabelExists("首页", "首页 tab 不存在"). AssertLabelExists("消息", "消息 tab 不存在"), NewStep("swipe up and down"). - IOS().SwipeUp().Times(3).SwipeDown(), + IOS(). + LoopTimes(3). + SwipeUp().SwipeDown(), }, } err := NewRunner(t).Run(testCase)