refactor: move action options to pkg/uixt/options/action

This commit is contained in:
lilong.129
2025-02-06 16:42:16 +08:00
parent f1ef161377
commit 0accec04a9
43 changed files with 982 additions and 911 deletions
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin" "github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/pkg/uixt" "github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@@ -134,7 +135,7 @@ func convertCompatMobileStep(mobileUI *MobileUI) {
} }
for i := 0; i < len(mobileUI.Actions); i++ { for i := 0; i < len(mobileUI.Actions); i++ {
ma := mobileUI.Actions[i] ma := mobileUI.Actions[i]
actionOptions := uixt.NewActionOptions(ma.GetOptions()...) actionOptions := options.NewActionOptions(ma.GetOptions()...)
// append tap_cv params to screenshot_with_ui_types option // append tap_cv params to screenshot_with_ui_types option
if ma.Method == uixt.ACTION_TapByCV { if ma.Method == uixt.ACTION_TapByCV {
uiTypes, _ := builtin.ConvertToStringSlice(ma.Params) uiTypes, _ := builtin.ConvertToStringSlice(ma.Params)
+6 -7
View File
@@ -4,7 +4,6 @@ import (
"testing" "testing"
hrp "github.com/httprunner/httprunner/v5" hrp "github.com/httprunner/httprunner/v5"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
@@ -26,8 +25,8 @@ func TestAndroidDouyinE2E(t *testing.T) {
Home(). Home().
SwipeToTapApp( SwipeToTapApp(
"抖音", "抖音",
uixt.WithMaxRetryTimes(5), options.WithMaxRetryTimes(5),
uixt.WithTapOffset(0, -50), options.WithTapOffset(0, -50),
). ).
Sleep(20). Sleep(20).
Validate(). Validate().
@@ -48,11 +47,11 @@ func TestAndroidDouyinE2E(t *testing.T) {
Android(). Android().
TapByOCR( TapByOCR(
"直播中", "直播中",
uixt.WithIgnoreNotFoundError(true), options.WithIgnoreNotFoundError(true),
uixt.WithIndex(-1), options.WithIndex(-1),
). ).
EndToEndDelay(uixt.WithInterval(5), uixt.WithTimeout(120)). EndToEndDelay(options.WithInterval(5), options.WithTimeout(120)).
TapByUITypes(uixt.WithScreenShotUITypes("close")), TapByUITypes(options.WithScreenShotUITypes("close")),
}, },
} }
+2 -2
View File
@@ -59,7 +59,7 @@ func launchAppDriver(pkgName string) (driver *uixt.DriverExt, err error) {
} }
// 进入推荐页 // 进入推荐页
err = driver.TapByOCR("推荐", uixt.WithScope(0, 0, 1, 0.3)) err = driver.TapByOCR("推荐", options.WithScope(0, 0, 1, 0.3))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -92,7 +92,7 @@ func watchVideo(driver *uixt.DriverExt) (err error) {
// 切换横屏 // 切换横屏
err = driver.TapByUIDetection( err = driver.TapByUIDetection(
uixt.WithScreenShotUITypes("fullScreen")) options.WithScreenShotUITypes("fullScreen"))
if err != nil { if err != nil {
// 未找到横屏图标,该页面可能不是横版视频(直播|广告|Feed) // 未找到横屏图标,该页面可能不是横版视频(直播|广告|Feed)
// 退出回到推荐页 // 退出回到推荐页
+2 -2
View File
@@ -59,7 +59,7 @@ func launchAppDriver(pkgName string) (driver *uixt.DriverExt, err error) {
} }
// 进入推荐页 // 进入推荐页
err = driver.TapByOCR("推荐", uixt.WithScope(0, 0, 1, 0.3)) err = driver.TapByOCR("推荐", options.WithScope(0, 0, 1, 0.3))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -92,7 +92,7 @@ func watchVideo(driver *uixt.DriverExt) (err error) {
// 切换横屏 // 切换横屏
err = driver.TapByUIDetection( err = driver.TapByUIDetection(
uixt.WithScreenShotUITypes("fullScreen")) options.WithScreenShotUITypes("fullScreen"))
if err != nil { if err != nil {
// 未找到横屏图标,该页面可能不是横版视频(直播|广告|Feed) // 未找到横屏图标,该页面可能不是横版视频(直播|广告|Feed)
// 退出回到推荐页 // 退出回到推荐页
@@ -6,7 +6,6 @@ import (
"testing" "testing"
hrp "github.com/httprunner/httprunner/v5" hrp "github.com/httprunner/httprunner/v5"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
@@ -27,7 +26,7 @@ func TestAndroidDouyinFeedTest(t *testing.T) {
AssertAppInForeground("com.ss.android.ugc.aweme"), AssertAppInForeground("com.ss.android.ugc.aweme"),
hrp.NewStep("处理青少年弹窗"). hrp.NewStep("处理青少年弹窗").
Android(). Android().
TapByOCR("我知道了", uixt.WithIgnoreNotFoundError(true)), TapByOCR("我知道了", options.WithIgnoreNotFoundError(true)),
hrp.NewStep("滑动 Feed 3 次,随机间隔 0-5s"). hrp.NewStep("滑动 Feed 3 次,随机间隔 0-5s").
Loop(3). Loop(3).
Android(). Android().
@@ -6,7 +6,6 @@ import (
"testing" "testing"
hrp "github.com/httprunner/httprunner/v5" hrp "github.com/httprunner/httprunner/v5"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
@@ -27,10 +26,10 @@ func TestAndroidLiveSwipeTest(t *testing.T) {
AssertAppInForeground("com.ss.android.ugc.aweme"), AssertAppInForeground("com.ss.android.ugc.aweme"),
hrp.NewStep("处理青少年弹窗"). hrp.NewStep("处理青少年弹窗").
Android(). Android().
TapByOCR("我知道了", uixt.WithIgnoreNotFoundError(true)), TapByOCR("我知道了", options.WithIgnoreNotFoundError(true)),
hrp.NewStep("在推荐页上划,直到出现「点击进入直播间」"). hrp.NewStep("在推荐页上划,直到出现「点击进入直播间」").
Android(). Android().
SwipeToTapText("点击进入直播间", uixt.WithMaxRetryTimes(10), uixt.WithIdentifier("进入直播间")), SwipeToTapText("点击进入直播间", options.WithMaxRetryTimes(10), options.WithIdentifier("进入直播间")),
hrp.NewStep("滑动 Feed 5 次,60% 随机间隔 0-5s40% 随机间隔 5-10s"). hrp.NewStep("滑动 Feed 5 次,60% 随机间隔 0-5s40% 随机间隔 5-10s").
Loop(5). Loop(5).
Android(). Android().
@@ -38,8 +37,8 @@ func TestAndroidLiveSwipeTest(t *testing.T) {
SleepRandom(0, 5, 0.6, 5, 10, 0.4), SleepRandom(0, 5, 0.6, 5, 10, 0.4),
hrp.NewStep("向上滑动,等待 10s"). hrp.NewStep("向上滑动,等待 10s").
Android(). Android().
SwipeUp(uixt.WithIdentifier("第一次上划")).Sleep(5).ScreenShot(). // 上划 1 次,等待 5s,截图保存 SwipeUp(options.WithIdentifier("第一次上划")).Sleep(5).ScreenShot(). // 上划 1 次,等待 5s,截图保存
SwipeUp(uixt.WithIdentifier("第二次上划")).Sleep(5).ScreenShot(), // 再上划 1 次,等待 5s,截图保存 SwipeUp(options.WithIdentifier("第二次上划")).Sleep(5).ScreenShot(), // 再上划 1 次,等待 5s,截图保存
hrp.NewStep("exit"). hrp.NewStep("exit").
Android(). Android().
AppTerminate("com.ss.android.ugc.aweme"). AppTerminate("com.ss.android.ugc.aweme").
@@ -6,7 +6,6 @@ import (
"testing" "testing"
hrp "github.com/httprunner/httprunner/v5" hrp "github.com/httprunner/httprunner/v5"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
@@ -26,22 +25,34 @@ func TestIOSDouyinFollowLive(t *testing.T) {
IOS(). IOS().
Home(). Home().
AppTerminate("com.ss.iphone.ugc.Aweme"). // 关闭已运行的抖音 AppTerminate("com.ss.iphone.ugc.Aweme"). // 关闭已运行的抖音
SwipeToTapApp("$app_name", uixt.WithMaxRetryTimes(5), uixt.WithIdentifier("启动抖音")).Sleep(5). SwipeToTapApp("$app_name",
options.WithMaxRetryTimes(5),
options.WithIdentifier("启动抖音")).
Sleep(5).
Validate(). Validate().
AssertOCRExists("推荐", "抖音启动失败,「推荐」不存在"), AssertOCRExists("推荐", "抖音启动失败,「推荐」不存在"),
hrp.NewStep("处理青少年弹窗"). hrp.NewStep("处理青少年弹窗").
IOS(). IOS().
TapByOCR("我知道了", uixt.WithIgnoreNotFoundError(true)), TapByOCR("我知道了", options.WithIgnoreNotFoundError(true)),
hrp.NewStep("点击首页"). hrp.NewStep("点击首页").
IOS(). IOS().
TapByOCR("首页", uixt.WithIndex(-1)).Sleep(10), TapByOCR("首页", options.WithIndex(-1)).Sleep(10),
hrp.NewStep("点击关注页"). hrp.NewStep("点击关注页").
IOS(). IOS().
TapByOCR("关注", uixt.WithIndex(1)).Sleep(10), TapByOCR("关注", options.WithIndex(1)).Sleep(10),
hrp.NewStep("向上滑动 2 次"). hrp.NewStep("向上滑动 2 次").
IOS().SwipeToTapTexts([]string{"理肤泉", "婉宝"}, uixt.WithCustomDirection(0.6, 0.2, 0.2, 0.2), uixt.WithIdentifier("click_live")).Sleep(10). IOS().
Swipe(0.9, 0.7, 0.9, 0.3, uixt.WithIdentifier("slide_in_live"), uixt.WithOffsetRandomRange(-10, 10)).Sleep(10).ScreenShot(). // 上划 1 次,等待 10s,截图保存 SwipeToTapTexts([]string{"理肤泉", "婉宝"},
Swipe(0.9, 0.7, 0.9, 0.3, uixt.WithIdentifier("slide_in_live"), uixt.WithOffsetRandomRange(-10, 10)).Sleep(10).ScreenShot(), // 再上划 1 次,等待 10s,截图保存 options.WithCustomDirection(0.6, 0.2, 0.2, 0.2),
options.WithIdentifier("click_live")).Sleep(10).
Swipe(0.9, 0.7, 0.9, 0.3,
options.WithIdentifier("slide_in_live"),
options.WithOffsetRandomRange(-10, 10)).
Sleep(10).ScreenShot(). // 上划 1 次,等待 10s,截图保存
Swipe(0.9, 0.7, 0.9, 0.3,
options.WithIdentifier("slide_in_live"),
options.WithOffsetRandomRange(-10, 10)).
Sleep(10).ScreenShot(), // 再上划 1 次,等待 10s,截图保存
}, },
} }
+9 -6
View File
@@ -6,7 +6,6 @@ import (
"testing" "testing"
hrp "github.com/httprunner/httprunner/v5" hrp "github.com/httprunner/httprunner/v5"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
@@ -26,19 +25,23 @@ func TestIOSDouyinLive(t *testing.T) {
IOS(). IOS().
Home(). Home().
AppTerminate("com.ss.iphone.ugc.Aweme"). // 关闭已运行的抖音 AppTerminate("com.ss.iphone.ugc.Aweme"). // 关闭已运行的抖音
SwipeToTapApp("$app_name", uixt.WithMaxRetryTimes(5), uixt.WithIdentifier("启动抖音")).Sleep(5). SwipeToTapApp("$app_name",
options.WithMaxRetryTimes(5),
options.WithIdentifier("启动抖音")).Sleep(5).
Validate(). Validate().
AssertOCRExists("推荐", "抖音启动失败,「推荐」不存在"), AssertOCRExists("推荐", "抖音启动失败,「推荐」不存在"),
hrp.NewStep("处理青少年弹窗"). hrp.NewStep("处理青少年弹窗").
IOS(). IOS().
TapByOCR("我知道了", uixt.WithIgnoreNotFoundError(true)), TapByOCR("我知道了", options.WithIgnoreNotFoundError(true)),
hrp.NewStep("向上滑动 2 次"). hrp.NewStep("向上滑动 2 次").
IOS(). IOS().
SwipeUp(uixt.WithIdentifier("第一次上划")).Sleep(2).ScreenShot(). // 上划 1 次,等待 2s,截图保存 SwipeUp(options.WithIdentifier("第一次上划")).Sleep(2).ScreenShot(). // 上划 1 次,等待 2s,截图保存
SwipeUp(uixt.WithIdentifier("第二次上划")).Sleep(2).ScreenShot(), // 再上划 1 次,等待 2s,截图保存 SwipeUp(options.WithIdentifier("第二次上划")).Sleep(2).ScreenShot(), // 再上划 1 次,等待 2s,截图保存
hrp.NewStep("在推荐页上划,直到出现「点击进入直播间」"). hrp.NewStep("在推荐页上划,直到出现「点击进入直播间」").
IOS(). IOS().
SwipeToTapText("点击进入直播间", uixt.WithMaxRetryTimes(10), uixt.WithIdentifier("进入直播间")), SwipeToTapText("点击进入直播间",
options.WithMaxRetryTimes(10),
options.WithIdentifier("进入直播间")),
}, },
} }
+11 -10
View File
@@ -6,7 +6,6 @@ import (
"testing" "testing"
hrp "github.com/httprunner/httprunner/v5" hrp "github.com/httprunner/httprunner/v5"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
@@ -26,23 +25,25 @@ func TestWDALog(t *testing.T) {
IOS(). IOS().
Home(). Home().
AppTerminate("com.ss.iphone.ugc.Aweme"). // 关闭已运行的抖音 AppTerminate("com.ss.iphone.ugc.Aweme"). // 关闭已运行的抖音
SwipeToTapApp("$app_name", uixt.WithMaxRetryTimes(5), uixt.WithIdentifier("启动抖音")).Sleep(5). SwipeToTapApp("$app_name",
options.WithMaxRetryTimes(5),
options.WithIdentifier("启动抖音")).Sleep(5).
Validate(). Validate().
AssertOCRExists("推荐", "抖音启动失败,「推荐」不存在"), AssertOCRExists("推荐", "抖音启动失败,「推荐」不存在"),
hrp.NewStep("处理青少年弹窗"). hrp.NewStep("处理青少年弹窗").
IOS(). IOS().
TapByOCR("我知道了", uixt.WithIgnoreNotFoundError(true)), TapByOCR("我知道了", options.WithIgnoreNotFoundError(true)),
hrp.NewStep("进入购物页"). hrp.NewStep("进入购物页").
IOS().TapByOCR("商城", uixt.WithIdentifier("点击商城")).Sleep(5), IOS().TapByOCR("商城", options.WithIdentifier("点击商城")).Sleep(5),
hrp.NewStep("进入推荐页"). hrp.NewStep("进入推荐页").
IOS().TapByOCR("推荐", uixt.WithIdentifier("点击推荐")).Sleep(5), IOS().TapByOCR("推荐", options.WithIdentifier("点击推荐")).Sleep(5),
hrp.NewStep("向上滑动 2 次"). hrp.NewStep("向上滑动 2 次").
IOS(). IOS().
SwipeUp(uixt.WithIdentifier("第 1 次上划")).Sleep(2). SwipeUp(options.WithIdentifier("第 1 次上划")).Sleep(2).
SwipeUp(uixt.WithIdentifier("第 2 次上划")).Sleep(2). SwipeUp(options.WithIdentifier("第 2 次上划")).Sleep(2).
SwipeUp(uixt.WithIdentifier("第 3 次上划")).Sleep(2). SwipeUp(options.WithIdentifier("第 3 次上划")).Sleep(2).
TapXY(0.9, 0.1, uixt.WithIdentifier("点击进入搜索框")).Sleep(2). TapXY(0.9, 0.1, options.WithIdentifier("点击进入搜索框")).Sleep(2).
Input("httprunner", uixt.WithIdentifier("输入搜索关键词")), Input("httprunner", options.WithIdentifier("输入搜索关键词")),
}, },
} }
+32 -33
View File
@@ -4,7 +4,6 @@ import (
"testing" "testing"
hrp "github.com/httprunner/httprunner/v5" hrp "github.com/httprunner/httprunner/v5"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
@@ -45,11 +44,11 @@ func TestAndroidExpertTest(t *testing.T) {
Android(). Android().
SwipeToTapTexts( SwipeToTapTexts(
[]string{"直播", "直播中", "点击进入直播间"}, []string{"直播", "直播中", "点击进入直播间"},
uixt.WithCustomDirection(0.5, 0.7, 0.5, 0.3), options.WithCustomDirection(0.5, 0.7, 0.5, 0.3),
uixt.WithScope(0.2, 0.2, 1, 0.8), options.WithScope(0.2, 0.2, 1, 0.8),
uixt.WithMaxRetryTimes(50), options.WithMaxRetryTimes(50),
uixt.WithWaitTime(1.5), options.WithWaitTime(1.5),
uixt.WithIdentifier("click_live"), options.WithIdentifier("click_live"),
), ),
hrp.NewStep("sleep 10s"). hrp.NewStep("sleep 10s").
Android(). Android().
@@ -58,7 +57,7 @@ func TestAndroidExpertTest(t *testing.T) {
Android(). Android().
Swipe( Swipe(
0.5, 0.7, 0.5, 0.3, 0.5, 0.7, 0.5, 0.3,
uixt.WithIdentifier("slide_in_live"), options.WithIdentifier("slide_in_live"),
). ).
Sleep(5). Sleep(5).
Back(). Back().
@@ -68,22 +67,22 @@ func TestAndroidExpertTest(t *testing.T) {
Android(). Android().
TapXY( TapXY(
0.9, 0.08, 0.9, 0.08,
uixt.WithIdentifier("click_search_in_middle_page"), options.WithIdentifier("click_search_in_middle_page"),
). ).
Sleep(5), Sleep(5),
hrp.NewStep("【搜索】输入query词 input"). hrp.NewStep("【搜索】输入query词 input").
Android(). Android().
Input( Input(
"$query", "$query",
uixt.WithIdentifier("input_query"), options.WithIdentifier("input_query"),
). ).
Sleep(5), Sleep(5),
hrp.NewStep("【搜索】点击搜索按钮 tap_ocr 自定义配置"). hrp.NewStep("【搜索】点击搜索按钮 tap_ocr 自定义配置").
Android(). Android().
TapByOCR( TapByOCR(
"搜索", "搜索",
uixt.WithIdentifier("click_search_after_input_query"), options.WithIdentifier("click_search_after_input_query"),
uixt.WithIndex(0), options.WithIndex(0),
). ).
Sleep(5), Sleep(5),
hrp.NewStep("选择直播页签 tap_ocr 默认配置"). hrp.NewStep("选择直播页签 tap_ocr 默认配置").
@@ -98,8 +97,8 @@ func TestAndroidExpertTest(t *testing.T) {
hrp.NewStep("【生活服务】点击货架商品 tap_ocr 自定义配置"). hrp.NewStep("【生活服务】点击货架商品 tap_ocr 自定义配置").
Android(). Android().
TapByUITypes( TapByUITypes(
uixt.WithScreenShotUITypes("dyhouse", "shoppingbag"), options.WithScreenShotUITypes("dyhouse", "shoppingbag"),
uixt.WithIdentifier("click_sales_rack"), options.WithIdentifier("click_sales_rack"),
). ).
Sleep(5), Sleep(5),
// 冷启动 // 冷启动
@@ -112,13 +111,13 @@ func TestAndroidExpertTest(t *testing.T) {
hrp.NewStep("home 以及 swipe_to_tap_app 自定义配置"). hrp.NewStep("home 以及 swipe_to_tap_app 自定义配置").
Android(). Android().
Home(). Home().
SwipeToTapApp("$app_name", uixt.WithMaxRetryTimes(5), uixt.WithInterval(1), uixt.WithTapOffset(0, -50)). SwipeToTapApp("$app_name", options.WithMaxRetryTimes(5), options.WithInterval(1), options.WithTapOffset(0, -50)).
Sleep(10), Sleep(10),
hrp.NewStep("处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言"). hrp.NewStep("处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言").
Android(). Android().
ClosePopups( ClosePopups(
uixt.WithMaxRetryTimes(3), options.WithMaxRetryTimes(3),
uixt.WithInterval(2), options.WithInterval(2),
). ).
Validate(). Validate().
AssertOCRExists("推荐", "进入抖音失败"), AssertOCRExists("推荐", "进入抖音失败"),
@@ -128,7 +127,7 @@ func TestAndroidExpertTest(t *testing.T) {
Home(). Home().
AppTerminate("$bundle_id"). AppTerminate("$bundle_id").
Sleep(3). Sleep(3).
SwipeToTapApp("local", uixt.WithMaxRetryTimes(5)).Sleep(10), SwipeToTapApp("local", options.WithMaxRetryTimes(5)).Sleep(10),
hrp.NewStep("screeshot 以及 sleep_random"). hrp.NewStep("screeshot 以及 sleep_random").
Loop(3). Loop(3).
Android(). Android().
@@ -179,11 +178,11 @@ func TestIOSExpertTest(t *testing.T) {
IOS(). IOS().
SwipeToTapTexts( SwipeToTapTexts(
[]string{"直播", "直播中", "点击进入直播间"}, []string{"直播", "直播中", "点击进入直播间"},
uixt.WithCustomDirection(0.5, 0.7, 0.5, 0.3), options.WithCustomDirection(0.5, 0.7, 0.5, 0.3),
uixt.WithScope(0.2, 0.2, 1, 0.8), options.WithScope(0.2, 0.2, 1, 0.8),
uixt.WithMaxRetryTimes(50), options.WithMaxRetryTimes(50),
uixt.WithWaitTime(1.5), options.WithWaitTime(1.5),
uixt.WithIdentifier("click_live"), options.WithIdentifier("click_live"),
), ),
hrp.NewStep("sleep 10s"). hrp.NewStep("sleep 10s").
IOS(). IOS().
@@ -192,7 +191,7 @@ func TestIOSExpertTest(t *testing.T) {
IOS(). IOS().
Swipe( Swipe(
0.5, 0.7, 0.5, 0.3, 0.5, 0.7, 0.5, 0.3,
uixt.WithIdentifier("slide_in_live"), options.WithIdentifier("slide_in_live"),
). ).
Sleep(5). Sleep(5).
Back(). Back().
@@ -202,22 +201,22 @@ func TestIOSExpertTest(t *testing.T) {
IOS(). IOS().
TapXY( TapXY(
0.9, 0.075, 0.9, 0.075,
uixt.WithIdentifier("click_search_in_middle_page"), options.WithIdentifier("click_search_in_middle_page"),
). ).
Sleep(5), Sleep(5),
hrp.NewStep("【搜索】输入query词 input"). hrp.NewStep("【搜索】输入query词 input").
IOS(). IOS().
Input( Input(
"$query", "$query",
uixt.WithIdentifier("input_query"), options.WithIdentifier("input_query"),
). ).
Sleep(5), Sleep(5),
hrp.NewStep("【搜索】点击搜索按钮 tap_ocr 自定义配置"). hrp.NewStep("【搜索】点击搜索按钮 tap_ocr 自定义配置").
IOS(). IOS().
TapByOCR( TapByOCR(
"搜索", "搜索",
uixt.WithIdentifier("click_search_after_input_query"), options.WithIdentifier("click_search_after_input_query"),
uixt.WithIndex(0), options.WithIndex(0),
). ).
Sleep(5), Sleep(5),
// 生活服务赛道 // 生活服务赛道
@@ -232,8 +231,8 @@ func TestIOSExpertTest(t *testing.T) {
hrp.NewStep("【生活服务】点击货架商品 tap_ocr 自定义配置"). hrp.NewStep("【生活服务】点击货架商品 tap_ocr 自定义配置").
IOS(). IOS().
TapByUITypes( TapByUITypes(
uixt.WithScreenShotUITypes("dyhouse", "shoppingbag"), options.WithScreenShotUITypes("dyhouse", "shoppingbag"),
uixt.WithIdentifier("click_sales_rack"), options.WithIdentifier("click_sales_rack"),
). ).
Sleep(5), Sleep(5),
// 冷启动 // 冷启动
@@ -245,13 +244,13 @@ func TestIOSExpertTest(t *testing.T) {
hrp.NewStep("home 以及 swipe_to_tap_app 自定义配置"). hrp.NewStep("home 以及 swipe_to_tap_app 自定义配置").
IOS(). IOS().
Home(). Home().
SwipeToTapApp("$app_name", uixt.WithMaxRetryTimes(5), uixt.WithInterval(1), uixt.WithTapOffset(0, -50)). SwipeToTapApp("$app_name", options.WithMaxRetryTimes(5), options.WithInterval(1), options.WithTapOffset(0, -50)).
Sleep(10), Sleep(10),
hrp.NewStep("处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言"). hrp.NewStep("处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言").
IOS(). IOS().
ClosePopups( ClosePopups(
uixt.WithMaxRetryTimes(3), options.WithMaxRetryTimes(3),
uixt.WithInterval(2), options.WithInterval(2),
). ).
Validate(). Validate().
AssertOCRExists("推荐", "进入抖音失败"), AssertOCRExists("推荐", "进入抖音失败"),
@@ -261,7 +260,7 @@ func TestIOSExpertTest(t *testing.T) {
Home(). Home().
AppTerminate("$bundle_id"). AppTerminate("$bundle_id").
Sleep(3). Sleep(3).
SwipeToTapApp("local", uixt.WithMaxRetryTimes(5)).Sleep(10), SwipeToTapApp("local", options.WithMaxRetryTimes(5)).Sleep(10),
hrp.NewStep("screeshot 以及 sleep_random"). hrp.NewStep("screeshot 以及 sleep_random").
Loop(3). Loop(3).
IOS(). IOS().
+6 -7
View File
@@ -4,7 +4,6 @@ import (
"testing" "testing"
hrp "github.com/httprunner/httprunner/v5" hrp "github.com/httprunner/httprunner/v5"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
@@ -26,8 +25,8 @@ func TestHarmonyDouyinE2E(t *testing.T) {
Home(). Home().
SwipeToTapApp( SwipeToTapApp(
"抖音", "抖音",
uixt.WithMaxRetryTimes(5), options.WithMaxRetryTimes(5),
uixt.WithTapOffset(0, -50), options.WithTapOffset(0, -50),
). ).
Sleep(20). Sleep(20).
Validate(). Validate().
@@ -48,11 +47,11 @@ func TestHarmonyDouyinE2E(t *testing.T) {
Harmony(). Harmony().
TapByOCR( TapByOCR(
"直播中", "直播中",
uixt.WithIgnoreNotFoundError(true), options.WithIgnoreNotFoundError(true),
uixt.WithIndex(-1), options.WithIndex(-1),
). ).
EndToEndDelay(uixt.WithInterval(5), uixt.WithTimeout(120)). EndToEndDelay(options.WithInterval(5), options.WithTimeout(120)).
TapByUITypes(uixt.WithScreenShotUITypes("close")), TapByUITypes(options.WithScreenShotUITypes("close")),
}, },
} }
+9 -8
View File
@@ -8,7 +8,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
hrp "github.com/httprunner/httprunner/v5" hrp "github.com/httprunner/httprunner/v5"
"github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
@@ -67,24 +66,26 @@ func TestIOSDouyinWorldCupLive(t *testing.T) {
Home(). Home().
AppTerminate("$appBundleID"). // 关闭已运行的抖音 AppTerminate("$appBundleID"). // 关闭已运行的抖音
AppLaunch("$appBundleID"). AppLaunch("$appBundleID").
TapByOCR("我知道了", uixt.WithIgnoreNotFoundError(true)). // 处理青少年弹窗 TapByOCR("我知道了", options.WithIgnoreNotFoundError(true)). // 处理青少年弹窗
Validate(). Validate().
AssertOCRExists("首页", "抖音启动失败,「首页」不存在"), AssertOCRExists("首页", "抖音启动失败,「首页」不存在"),
hrp.NewStep("点击首页"). hrp.NewStep("点击首页").
IOS(). IOS().
TapByOCR("首页", uixt.WithIndex(-1)).Sleep(2), TapByOCR("首页", options.WithIndex(-1)).Sleep(2),
hrp.NewStep("点击世界杯页"). hrp.NewStep("点击世界杯页").
IOS(). IOS().
SwipeToTapText("世界杯", SwipeToTapText("世界杯",
uixt.WithMaxRetryTimes(5), options.WithMaxRetryTimes(5),
uixt.WithCustomDirection(0.4, 0.07, 0.6, 0.07), // 滑动 tab,从左到右,解决「世界杯」被遮挡的问题 options.WithCustomDirection(0.4, 0.07, 0.6, 0.07), // 滑动 tab,从左到右,解决「世界杯」被遮挡的问题
uixt.WithScope(0, 0, 1, 0.15), // 限定 tab 区域 options.WithScope(0, 0, 1, 0.15), // 限定 tab 区域
uixt.WithInterval(1), options.WithInterval(1),
), ),
hrp.NewStep("点击进入赛程晋级"). hrp.NewStep("点击进入赛程晋级").
Loop(5). // 重复执行 5 次 Loop(5). // 重复执行 5 次
IOS(). IOS().
TapByOCR("赛程晋级", uixt.WithIdentifier("click_live"), uixt.WithIndex(-1)). TapByOCR("赛程晋级",
options.WithIdentifier("click_live"),
options.WithIndex(-1)).
Sleep(3).Back().Sleep(3), Sleep(3).Back().Sleep(3),
hrp.NewStep("关闭抖音"). hrp.NewStep("关闭抖音").
IOS(). IOS().
+14 -457
View File
@@ -12,6 +12,7 @@ import (
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin" "github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
type ActionMethod string type ActionMethod string
@@ -79,15 +80,15 @@ const (
) )
type MobileAction struct { type MobileAction struct {
Method ActionMethod `json:"method,omitempty" yaml:"method,omitempty"` Method ActionMethod `json:"method,omitempty" yaml:"method,omitempty"`
Params interface{} `json:"params,omitempty" yaml:"params,omitempty"` Params interface{} `json:"params,omitempty" yaml:"params,omitempty"`
Fn func() `json:"-" yaml:"-"` // only used for function action, not serialized Fn func() `json:"-" yaml:"-"` // only used for function action, not serialized
Options *ActionOptions `json:"options,omitempty" yaml:"options,omitempty"` Options *options.ActionOptions `json:"options,omitempty" yaml:"options,omitempty"`
ActionOptions options.ActionOptions
} }
func (ma MobileAction) GetOptions() []ActionOption { func (ma MobileAction) GetOptions() []options.ActionOption {
var actionOptionList []ActionOption var actionOptionList []options.ActionOption
// Notice: merge options from ma.Options and ma.ActionOptions // Notice: merge options from ma.Options and ma.ActionOptions
if ma.Options != nil { if ma.Options != nil {
actionOptionList = append(actionOptionList, ma.Options.Options()...) actionOptionList = append(actionOptionList, ma.Options.Options()...)
@@ -96,457 +97,13 @@ func (ma MobileAction) GetOptions() []ActionOption {
return actionOptionList return actionOptionList
} }
// (x1, y1) is the top left corner, (x2, y2) is the bottom right corner
// [x1, y1, x2, y2] in percentage of the screen
type Scope []float64
// [x1, y1, x2, y2] in absolute pixels
type AbsScope []int
func (s AbsScope) Option() ActionOption {
return WithAbsScope(s[0], s[1], s[2], s[3])
}
type ActionOptions struct {
// log
Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // used to identify the action in log
// control related
MaxRetryTimes int `json:"max_retry_times,omitempty" yaml:"max_retry_times,omitempty"` // max retry times
IgnoreNotFoundError bool `json:"ignore_NotFoundError,omitempty" yaml:"ignore_NotFoundError,omitempty"` // ignore error if target element not found
Interval float64 `json:"interval,omitempty" yaml:"interval,omitempty"` // interval between retries in seconds
Duration float64 `json:"duration,omitempty" yaml:"duration,omitempty"` // used to set duration of ios swipe action
PressDuration float64 `json:"press_duration,omitempty" yaml:"press_duration,omitempty"` // used to set duration of ios swipe action
Steps int `json:"steps,omitempty" yaml:"steps,omitempty"` // used to set steps of android swipe action
Direction interface{} `json:"direction,omitempty" yaml:"direction,omitempty"` // used by swipe to tap text or app
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty"` // TODO: wait timeout in seconds for mobile action
Frequency int `json:"frequency,omitempty" yaml:"frequency,omitempty"`
// scope related
Scope Scope `json:"scope,omitempty" yaml:"scope,omitempty"`
AbsScope AbsScope `json:"abs_scope,omitempty" yaml:"abs_scope,omitempty"`
Regex bool `json:"regex,omitempty" yaml:"regex,omitempty"` // use regex to match text
Offset []int `json:"offset,omitempty" yaml:"offset,omitempty"` // used to tap offset of point
OffsetRandomRange []int `json:"offset_random_range,omitempty" yaml:"offset_random_range,omitempty"` // set random range [min, max] for tap/swipe points
Index int `json:"index,omitempty" yaml:"index,omitempty"` // index of the target element
MatchOne bool `json:"match_one,omitempty" yaml:"match_one,omitempty"` // match one of the targets if existed
// set custiom options such as textview, id, description
Custom map[string]interface{} `json:"custom,omitempty" yaml:"custom,omitempty"`
// screenshot related
ScreenShotWithOCR bool `json:"screenshot_with_ocr,omitempty" yaml:"screenshot_with_ocr,omitempty"`
ScreenShotWithUpload bool `json:"screenshot_with_upload,omitempty" yaml:"screenshot_with_upload,omitempty"`
ScreenShotWithLiveType bool `json:"screenshot_with_live_type,omitempty" yaml:"screenshot_with_live_type,omitempty"`
ScreenShotWithLivePopularity bool `json:"screenshot_with_live_popularity,omitempty" yaml:"screenshot_with_live_popularity,omitempty"`
ScreenShotWithUITypes []string `json:"screenshot_with_ui_types,omitempty" yaml:"screenshot_with_ui_types,omitempty"`
ScreenShotWithClosePopups bool `json:"screenshot_with_close_popups,omitempty" yaml:"screenshot_with_close_popups,omitempty"`
ScreenShotWithOCRCluster string `json:"screenshot_with_ocr_cluster,omitempty" yaml:"screenshot_with_ocr_cluster,omitempty"`
ScreenShotFileName string `json:"screenshot_file_name,omitempty" yaml:"screenshot_file_name,omitempty"`
}
func (o *ActionOptions) Options() []ActionOption {
options := make([]ActionOption, 0)
if o == nil {
return options
}
if o.Identifier != "" {
options = append(options, WithIdentifier(o.Identifier))
}
if o.MaxRetryTimes != 0 {
options = append(options, WithMaxRetryTimes(o.MaxRetryTimes))
}
if o.IgnoreNotFoundError {
options = append(options, WithIgnoreNotFoundError(true))
}
if o.Interval != 0 {
options = append(options, WithInterval(o.Interval))
}
if o.Duration != 0 {
options = append(options, WithDuration(o.Duration))
}
if o.PressDuration != 0 {
options = append(options, WithPressDuration(o.PressDuration))
}
if o.Steps != 0 {
options = append(options, WithSteps(o.Steps))
}
switch v := o.Direction.(type) {
case string:
options = append(options, WithDirection(v))
case []float64:
options = append(options, WithCustomDirection(
v[0], v[1],
v[2], v[3],
))
case []interface{}:
// loaded from json case
// custom direction: [fromX, fromY, toX, toY]
sx, _ := builtin.Interface2Float64(v[0])
sy, _ := builtin.Interface2Float64(v[1])
ex, _ := builtin.Interface2Float64(v[2])
ey, _ := builtin.Interface2Float64(v[3])
options = append(options, WithCustomDirection(
sx, sy,
ex, ey,
))
}
if o.Timeout != 0 {
options = append(options, WithTimeout(o.Timeout))
}
if o.Frequency != 0 {
options = append(options, WithFrequency(o.Frequency))
}
if len(o.AbsScope) == 4 {
options = append(options, WithAbsScope(
o.AbsScope[0], o.AbsScope[1], o.AbsScope[2], o.AbsScope[3]))
} else if len(o.Scope) == 4 {
options = append(options, WithScope(
o.Scope[0], o.Scope[1], o.Scope[2], o.Scope[3]))
}
if len(o.Offset) == 2 {
// for tap [x,y] offset
options = append(options, WithTapOffset(o.Offset[0], o.Offset[1]))
} else if len(o.Offset) == 4 {
// for swipe [fromX, fromY, toX, toY] offset
options = append(options, WithSwipeOffset(
o.Offset[0], o.Offset[1], o.Offset[2], o.Offset[3]))
}
if len(o.OffsetRandomRange) == 2 {
options = append(options, WithOffsetRandomRange(
o.OffsetRandomRange[0], o.OffsetRandomRange[1]))
}
if o.Regex {
options = append(options, WithRegex(true))
}
if o.Index != 0 {
options = append(options, WithIndex(o.Index))
}
if o.MatchOne {
options = append(options, WithMatchOne(true))
}
// custom options
if o.Custom != nil {
for k, v := range o.Custom {
options = append(options, WithCustomOption(k, v))
}
}
// screenshot options
if o.ScreenShotWithOCR {
options = append(options, WithScreenShotOCR(true))
}
if o.ScreenShotWithUpload {
options = append(options, WithScreenShotUpload(true))
}
if o.ScreenShotWithLiveType {
options = append(options, WithScreenShotLiveType(true))
}
if o.ScreenShotWithLivePopularity {
options = append(options, WithScreenShotLivePopularity(true))
}
if len(o.ScreenShotWithUITypes) > 0 {
options = append(options, WithScreenShotUITypes(o.ScreenShotWithUITypes...))
}
if o.ScreenShotWithClosePopups {
options = append(options, WithScreenShotClosePopups(true))
}
if o.ScreenShotWithOCRCluster != "" {
options = append(options, WithScreenOCRCluster(o.ScreenShotWithOCRCluster))
}
if o.ScreenShotFileName != "" {
options = append(options, WithScreenShotFileName(o.ScreenShotFileName))
}
return options
}
func (o *ActionOptions) screenshotActions() []string {
actions := []string{}
if o.ScreenShotWithUpload {
actions = append(actions, "upload")
}
if o.ScreenShotWithOCR {
actions = append(actions, "ocr")
}
if o.ScreenShotWithLiveType {
actions = append(actions, "liveType")
}
if o.ScreenShotWithLivePopularity {
actions = append(actions, "livePopularity")
}
// UI detection
if len(o.ScreenShotWithUITypes) > 0 {
actions = append(actions, "ui")
}
if o.ScreenShotWithClosePopups {
actions = append(actions, "close")
}
return actions
}
func (o *ActionOptions) getRandomOffset() float64 {
if len(o.OffsetRandomRange) != 2 {
// invalid offset random range, should be [min, max]
return 0
}
minOffset := o.OffsetRandomRange[0]
maxOffset := o.OffsetRandomRange[1]
return float64(builtin.GetRandomNumber(minOffset, maxOffset)) + rand.Float64()
}
func (o *ActionOptions) updateData(data map[string]interface{}) {
if o.Identifier != "" {
data["log"] = map[string]interface{}{
"enable": true,
"data": o.Identifier,
}
}
if o.Steps > 0 {
data["steps"] = o.Steps
}
if _, ok := data["steps"]; !ok {
data["steps"] = 12 // default steps
}
if o.Duration > 0 {
data["duration"] = o.Duration
}
if _, ok := data["duration"]; !ok {
data["duration"] = 0 // default duration
}
if o.Frequency > 0 {
data["frequency"] = o.Frequency
}
if _, ok := data["frequency"]; !ok {
data["frequency"] = 10 // default frequency
}
if _, ok := data["replace"]; !ok {
data["replace"] = true // default true
}
// custom options
if o.Custom != nil {
for k, v := range o.Custom {
data[k] = v
}
}
}
func NewActionOptions(options ...ActionOption) *ActionOptions {
actionOptions := &ActionOptions{}
for _, option := range options {
option(actionOptions)
}
return actionOptions
}
type TapTextAction struct { type TapTextAction struct {
Text string Text string
Options []ActionOption Options []options.ActionOption
} }
type ActionOption func(o *ActionOptions) func (dExt *DriverExt) ParseActionOptions(opts ...options.ActionOption) []options.ActionOption {
actionOptions := options.NewActionOptions(opts...)
func WithCustomOption(key string, value interface{}) ActionOption {
return func(o *ActionOptions) {
if o.Custom == nil {
o.Custom = make(map[string]interface{})
}
o.Custom[key] = value
}
}
func WithIdentifier(identifier string) ActionOption {
return func(o *ActionOptions) {
o.Identifier = identifier
}
}
func WithIndex(index int) ActionOption {
return func(o *ActionOptions) {
o.Index = index
}
}
// set alias for compatibility
var WithWaitTime = WithInterval
func WithInterval(sec float64) ActionOption {
return func(o *ActionOptions) {
o.Interval = sec
}
}
func WithDuration(duration float64) ActionOption {
return func(o *ActionOptions) {
o.Duration = duration
}
}
func WithPressDuration(pressDuration float64) ActionOption {
return func(o *ActionOptions) {
o.PressDuration = pressDuration
}
}
func WithSteps(steps int) ActionOption {
return func(o *ActionOptions) {
o.Steps = steps
}
}
// WithDirection inputs direction (up, down, left, right)
func WithDirection(direction string) ActionOption {
return func(o *ActionOptions) {
o.Direction = direction
}
}
// WithCustomDirection inputs sx, sy, ex, ey
func WithCustomDirection(sx, sy, ex, ey float64) ActionOption {
return func(o *ActionOptions) {
o.Direction = []float64{sx, sy, ex, ey}
}
}
// WithScope inputs area of [(x1,y1), (x2,y2)]
// x1, y1, x2, y2 are all in [0, 1], which means the relative position of the screen
func WithScope(x1, y1, x2, y2 float64) ActionOption {
return func(o *ActionOptions) {
o.Scope = Scope{x1, y1, x2, y2}
}
}
// WithAbsScope inputs area of [(x1,y1), (x2,y2)]
// x1, y1, x2, y2 are all absolute position of the screen
func WithAbsScope(x1, y1, x2, y2 int) ActionOption {
return func(o *ActionOptions) {
o.AbsScope = AbsScope{x1, y1, x2, y2}
}
}
// Deprecated: use WithTapOffset instead
func WithOffset(offsetX, offsetY int) ActionOption {
return func(o *ActionOptions) {
o.Offset = []int{offsetX, offsetY}
}
}
// tap [x, y] with offset [offsetX, offsetY]
var WithTapOffset = WithOffset
// swipe [fromX, fromY, toX, toY] with offset [offsetFromX, offsetFromY, offsetToX, offsetToY]
func WithSwipeOffset(offsetFromX, offsetFromY, offsetToX, offsetToY int) ActionOption {
return func(o *ActionOptions) {
o.Offset = []int{offsetFromX, offsetFromY, offsetToX, offsetToY}
}
}
func WithOffsetRandomRange(min, max int) ActionOption {
return func(o *ActionOptions) {
o.OffsetRandomRange = []int{min, max}
}
}
func WithRegex(regex bool) ActionOption {
return func(o *ActionOptions) {
o.Regex = regex
}
}
func WithMatchOne(matchOne bool) ActionOption {
return func(o *ActionOptions) {
o.MatchOne = matchOne
}
}
func WithFrequency(frequency int) ActionOption {
return func(o *ActionOptions) {
o.Frequency = frequency
}
}
func WithMaxRetryTimes(maxRetryTimes int) ActionOption {
return func(o *ActionOptions) {
o.MaxRetryTimes = maxRetryTimes
}
}
func WithTimeout(timeout int) ActionOption {
return func(o *ActionOptions) {
o.Timeout = timeout
}
}
func WithIgnoreNotFoundError(ignoreError bool) ActionOption {
return func(o *ActionOptions) {
o.IgnoreNotFoundError = ignoreError
}
}
func WithScreenShotOCR(ocrOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithOCR = ocrOn
}
}
func WithScreenShotUpload(uploadOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithUpload = uploadOn
}
}
func WithScreenShotLiveType(liveTypeOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithLiveType = liveTypeOn
}
}
func WithScreenShotLivePopularity(livePopularityOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithLivePopularity = livePopularityOn
}
}
func WithScreenShotUITypes(uiTypes ...string) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithUITypes = uiTypes
}
}
func WithScreenShotClosePopups(closeOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithClosePopups = closeOn
}
}
func WithScreenOCRCluster(ocrCluster string) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithOCRCluster = ocrCluster
}
}
func WithScreenShotFileName(fileName string) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotFileName = fileName
}
}
func (dExt *DriverExt) ParseActionOptions(options ...ActionOption) []ActionOption {
actionOptions := NewActionOptions(options...)
// convert relative scope to absolute scope // convert relative scope to absolute scope
if len(actionOptions.AbsScope) != 4 && len(actionOptions.Scope) == 4 { if len(actionOptions.AbsScope) != 4 && len(actionOptions.Scope) == 4 {
@@ -558,14 +115,14 @@ func (dExt *DriverExt) ParseActionOptions(options ...ActionOption) []ActionOptio
return actionOptions.Options() return actionOptions.Options()
} }
func (dExt *DriverExt) GenAbsScope(x1, y1, x2, y2 float64) AbsScope { func (dExt *DriverExt) GenAbsScope(x1, y1, x2, y2 float64) options.AbsScope {
// convert relative scope to absolute scope // convert relative scope to absolute scope
windowSize, _ := dExt.Driver.WindowSize() windowSize, _ := dExt.Driver.WindowSize()
absX1 := int(x1 * float64(windowSize.Width)) absX1 := int(x1 * float64(windowSize.Width))
absY1 := int(y1 * float64(windowSize.Height)) absY1 := int(y1 * float64(windowSize.Height))
absX2 := int(x2 * float64(windowSize.Width)) absX2 := int(x2 * float64(windowSize.Width))
absY2 := int(y2 * float64(windowSize.Height)) absY2 := int(y2 * float64(windowSize.Height))
return AbsScope{absX1, absY1, absX2, absY2} return options.AbsScope{absX1, absY1, absX2, absY2}
} }
func (dExt *DriverExt) DoAction(action MobileAction) (err error) { func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
@@ -700,7 +257,7 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
} }
return fmt.Errorf("invalid %s params: %v", ACTION_TapByOCR, action.Params) return fmt.Errorf("invalid %s params: %v", ACTION_TapByOCR, action.Params)
case ACTION_TapByCV: case ACTION_TapByCV:
actionOptions := NewActionOptions(action.GetOptions()...) actionOptions := options.NewActionOptions(action.GetOptions()...)
if len(actionOptions.ScreenShotWithUITypes) > 0 { if len(actionOptions.ScreenShotWithUITypes) > 0 {
return dExt.TapByUIDetection(action.GetOptions()...) return dExt.TapByUIDetection(action.GetOptions()...)
} }
+11 -10
View File
@@ -11,11 +11,12 @@ import (
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin" "github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
type IImageService interface { type IImageService interface {
// GetImage returns image result including ocr texts, uploaded image url, etc // GetImage returns image result including ocr texts, uploaded image url, etc
GetImage(imageBuf *bytes.Buffer, options ...ActionOption) (imageResult *ImageResult, err error) GetImage(imageBuf *bytes.Buffer, opts ...options.ActionOption) (imageResult *ImageResult, err error)
} }
type ImageResult struct { type ImageResult struct {
@@ -105,7 +106,7 @@ func (t OCRTexts) texts() (texts []string) {
return texts return texts
} }
func (t OCRTexts) FilterScope(scope AbsScope) (results OCRTexts) { func (t OCRTexts) FilterScope(scope options.AbsScope) (results OCRTexts) {
for _, ocrText := range t { for _, ocrText := range t {
rect := ocrText.Rect rect := ocrText.Rect
@@ -127,8 +128,8 @@ func (t OCRTexts) FilterScope(scope AbsScope) (results OCRTexts) {
// FindText returns matched text with options // FindText returns matched text with options
// Notice: filter scope should be specified with WithAbsScope // Notice: filter scope should be specified with WithAbsScope
func (t OCRTexts) FindText(text string, options ...ActionOption) (result OCRText, err error) { func (t OCRTexts) FindText(text string, opts ...options.ActionOption) (result OCRText, err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
var results []OCRText var results []OCRText
for _, ocrText := range t.FilterScope(actionOptions.AbsScope) { for _, ocrText := range t.FilterScope(actionOptions.AbsScope) {
@@ -172,10 +173,10 @@ func (t OCRTexts) FindText(text string, options ...ActionOption) (result OCRText
return results[idx], nil return results[idx], nil
} }
func (t OCRTexts) FindTexts(texts []string, options ...ActionOption) (results OCRTexts, err error) { func (t OCRTexts) FindTexts(texts []string, opts ...options.ActionOption) (results OCRTexts, err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
for _, text := range texts { for _, text := range texts {
ocrText, err := t.FindText(text, options...) ocrText, err := t.FindText(text, opts...)
if err != nil { if err != nil {
continue continue
} }
@@ -239,7 +240,7 @@ func (box Box) Center() PointF {
type UIResults []UIResult type UIResults []UIResult
func (u UIResults) FilterScope(scope AbsScope) (results UIResults) { func (u UIResults) FilterScope(scope options.AbsScope) (results UIResults) {
for _, uiResult := range u { for _, uiResult := range u {
rect := image.Rectangle{ rect := image.Rectangle{
Min: image.Point{ Min: image.Point{
@@ -267,8 +268,8 @@ func (u UIResults) FilterScope(scope AbsScope) (results UIResults) {
return return
} }
func (u UIResults) GetUIResult(options ...ActionOption) (UIResult, error) { func (u UIResults) GetUIResult(opts ...options.ActionOption) (UIResult, error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
uiResults := u.FilterScope(actionOptions.AbsScope) uiResults := u.FilterScope(actionOptions.AbsScope)
if len(uiResults) == 0 { if len(uiResults) == 0 {
+4 -3
View File
@@ -16,6 +16,7 @@ import (
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin" "github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/internal/json" "github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
var client = &http.Client{ var client = &http.Client{
@@ -46,9 +47,9 @@ func newVEDEMImageService() (*veDEMImageService, error) {
// ui - get ui position by type(s) // ui - get ui position by type(s)
type veDEMImageService struct{} type veDEMImageService struct{}
func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, options ...ActionOption) (imageResult *ImageResult, err error) { func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, opts ...options.ActionOption) (imageResult *ImageResult, err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
screenshotActions := actionOptions.screenshotActions() screenshotActions := actionOptions.ScreenshotActions()
if len(screenshotActions) == 0 { if len(screenshotActions) == 0 {
// skip // skip
return nil, nil return nil, nil
+1 -1
View File
@@ -62,7 +62,7 @@ func TestTapUIWithScreenshot(t *testing.T) {
} }
err = driver.TapByUIDetection( err = driver.TapByUIDetection(
WithScreenShotUITypes("dyhouse", "shoppingbag")) options.WithScreenShotUITypes("dyhouse", "shoppingbag"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+5 -5
View File
@@ -301,20 +301,20 @@ func (dev *AndroidDevice) Uninstall(packageName string) error {
return err return err
} }
func (dev *AndroidDevice) Install(apkPath string, options ...InstallOption) error { func (dev *AndroidDevice) Install(apkPath string, opts ...InstallOption) error {
opts := NewInstallOptions(options...) installOpts := NewInstallOptions(opts...)
brand, err := dev.d.Brand() brand, err := dev.d.Brand()
if err != nil { if err != nil {
return err return err
} }
args := []string{} args := []string{}
if opts.Reinstall { if installOpts.Reinstall {
args = append(args, "-r") args = append(args, "-r")
} }
if opts.GrantPermission { if installOpts.GrantPermission {
args = append(args, "-g") args = append(args, "-g")
} }
if opts.Downgrade { if installOpts.Downgrade {
args = append(args, "-d") args = append(args, "-d")
} }
switch strings.ToLower(brand) { switch strings.ToLower(brand) {
+41 -41
View File
@@ -193,7 +193,7 @@ func (ad *adbDriver) GetTimestamp() (timestamp int64, err error) {
} }
// PressBack simulates a short press on the BACK button. // PressBack simulates a short press on the BACK button.
func (ad *adbDriver) PressBack(options ...ActionOption) (err error) { func (ad *adbDriver) PressBack(opts ...options.ActionOption) (err error) {
// adb shell input keyevent 4 // adb shell input keyevent 4
_, err = ad.runShellCommand("input", "keyevent", fmt.Sprintf("%d", KCBack)) _, err = ad.runShellCommand("input", "keyevent", fmt.Sprintf("%d", KCBack))
if err != nil { if err != nil {
@@ -294,7 +294,7 @@ func (ad *adbDriver) Unlock() (err error) {
return ad.Swipe(500, 1500, 500, 500) return ad.Swipe(500, 1500, 500, 500)
} }
func (ad *adbDriver) Backspace(count int, options ...ActionOption) (err error) { func (ad *adbDriver) Backspace(count int, opts ...options.ActionOption) (err error) {
if count == 0 { if count == 0 {
return nil return nil
} }
@@ -370,15 +370,15 @@ func (ad *adbDriver) AppTerminate(packageName string) (successful bool, err erro
return true, nil return true, nil
} }
func (ad *adbDriver) Tap(x, y float64, options ...ActionOption) error { func (ad *adbDriver) Tap(x, y float64, opts ...options.ActionOption) error {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(actionOptions.Offset) == 2 { if len(actionOptions.Offset) == 2 {
x += float64(actionOptions.Offset[0]) x += float64(actionOptions.Offset[0])
y += float64(actionOptions.Offset[1]) y += float64(actionOptions.Offset[1])
} }
x += actionOptions.getRandomOffset() x += actionOptions.GetRandomOffset()
y += actionOptions.getRandomOffset() y += actionOptions.GetRandomOffset()
// adb shell input tap x y // adb shell input tap x y
xStr := fmt.Sprintf("%.1f", x) xStr := fmt.Sprintf("%.1f", x)
@@ -391,7 +391,7 @@ func (ad *adbDriver) Tap(x, y float64, options ...ActionOption) error {
return nil return nil
} }
func (ad *adbDriver) DoubleTap(x, y float64, options ...ActionOption) error { func (ad *adbDriver) DoubleTap(x, y float64, opts ...options.ActionOption) error {
// adb shell input tap x y // adb shell input tap x y
xStr := fmt.Sprintf("%.1f", x) xStr := fmt.Sprintf("%.1f", x)
yStr := fmt.Sprintf("%.1f", y) yStr := fmt.Sprintf("%.1f", y)
@@ -409,15 +409,15 @@ func (ad *adbDriver) DoubleTap(x, y float64, options ...ActionOption) error {
return nil return nil
} }
func (ad *adbDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) { func (ad *adbDriver) TouchAndHold(x, y float64, opts ...options.ActionOption) (err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(actionOptions.Offset) == 2 { if len(actionOptions.Offset) == 2 {
x += float64(actionOptions.Offset[0]) x += float64(actionOptions.Offset[0])
y += float64(actionOptions.Offset[1]) y += float64(actionOptions.Offset[1])
} }
x += actionOptions.getRandomOffset() x += actionOptions.GetRandomOffset()
y += actionOptions.getRandomOffset() y += actionOptions.GetRandomOffset()
duration := 1000.0 duration := 1000.0
if actionOptions.Duration > 0 { if actionOptions.Duration > 0 {
duration = actionOptions.Duration * 1000 duration = actionOptions.Duration * 1000
@@ -435,8 +435,8 @@ func (ad *adbDriver) TouchAndHold(x, y float64, options ...ActionOption) (err er
return nil return nil
} }
func (ad *adbDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) { func (ad *adbDriver) Drag(fromX, fromY, toX, toY float64, opts ...options.ActionOption) (err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(actionOptions.Offset) == 4 { if len(actionOptions.Offset) == 4 {
fromX += float64(actionOptions.Offset[0]) fromX += float64(actionOptions.Offset[0])
@@ -444,10 +444,10 @@ func (ad *adbDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOptio
toX += float64(actionOptions.Offset[2]) toX += float64(actionOptions.Offset[2])
toY += float64(actionOptions.Offset[3]) toY += float64(actionOptions.Offset[3])
} }
fromX += actionOptions.getRandomOffset() fromX += actionOptions.GetRandomOffset()
fromY += actionOptions.getRandomOffset() fromY += actionOptions.GetRandomOffset()
toX += actionOptions.getRandomOffset() toX += actionOptions.GetRandomOffset()
toY += actionOptions.getRandomOffset() toY += actionOptions.GetRandomOffset()
duration := 200.0 duration := 200.0
if actionOptions.Duration > 0 { if actionOptions.Duration > 0 {
duration = actionOptions.Duration * 1000 duration = actionOptions.Duration * 1000
@@ -469,8 +469,8 @@ func (ad *adbDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOptio
return nil return nil
} }
func (ad *adbDriver) Swipe(fromX, fromY, toX, toY float64, options ...ActionOption) error { func (ad *adbDriver) Swipe(fromX, fromY, toX, toY float64, opts ...options.ActionOption) error {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(actionOptions.Offset) == 4 { if len(actionOptions.Offset) == 4 {
fromX += float64(actionOptions.Offset[0]) fromX += float64(actionOptions.Offset[0])
@@ -478,10 +478,10 @@ func (ad *adbDriver) Swipe(fromX, fromY, toX, toY float64, options ...ActionOpti
toX += float64(actionOptions.Offset[2]) toX += float64(actionOptions.Offset[2])
toY += float64(actionOptions.Offset[3]) toY += float64(actionOptions.Offset[3])
} }
fromX += actionOptions.getRandomOffset() fromX += actionOptions.GetRandomOffset()
fromY += actionOptions.getRandomOffset() fromY += actionOptions.GetRandomOffset()
toX += actionOptions.getRandomOffset() toX += actionOptions.GetRandomOffset()
toY += actionOptions.getRandomOffset() toY += actionOptions.GetRandomOffset()
// adb shell input swipe fromX fromY toX toY // adb shell input swipe fromX fromY toX toY
_, err := ad.runShellCommand( _, err := ad.runShellCommand(
@@ -514,16 +514,16 @@ func (ad *adbDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe
return return
} }
func (ad *adbDriver) SendKeys(text string, options ...ActionOption) (err error) { func (ad *adbDriver) SendKeys(text string, opts ...options.ActionOption) (err error) {
err = ad.SendUnicodeKeys(text, options...) err = ad.SendUnicodeKeys(text, opts...)
if err == nil { if err == nil {
return return
} }
err = ad.InputText(text, options...) err = ad.InputText(text, opts...)
return return
} }
func (ad *adbDriver) InputText(text string, options ...ActionOption) error { func (ad *adbDriver) InputText(text string, opts ...options.ActionOption) error {
// adb shell input text <text> // adb shell input text <text>
_, err := ad.runShellCommand("input", "text", text) _, err := ad.runShellCommand("input", "text", text)
if err != nil { if err != nil {
@@ -532,7 +532,7 @@ func (ad *adbDriver) InputText(text string, options ...ActionOption) error {
return nil return nil
} }
func (ad *adbDriver) SendUnicodeKeys(text string, options ...ActionOption) (err error) { func (ad *adbDriver) SendUnicodeKeys(text string, opts ...options.ActionOption) (err error) {
// If the Unicode IME is not installed, fall back to the old interface. // If the Unicode IME is not installed, fall back to the old interface.
// There might be differences in the tracking schemes across different phones, and it is pending further verification. // There might be differences in the tracking schemes across different phones, and it is pending further verification.
// In release version: without the Unicode IME installed, the test cannot execute. // In release version: without the Unicode IME installed, the test cannot execute.
@@ -558,7 +558,7 @@ func (ad *adbDriver) SendUnicodeKeys(text string, options ...ActionOption) (err
log.Warn().Err(err).Msgf("encode text with modified utf7 failed") log.Warn().Err(err).Msgf("encode text with modified utf7 failed")
return return
} }
err = ad.InputText("\""+strings.ReplaceAll(encodedStr, "\"", "\\\"")+"\"", options...) err = ad.InputText("\""+strings.ReplaceAll(encodedStr, "\"", "\\\"")+"\"", opts...)
return return
} }
@@ -619,8 +619,8 @@ func (ad *adbDriver) SendKeysByAdbKeyBoard(text string) (err error) {
return return
} }
func (ad *adbDriver) Input(text string, options ...ActionOption) (err error) { func (ad *adbDriver) Input(text string, opts ...options.ActionOption) (err error) {
return ad.SendKeys(text, options...) return ad.SendKeys(text, opts...)
} }
func (ad *adbDriver) Clear(packageName string) error { func (ad *adbDriver) Clear(packageName string) error {
@@ -694,17 +694,17 @@ func (ad *adbDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy,
return return
} }
func (ad *adbDriver) TapByText(text string, options ...ActionOption) error { func (ad *adbDriver) TapByText(text string, opts ...options.ActionOption) error {
sourceTree, err := ad.sourceTree() sourceTree, err := ad.sourceTree()
if err != nil { if err != nil {
return err return err
} }
return ad.tapByTextUsingHierarchy(sourceTree, text, options...) return ad.tapByTextUsingHierarchy(sourceTree, text, opts...)
} }
func (ad *adbDriver) tapByTextUsingHierarchy(hierarchy *Hierarchy, text string, options ...ActionOption) error { func (ad *adbDriver) tapByTextUsingHierarchy(hierarchy *Hierarchy, text string, opts ...options.ActionOption) error {
bounds := ad.searchNodes(hierarchy.Layout, text, options...) bounds := ad.searchNodes(hierarchy.Layout, text, opts...)
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(bounds) == 0 { if len(bounds) == 0 {
if actionOptions.IgnoreNotFoundError { if actionOptions.IgnoreNotFoundError {
log.Info().Msg("not found element by text " + text) log.Info().Msg("not found element by text " + text)
@@ -714,7 +714,7 @@ func (ad *adbDriver) tapByTextUsingHierarchy(hierarchy *Hierarchy, text string,
} }
for _, bound := range bounds { for _, bound := range bounds {
width, height := bound.Center() width, height := bound.Center()
err := ad.Tap(width, height, options...) err := ad.Tap(width, height, opts...)
if err != nil { if err != nil {
return err return err
} }
@@ -737,11 +737,11 @@ func (ad *adbDriver) TapByTexts(actions ...TapTextAction) error {
return nil return nil
} }
func (ad *adbDriver) searchNodes(nodes []Layout, text string, options ...ActionOption) []Bounds { func (ad *adbDriver) searchNodes(nodes []Layout, text string, opts ...options.ActionOption) []Bounds {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
var results []Bounds var results []Bounds
for _, node := range nodes { for _, node := range nodes {
result := ad.searchNodes(node.Layout, text, options...) result := ad.searchNodes(node.Layout, text, opts...)
results = append(results, result...) results = append(results, result...)
if actionOptions.Regex { if actionOptions.Regex {
// regex on, check if match regex // regex on, check if match regex
@@ -751,7 +751,7 @@ func (ad *adbDriver) searchNodes(nodes []Layout, text string, options ...ActionO
} else { } else {
// regex off, check if match exactly // regex off, check if match exactly
if node.Text != text { if node.Text != text {
ad.searchNodes(node.Layout, text, options...) ad.searchNodes(node.Layout, text, opts...)
continue continue
} }
} }
+2 -1
View File
@@ -79,7 +79,8 @@ func TestDoubleTap(t *testing.T) {
func TestLongPress(t *testing.T) { func TestLongPress(t *testing.T) {
setupStubDriver(t) setupStubDriver(t)
err := androidStubDriver.Swipe(1036, 1076, 1036, 1076, WithDuration(3)) err := androidStubDriver.Swipe(1036, 1076, 1036, 1076,
options.WithDuration(3))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+37 -37
View File
@@ -250,7 +250,7 @@ func (ud *uiaDriver) WindowSize() (size Size, err error) {
} }
// PressBack simulates a short press on the BACK button. // PressBack simulates a short press on the BACK button.
func (ud *uiaDriver) PressBack(options ...ActionOption) (err error) { func (ud *uiaDriver) PressBack(opts ...options.ActionOption) (err error) {
// register(postHandler, new PressBack("/wd/hub/session/:sessionId/back")) // register(postHandler, new PressBack("/wd/hub/session/:sessionId/back"))
_, err = ud.httpPOST(nil, "/session", ud.session.ID, "back") _, err = ud.httpPOST(nil, "/session", ud.session.ID, "back")
return return
@@ -293,7 +293,7 @@ func (ud *uiaDriver) Orientation() (orientation Orientation, err error) {
return return
} }
func (ud *uiaDriver) DoubleTap(x, y float64, options ...ActionOption) error { func (ud *uiaDriver) DoubleTap(x, y float64, opts ...options.ActionOption) error {
return ud.DoubleFloatTap(x, y) return ud.DoubleFloatTap(x, y)
} }
@@ -319,16 +319,16 @@ func (ud *uiaDriver) DoubleFloatTap(x, y float64) error {
return err return err
} }
func (ud *uiaDriver) Tap(x, y float64, options ...ActionOption) (err error) { func (ud *uiaDriver) Tap(x, y float64, opts ...options.ActionOption) (err error) {
// register(postHandler, new Tap("/wd/hub/session/:sessionId/appium/tap")) // register(postHandler, new Tap("/wd/hub/session/:sessionId/appium/tap"))
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(actionOptions.Offset) == 2 { if len(actionOptions.Offset) == 2 {
x += float64(actionOptions.Offset[0]) x += float64(actionOptions.Offset[0])
y += float64(actionOptions.Offset[1]) y += float64(actionOptions.Offset[1])
} }
x += actionOptions.getRandomOffset() x += actionOptions.GetRandomOffset()
y += actionOptions.getRandomOffset() y += actionOptions.GetRandomOffset()
duration := 100.0 duration := 100.0
if actionOptions.PressDuration > 0 { if actionOptions.PressDuration > 0 {
@@ -351,15 +351,15 @@ func (ud *uiaDriver) Tap(x, y float64, options ...ActionOption) (err error) {
} }
// update data options in post data for extra uiautomator configurations // update data options in post data for extra uiautomator configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
_, err = ud.httpPOST(data, "/session", ud.session.ID, "actions/tap") _, err = ud.httpPOST(data, "/session", ud.session.ID, "actions/tap")
return err return err
} }
func (ud *uiaDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) { func (ud *uiaDriver) TouchAndHold(x, y float64, opts ...options.ActionOption) (err error) {
opts := NewActionOptions(options...) actionOpts := options.NewActionOptions(opts...)
duration := opts.Duration duration := actionOpts.Duration
if duration == 0 { if duration == 0 {
duration = 1.0 duration = 1.0
} }
@@ -379,18 +379,18 @@ func (ud *uiaDriver) TouchAndHold(x, y float64, options ...ActionOption) (err er
// the smoothness and speed of the swipe by specifying the number of steps. // the smoothness and speed of the swipe by specifying the number of steps.
// Each step execution is throttled to 5 milliseconds per step, so for a 100 // Each step execution is throttled to 5 milliseconds per step, so for a 100
// steps, the swipe will take around 0.5 seconds to complete. // steps, the swipe will take around 0.5 seconds to complete.
func (ud *uiaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) { func (ud *uiaDriver) Drag(fromX, fromY, toX, toY float64, opts ...options.ActionOption) (err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(actionOptions.Offset) == 4 { if len(actionOptions.Offset) == 4 {
fromX += float64(actionOptions.Offset[0]) fromX += float64(actionOptions.Offset[0])
fromY += float64(actionOptions.Offset[1]) fromY += float64(actionOptions.Offset[1])
toX += float64(actionOptions.Offset[2]) toX += float64(actionOptions.Offset[2])
toY += float64(actionOptions.Offset[3]) toY += float64(actionOptions.Offset[3])
} }
fromX += actionOptions.getRandomOffset() fromX += actionOptions.GetRandomOffset()
fromY += actionOptions.getRandomOffset() fromY += actionOptions.GetRandomOffset()
toX += actionOptions.getRandomOffset() toX += actionOptions.GetRandomOffset()
toY += actionOptions.getRandomOffset() toY += actionOptions.GetRandomOffset()
data := map[string]interface{}{ data := map[string]interface{}{
"startX": fromX, "startX": fromX,
@@ -400,7 +400,7 @@ func (ud *uiaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOptio
} }
// update data options in post data for extra uiautomator configurations // update data options in post data for extra uiautomator configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
// register(postHandler, new Drag("/wd/hub/session/:sessionId/touch/drag")) // register(postHandler, new Drag("/wd/hub/session/:sessionId/touch/drag"))
_, err = ud.httpPOST(data, "/session", ud.session.ID, "touch/drag") _, err = ud.httpPOST(data, "/session", ud.session.ID, "touch/drag")
@@ -412,19 +412,19 @@ func (ud *uiaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOptio
// per step. So for a 100 steps, the swipe will take about 1/2 second to complete. // per step. So for a 100 steps, the swipe will take about 1/2 second to complete.
// //
// `steps` is the number of move steps sent to the system // `steps` is the number of move steps sent to the system
func (ud *uiaDriver) Swipe(fromX, fromY, toX, toY float64, options ...ActionOption) error { func (ud *uiaDriver) Swipe(fromX, fromY, toX, toY float64, opts ...options.ActionOption) error {
// register(postHandler, new Swipe("/wd/hub/session/:sessionId/touch/perform")) // register(postHandler, new Swipe("/wd/hub/session/:sessionId/touch/perform"))
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(actionOptions.Offset) == 4 { if len(actionOptions.Offset) == 4 {
fromX += float64(actionOptions.Offset[0]) fromX += float64(actionOptions.Offset[0])
fromY += float64(actionOptions.Offset[1]) fromY += float64(actionOptions.Offset[1])
toX += float64(actionOptions.Offset[2]) toX += float64(actionOptions.Offset[2])
toY += float64(actionOptions.Offset[3]) toY += float64(actionOptions.Offset[3])
} }
fromX += actionOptions.getRandomOffset() fromX += actionOptions.GetRandomOffset()
fromY += actionOptions.getRandomOffset() fromY += actionOptions.GetRandomOffset()
toX += actionOptions.getRandomOffset() toX += actionOptions.GetRandomOffset()
toY += actionOptions.getRandomOffset() toY += actionOptions.GetRandomOffset()
duration := 200.0 duration := 200.0
if actionOptions.PressDuration > 0 { if actionOptions.PressDuration > 0 {
@@ -447,7 +447,7 @@ func (ud *uiaDriver) Swipe(fromX, fromY, toX, toY float64, options ...ActionOpti
} }
// update data options in post data for extra uiautomator configurations // update data options in post data for extra uiautomator configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
_, err := ud.httpPOST(data, "/session", ud.session.ID, "actions/swipe") _, err := ud.httpPOST(data, "/session", ud.session.ID, "actions/swipe")
return err return err
@@ -497,25 +497,25 @@ func (ud *uiaDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe
} }
// SendKeys Android input does not support setting frequency. // SendKeys Android input does not support setting frequency.
func (ud *uiaDriver) SendKeys(text string, options ...ActionOption) (err error) { func (ud *uiaDriver) SendKeys(text string, opts ...options.ActionOption) (err error) {
// register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/keys")) // register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/keys"))
// https://github.com/appium/appium-uiautomator2-server/blob/master/app/src/main/java/io/appium/uiautomator2/handler/SendKeysToElement.java#L76-L85 // https://github.com/appium/appium-uiautomator2-server/blob/master/app/src/main/java/io/appium/uiautomator2/handler/SendKeysToElement.java#L76-L85
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
err = ud.SendUnicodeKeys(text, options...) err = ud.SendUnicodeKeys(text, opts...)
if err != nil { if err != nil {
data := map[string]interface{}{ data := map[string]interface{}{
"text": text, "text": text,
} }
// new data options in post data for extra uiautomator configurations // new data options in post data for extra uiautomator configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
_, err = ud.httpPOST(data, "/session", ud.session.ID, "/keys") _, err = ud.httpPOST(data, "/session", ud.session.ID, "/keys")
} }
return return
} }
func (ud *uiaDriver) SendUnicodeKeys(text string, options ...ActionOption) (err error) { func (ud *uiaDriver) SendUnicodeKeys(text string, opts ...options.ActionOption) (err error) {
// If the Unicode IME is not installed, fall back to the old interface. // If the Unicode IME is not installed, fall back to the old interface.
// There might be differences in the tracking schemes across different phones, and it is pending further verification. // There might be differences in the tracking schemes across different phones, and it is pending further verification.
// In release version: without the Unicode IME installed, the test cannot execute. // In release version: without the Unicode IME installed, the test cannot execute.
@@ -541,12 +541,12 @@ func (ud *uiaDriver) SendUnicodeKeys(text string, options ...ActionOption) (err
log.Warn().Err(err).Msgf("encode text with modified utf7 failed") log.Warn().Err(err).Msgf("encode text with modified utf7 failed")
return return
} }
err = ud.SendActionKey(encodedStr, options...) err = ud.SendActionKey(encodedStr, opts...)
return return
} }
func (ud *uiaDriver) SendActionKey(text string, options ...ActionOption) (err error) { func (ud *uiaDriver) SendActionKey(text string, opts ...options.ActionOption) (err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
var actions []interface{} var actions []interface{}
for i, c := range text { for i, c := range text {
actions = append(actions, map[string]interface{}{"type": "keyDown", "value": string(c)}, actions = append(actions, map[string]interface{}{"type": "keyDown", "value": string(c)},
@@ -567,13 +567,13 @@ func (ud *uiaDriver) SendActionKey(text string, options ...ActionOption) (err er
} }
// new data options in post data for extra uiautomator configurations // new data options in post data for extra uiautomator configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
_, err = ud.httpPOST(data, "/session", ud.session.ID, "/actions/keys") _, err = ud.httpPOST(data, "/session", ud.session.ID, "/actions/keys")
return return
} }
func (ud *uiaDriver) Input(text string, options ...ActionOption) (err error) { func (ud *uiaDriver) Input(text string, opts ...options.ActionOption) (err error) {
return ud.SendKeys(text, options...) return ud.SendKeys(text, opts...)
} }
func (ud *uiaDriver) Rotation() (rotation Rotation, err error) { func (ud *uiaDriver) Rotation() (rotation Rotation, err error) {
@@ -625,12 +625,12 @@ func (ud *uiaDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy,
return return
} }
func (ud *uiaDriver) TapByText(text string, options ...ActionOption) error { func (ud *uiaDriver) TapByText(text string, opts ...options.ActionOption) error {
sourceTree, err := ud.sourceTree() sourceTree, err := ud.sourceTree()
if err != nil { if err != nil {
return err return err
} }
return ud.tapByTextUsingHierarchy(sourceTree, text, options...) return ud.tapByTextUsingHierarchy(sourceTree, text, opts...)
} }
func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error { func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error {
+30 -6
View File
@@ -220,7 +220,9 @@ func TestDriver_DeviceInfo(t *testing.T) {
func TestDriver_Tap(t *testing.T) { func TestDriver_Tap(t *testing.T) {
setupAndroidUIA2Driver(t) setupAndroidUIA2Driver(t)
driverExt.Driver.StartCaptureLog("") driverExt.Driver.StartCaptureLog("")
err := driverExt.TapXY(0.5, 0.5, WithIdentifier("test"), WithPressDuration(4)) err := driverExt.TapXY(0.5, 0.5,
options.WithIdentifier("test"),
options.WithPressDuration(4))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -237,7 +239,8 @@ func TestDriver_Tap(t *testing.T) {
func TestDriver_Swipe(t *testing.T) { func TestDriver_Swipe(t *testing.T) {
setupAndroidUIA2Driver(t) setupAndroidUIA2Driver(t)
err := driverExt.Driver.Swipe(400, 1000, 400, 500, WithPressDuration(0.5)) err := driverExt.Driver.Swipe(400, 1000, 400, 500,
options.WithPressDuration(0.5))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -273,7 +276,8 @@ func TestDriver_Drag(t *testing.T) {
func TestDriver_SendKeys(t *testing.T) { func TestDriver_SendKeys(t *testing.T) {
setupAndroidUIA2Driver(t) setupAndroidUIA2Driver(t)
err := driverExt.Driver.SendKeys("辽宁省沈阳市新民市民族街36-4", WithIdentifier("test")) err := driverExt.Driver.SendKeys("辽宁省沈阳市新民市民族街36-4",
options.WithIdentifier("test"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -494,9 +498,29 @@ func TestDriver_ShellInputUnicode(t *testing.T) {
func TestTapTexts(t *testing.T) { func TestTapTexts(t *testing.T) {
setupAndroidUIA2Driver(t) setupAndroidUIA2Driver(t)
actions := []TapTextAction{ actions := []TapTextAction{
{Text: "^.*无视风险安装$", Options: []ActionOption{WithTapOffset(100, 0), WithRegex(true), WithIgnoreNotFoundError(true)}}, {
{Text: "已了解此应用未经检测.*", Options: []ActionOption{WithTapOffset(-450, 0), WithRegex(true), WithIgnoreNotFoundError(true)}}, Text: "^.*无视风险安装$",
{Text: "^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|安装|授权本次安装|继续安装|重新安装)$", Options: []ActionOption{WithRegex(true), WithIgnoreNotFoundError(true)}}, Options: []options.ActionOption{
options.WithTapOffset(100, 0),
options.WithRegex(true),
options.WithIgnoreNotFoundError(true),
},
},
{
Text: "已了解此应用未经检测.*",
Options: []options.ActionOption{
options.WithTapOffset(-450, 0),
options.WithRegex(true),
options.WithIgnoreNotFoundError(true),
},
},
{
Text: "^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|安装|授权本次安装|继续安装|重新安装)$",
Options: []options.ActionOption{
options.WithRegex(true),
options.WithIgnoreNotFoundError(true),
},
},
} }
err := driverExt.Driver.TapByTexts(actions...) err := driverExt.Driver.TapByTexts(actions...)
if err != nil { if err != nil {
+8 -8
View File
@@ -63,29 +63,29 @@ func (dExt *DriverExt) Init() error {
} }
func (dExt *DriverExt) assertOCR(text, assert string) error { func (dExt *DriverExt) assertOCR(text, assert string) error {
var options []ActionOption var opts []options.ActionOption
options = append(options, WithScreenShotFileName(fmt.Sprintf("assert_ocr_%s", text))) opts = append(opts, options.WithScreenShotFileName(fmt.Sprintf("assert_ocr_%s", text)))
switch assert { switch assert {
case AssertionEqual: case AssertionEqual:
_, err := dExt.FindScreenText(text, options...) _, err := dExt.FindScreenText(text, opts...)
if err != nil { if err != nil {
return errors.Wrap(err, "assert ocr equal failed") return errors.Wrap(err, "assert ocr equal failed")
} }
case AssertionNotEqual: case AssertionNotEqual:
_, err := dExt.FindScreenText(text, options...) _, err := dExt.FindScreenText(text, opts...)
if err == nil { if err == nil {
return errors.New("assert ocr not equal failed") return errors.New("assert ocr not equal failed")
} }
case AssertionExists: case AssertionExists:
options = append(options, WithRegex(true)) opts = append(opts, options.WithRegex(true))
_, err := dExt.FindScreenText(text, options...) _, err := dExt.FindScreenText(text, opts...)
if err != nil { if err != nil {
return errors.Wrap(err, "assert ocr exists failed") return errors.Wrap(err, "assert ocr exists failed")
} }
case AssertionNotExists: case AssertionNotExists:
options = append(options, WithRegex(true)) opts = append(opts, options.WithRegex(true))
_, err := dExt.FindScreenText(text, options...) _, err := dExt.FindScreenText(text, opts...)
if err == nil { if err == nil {
return errors.New("assert ocr not exists failed") return errors.New("assert ocr not exists failed")
} }
+1 -1
View File
@@ -120,7 +120,7 @@ func (dev *HarmonyDevice) NewUSBDriver(opts ...options.DriverOption) (driver IWe
return harmonyDriver, nil return harmonyDriver, nil
} }
func (dev *HarmonyDevice) Install(appPath string, options ...InstallOption) error { func (dev *HarmonyDevice) Install(appPath string, opts ...InstallOption) error {
return nil return nil
} }
+18 -18
View File
@@ -155,16 +155,16 @@ func (hd *hdcDriver) Orientation() (orientation Orientation, err error) {
return OrientationPortrait, nil return OrientationPortrait, nil
} }
func (hd *hdcDriver) Tap(x, y float64, options ...ActionOption) error { func (hd *hdcDriver) Tap(x, y float64, opts ...options.ActionOption) error {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(actionOptions.Offset) == 2 { if len(actionOptions.Offset) == 2 {
x += float64(actionOptions.Offset[0]) x += float64(actionOptions.Offset[0])
y += float64(actionOptions.Offset[1]) y += float64(actionOptions.Offset[1])
} }
x += actionOptions.getRandomOffset() x += actionOptions.GetRandomOffset()
y += actionOptions.getRandomOffset() y += actionOptions.GetRandomOffset()
if actionOptions.Identifier != "" { if actionOptions.Identifier != "" {
startTime := int(time.Now().UnixMilli()) startTime := int(time.Now().UnixMilli())
hd.points = append(hd.points, ExportPoint{Start: startTime, End: startTime + 100, Ext: actionOptions.Identifier, RunTime: 100}) hd.points = append(hd.points, ExportPoint{Start: startTime, End: startTime + 100, Ext: actionOptions.Identifier, RunTime: 100})
@@ -172,31 +172,31 @@ func (hd *hdcDriver) Tap(x, y float64, options ...ActionOption) error {
return hd.uiDriver.InjectGesture(ghdc.NewGesture().Start(ghdc.Point{X: int(x), Y: int(y)}).Pause(100)) return hd.uiDriver.InjectGesture(ghdc.NewGesture().Start(ghdc.Point{X: int(x), Y: int(y)}).Pause(100))
} }
func (hd *hdcDriver) DoubleTap(x, y float64, options ...ActionOption) error { func (hd *hdcDriver) DoubleTap(x, y float64, opts ...options.ActionOption) error {
return errDriverNotImplemented return errDriverNotImplemented
} }
func (hd *hdcDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) { func (hd *hdcDriver) TouchAndHold(x, y float64, opts ...options.ActionOption) (err error) {
return errDriverNotImplemented return errDriverNotImplemented
} }
func (hd *hdcDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) error { func (hd *hdcDriver) Drag(fromX, fromY, toX, toY float64, opts ...options.ActionOption) error {
return errDriverNotImplemented return errDriverNotImplemented
} }
// Swipe works like Drag, but `pressForDuration` value is 0 // Swipe works like Drag, but `pressForDuration` value is 0
func (hd *hdcDriver) Swipe(fromX, fromY, toX, toY float64, options ...ActionOption) error { func (hd *hdcDriver) Swipe(fromX, fromY, toX, toY float64, opts ...options.ActionOption) error {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if len(actionOptions.Offset) == 4 { if len(actionOptions.Offset) == 4 {
fromX += float64(actionOptions.Offset[0]) fromX += float64(actionOptions.Offset[0])
fromY += float64(actionOptions.Offset[1]) fromY += float64(actionOptions.Offset[1])
toX += float64(actionOptions.Offset[2]) toX += float64(actionOptions.Offset[2])
toY += float64(actionOptions.Offset[3]) toY += float64(actionOptions.Offset[3])
} }
fromX += actionOptions.getRandomOffset() fromX += actionOptions.GetRandomOffset()
fromY += actionOptions.getRandomOffset() fromY += actionOptions.GetRandomOffset()
toX += actionOptions.getRandomOffset() toX += actionOptions.GetRandomOffset()
toY += actionOptions.getRandomOffset() toY += actionOptions.GetRandomOffset()
duration := 200 duration := 200
if actionOptions.PressDuration > 0 { if actionOptions.PressDuration > 0 {
@@ -221,11 +221,11 @@ func (hd *hdcDriver) SetIme(ime string) error {
return errDriverNotImplemented return errDriverNotImplemented
} }
func (hd *hdcDriver) SendKeys(text string, options ...ActionOption) error { func (hd *hdcDriver) SendKeys(text string, opts ...options.ActionOption) error {
return hd.uiDriver.InputText(text) return hd.uiDriver.InputText(text)
} }
func (hd *hdcDriver) Input(text string, options ...ActionOption) error { func (hd *hdcDriver) Input(text string, opts ...options.ActionOption) error {
return hd.uiDriver.InputText(text) return hd.uiDriver.InputText(text)
} }
@@ -237,11 +237,11 @@ func (hd *hdcDriver) PressButton(devBtn DeviceButton) error {
return errDriverNotImplemented return errDriverNotImplemented
} }
func (hd *hdcDriver) PressBack(options ...ActionOption) error { func (hd *hdcDriver) PressBack(opts ...options.ActionOption) error {
return hd.uiDriver.PressBack() return hd.uiDriver.PressBack()
} }
func (hd *hdcDriver) Backspace(count int, options ...ActionOption) (err error) { func (hd *hdcDriver) Backspace(count int, opts ...options.ActionOption) (err error) {
return nil return nil
} }
@@ -286,7 +286,7 @@ func (hd *hdcDriver) LogoutNoneUI(packageName string) error {
return errDriverNotImplemented return errDriverNotImplemented
} }
func (hd *hdcDriver) TapByText(text string, options ...ActionOption) error { func (hd *hdcDriver) TapByText(text string, opts ...options.ActionOption) error {
return errDriverNotImplemented return errDriverNotImplemented
} }
+23 -13
View File
@@ -9,6 +9,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v5/internal/builtin" "github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
type InstallOptions struct { type InstallOptions struct {
@@ -20,9 +21,9 @@ type InstallOptions struct {
type InstallOption func(o *InstallOptions) type InstallOption func(o *InstallOptions)
func NewInstallOptions(options ...InstallOption) *InstallOptions { func NewInstallOptions(opts ...InstallOption) *InstallOptions {
installOptions := &InstallOptions{} installOptions := &InstallOptions{}
for _, option := range options { for _, option := range opts {
option(installOptions) option(installOptions)
} }
return installOptions return installOptions
@@ -58,7 +59,7 @@ type InstallResult struct {
ErrorMsg string `json:"errorMsg"` ErrorMsg string `json:"errorMsg"`
} }
func (dExt *DriverExt) InstallByUrl(url string, options ...InstallOption) error { func (dExt *DriverExt) InstallByUrl(url string, opts ...InstallOption) error {
// 获取当前目录 // 获取当前目录
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
@@ -73,7 +74,7 @@ func (dExt *DriverExt) InstallByUrl(url string, options ...InstallOption) error
return err return err
} }
err = dExt.Install(appPath, options...) err = dExt.Install(appPath, opts...)
if err != nil { if err != nil {
log.Error().Err(err).Msg("install app failed") log.Error().Err(err).Msg("install app failed")
return err return err
@@ -81,7 +82,7 @@ func (dExt *DriverExt) InstallByUrl(url string, options ...InstallOption) error
return nil return nil
} }
func (dExt *DriverExt) Install(filePath string, options ...InstallOption) error { func (dExt *DriverExt) Install(filePath string, opts ...InstallOption) error {
if _, ok := dExt.Device.(*AndroidDevice); ok { if _, ok := dExt.Device.(*AndroidDevice); ok {
stopChan := make(chan struct{}) stopChan := make(chan struct{})
go func() { go func() {
@@ -93,19 +94,28 @@ func (dExt *DriverExt) Install(filePath string, options ...InstallOption) error
case <-ticker.C: case <-ticker.C:
actions := []TapTextAction{ actions := []TapTextAction{
{ {
Text: "^.*无视风险安装$", Text: "^.*无视风险安装$",
Options: []ActionOption{WithTapOffset(100, 0), WithRegex(true), WithIgnoreNotFoundError(true)}, Options: []options.ActionOption{
options.WithTapOffset(100, 0),
options.WithRegex(true),
options.WithIgnoreNotFoundError(true),
},
}, },
{ {
Text: "^已了解此应用未经检测.*", Text: "^已了解此应用未经检测.*",
Options: []ActionOption{WithTapOffset(-450, 0), WithRegex(true), WithIgnoreNotFoundError(true)}, Options: []options.ActionOption{
options.WithTapOffset(-450, 0),
options.WithRegex(true),
options.WithIgnoreNotFoundError(true),
},
}, },
} }
_ = dExt.Driver.TapByTexts(actions...) _ = dExt.Driver.TapByTexts(actions...)
_ = dExt.TapByOCR( _ = dExt.TapByOCR(
"^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|授权本次安装|稍后提醒|继续安装|重新安装|安装)$", "^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|授权本次安装|稍后提醒|继续安装|重新安装|安装)$",
WithRegex(true), WithIgnoreNotFoundError(true), options.WithRegex(true),
options.WithIgnoreNotFoundError(true),
) )
case <-stopChan: case <-stopChan:
log.Info().Msg("Ticker stopped") log.Info().Msg("Ticker stopped")
@@ -118,11 +128,11 @@ func (dExt *DriverExt) Install(filePath string, options ...InstallOption) error
}() }()
} }
return dExt.Device.Install(filePath, options...) return dExt.Device.Install(filePath, opts...)
} }
func (dExt *DriverExt) Uninstall(packageName string, options ...ActionOption) error { func (dExt *DriverExt) Uninstall(packageName string, opts ...options.ActionOption) error {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
err := dExt.Device.Uninstall(packageName) err := dExt.Device.Uninstall(packageName)
if err != nil { if err != nil {
log.Warn().Err(err).Msg("failed to uninstall") log.Warn().Err(err).Msg("failed to uninstall")
+11 -11
View File
@@ -359,7 +359,7 @@ type IDevice interface {
// TODO: add ctx to NewDriver // TODO: add ctx to NewDriver
NewDriver(...options.DriverOption) (driverExt *DriverExt, err error) NewDriver(...options.DriverOption) (driverExt *DriverExt, err error)
Install(appPath string, options ...InstallOption) error Install(appPath string, opts ...InstallOption) error
Uninstall(packageName string) error Uninstall(packageName string) error
GetPackageInfo(packageName string) (AppInfo, error) GetPackageInfo(packageName string) (AppInfo, error)
@@ -438,21 +438,21 @@ type IWebDriver interface {
Orientation() (orientation Orientation, err error) Orientation() (orientation Orientation, err error)
// Tap Sends a tap event at the coordinate. // Tap Sends a tap event at the coordinate.
Tap(x, y float64, options ...ActionOption) error Tap(x, y float64, opts ...options.ActionOption) error
// DoubleTap Sends a double tap event at the coordinate. // DoubleTap Sends a double tap event at the coordinate.
DoubleTap(x, y float64, options ...ActionOption) error DoubleTap(x, y float64, opts ...options.ActionOption) error
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration. // TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
// second: The default value is 1 // second: The default value is 1
TouchAndHold(x, y float64, options ...ActionOption) error TouchAndHold(x, y float64, opts ...options.ActionOption) error
// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate. // Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate.
// WithPressDurationOption option can be used to set pressForDuration (default to 1 second). // WithPressDurationOption option can be used to set pressForDuration (default to 1 second).
Drag(fromX, fromY, toX, toY float64, options ...ActionOption) error Drag(fromX, fromY, toX, toY float64, opts ...options.ActionOption) error
// Swipe works like Drag, but `pressForDuration` value is 0 // Swipe works like Drag, but `pressForDuration` value is 0
Swipe(fromX, fromY, toX, toY float64, options ...ActionOption) error Swipe(fromX, fromY, toX, toY float64, opts ...options.ActionOption) error
// SetPasteboard Sets data to the general pasteboard // SetPasteboard Sets data to the general pasteboard
SetPasteboard(contentType PasteboardType, content string) error SetPasteboard(contentType PasteboardType, content string) error
@@ -465,10 +465,10 @@ type IWebDriver interface {
// SendKeys Types a string into active element. There must be element with keyboard focus, // SendKeys Types a string into active element. There must be element with keyboard focus,
// otherwise an error is raised. // otherwise an error is raised.
// WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60 // WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60
SendKeys(text string, options ...ActionOption) error SendKeys(text string, opts ...options.ActionOption) error
// Input works like SendKeys // Input works like SendKeys
Input(text string, options ...ActionOption) error Input(text string, opts ...options.ActionOption) error
Clear(packageName string) error Clear(packageName string) error
@@ -476,11 +476,11 @@ type IWebDriver interface {
PressButton(devBtn DeviceButton) error PressButton(devBtn DeviceButton) error
// PressBack Presses the back button // PressBack Presses the back button
PressBack(options ...ActionOption) error PressBack(opts ...options.ActionOption) error
PressKeyCode(keyCode KeyCode) (err error) PressKeyCode(keyCode KeyCode) (err error)
Backspace(count int, options ...ActionOption) (err error) Backspace(count int, opts ...options.ActionOption) (err error)
Screenshot() (*bytes.Buffer, error) Screenshot() (*bytes.Buffer, error)
@@ -490,7 +490,7 @@ type IWebDriver interface {
LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error)
LogoutNoneUI(packageName string) error LogoutNoneUI(packageName string) error
TapByText(text string, options ...ActionOption) error TapByText(text string, opts ...options.ActionOption) error
TapByTexts(actions ...TapTextAction) error TapByTexts(actions ...TapTextAction) error
// AccessibleSource Return application elements accessibility tree // AccessibleSource Return application elements accessibility tree
+5 -5
View File
@@ -323,9 +323,9 @@ func (dev *IOSDevice) NewDriver(opts ...options.DriverOption) (driverExt *Driver
return driverExt, nil return driverExt, nil
} }
func (dev *IOSDevice) Install(appPath string, options ...InstallOption) (err error) { func (dev *IOSDevice) Install(appPath string, opts ...InstallOption) (err error) {
opts := NewInstallOptions(options...) installOpts := NewInstallOptions(opts...)
for i := 0; i <= opts.RetryTimes; i++ { for i := 0; i <= installOpts.RetryTimes; i++ {
var conn *zipconduit.Connection var conn *zipconduit.Connection
conn, err = zipconduit.New(dev.d) conn, err = zipconduit.New(dev.d)
if err != nil { if err != nil {
@@ -343,12 +343,12 @@ func (dev *IOSDevice) Install(appPath string, options ...InstallOption) (err err
return err return err
} }
func (dev *IOSDevice) InstallByUrl(url string, options ...InstallOption) (err error) { func (dev *IOSDevice) InstallByUrl(url string, opts ...InstallOption) (err error) {
appPath, err := builtin.DownloadFileByUrl(url) appPath, err := builtin.DownloadFileByUrl(url)
if err != nil { if err != nil {
return err return err
} }
err = dev.Install(appPath, options...) err = dev.Install(appPath, opts...)
if err != nil { if err != nil {
return err return err
} }
+16 -16
View File
@@ -220,42 +220,42 @@ func (s *stubIOSDriver) Orientation() (orientation Orientation, err error) {
} }
// Tap Sends a tap event at the coordinate. // Tap Sends a tap event at the coordinate.
func (s *stubIOSDriver) Tap(x, y float64, options ...ActionOption) error { func (s *stubIOSDriver) Tap(x, y float64, opts ...options.ActionOption) error {
err := s.setUpWda() err := s.setUpWda()
if err != nil { if err != nil {
return err return err
} }
return s.wdaDriver.Tap(x, y, options...) return s.wdaDriver.Tap(x, y, opts...)
} }
// DoubleTap Sends a double tap event at the coordinate. // DoubleTap Sends a double tap event at the coordinate.
func (s *stubIOSDriver) DoubleTap(x, y float64, options ...ActionOption) error { func (s *stubIOSDriver) DoubleTap(x, y float64, opts ...options.ActionOption) error {
err := s.setUpWda() err := s.setUpWda()
if err != nil { if err != nil {
return err return err
} }
return s.wdaDriver.DoubleTap(x, y, options...) return s.wdaDriver.DoubleTap(x, y, opts...)
} }
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration. // TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
// //
// second: The default value is 1 // second: The default value is 1
func (s *stubIOSDriver) TouchAndHold(x, y float64, options ...ActionOption) error { func (s *stubIOSDriver) TouchAndHold(x, y float64, opts ...options.ActionOption) error {
err := s.setUpWda() err := s.setUpWda()
if err != nil { if err != nil {
return err return err
} }
return s.wdaDriver.TouchAndHold(x, y, options...) return s.wdaDriver.TouchAndHold(x, y, opts...)
} }
// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate. // Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate.
// WithPressDurationOption option can be used to set pressForDuration (default to 1 second). // WithPressDurationOption option can be used to set pressForDuration (default to 1 second).
func (s *stubIOSDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) error { func (s *stubIOSDriver) Drag(fromX, fromY, toX, toY float64, opts ...options.ActionOption) error {
err := s.setUpWda() err := s.setUpWda()
if err != nil { if err != nil {
return err return err
} }
return s.wdaDriver.Drag(fromX, fromY, toX, toY, options...) return s.wdaDriver.Drag(fromX, fromY, toX, toY, opts...)
} }
// SetPasteboard Sets data to the general pasteboard // SetPasteboard Sets data to the general pasteboard
@@ -289,21 +289,21 @@ func (s *stubIOSDriver) SetIme(ime string) error {
// SendKeys Types a string into active element. There must be element with keyboard focus, // SendKeys Types a string into active element. There must be element with keyboard focus,
// otherwise an error is raised. // otherwise an error is raised.
// WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60 // WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60
func (s *stubIOSDriver) SendKeys(text string, options ...ActionOption) error { func (s *stubIOSDriver) SendKeys(text string, opts ...options.ActionOption) error {
err := s.setUpWda() err := s.setUpWda()
if err != nil { if err != nil {
return err return err
} }
return s.wdaDriver.SendKeys(text, options...) return s.wdaDriver.SendKeys(text, opts...)
} }
// Input works like SendKeys // Input works like SendKeys
func (s *stubIOSDriver) Input(text string, options ...ActionOption) error { func (s *stubIOSDriver) Input(text string, opts ...options.ActionOption) error {
err := s.setUpWda() err := s.setUpWda()
if err != nil { if err != nil {
return err return err
} }
return s.wdaDriver.Input(text, options...) return s.wdaDriver.Input(text, opts...)
} }
func (s *stubIOSDriver) Clear(packageName string) error { func (s *stubIOSDriver) Clear(packageName string) error {
@@ -324,12 +324,12 @@ func (s *stubIOSDriver) PressButton(devBtn DeviceButton) error {
} }
// PressBack Presses the back button // PressBack Presses the back button
func (s *stubIOSDriver) PressBack(options ...ActionOption) error { func (s *stubIOSDriver) PressBack(opts ...options.ActionOption) error {
err := s.setUpWda() err := s.setUpWda()
if err != nil { if err != nil {
return err return err
} }
return s.wdaDriver.PressBack(options...) return s.wdaDriver.PressBack(opts...)
} }
func (s *stubIOSDriver) PressKeyCode(keyCode KeyCode) (err error) { func (s *stubIOSDriver) PressKeyCode(keyCode KeyCode) (err error) {
@@ -361,12 +361,12 @@ func (s *stubIOSDriver) Screenshot() (*bytes.Buffer, error) {
//return bytes.NewBuffer(imageBytes), nil //return bytes.NewBuffer(imageBytes), nil
} }
func (s *stubIOSDriver) TapByText(text string, options ...ActionOption) error { func (s *stubIOSDriver) TapByText(text string, opts ...options.ActionOption) error {
err := s.setUpWda() err := s.setUpWda()
if err != nil { if err != nil {
return err return err
} }
return s.wdaDriver.TapByText(text, options...) return s.wdaDriver.TapByText(text, opts...)
} }
func (s *stubIOSDriver) TapByTexts(actions ...TapTextAction) error { func (s *stubIOSDriver) TapByTexts(actions ...TapTextAction) error {
+38 -38
View File
@@ -508,9 +508,9 @@ func (wd *wdaDriver) AssertForegroundApp(bundleId string, viewControllerType ...
return nil return nil
} }
func (wd *wdaDriver) Tap(x, y float64, options ...ActionOption) (err error) { func (wd *wdaDriver) Tap(x, y float64, opts ...options.ActionOption) (err error) {
// [[FBRoute POST:@"/wda/tap/:uuid"] respondWithTarget:self action:@selector(handleTap:)] // [[FBRoute POST:@"/wda/tap/:uuid"] respondWithTarget:self action:@selector(handleTap:)]
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
x = wd.toScale(x) x = wd.toScale(x)
y = wd.toScale(y) y = wd.toScale(y)
@@ -518,31 +518,31 @@ func (wd *wdaDriver) Tap(x, y float64, options ...ActionOption) (err error) {
x += float64(actionOptions.Offset[0]) x += float64(actionOptions.Offset[0])
y += float64(actionOptions.Offset[1]) y += float64(actionOptions.Offset[1])
} }
x += actionOptions.getRandomOffset() x += actionOptions.GetRandomOffset()
y += actionOptions.getRandomOffset() y += actionOptions.GetRandomOffset()
data := map[string]interface{}{ data := map[string]interface{}{
"x": x, "x": x,
"y": y, "y": y,
} }
// update data options in post data for extra WDA configurations // update data options in post data for extra WDA configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/tap/0") _, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/tap/0")
return return
} }
func (wd *wdaDriver) DoubleTap(x, y float64, options ...ActionOption) (err error) { func (wd *wdaDriver) DoubleTap(x, y float64, opts ...options.ActionOption) (err error) {
// [[FBRoute POST:@"/wda/doubleTap"] respondWithTarget:self action:@selector(handleDoubleTapCoordinate:)] // [[FBRoute POST:@"/wda/doubleTap"] respondWithTarget:self action:@selector(handleDoubleTapCoordinate:)]
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
x = wd.toScale(x) x = wd.toScale(x)
y = wd.toScale(y) y = wd.toScale(y)
if len(actionOptions.Offset) == 2 { if len(actionOptions.Offset) == 2 {
x += float64(actionOptions.Offset[0]) x += float64(actionOptions.Offset[0])
y += float64(actionOptions.Offset[1]) y += float64(actionOptions.Offset[1])
} }
x += actionOptions.getRandomOffset() x += actionOptions.GetRandomOffset()
y += actionOptions.getRandomOffset() y += actionOptions.GetRandomOffset()
data := map[string]interface{}{ data := map[string]interface{}{
"x": x, "x": x,
@@ -552,17 +552,17 @@ func (wd *wdaDriver) DoubleTap(x, y float64, options ...ActionOption) (err error
return return
} }
func (wd *wdaDriver) TouchAndHold(x, y float64, options ...ActionOption) (err error) { func (wd *wdaDriver) TouchAndHold(x, y float64, opts ...options.ActionOption) (err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if actionOptions.Duration == 0 { if actionOptions.Duration == 0 {
options = append(options, WithDuration(1)) opts = append(opts, options.WithDuration(1))
} }
return wd.Tap(x, y, options...) return wd.Tap(x, y, opts...)
} }
func (wd *wdaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) { func (wd *wdaDriver) Drag(fromX, fromY, toX, toY float64, opts ...options.ActionOption) (err error) {
// [[FBRoute POST:@"/wda/dragfromtoforduration"] respondWithTarget:self action:@selector(handleDragCoordinate:)] // [[FBRoute POST:@"/wda/dragfromtoforduration"] respondWithTarget:self action:@selector(handleDragCoordinate:)]
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
fromX = wd.toScale(fromX) fromX = wd.toScale(fromX)
fromY = wd.toScale(fromY) fromY = wd.toScale(fromY)
@@ -574,10 +574,10 @@ func (wd *wdaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOptio
toX += float64(actionOptions.Offset[2]) toX += float64(actionOptions.Offset[2])
toY += float64(actionOptions.Offset[3]) toY += float64(actionOptions.Offset[3])
} }
fromX += actionOptions.getRandomOffset() fromX += actionOptions.GetRandomOffset()
fromY += actionOptions.getRandomOffset() fromY += actionOptions.GetRandomOffset()
toX += actionOptions.getRandomOffset() toX += actionOptions.GetRandomOffset()
toY += actionOptions.getRandomOffset() toY += actionOptions.GetRandomOffset()
data := map[string]interface{}{ data := map[string]interface{}{
"fromX": math.Round(fromX*10) / 10, "fromX": math.Round(fromX*10) / 10,
@@ -590,15 +590,15 @@ func (wd *wdaDriver) Drag(fromX, fromY, toX, toY float64, options ...ActionOptio
} }
// update data options in post data for extra WDA configurations // update data options in post data for extra WDA configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
// wda 43 version // wda 43 version
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/dragfromtoforduration") _, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/dragfromtoforduration")
// _, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/drag") // _, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/drag")
return return
} }
func (wd *wdaDriver) Swipe(fromX, fromY, toX, toY float64, options ...ActionOption) error { func (wd *wdaDriver) Swipe(fromX, fromY, toX, toY float64, opts ...options.ActionOption) error {
return wd.Drag(fromX, fromY, toX, toY, options...) return wd.Drag(fromX, fromY, toX, toY, opts...)
} }
func (wd *wdaDriver) SetPasteboard(contentType PasteboardType, content string) (err error) { func (wd *wdaDriver) SetPasteboard(contentType PasteboardType, content string) (err error) {
@@ -632,34 +632,34 @@ func (wd *wdaDriver) PressKeyCode(keyCode KeyCode) (err error) {
return errDriverNotImplemented return errDriverNotImplemented
} }
func (wd *wdaDriver) SendKeys(text string, options ...ActionOption) (err error) { func (wd *wdaDriver) SendKeys(text string, opts ...options.ActionOption) (err error) {
// [[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)] // [[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)]
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
data := map[string]interface{}{"value": strings.Split(text, "")} data := map[string]interface{}{"value": strings.Split(text, "")}
// new data options in post data for extra WDA configurations // new data options in post data for extra WDA configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/keys") _, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/keys")
return return
} }
func (wd *wdaDriver) Backspace(count int, options ...ActionOption) (err error) { func (wd *wdaDriver) Backspace(count int, opts ...options.ActionOption) (err error) {
if count == 0 { if count == 0 {
return nil return nil
} }
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
data := map[string]interface{}{"count": count} data := map[string]interface{}{"count": count}
// new data options in post data for extra WDA configurations // new data options in post data for extra WDA configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
_, err = wd.httpPOST(data, "/gtf/interaction/input/backspace") _, err = wd.httpPOST(data, "/gtf/interaction/input/backspace")
return return
} }
func (wd *wdaDriver) Input(text string, options ...ActionOption) (err error) { func (wd *wdaDriver) Input(text string, opts ...options.ActionOption) (err error) {
return wd.SendKeys(text, options...) return wd.SendKeys(text, opts...)
} }
func (wd *wdaDriver) Clear(packageName string) error { func (wd *wdaDriver) Clear(packageName string) error {
@@ -667,8 +667,8 @@ func (wd *wdaDriver) Clear(packageName string) error {
} }
// PressBack simulates a short press on the BACK button. // PressBack simulates a short press on the BACK button.
func (wd *wdaDriver) PressBack(options ...ActionOption) (err error) { func (wd *wdaDriver) PressBack(opts ...options.ActionOption) (err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
windowSize, err := wd.WindowSize() windowSize, err := wd.WindowSize()
if err != nil { if err != nil {
@@ -684,10 +684,10 @@ func (wd *wdaDriver) PressBack(options ...ActionOption) (err error) {
toX += float64(actionOptions.Offset[2]) toX += float64(actionOptions.Offset[2])
toY += float64(actionOptions.Offset[3]) toY += float64(actionOptions.Offset[3])
} }
fromX += actionOptions.getRandomOffset() fromX += actionOptions.GetRandomOffset()
fromY += actionOptions.getRandomOffset() fromY += actionOptions.GetRandomOffset()
toX += actionOptions.getRandomOffset() toX += actionOptions.GetRandomOffset()
toY += actionOptions.getRandomOffset() toY += actionOptions.GetRandomOffset()
data := map[string]interface{}{ data := map[string]interface{}{
"fromX": fromX, "fromX": fromX,
@@ -697,7 +697,7 @@ func (wd *wdaDriver) PressBack(options ...ActionOption) (err error) {
} }
// update data options in post data for extra WDA configurations // update data options in post data for extra WDA configurations
actionOptions.updateData(data) actionOptions.UpdateData(data)
_, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/dragfromtoforduration") _, err = wd.httpPOST(data, "/session", wd.session.ID, "/wda/dragfromtoforduration")
return return
@@ -826,7 +826,7 @@ func (wd *wdaDriver) Source(srcOpt ...SourceOption) (source string, err error) {
return return
} }
func (wd *wdaDriver) TapByText(text string, options ...ActionOption) error { func (wd *wdaDriver) TapByText(text string, opts ...options.ActionOption) error {
return errDriverNotImplemented return errDriverNotImplemented
} }
+3 -2
View File
@@ -297,7 +297,8 @@ func Test_remoteWD_Drag(t *testing.T) {
setup(t) setup(t)
// err := driver.Drag(200, 300, 200, 500, WithDataPressDuration(0.5)) // err := driver.Drag(200, 300, 200, 500, WithDataPressDuration(0.5))
err := driver.Drag(200, 300, 200, 500, WithPressDuration(2), WithDuration(3)) err := driver.Drag(200, 300, 200, 500,
options.WithPressDuration(2), options.WithDuration(3))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -352,7 +353,7 @@ func Test_remoteWD_GetPasteboard(t *testing.T) {
func Test_remoteWD_SendKeys(t *testing.T) { func Test_remoteWD_SendKeys(t *testing.T) {
setup(t) setup(t)
// driver.StartCaptureLog("hrp_wda_log") // driver.StartCaptureLog("hrp_wda_log")
err := driver.SendKeys("test", WithIdentifier("test")) err := driver.SendKeys("test", options.WithIdentifier("test"))
// result, _ := driver.StopCaptureLog() // result, _ := driver.StopCaptureLog()
// err := driver.SendKeys("App Store", WithFrequency(3)) // err := driver.SendKeys("App Store", WithFrequency(3))
if err != nil { if err != nil {
+3 -2
View File
@@ -8,6 +8,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -28,8 +29,8 @@ type EndToEndDelay struct {
Timelines []timeLog `json:"timelines"` Timelines []timeLog `json:"timelines"`
} }
func CollectEndToEndDelay(dExt *DriverExt, options ...ActionOption) { func CollectEndToEndDelay(dExt *DriverExt, opts ...options.ActionOption) {
dataOptions := NewActionOptions(options...) dataOptions := options.NewActionOptions(opts...)
startTime := time.Now() startTime := time.Now()
if dataOptions.Interval == 0 { if dataOptions.Interval == 0 {
+451
View File
@@ -0,0 +1,451 @@
package options
import (
"math/rand/v2"
"github.com/httprunner/httprunner/v5/internal/builtin"
)
// (x1, y1) is the top left corner, (x2, y2) is the bottom right corner
// [x1, y1, x2, y2] in percentage of the screen
type Scope []float64
// [x1, y1, x2, y2] in absolute pixels
type AbsScope []int
func (s AbsScope) Option() ActionOption {
return WithAbsScope(s[0], s[1], s[2], s[3])
}
type ActionOptions struct {
// log
Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // used to identify the action in log
// control related
MaxRetryTimes int `json:"max_retry_times,omitempty" yaml:"max_retry_times,omitempty"` // max retry times
IgnoreNotFoundError bool `json:"ignore_NotFoundError,omitempty" yaml:"ignore_NotFoundError,omitempty"` // ignore error if target element not found
Interval float64 `json:"interval,omitempty" yaml:"interval,omitempty"` // interval between retries in seconds
Duration float64 `json:"duration,omitempty" yaml:"duration,omitempty"` // used to set duration of ios swipe action
PressDuration float64 `json:"press_duration,omitempty" yaml:"press_duration,omitempty"` // used to set duration of ios swipe action
Steps int `json:"steps,omitempty" yaml:"steps,omitempty"` // used to set steps of android swipe action
Direction interface{} `json:"direction,omitempty" yaml:"direction,omitempty"` // used by swipe to tap text or app
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty"` // TODO: wait timeout in seconds for mobile action
Frequency int `json:"frequency,omitempty" yaml:"frequency,omitempty"`
// scope related
Scope Scope `json:"scope,omitempty" yaml:"scope,omitempty"`
AbsScope AbsScope `json:"abs_scope,omitempty" yaml:"abs_scope,omitempty"`
Regex bool `json:"regex,omitempty" yaml:"regex,omitempty"` // use regex to match text
Offset []int `json:"offset,omitempty" yaml:"offset,omitempty"` // used to tap offset of point
OffsetRandomRange []int `json:"offset_random_range,omitempty" yaml:"offset_random_range,omitempty"` // set random range [min, max] for tap/swipe points
Index int `json:"index,omitempty" yaml:"index,omitempty"` // index of the target element
MatchOne bool `json:"match_one,omitempty" yaml:"match_one,omitempty"` // match one of the targets if existed
// set custiom options such as textview, id, description
Custom map[string]interface{} `json:"custom,omitempty" yaml:"custom,omitempty"`
// screenshot related
ScreenShotWithOCR bool `json:"screenshot_with_ocr,omitempty" yaml:"screenshot_with_ocr,omitempty"`
ScreenShotWithUpload bool `json:"screenshot_with_upload,omitempty" yaml:"screenshot_with_upload,omitempty"`
ScreenShotWithLiveType bool `json:"screenshot_with_live_type,omitempty" yaml:"screenshot_with_live_type,omitempty"`
ScreenShotWithLivePopularity bool `json:"screenshot_with_live_popularity,omitempty" yaml:"screenshot_with_live_popularity,omitempty"`
ScreenShotWithUITypes []string `json:"screenshot_with_ui_types,omitempty" yaml:"screenshot_with_ui_types,omitempty"`
ScreenShotWithClosePopups bool `json:"screenshot_with_close_popups,omitempty" yaml:"screenshot_with_close_popups,omitempty"`
ScreenShotWithOCRCluster string `json:"screenshot_with_ocr_cluster,omitempty" yaml:"screenshot_with_ocr_cluster,omitempty"`
ScreenShotFileName string `json:"screenshot_file_name,omitempty" yaml:"screenshot_file_name,omitempty"`
}
func (o *ActionOptions) Options() []ActionOption {
options := make([]ActionOption, 0)
if o == nil {
return options
}
if o.Identifier != "" {
options = append(options, WithIdentifier(o.Identifier))
}
if o.MaxRetryTimes != 0 {
options = append(options, WithMaxRetryTimes(o.MaxRetryTimes))
}
if o.IgnoreNotFoundError {
options = append(options, WithIgnoreNotFoundError(true))
}
if o.Interval != 0 {
options = append(options, WithInterval(o.Interval))
}
if o.Duration != 0 {
options = append(options, WithDuration(o.Duration))
}
if o.PressDuration != 0 {
options = append(options, WithPressDuration(o.PressDuration))
}
if o.Steps != 0 {
options = append(options, WithSteps(o.Steps))
}
switch v := o.Direction.(type) {
case string:
options = append(options, WithDirection(v))
case []float64:
options = append(options, WithCustomDirection(
v[0], v[1],
v[2], v[3],
))
case []interface{}:
// loaded from json case
// custom direction: [fromX, fromY, toX, toY]
sx, _ := builtin.Interface2Float64(v[0])
sy, _ := builtin.Interface2Float64(v[1])
ex, _ := builtin.Interface2Float64(v[2])
ey, _ := builtin.Interface2Float64(v[3])
options = append(options, WithCustomDirection(
sx, sy,
ex, ey,
))
}
if o.Timeout != 0 {
options = append(options, WithTimeout(o.Timeout))
}
if o.Frequency != 0 {
options = append(options, WithFrequency(o.Frequency))
}
if len(o.AbsScope) == 4 {
options = append(options, WithAbsScope(
o.AbsScope[0], o.AbsScope[1], o.AbsScope[2], o.AbsScope[3]))
} else if len(o.Scope) == 4 {
options = append(options, WithScope(
o.Scope[0], o.Scope[1], o.Scope[2], o.Scope[3]))
}
if len(o.Offset) == 2 {
// for tap [x,y] offset
options = append(options, WithTapOffset(o.Offset[0], o.Offset[1]))
} else if len(o.Offset) == 4 {
// for swipe [fromX, fromY, toX, toY] offset
options = append(options, WithSwipeOffset(
o.Offset[0], o.Offset[1], o.Offset[2], o.Offset[3]))
}
if len(o.OffsetRandomRange) == 2 {
options = append(options, WithOffsetRandomRange(
o.OffsetRandomRange[0], o.OffsetRandomRange[1]))
}
if o.Regex {
options = append(options, WithRegex(true))
}
if o.Index != 0 {
options = append(options, WithIndex(o.Index))
}
if o.MatchOne {
options = append(options, WithMatchOne(true))
}
// custom options
if o.Custom != nil {
for k, v := range o.Custom {
options = append(options, WithCustomOption(k, v))
}
}
// screenshot options
if o.ScreenShotWithOCR {
options = append(options, WithScreenShotOCR(true))
}
if o.ScreenShotWithUpload {
options = append(options, WithScreenShotUpload(true))
}
if o.ScreenShotWithLiveType {
options = append(options, WithScreenShotLiveType(true))
}
if o.ScreenShotWithLivePopularity {
options = append(options, WithScreenShotLivePopularity(true))
}
if len(o.ScreenShotWithUITypes) > 0 {
options = append(options, WithScreenShotUITypes(o.ScreenShotWithUITypes...))
}
if o.ScreenShotWithClosePopups {
options = append(options, WithScreenShotClosePopups(true))
}
if o.ScreenShotWithOCRCluster != "" {
options = append(options, WithScreenOCRCluster(o.ScreenShotWithOCRCluster))
}
if o.ScreenShotFileName != "" {
options = append(options, WithScreenShotFileName(o.ScreenShotFileName))
}
return options
}
func (o *ActionOptions) ScreenshotActions() []string {
actions := []string{}
if o.ScreenShotWithUpload {
actions = append(actions, "upload")
}
if o.ScreenShotWithOCR {
actions = append(actions, "ocr")
}
if o.ScreenShotWithLiveType {
actions = append(actions, "liveType")
}
if o.ScreenShotWithLivePopularity {
actions = append(actions, "livePopularity")
}
// UI detection
if len(o.ScreenShotWithUITypes) > 0 {
actions = append(actions, "ui")
}
if o.ScreenShotWithClosePopups {
actions = append(actions, "close")
}
return actions
}
func (o *ActionOptions) GetRandomOffset() float64 {
if len(o.OffsetRandomRange) != 2 {
// invalid offset random range, should be [min, max]
return 0
}
minOffset := o.OffsetRandomRange[0]
maxOffset := o.OffsetRandomRange[1]
return float64(builtin.GetRandomNumber(minOffset, maxOffset)) + rand.Float64()
}
func (o *ActionOptions) UpdateData(data map[string]interface{}) {
if o.Identifier != "" {
data["log"] = map[string]interface{}{
"enable": true,
"data": o.Identifier,
}
}
if o.Steps > 0 {
data["steps"] = o.Steps
}
if _, ok := data["steps"]; !ok {
data["steps"] = 12 // default steps
}
if o.Duration > 0 {
data["duration"] = o.Duration
}
if _, ok := data["duration"]; !ok {
data["duration"] = 0 // default duration
}
if o.Frequency > 0 {
data["frequency"] = o.Frequency
}
if _, ok := data["frequency"]; !ok {
data["frequency"] = 10 // default frequency
}
if _, ok := data["replace"]; !ok {
data["replace"] = true // default true
}
// custom options
if o.Custom != nil {
for k, v := range o.Custom {
data[k] = v
}
}
}
func NewActionOptions(opts ...ActionOption) *ActionOptions {
actionOptions := &ActionOptions{}
for _, option := range opts {
option(actionOptions)
}
return actionOptions
}
type ActionOption func(o *ActionOptions)
func WithCustomOption(key string, value interface{}) ActionOption {
return func(o *ActionOptions) {
if o.Custom == nil {
o.Custom = make(map[string]interface{})
}
o.Custom[key] = value
}
}
func WithIdentifier(identifier string) ActionOption {
return func(o *ActionOptions) {
o.Identifier = identifier
}
}
func WithIndex(index int) ActionOption {
return func(o *ActionOptions) {
o.Index = index
}
}
// set alias for compatibility
var WithWaitTime = WithInterval
func WithInterval(sec float64) ActionOption {
return func(o *ActionOptions) {
o.Interval = sec
}
}
func WithDuration(duration float64) ActionOption {
return func(o *ActionOptions) {
o.Duration = duration
}
}
func WithPressDuration(pressDuration float64) ActionOption {
return func(o *ActionOptions) {
o.PressDuration = pressDuration
}
}
func WithSteps(steps int) ActionOption {
return func(o *ActionOptions) {
o.Steps = steps
}
}
// WithDirection inputs direction (up, down, left, right)
func WithDirection(direction string) ActionOption {
return func(o *ActionOptions) {
o.Direction = direction
}
}
// WithCustomDirection inputs sx, sy, ex, ey
func WithCustomDirection(sx, sy, ex, ey float64) ActionOption {
return func(o *ActionOptions) {
o.Direction = []float64{sx, sy, ex, ey}
}
}
// WithScope inputs area of [(x1,y1), (x2,y2)]
// x1, y1, x2, y2 are all in [0, 1], which means the relative position of the screen
func WithScope(x1, y1, x2, y2 float64) ActionOption {
return func(o *ActionOptions) {
o.Scope = Scope{x1, y1, x2, y2}
}
}
// WithAbsScope inputs area of [(x1,y1), (x2,y2)]
// x1, y1, x2, y2 are all absolute position of the screen
func WithAbsScope(x1, y1, x2, y2 int) ActionOption {
return func(o *ActionOptions) {
o.AbsScope = AbsScope{x1, y1, x2, y2}
}
}
// Deprecated: use WithTapOffset instead
func WithOffset(offsetX, offsetY int) ActionOption {
return func(o *ActionOptions) {
o.Offset = []int{offsetX, offsetY}
}
}
// tap [x, y] with offset [offsetX, offsetY]
var WithTapOffset = WithOffset
// swipe [fromX, fromY, toX, toY] with offset [offsetFromX, offsetFromY, offsetToX, offsetToY]
func WithSwipeOffset(offsetFromX, offsetFromY, offsetToX, offsetToY int) ActionOption {
return func(o *ActionOptions) {
o.Offset = []int{offsetFromX, offsetFromY, offsetToX, offsetToY}
}
}
func WithOffsetRandomRange(min, max int) ActionOption {
return func(o *ActionOptions) {
o.OffsetRandomRange = []int{min, max}
}
}
func WithRegex(regex bool) ActionOption {
return func(o *ActionOptions) {
o.Regex = regex
}
}
func WithMatchOne(matchOne bool) ActionOption {
return func(o *ActionOptions) {
o.MatchOne = matchOne
}
}
func WithFrequency(frequency int) ActionOption {
return func(o *ActionOptions) {
o.Frequency = frequency
}
}
func WithMaxRetryTimes(maxRetryTimes int) ActionOption {
return func(o *ActionOptions) {
o.MaxRetryTimes = maxRetryTimes
}
}
func WithTimeout(timeout int) ActionOption {
return func(o *ActionOptions) {
o.Timeout = timeout
}
}
func WithIgnoreNotFoundError(ignoreError bool) ActionOption {
return func(o *ActionOptions) {
o.IgnoreNotFoundError = ignoreError
}
}
func WithScreenShotOCR(ocrOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithOCR = ocrOn
}
}
func WithScreenShotUpload(uploadOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithUpload = uploadOn
}
}
func WithScreenShotLiveType(liveTypeOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithLiveType = liveTypeOn
}
}
func WithScreenShotLivePopularity(livePopularityOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithLivePopularity = livePopularityOn
}
}
func WithScreenShotUITypes(uiTypes ...string) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithUITypes = uiTypes
}
}
func WithScreenShotClosePopups(closeOn bool) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithClosePopups = closeOn
}
}
func WithScreenOCRCluster(ocrCluster string) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotWithOCRCluster = ocrCluster
}
}
func WithScreenShotFileName(fileName string) ActionOption {
return func(o *ActionOptions) {
o.ScreenShotFileName = fileName
}
}
+2 -2
View File
@@ -15,9 +15,9 @@ func (dev *HarmonyDeviceConfig) Options() (deviceOptions []HarmonyDeviceOption)
return return
} }
func NewHarmonyDeviceConfig(options ...HarmonyDeviceOption) (device *HarmonyDeviceConfig) { func NewHarmonyDeviceConfig(opts ...HarmonyDeviceOption) (device *HarmonyDeviceConfig) {
device = &HarmonyDeviceConfig{} device = &HarmonyDeviceConfig{}
for _, option := range options { for _, option := range opts {
option(device) option(device)
} }
return return
+8 -7
View File
@@ -5,6 +5,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
// TODO: add more popup texts // TODO: add more popup texts
@@ -30,7 +31,7 @@ func findTextPopup(screenTexts OCRTexts) (closePoint *OCRText) {
continue continue
} }
points, err := screenTexts.FindTexts([]string{popup[0], popup[1]}, WithRegex(true)) points, err := screenTexts.FindTexts([]string{popup[0], popup[1]}, options.WithRegex(true))
if err == nil { if err == nil {
log.Warn().Interface("popup", popup). log.Warn().Interface("popup", popup).
Interface("texts", screenTexts).Msg("text popup found") Interface("texts", screenTexts).Msg("text popup found")
@@ -63,9 +64,9 @@ func (dExt *DriverExt) AutoPopupHandler() error {
// check popup by screenshot // check popup by screenshot
screenResult, err := dExt.GetScreenResult( screenResult, err := dExt.GetScreenResult(
WithScreenShotOCR(true), options.WithScreenShotOCR(true),
WithScreenShotUpload(true), options.WithScreenShotUpload(true),
WithScreenShotFileName("check_popup"), options.WithScreenShotFileName("check_popup"),
) )
if err != nil { if err != nil {
return errors.Wrap(err, "get screen result failed for popup handler") return errors.Wrap(err, "get screen result failed for popup handler")
@@ -98,9 +99,9 @@ func (p *PopupInfo) ClosePoint() *PointF {
func (dExt *DriverExt) CheckPopup() (popup *PopupInfo, err error) { func (dExt *DriverExt) CheckPopup() (popup *PopupInfo, err error) {
screenResult, err := dExt.GetScreenResult( screenResult, err := dExt.GetScreenResult(
WithScreenShotUpload(true), options.WithScreenShotUpload(true),
WithScreenShotClosePopups(true), // get popup area and close area options.WithScreenShotClosePopups(true), // get popup area and close area
WithScreenShotFileName("check_popup"), options.WithScreenShotFileName("check_popup"),
) )
if err != nil { if err != nil {
return nil, errors.Wrap(err, "get screen result failed for popup handler") return nil, errors.Wrap(err, "get screen result failed for popup handler")
+23 -22
View File
@@ -18,6 +18,7 @@ import (
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin" "github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/internal/config" "github.com/httprunner/httprunner/v5/internal/config"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
type ScreenResult struct { type ScreenResult struct {
@@ -36,21 +37,21 @@ func (s *ScreenResult) FilterTextsByScope(x1, y1, x2, y2 float64) OCRTexts {
log.Warn().Msg("x1, y1, x2, y2 should be in percentage, skip filter scope") log.Warn().Msg("x1, y1, x2, y2 should be in percentage, skip filter scope")
return s.Texts return s.Texts
} }
return s.Texts.FilterScope(AbsScope{ return s.Texts.FilterScope(options.AbsScope{
int(float64(s.Resolution.Width) * x1), int(float64(s.Resolution.Height) * y1), int(float64(s.Resolution.Width) * x1), int(float64(s.Resolution.Height) * y1),
int(float64(s.Resolution.Width) * x2), int(float64(s.Resolution.Height) * y2), int(float64(s.Resolution.Width) * x2), int(float64(s.Resolution.Height) * y2),
}) })
} }
// GetScreenResult takes a screenshot, returns the image recognition result // GetScreenResult takes a screenshot, returns the image recognition result
func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *ScreenResult, err error) { func (dExt *DriverExt) GetScreenResult(opts ...options.ActionOption) (screenResult *ScreenResult, err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if actionOptions.MaxRetryTimes == 0 { if actionOptions.MaxRetryTimes == 0 {
actionOptions.MaxRetryTimes = 1 actionOptions.MaxRetryTimes = 1
} }
var fileName string var fileName string
screenshotActions := actionOptions.screenshotActions() screenshotActions := actionOptions.ScreenshotActions()
if actionOptions.ScreenShotFileName != "" { if actionOptions.ScreenShotFileName != "" {
fileName = builtin.GenNameWithTimestamp("%d_" + actionOptions.ScreenShotFileName) fileName = builtin.GenNameWithTimestamp("%d_" + actionOptions.ScreenShotFileName)
} else if len(screenshotActions) != 0 { } else if len(screenshotActions) != 0 {
@@ -86,7 +87,7 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S
Tags: nil, Tags: nil,
Resolution: windowSize, Resolution: windowSize,
} }
imageResult, err = dExt.ImageService.GetImage(bufSource, options...) imageResult, err = dExt.ImageService.GetImage(bufSource, opts...)
if err != nil { if err != nil {
log.Error().Err(err).Msg("GetImage from ImageService failed") log.Error().Err(err).Msg("GetImage from ImageService failed")
lastErr = err lastErr = err
@@ -129,40 +130,40 @@ func (dExt *DriverExt) GetScreenResult(options ...ActionOption) (screenResult *S
return screenResult, nil return screenResult, nil
} }
func (dExt *DriverExt) GetScreenTexts(options ...ActionOption) (ocrTexts OCRTexts, err error) { func (dExt *DriverExt) GetScreenTexts(opts ...options.ActionOption) (ocrTexts OCRTexts, err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if actionOptions.ScreenShotFileName == "" { if actionOptions.ScreenShotFileName == "" {
options = append(options, WithScreenShotFileName("get_screen_texts")) opts = append(opts, options.WithScreenShotFileName("get_screen_texts"))
} }
options = append(options, WithScreenShotOCR(true), WithScreenShotUpload(true)) opts = append(opts, options.WithScreenShotOCR(true), options.WithScreenShotUpload(true))
screenResult, err := dExt.GetScreenResult(options...) screenResult, err := dExt.GetScreenResult(opts...)
if err != nil { if err != nil {
return return
} }
return screenResult.Texts, nil return screenResult.Texts, nil
} }
func (dExt *DriverExt) FindUIRectInUIKit(search string, options ...ActionOption) (point PointF, err error) { func (dExt *DriverExt) FindUIRectInUIKit(search string, opts ...options.ActionOption) (point PointF, err error) {
// find text using OCR // find text using OCR
if !builtin.IsPathExists(search) { if !builtin.IsPathExists(search) {
return dExt.FindScreenText(search, options...) return dExt.FindScreenText(search, opts...)
} }
// TODO: find image using CV // TODO: find image using CV
err = errors.New("ocr text not found") err = errors.New("ocr text not found")
return return
} }
func (dExt *DriverExt) FindScreenText(text string, options ...ActionOption) (point PointF, err error) { func (dExt *DriverExt) FindScreenText(text string, opts ...options.ActionOption) (point PointF, err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if actionOptions.ScreenShotFileName == "" { if actionOptions.ScreenShotFileName == "" {
options = append(options, WithScreenShotFileName(fmt.Sprintf("find_screen_text_%s", text))) opts = append(opts, options.WithScreenShotFileName(fmt.Sprintf("find_screen_text_%s", text)))
} }
ocrTexts, err := dExt.GetScreenTexts(options...) ocrTexts, err := dExt.GetScreenTexts(opts...)
if err != nil { if err != nil {
return return
} }
result, err := ocrTexts.FindText(text, dExt.ParseActionOptions(options...)...) result, err := ocrTexts.FindText(text, dExt.ParseActionOptions(opts...)...)
if err != nil { if err != nil {
log.Warn().Msgf("FindText failed: %s", err.Error()) log.Warn().Msgf("FindText failed: %s", err.Error())
return return
@@ -174,14 +175,14 @@ func (dExt *DriverExt) FindScreenText(text string, options ...ActionOption) (poi
return return
} }
func (dExt *DriverExt) FindUIResult(options ...ActionOption) (point PointF, err error) { func (dExt *DriverExt) FindUIResult(opts ...options.ActionOption) (point PointF, err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if actionOptions.ScreenShotFileName == "" { if actionOptions.ScreenShotFileName == "" {
options = append(options, WithScreenShotFileName( opts = append(opts, options.WithScreenShotFileName(
fmt.Sprintf("find_ui_result_%s", strings.Join(actionOptions.ScreenShotWithUITypes, "_")))) fmt.Sprintf("find_ui_result_%s", strings.Join(actionOptions.ScreenShotWithUITypes, "_"))))
} }
screenResult, err := dExt.GetScreenResult(options...) screenResult, err := dExt.GetScreenResult(opts...)
if err != nil { if err != nil {
return return
} }
@@ -190,7 +191,7 @@ func (dExt *DriverExt) FindUIResult(options ...ActionOption) (point PointF, err
if err != nil { if err != nil {
return return
} }
uiResult, err := uiResults.GetUIResult(dExt.ParseActionOptions(options...)...) uiResult, err := uiResults.GetUIResult(dExt.ParseActionOptions(opts...)...)
point = uiResult.Center() point = uiResult.Center()
log.Info().Interface("text", actionOptions.ScreenShotWithUITypes). log.Info().Interface("text", actionOptions.ScreenShotWithUITypes).
+3 -2
View File
@@ -4,10 +4,11 @@ import (
"fmt" "fmt"
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func (dExt *DriverExt) Drag(fromX, fromY, toX, toY float64, options ...ActionOption) (err error) { func (dExt *DriverExt) Drag(fromX, fromY, toX, toY float64, opts ...options.ActionOption) (err error) {
windowSize, err := dExt.Driver.WindowSize() windowSize, err := dExt.Driver.WindowSize()
if err != nil { if err != nil {
return errors.Wrap(code.DeviceGetInfoError, err.Error()) return errors.Wrap(code.DeviceGetInfoError, err.Error())
@@ -25,5 +26,5 @@ func (dExt *DriverExt) Drag(fromX, fromY, toX, toY float64, options ...ActionOpt
toX = float64(width) * toX toX = float64(width) * toX
toY = float64(height) * toY toY = float64(height) * toY
return dExt.Driver.Drag(fromX, fromY, toX, toY, options...) return dExt.Driver.Drag(fromX, fromY, toX, toY, opts...)
} }
+34 -33
View File
@@ -10,6 +10,7 @@ import (
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin" "github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
func assertRelative(p float64) bool { func assertRelative(p float64) bool {
@@ -17,7 +18,7 @@ func assertRelative(p float64) bool {
} }
// SwipeRelative swipe from relative position [fromX, fromY] to relative position [toX, toY] // SwipeRelative swipe from relative position [fromX, fromY] to relative position [toX, toY]
func (dExt *DriverExt) SwipeRelative(fromX, fromY, toX, toY float64, options ...ActionOption) error { func (dExt *DriverExt) SwipeRelative(fromX, fromY, toX, toY float64, opts ...options.ActionOption) error {
if !assertRelative(fromX) || !assertRelative(fromY) || if !assertRelative(fromX) || !assertRelative(fromY) ||
!assertRelative(toX) || !assertRelative(toY) { !assertRelative(toX) || !assertRelative(toY) {
return errors.Wrap(code.InvalidCaseError, return errors.Wrap(code.InvalidCaseError,
@@ -36,33 +37,33 @@ func (dExt *DriverExt) SwipeRelative(fromX, fromY, toX, toY float64, options ...
fromY = float64(height) * fromY fromY = float64(height) * fromY
toX = float64(width) * toX toX = float64(width) * toX
toY = float64(height) * toY toY = float64(height) * toY
err = dExt.Driver.Swipe(fromX, fromY, toX, toY, options...) err = dExt.Driver.Swipe(fromX, fromY, toX, toY, opts...)
if err != nil { if err != nil {
return errors.Wrap(code.MobileUISwipeError, err.Error()) return errors.Wrap(code.MobileUISwipeError, err.Error())
} }
return nil return nil
} }
func (dExt *DriverExt) SwipeUp(options ...ActionOption) (err error) { func (dExt *DriverExt) SwipeUp(opts ...options.ActionOption) (err error) {
return dExt.SwipeRelative(0.5, 0.5, 0.5, 0.1, options...) return dExt.SwipeRelative(0.5, 0.5, 0.5, 0.1, opts...)
} }
func (dExt *DriverExt) SwipeDown(options ...ActionOption) (err error) { func (dExt *DriverExt) SwipeDown(opts ...options.ActionOption) (err error) {
return dExt.SwipeRelative(0.5, 0.5, 0.5, 0.9, options...) return dExt.SwipeRelative(0.5, 0.5, 0.5, 0.9, opts...)
} }
func (dExt *DriverExt) SwipeLeft(options ...ActionOption) (err error) { func (dExt *DriverExt) SwipeLeft(opts ...options.ActionOption) (err error) {
return dExt.SwipeRelative(0.5, 0.5, 0.1, 0.5, options...) return dExt.SwipeRelative(0.5, 0.5, 0.1, 0.5, opts...)
} }
func (dExt *DriverExt) SwipeRight(options ...ActionOption) (err error) { func (dExt *DriverExt) SwipeRight(opts ...options.ActionOption) (err error) {
return dExt.SwipeRelative(0.5, 0.5, 0.9, 0.5, options...) return dExt.SwipeRelative(0.5, 0.5, 0.9, 0.5, opts...)
} }
type Action func(driver *DriverExt) error type Action func(driver *DriverExt) error
func (dExt *DriverExt) LoopUntil(findAction, findCondition, foundAction Action, options ...ActionOption) error { func (dExt *DriverExt) LoopUntil(findAction, findCondition, foundAction Action, opts ...options.ActionOption) error {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
maxRetryTimes := actionOptions.MaxRetryTimes maxRetryTimes := actionOptions.MaxRetryTimes
interval := actionOptions.Interval interval := actionOptions.Interval
@@ -84,8 +85,8 @@ func (dExt *DriverExt) LoopUntil(findAction, findCondition, foundAction Action,
fmt.Sprintf("loop %d times, match find condition failed", maxRetryTimes)) fmt.Sprintf("loop %d times, match find condition failed", maxRetryTimes))
} }
func (dExt *DriverExt) prepareSwipeAction(params interface{}, options ...ActionOption) func(d *DriverExt) error { func (dExt *DriverExt) prepareSwipeAction(params interface{}, opts ...options.ActionOption) func(d *DriverExt) error {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
var swipeDirection interface{} var swipeDirection interface{}
// priority: params > actionOptions.Direction, default swipe up // priority: params > actionOptions.Direction, default swipe up
@@ -111,20 +112,20 @@ func (dExt *DriverExt) prepareSwipeAction(params interface{}, options ...ActionO
// enum direction: up, down, left, right // enum direction: up, down, left, right
switch d { switch d {
case "up": case "up":
return dExt.SwipeUp(options...) return dExt.SwipeUp(opts...)
case "down": case "down":
return dExt.SwipeDown(options...) return dExt.SwipeDown(opts...)
case "left": case "left":
return dExt.SwipeLeft(options...) return dExt.SwipeLeft(opts...)
case "right": case "right":
return dExt.SwipeRight(options...) return dExt.SwipeRight(opts...)
default: default:
return errors.Wrap(code.InvalidParamError, return errors.Wrap(code.InvalidParamError,
fmt.Sprintf("get unexpected swipe direction: %s", d)) fmt.Sprintf("get unexpected swipe direction: %s", d))
} }
} else if params, err := builtin.ConvertToFloat64Slice(swipeDirection); err == nil && len(params) == 4 { } else if params, err := builtin.ConvertToFloat64Slice(swipeDirection); err == nil && len(params) == 4 {
// custom direction: [fromX, fromY, toX, toY] // custom direction: [fromX, fromY, toX, toY]
if err := dExt.SwipeRelative(params[0], params[1], params[2], params[3], options...); err != nil { if err := dExt.SwipeRelative(params[0], params[1], params[2], params[3], opts...); err != nil {
log.Error().Err(err).Msgf("swipe from (%v, %v) to (%v, %v) failed", log.Error().Err(err).Msgf("swipe from (%v, %v) to (%v, %v) failed",
params[0], params[1], params[2], params[3]) params[0], params[1], params[2], params[3])
return err return err
@@ -136,22 +137,22 @@ func (dExt *DriverExt) prepareSwipeAction(params interface{}, options ...ActionO
} }
} }
func (dExt *DriverExt) swipeToTapTexts(texts []string, options ...ActionOption) error { func (dExt *DriverExt) swipeToTapTexts(texts []string, opts ...options.ActionOption) error {
if len(texts) == 0 { if len(texts) == 0 {
return errors.New("no text to tap") return errors.New("no text to tap")
} }
options = append(options, WithMatchOne(true), WithRegex(true)) opts = append(opts, options.WithMatchOne(true), options.WithRegex(true))
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
actionOptions.Identifier = "" actionOptions.Identifier = ""
optionsWithoutIdentifier := actionOptions.Options() optionsWithoutIdentifier := actionOptions.Options()
var point PointF var point PointF
findTexts := func(d *DriverExt) error { findTexts := func(d *DriverExt) error {
var err error var err error
screenResult, err := d.GetScreenResult( screenResult, err := d.GetScreenResult(
WithScreenShotOCR(true), options.WithScreenShotOCR(true),
WithScreenShotUpload(true), options.WithScreenShotUpload(true),
WithScreenShotFileName( options.WithScreenShotFileName(
fmt.Sprintf("swipe_to_tap_texts_%s", strings.Join(texts, "_")), fmt.Sprintf("swipe_to_tap_texts_%s", strings.Join(texts, "_")),
), ),
) )
@@ -172,14 +173,14 @@ func (dExt *DriverExt) swipeToTapTexts(texts []string, options ...ActionOption)
} }
foundTextAction := func(d *DriverExt) error { foundTextAction := func(d *DriverExt) error {
// tap text // tap text
return d.TapAbsXY(point.X, point.Y, options...) return d.TapAbsXY(point.X, point.Y, opts...)
} }
findAction := dExt.prepareSwipeAction(nil, optionsWithoutIdentifier...) findAction := dExt.prepareSwipeAction(nil, optionsWithoutIdentifier...)
return dExt.LoopUntil(findAction, findTexts, foundTextAction, optionsWithoutIdentifier...) return dExt.LoopUntil(findAction, findTexts, foundTextAction, optionsWithoutIdentifier...)
} }
func (dExt *DriverExt) swipeToTapApp(appName string, options ...ActionOption) error { func (dExt *DriverExt) swipeToTapApp(appName string, opts ...options.ActionOption) error {
// go to home screen // go to home screen
if err := dExt.Driver.Homescreen(); err != nil { if err := dExt.Driver.Homescreen(); err != nil {
return errors.Wrap(err, "go to home screen failed") return errors.Wrap(err, "go to home screen failed")
@@ -195,21 +196,21 @@ func (dExt *DriverExt) swipeToTapApp(appName string, options ...ActionOption) er
dExt.SwipeRight() dExt.SwipeRight()
} }
options = append(options, WithDirection("left")) opts = append(opts, options.WithDirection("left"))
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
// default to retry 5 times // default to retry 5 times
if actionOptions.MaxRetryTimes == 0 { if actionOptions.MaxRetryTimes == 0 {
options = append(options, WithMaxRetryTimes(5)) opts = append(opts, options.WithMaxRetryTimes(5))
} }
// tap app icon above the text // tap app icon above the text
if len(actionOptions.Offset) == 0 { if len(actionOptions.Offset) == 0 {
options = append(options, WithTapOffset(0, -25)) opts = append(opts, options.WithTapOffset(0, -25))
} }
// set default swipe interval to 1 second // set default swipe interval to 1 second
if builtin.IsZeroFloat64(actionOptions.Interval) { if builtin.IsZeroFloat64(actionOptions.Interval) {
options = append(options, WithInterval(1)) opts = append(opts, options.WithInterval(1))
} }
return dExt.swipeToTapTexts([]string{appName}, options...) return dExt.swipeToTapTexts([]string{appName}, opts...)
} }
+5 -3
View File
@@ -4,16 +4,18 @@ package uixt
import ( import (
"testing" "testing"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
func TestAndroidSwipeAction(t *testing.T) { func TestAndroidSwipeAction(t *testing.T) {
setupAndroidAdbDriver(t) setupAndroidAdbDriver(t)
swipeAction := driverExt.prepareSwipeAction("up", WithDirection("down")) swipeAction := driverExt.prepareSwipeAction("up", options.WithDirection("down"))
err := swipeAction(driverExt) err := swipeAction(driverExt)
checkErr(t, err) checkErr(t, err)
swipeAction = driverExt.prepareSwipeAction("up", WithCustomDirection(0.5, 0.5, 0.5, 0.9)) swipeAction = driverExt.prepareSwipeAction("up", options.WithCustomDirection(0.5, 0.5, 0.5, 0.9))
err = swipeAction(driverExt) err = swipeAction(driverExt)
checkErr(t, err) checkErr(t, err)
} }
@@ -31,6 +33,6 @@ func TestAndroidSwipeToTapTexts(t *testing.T) {
err := driverExt.Driver.AppLaunch("com.ss.android.ugc.aweme") err := driverExt.Driver.AppLaunch("com.ss.android.ugc.aweme")
checkErr(t, err) checkErr(t, err)
err = driverExt.swipeToTapTexts([]string{"点击进入直播间", "直播中"}, WithDirection("up")) err = driverExt.swipeToTapTexts([]string{"点击进入直播间", "直播中"}, options.WithDirection("up"))
checkErr(t, err) checkErr(t, err)
} }
+26 -25
View File
@@ -6,18 +6,19 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
func (dExt *DriverExt) TapAbsXY(x, y float64, options ...ActionOption) error { func (dExt *DriverExt) TapAbsXY(x, y float64, opts ...options.ActionOption) error {
// tap on absolute coordinate [x, y] // tap on absolute coordinate [x, y]
err := dExt.Driver.Tap(x, y, options...) err := dExt.Driver.Tap(x, y, opts...)
if err != nil { if err != nil {
return errors.Wrap(code.MobileUITapError, err.Error()) return errors.Wrap(code.MobileUITapError, err.Error())
} }
return nil return nil
} }
func (dExt *DriverExt) TapXY(x, y float64, options ...ActionOption) error { func (dExt *DriverExt) TapXY(x, y float64, opts ...options.ActionOption) error {
// tap on [x, y] percent of window size // tap on [x, y] percent of window size
if x > 1 || y > 1 { if x > 1 || y > 1 {
return fmt.Errorf("x, y percentage should be <= 1, got x=%v, y=%v", x, y) return fmt.Errorf("x, y percentage should be <= 1, got x=%v, y=%v", x, y)
@@ -29,16 +30,16 @@ func (dExt *DriverExt) TapXY(x, y float64, options ...ActionOption) error {
} }
x = x * float64(windowSize.Width) x = x * float64(windowSize.Width)
y = y * float64(windowSize.Height) y = y * float64(windowSize.Height)
return dExt.TapAbsXY(x, y, options...) return dExt.TapAbsXY(x, y, opts...)
} }
func (dExt *DriverExt) TapByOCR(ocrText string, options ...ActionOption) error { func (dExt *DriverExt) TapByOCR(ocrText string, opts ...options.ActionOption) error {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
if actionOptions.ScreenShotFileName == "" { if actionOptions.ScreenShotFileName == "" {
options = append(options, WithScreenShotFileName(fmt.Sprintf("tap_by_ocr_%s", ocrText))) opts = append(opts, options.WithScreenShotFileName(fmt.Sprintf("tap_by_ocr_%s", ocrText)))
} }
point, err := dExt.FindScreenText(ocrText, options...) point, err := dExt.FindScreenText(ocrText, opts...)
if err != nil { if err != nil {
if actionOptions.IgnoreNotFoundError { if actionOptions.IgnoreNotFoundError {
return nil return nil
@@ -46,13 +47,13 @@ func (dExt *DriverExt) TapByOCR(ocrText string, options ...ActionOption) error {
return err return err
} }
return dExt.TapAbsXY(point.X, point.Y, options...) return dExt.TapAbsXY(point.X, point.Y, opts...)
} }
func (dExt *DriverExt) TapByUIDetection(options ...ActionOption) error { func (dExt *DriverExt) TapByUIDetection(opts ...options.ActionOption) error {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
point, err := dExt.FindUIResult(options...) point, err := dExt.FindUIResult(opts...)
if err != nil { if err != nil {
if actionOptions.IgnoreNotFoundError { if actionOptions.IgnoreNotFoundError {
return nil return nil
@@ -60,17 +61,17 @@ func (dExt *DriverExt) TapByUIDetection(options ...ActionOption) error {
return err return err
} }
return dExt.TapAbsXY(point.X, point.Y, options...) return dExt.TapAbsXY(point.X, point.Y, opts...)
} }
func (dExt *DriverExt) Tap(param string, options ...ActionOption) error { func (dExt *DriverExt) Tap(param string, opts ...options.ActionOption) error {
return dExt.TapOffset(param, 0, 0, options...) return dExt.TapOffset(param, 0, 0, opts...)
} }
func (dExt *DriverExt) TapOffset(param string, xOffset, yOffset float64, options ...ActionOption) (err error) { func (dExt *DriverExt) TapOffset(param string, xOffset, yOffset float64, opts ...options.ActionOption) (err error) {
actionOptions := NewActionOptions(options...) actionOptions := options.NewActionOptions(opts...)
point, err := dExt.FindUIRectInUIKit(param, options...) point, err := dExt.FindUIRectInUIKit(param, opts...)
if err != nil { if err != nil {
if actionOptions.IgnoreNotFoundError { if actionOptions.IgnoreNotFoundError {
return nil return nil
@@ -78,10 +79,10 @@ func (dExt *DriverExt) TapOffset(param string, xOffset, yOffset float64, options
return err return err
} }
return dExt.TapAbsXY(point.X+xOffset, point.Y+yOffset, options...) return dExt.TapAbsXY(point.X+xOffset, point.Y+yOffset, opts...)
} }
func (dExt *DriverExt) DoubleTapXY(x, y float64, options ...ActionOption) error { func (dExt *DriverExt) DoubleTapXY(x, y float64, opts ...options.ActionOption) error {
// double tap on coordinate: [x, y] should be relative // double tap on coordinate: [x, y] should be relative
if x > 1 || y > 1 { if x > 1 || y > 1 {
return fmt.Errorf("x, y percentage should be < 1, got x=%v, y=%v", x, y) return fmt.Errorf("x, y percentage should be < 1, got x=%v, y=%v", x, y)
@@ -93,24 +94,24 @@ func (dExt *DriverExt) DoubleTapXY(x, y float64, options ...ActionOption) error
} }
x = x * float64(windowSize.Width) x = x * float64(windowSize.Width)
y = y * float64(windowSize.Height) y = y * float64(windowSize.Height)
err = dExt.Driver.DoubleTap(x, y, options...) err = dExt.Driver.DoubleTap(x, y, opts...)
if err != nil { if err != nil {
return errors.Wrap(code.MobileUITapError, err.Error()) return errors.Wrap(code.MobileUITapError, err.Error())
} }
return nil return nil
} }
func (dExt *DriverExt) DoubleTap(param string, options ...ActionOption) (err error) { func (dExt *DriverExt) DoubleTap(param string, opts ...options.ActionOption) (err error) {
return dExt.DoubleTapOffset(param, 0, 0, options...) return dExt.DoubleTapOffset(param, 0, 0, opts...)
} }
func (dExt *DriverExt) DoubleTapOffset(param string, xOffset, yOffset float64, options ...ActionOption) (err error) { func (dExt *DriverExt) DoubleTapOffset(param string, xOffset, yOffset float64, opts ...options.ActionOption) (err error) {
point, err := dExt.FindUIRectInUIKit(param) point, err := dExt.FindUIRectInUIKit(param)
if err != nil { if err != nil {
return err return err
} }
err = dExt.Driver.DoubleTap(point.X+xOffset, point.Y+yOffset, options...) err = dExt.Driver.DoubleTap(point.X+xOffset, point.Y+yOffset, opts...)
if err != nil { if err != nil {
return errors.Wrap(code.MobileUITapError, err.Error()) return errors.Wrap(code.MobileUITapError, err.Error())
} }
+6 -4
View File
@@ -1,6 +1,8 @@
package server package server
import "github.com/httprunner/httprunner/v5/pkg/uixt" import (
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
)
type HttpResponse struct { type HttpResponse struct {
Code int `json:"code"` Code int `json:"code"`
@@ -13,7 +15,7 @@ type TapRequest struct {
Y float64 `json:"y"` Y float64 `json:"y"`
Text string `json:"text"` Text string `json:"text"`
Options *uixt.ActionOptions `json:"options,omitempty"` Options *options.ActionOptions `json:"options,omitempty"`
} }
type DragRequest struct { type DragRequest struct {
@@ -22,7 +24,7 @@ type DragRequest struct {
ToX float64 `json:"to_x"` ToX float64 `json:"to_x"`
ToY float64 `json:"to_y"` ToY float64 `json:"to_y"`
Options *uixt.ActionOptions `json:"options,omitempty"` Options *options.ActionOptions `json:"options,omitempty"`
} }
type InputRequest struct { type InputRequest struct {
@@ -31,7 +33,7 @@ type InputRequest struct {
} }
type ScreenRequest struct { type ScreenRequest struct {
Options *uixt.ActionOptions `json:"options,omitempty"` Options *options.ActionOptions `json:"options,omitempty"`
} }
type KeycodeRequest struct { type KeycodeRequest struct {
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/pkg/uixt" "github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -51,7 +52,7 @@ func screenResultHandler(c *gin.Context) {
return return
} }
var actionOptions []uixt.ActionOption var actionOptions []options.ActionOption
if screenReq.Options != nil { if screenReq.Options != nil {
actionOptions = screenReq.Options.Options() actionOptions = screenReq.Options.Options()
} }
+5 -4
View File
@@ -6,7 +6,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/pkg/uixt" "github.com/httprunner/httprunner/v5/pkg/uixt/options"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -22,7 +22,7 @@ func tapHandler(c *gin.Context) {
return return
} }
var actionOptions []uixt.ActionOption var actionOptions []options.ActionOption
if tapReq.Options != nil { if tapReq.Options != nil {
actionOptions = tapReq.Options.Options() actionOptions = tapReq.Options.Options()
} }
@@ -82,7 +82,7 @@ func dragHandler(c *gin.Context) {
return return
} }
var actionOptions []uixt.ActionOption var actionOptions []options.ActionOption
if dragReq.Options != nil { if dragReq.Options != nil {
actionOptions = dragReq.Options.Options() actionOptions = dragReq.Options.Options()
} }
@@ -139,7 +139,8 @@ func inputHandler(c *gin.Context) {
return return
} }
err = dExt.Driver.SendKeys(inputReq.Text, uixt.WithFrequency(inputReq.Frequency)) err = dExt.Driver.SendKeys(inputReq.Text,
options.WithFrequency(inputReq.Frequency))
if err != nil { if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input text %s", c.HandlerName(), inputReq.Text)) log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input text %s", c.HandlerName(), inputReq.Text))
c.JSON(http.StatusInternalServerError, c.JSON(http.StatusInternalServerError,
+43 -42
View File
@@ -11,6 +11,7 @@ import (
"github.com/httprunner/httprunner/v5/code" "github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/sdk" "github.com/httprunner/httprunner/v5/internal/sdk"
"github.com/httprunner/httprunner/v5/pkg/uixt" "github.com/httprunner/httprunner/v5/pkg/uixt"
"github.com/httprunner/httprunner/v5/pkg/uixt/options"
) )
type MobileUI struct { type MobileUI struct {
@@ -103,11 +104,11 @@ func (s *StepMobile) Home() *StepMobile {
} }
// TapXY taps the point {X,Y}, X & Y is percentage of coordinates // TapXY taps the point {X,Y}, X & Y is percentage of coordinates
func (s *StepMobile) TapXY(x, y float64, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) TapXY(x, y float64, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_TapXY, Method: uixt.ACTION_TapXY,
Params: []float64{x, y}, Params: []float64{x, y},
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
@@ -115,11 +116,11 @@ func (s *StepMobile) TapXY(x, y float64, options ...uixt.ActionOption) *StepMobi
} }
// TapAbsXY taps the point {X,Y}, X & Y is absolute coordinates // TapAbsXY taps the point {X,Y}, X & Y is absolute coordinates
func (s *StepMobile) TapAbsXY(x, y float64, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) TapAbsXY(x, y float64, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_TapAbsXY, Method: uixt.ACTION_TapAbsXY,
Params: []float64{x, y}, Params: []float64{x, y},
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
@@ -127,11 +128,11 @@ func (s *StepMobile) TapAbsXY(x, y float64, options ...uixt.ActionOption) *StepM
} }
// Tap taps on the target element // Tap taps on the target element
func (s *StepMobile) Tap(params string, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) Tap(params string, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_Tap, Method: uixt.ACTION_Tap,
Params: params, Params: params,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
@@ -139,11 +140,11 @@ func (s *StepMobile) Tap(params string, options ...uixt.ActionOption) *StepMobil
} }
// TapByOCR taps on the target element by OCR recognition // TapByOCR taps on the target element by OCR recognition
func (s *StepMobile) TapByOCR(ocrText string, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) TapByOCR(ocrText string, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_TapByOCR, Method: uixt.ACTION_TapByOCR,
Params: ocrText, Params: ocrText,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
@@ -151,11 +152,11 @@ func (s *StepMobile) TapByOCR(ocrText string, options ...uixt.ActionOption) *Ste
} }
// TapByCV taps on the target element by CV recognition // TapByCV taps on the target element by CV recognition
func (s *StepMobile) TapByCV(imagePath string, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) TapByCV(imagePath string, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_TapByCV, Method: uixt.ACTION_TapByCV,
Params: imagePath, Params: imagePath,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
@@ -163,10 +164,10 @@ func (s *StepMobile) TapByCV(imagePath string, options ...uixt.ActionOption) *St
} }
// TapByUITypes taps on the target element specified by uiTypes, the higher the uiTypes, the higher the priority // TapByUITypes taps on the target element specified by uiTypes, the higher the uiTypes, the higher the priority
func (s *StepMobile) TapByUITypes(options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) TapByUITypes(opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_TapByCV, Method: uixt.ACTION_TapByCV,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
@@ -174,31 +175,31 @@ func (s *StepMobile) TapByUITypes(options ...uixt.ActionOption) *StepMobile {
} }
// DoubleTapXY double taps the point {X,Y}, X & Y is percentage of coordinates // DoubleTapXY double taps the point {X,Y}, X & Y is percentage of coordinates
func (s *StepMobile) DoubleTapXY(x, y float64, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) DoubleTapXY(x, y float64, opts ...options.ActionOption) *StepMobile {
s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{
Method: uixt.ACTION_DoubleTapXY, Method: uixt.ACTION_DoubleTapXY,
Params: []float64{x, y}, Params: []float64{x, y},
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
}) })
return s return s
} }
func (s *StepMobile) DoubleTap(params string, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) DoubleTap(params string, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_DoubleTap, Method: uixt.ACTION_DoubleTap,
Params: params, Params: params,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
return s return s
} }
func (s *StepMobile) Back(options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) Back(opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_Back, Method: uixt.ACTION_Back,
Params: nil, Params: nil,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
@@ -206,99 +207,99 @@ func (s *StepMobile) Back(options ...uixt.ActionOption) *StepMobile {
} }
// Swipe drags from [sx, sy] to [ex, ey] // Swipe drags from [sx, sy] to [ex, ey]
func (s *StepMobile) Swipe(sx, sy, ex, ey float64, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) Swipe(sx, sy, ex, ey float64, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_Swipe, Method: uixt.ACTION_Swipe,
Params: []float64{sx, sy, ex, ey}, Params: []float64{sx, sy, ex, ey},
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
return s return s
} }
func (s *StepMobile) SwipeUp(options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) SwipeUp(opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_Swipe, Method: uixt.ACTION_Swipe,
Params: "up", Params: "up",
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
return s return s
} }
func (s *StepMobile) SwipeDown(options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) SwipeDown(opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_Swipe, Method: uixt.ACTION_Swipe,
Params: "down", Params: "down",
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
return s return s
} }
func (s *StepMobile) SwipeLeft(options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) SwipeLeft(opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_Swipe, Method: uixt.ACTION_Swipe,
Params: "left", Params: "left",
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
return s return s
} }
func (s *StepMobile) SwipeRight(options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) SwipeRight(opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_Swipe, Method: uixt.ACTION_Swipe,
Params: "right", Params: "right",
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
return s return s
} }
func (s *StepMobile) SwipeToTapApp(appName string, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) SwipeToTapApp(appName string, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_SwipeToTapApp, Method: uixt.ACTION_SwipeToTapApp,
Params: appName, Params: appName,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
return s return s
} }
func (s *StepMobile) SwipeToTapText(text string, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) SwipeToTapText(text string, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_SwipeToTapText, Method: uixt.ACTION_SwipeToTapText,
Params: text, Params: text,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
return s return s
} }
func (s *StepMobile) SwipeToTapTexts(texts interface{}, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) SwipeToTapTexts(texts interface{}, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_SwipeToTapTexts, Method: uixt.ACTION_SwipeToTapTexts,
Params: texts, Params: texts,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
return s return s
} }
func (s *StepMobile) Input(text string, options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) Input(text string, opts ...options.ActionOption) *StepMobile {
action := uixt.MobileAction{ action := uixt.MobileAction{
Method: uixt.ACTION_Input, Method: uixt.ACTION_Input,
Params: text, Params: text,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
} }
s.obj().Actions = append(s.obj().Actions, action) s.obj().Actions = append(s.obj().Actions, action)
@@ -351,20 +352,20 @@ func (s *StepMobile) SleepRandom(params ...float64) *StepMobile {
return s return s
} }
func (s *StepMobile) EndToEndDelay(options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) EndToEndDelay(opts ...options.ActionOption) *StepMobile {
s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{
Method: uixt.ACTION_EndToEndDelay, Method: uixt.ACTION_EndToEndDelay,
Params: nil, Params: nil,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
}) })
return s return s
} }
func (s *StepMobile) ScreenShot(options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) ScreenShot(opts ...options.ActionOption) *StepMobile {
s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{
Method: uixt.ACTION_ScreenShot, Method: uixt.ACTION_ScreenShot,
Params: nil, Params: nil,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
}) })
return s return s
} }
@@ -392,11 +393,11 @@ func (s *StepMobile) DisableAutoPopupHandler() *StepMobile {
return s return s
} }
func (s *StepMobile) ClosePopups(options ...uixt.ActionOption) *StepMobile { func (s *StepMobile) ClosePopups(opts ...options.ActionOption) *StepMobile {
s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{ s.obj().Actions = append(s.obj().Actions, uixt.MobileAction{
Method: uixt.ACTION_ClosePopups, Method: uixt.ACTION_ClosePopups,
Params: nil, Params: nil,
Options: uixt.NewActionOptions(options...), Options: options.NewActionOptions(opts...),
}) })
return s return s
} }