mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 23:51:25 +08:00
Merge branch 'fix-v5' into 'master'
Fix v5 See merge request iesqa/httprunner!121
This commit is contained in:
@@ -9,38 +9,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"teststeps": [
|
"teststeps": [
|
||||||
{
|
|
||||||
"name": "启动抖音 app",
|
|
||||||
"android": {
|
|
||||||
"os_type": "android",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"method": "app_launch",
|
|
||||||
"params": "$package_name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "sleep",
|
|
||||||
"params": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"validate": [
|
|
||||||
{
|
|
||||||
"check": "ui_foreground_app",
|
|
||||||
"assert": "equal",
|
|
||||||
"expect": "$package_name",
|
|
||||||
"msg": "app [$package_name] should be in foreground"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "进入「浪漫餐厅」小游戏",
|
"name": "进入「浪漫餐厅」小游戏",
|
||||||
"android": {
|
"android": {
|
||||||
"os_type": "android",
|
"os_type": "android",
|
||||||
"actions": [
|
"actions": [
|
||||||
|
{
|
||||||
|
"method": "home"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"method": "start_to_goal",
|
"method": "start_to_goal",
|
||||||
"params": "搜索「浪漫餐厅」,进入小游戏",
|
"params": "在手机桌面点击「浪漫餐厅」启动小游戏,等待游戏加载完成",
|
||||||
"options": {
|
"options": {
|
||||||
"pre_mark_operation": true
|
"pre_mark_operation": true
|
||||||
}
|
}
|
||||||
@@ -63,7 +42,7 @@
|
|||||||
"actions": [
|
"actions": [
|
||||||
{
|
{
|
||||||
"method": "start_to_goal",
|
"method": "start_to_goal",
|
||||||
"params": "浪漫餐厅是一款经营类游戏,以下是游戏的基本规则说明:\n1、点击右下角锅铲,开始任务\n2、将棋子拖拽至相同棋子,可升级生成新棋子\n3、拖拽相同棋子时,被部分遮挡的棋子只能作为拖拽终点,不能作为拖拽起点\n4、当游戏界面中没有相同棋子时,可以点击游戏页面中央的购物袋生成新的棋子\n5、若不知道如何操作,请按照游戏指引进行游玩\n\n请严格按照以上游戏规则,开始游戏\n",
|
"params": "浪漫餐厅是一款经营类游戏,以下是游戏的基本规则说明:\n1、点击右下角锅铲,开始任务\n2、将棋子拖拽至相同棋子,可升级生成新棋子;注意,必须是相同类别和形状的棋子才能合成,例如,长面包和圆面包不能合成,方形蛋糕和三角形蛋糕不能合成\n3、拖拽相同棋子时,被部分遮挡的棋子只能作为拖拽终点,不能作为拖拽起点\n4、当游戏界面中没有相同棋子时,可以点击游戏页面中央的购物袋生成新的棋子\n5、若不知道如何操作,请按照游戏指引进行游玩\n6、不要连续重复上一步操作,合成失败后及时更换策略\n\n请严格按照以上游戏规则,开始游戏\n",
|
||||||
"options": {
|
"options": {
|
||||||
"time_limit": 300,
|
"time_limit": 300,
|
||||||
"pre_mark_operation": true
|
"pre_mark_operation": true
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ import (
|
|||||||
func TestGameRomanticRestaurant(t *testing.T) {
|
func TestGameRomanticRestaurant(t *testing.T) {
|
||||||
userInstruction := `浪漫餐厅是一款经营类游戏,以下是游戏的基本规则说明:
|
userInstruction := `浪漫餐厅是一款经营类游戏,以下是游戏的基本规则说明:
|
||||||
1、点击右下角锅铲,开始任务
|
1、点击右下角锅铲,开始任务
|
||||||
2、将棋子拖拽至相同棋子,可升级生成新棋子
|
2、将棋子拖拽至相同棋子,可升级生成新棋子;注意,必须是相同类别和形状的棋子才能合成,例如,长面包和圆面包不能合成,方形蛋糕和三角形蛋糕不能合成
|
||||||
3、拖拽相同棋子时,被部分遮挡的棋子只能作为拖拽终点,不能作为拖拽起点
|
3、拖拽相同棋子时,被部分遮挡的棋子只能作为拖拽终点,不能作为拖拽起点
|
||||||
4、当游戏界面中没有相同棋子时,可以点击游戏页面中央的购物袋生成新的棋子
|
4、当游戏界面中没有相同棋子时,可以点击游戏页面中央的购物袋生成新的棋子
|
||||||
5、若不知道如何操作,请按照游戏指引进行游玩
|
5、若不知道如何操作,请按照游戏指引进行游玩
|
||||||
|
6、不要连续重复上一步操作,合成失败后及时更换策略
|
||||||
|
|
||||||
请严格按照以上游戏规则,开始游戏
|
请严格按照以上游戏规则,开始游戏
|
||||||
`
|
`
|
||||||
@@ -26,15 +27,22 @@ func TestGameRomanticRestaurant(t *testing.T) {
|
|||||||
"package_name": "com.ss.android.ugc.aweme",
|
"package_name": "com.ss.android.ugc.aweme",
|
||||||
}),
|
}),
|
||||||
TestSteps: []hrp.IStep{
|
TestSteps: []hrp.IStep{
|
||||||
hrp.NewStep("启动抖音 app").
|
// hrp.NewStep("启动抖音 app").
|
||||||
Android().
|
// Android().
|
||||||
AppLaunch("$package_name").
|
// AppLaunch("$package_name").
|
||||||
Sleep(5).
|
// Sleep(5).
|
||||||
Validate().
|
// Validate().
|
||||||
AssertAppInForeground("$package_name"),
|
// AssertAppInForeground("$package_name"),
|
||||||
|
// hrp.NewStep("进入「浪漫餐厅」小游戏").
|
||||||
|
// Android().
|
||||||
|
// StartToGoal("搜索「浪漫餐厅」,点击进入「游戏」tab,进入小游戏",
|
||||||
|
// option.WithPreMarkOperation(true)).
|
||||||
|
// Validate().
|
||||||
|
// AssertAI("当前位于游戏界面"),
|
||||||
hrp.NewStep("进入「浪漫餐厅」小游戏").
|
hrp.NewStep("进入「浪漫餐厅」小游戏").
|
||||||
Android().
|
Android().
|
||||||
StartToGoal("搜索「浪漫餐厅」,进入小游戏",
|
Home().
|
||||||
|
StartToGoal("在手机桌面点击「浪漫餐厅」启动小游戏,等待游戏加载完成",
|
||||||
option.WithPreMarkOperation(true)).
|
option.WithPreMarkOperation(true)).
|
||||||
Validate().
|
Validate().
|
||||||
AssertAI("当前位于游戏界面"),
|
AssertAI("当前位于游戏界面"),
|
||||||
|
|||||||
@@ -9,38 +9,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"teststeps": [
|
"teststeps": [
|
||||||
{
|
|
||||||
"name": "启动抖音 app",
|
|
||||||
"android": {
|
|
||||||
"os_type": "android",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"method": "app_launch",
|
|
||||||
"params": "$package_name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "sleep",
|
|
||||||
"params": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"validate": [
|
|
||||||
{
|
|
||||||
"check": "ui_foreground_app",
|
|
||||||
"assert": "equal",
|
|
||||||
"expect": "$package_name",
|
|
||||||
"msg": "app [$package_name] should be in foreground"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "进入「每天数独」小游戏",
|
"name": "进入「每天数独」小游戏",
|
||||||
"android": {
|
"android": {
|
||||||
"os_type": "android",
|
"os_type": "android",
|
||||||
"actions": [
|
"actions": [
|
||||||
|
{
|
||||||
|
"method": "home"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"method": "start_to_goal",
|
"method": "start_to_goal",
|
||||||
"params": "搜索「每天数独」,进入小游戏",
|
"params": "在手机桌面点击「每天数独」启动小游戏,等待游戏加载完成\n\n1、点击【开始】按钮开始游戏,进入数独的棋盘界面\n2、若提示「体力不足」,可通过观看广告免费获得体力,观看完成后继续开始游戏\n3、进入棋盘界面后即算作目标达成\n",
|
||||||
"options": {
|
"options": {
|
||||||
"pre_mark_operation": true
|
"pre_mark_operation": true
|
||||||
}
|
}
|
||||||
@@ -51,8 +30,8 @@
|
|||||||
{
|
{
|
||||||
"check": "ui_ai",
|
"check": "ui_ai",
|
||||||
"assert": "ai_assert",
|
"assert": "ai_assert",
|
||||||
"expect": "当前页面底部包含「开始」按钮",
|
"expect": "当前界面包含网格状的棋盘",
|
||||||
"msg": "assert ai prompt [当前页面底部包含「开始」按钮] failed"
|
"msg": "assert ai prompt [当前界面包含网格状的棋盘] failed"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -63,7 +42,7 @@
|
|||||||
"actions": [
|
"actions": [
|
||||||
{
|
{
|
||||||
"method": "start_to_goal",
|
"method": "start_to_goal",
|
||||||
"params": "每天数独是一款逻辑推理游戏,玩家需要通过推理来确定黄色方块的所在位置,以下是游戏的基本规则说明:\n1、方块外面的数字代表所在那一行或一列的黄色方块数量。\n2、初始状态为白色方块,选择正确后变为黄色方块,选择错误后变为红底的 X。\n3、如果同一行或列有两个数字,则至少需要一个白底 X 分割它们作为间隔。\n4、如果数字与格子最大数相同时,该列或行必然全都是黄色方块。\n5、只能点击白色方块,不要重复点击同一个方块。\n6、若出现「桌面入口」弹窗则直接关闭。\n\n请严格按照以上游戏规则,开始游戏\n",
|
"params": "每天数独是一款逻辑推理游戏,玩家需要通过推理来确定黄色方块的所在位置,以下是游戏的基本规则说明:\n1、方块外面的数字代表所在那一行或一列的黄色方块数量。\n2、初始状态为白色方块,选择正确后变为黄色方块,选择错误后变为红底的 X。\n3、如果同一行或列有两个数字,则至少需要一个白底 X 分割它们作为间隔。\n4、如果数字与格子最大数相同时,该列或行必然全都是黄色方块。\n5、只能点击白色方块,不要重复点击同一个方块。\n6、若出现「桌面入口」弹窗则直接关闭。\n7、若游戏失败弹出恢复血量的弹窗,请关闭弹窗重新开始游戏。\n\n请严格按照以上游戏规则,开始游戏\n",
|
||||||
"options": {
|
"options": {
|
||||||
"time_limit": 300,
|
"time_limit": 300,
|
||||||
"pre_mark_operation": true
|
"pre_mark_operation": true
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGameSudoku(t *testing.T) {
|
func TestGameSudoku(t *testing.T) {
|
||||||
|
startGameInstruction := `在手机桌面点击「每天数独」启动小游戏,等待游戏加载完成
|
||||||
|
|
||||||
|
1、点击【开始】按钮开始游戏,进入数独的棋盘界面
|
||||||
|
2、若提示「体力不足」,可通过观看广告免费获得体力,观看完成后继续开始游戏
|
||||||
|
3、进入棋盘界面后即算作目标达成
|
||||||
|
`
|
||||||
userInstruction := `每天数独是一款逻辑推理游戏,玩家需要通过推理来确定黄色方块的所在位置,以下是游戏的基本规则说明:
|
userInstruction := `每天数独是一款逻辑推理游戏,玩家需要通过推理来确定黄色方块的所在位置,以下是游戏的基本规则说明:
|
||||||
1、方块外面的数字代表所在那一行或一列的黄色方块数量。
|
1、方块外面的数字代表所在那一行或一列的黄色方块数量。
|
||||||
2、初始状态为白色方块,选择正确后变为黄色方块,选择错误后变为红底的 X。
|
2、初始状态为白色方块,选择正确后变为黄色方块,选择错误后变为红底的 X。
|
||||||
@@ -17,6 +23,7 @@ func TestGameSudoku(t *testing.T) {
|
|||||||
4、如果数字与格子最大数相同时,该列或行必然全都是黄色方块。
|
4、如果数字与格子最大数相同时,该列或行必然全都是黄色方块。
|
||||||
5、只能点击白色方块,不要重复点击同一个方块。
|
5、只能点击白色方块,不要重复点击同一个方块。
|
||||||
6、若出现「桌面入口」弹窗则直接关闭。
|
6、若出现「桌面入口」弹窗则直接关闭。
|
||||||
|
7、若游戏失败弹出恢复血量的弹窗,请关闭弹窗重新开始游戏。
|
||||||
|
|
||||||
请严格按照以上游戏规则,开始游戏
|
请严格按照以上游戏规则,开始游戏
|
||||||
`
|
`
|
||||||
@@ -27,18 +34,19 @@ func TestGameSudoku(t *testing.T) {
|
|||||||
"package_name": "com.ss.android.ugc.aweme",
|
"package_name": "com.ss.android.ugc.aweme",
|
||||||
}),
|
}),
|
||||||
TestSteps: []hrp.IStep{
|
TestSteps: []hrp.IStep{
|
||||||
hrp.NewStep("启动抖音 app").
|
// hrp.NewStep("启动抖音 app").
|
||||||
Android().
|
// Android().
|
||||||
AppLaunch("$package_name").
|
// AppLaunch("$package_name").
|
||||||
Sleep(5).
|
// Sleep(5).
|
||||||
Validate().
|
// Validate().
|
||||||
AssertAppInForeground("$package_name"),
|
// AssertAppInForeground("$package_name"),
|
||||||
hrp.NewStep("进入「每天数独」小游戏").
|
hrp.NewStep("进入「每天数独」小游戏").
|
||||||
Android().
|
Android().
|
||||||
StartToGoal("搜索「每天数独」,进入小游戏",
|
Home().
|
||||||
|
StartToGoal(startGameInstruction,
|
||||||
option.WithPreMarkOperation(true)).
|
option.WithPreMarkOperation(true)).
|
||||||
Validate().
|
Validate().
|
||||||
AssertAI("当前页面底部包含「开始」按钮"),
|
AssertAI("当前界面包含网格状的棋盘"),
|
||||||
hrp.NewStep("开始游戏").
|
hrp.NewStep("开始游戏").
|
||||||
Android().
|
Android().
|
||||||
StartToGoal(userInstruction,
|
StartToGoal(userInstruction,
|
||||||
|
|||||||
@@ -9,38 +9,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"teststeps": [
|
"teststeps": [
|
||||||
{
|
|
||||||
"name": "启动抖音 app",
|
|
||||||
"android": {
|
|
||||||
"os_type": "android",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"method": "app_launch",
|
|
||||||
"params": "$package_name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "sleep",
|
|
||||||
"params": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"validate": [
|
|
||||||
{
|
|
||||||
"check": "ui_foreground_app",
|
|
||||||
"assert": "equal",
|
|
||||||
"expect": "$package_name",
|
|
||||||
"msg": "app [$package_name] should be in foreground"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "启动「跃动小子」小游戏",
|
"name": "启动「跃动小子」小游戏",
|
||||||
"android": {
|
"android": {
|
||||||
"os_type": "android",
|
"os_type": "android",
|
||||||
"actions": [
|
"actions": [
|
||||||
|
{
|
||||||
|
"method": "home"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"method": "start_to_goal",
|
"method": "start_to_goal",
|
||||||
"params": "搜索「跃动小子」,启动小游戏",
|
"params": "在手机桌面点击「跃动小子」启动小游戏,等待游戏加载完成",
|
||||||
"options": {
|
"options": {
|
||||||
"pre_mark_operation": true
|
"pre_mark_operation": true
|
||||||
}
|
}
|
||||||
@@ -51,8 +30,8 @@
|
|||||||
{
|
{
|
||||||
"check": "ui_ai",
|
"check": "ui_ai",
|
||||||
"assert": "ai_assert",
|
"assert": "ai_assert",
|
||||||
"expect": "当前页面底部包含「领地」「试炼」按钮",
|
"expect": "当前在小游戏页面",
|
||||||
"msg": "assert ai prompt [当前页面底部包含「领地」「试炼」按钮] failed"
|
"msg": "assert ai prompt [当前在小游戏页面] failed"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -63,7 +42,7 @@
|
|||||||
"actions": [
|
"actions": [
|
||||||
{
|
{
|
||||||
"method": "start_to_goal",
|
"method": "start_to_goal",
|
||||||
"params": "跃动小子是一款开宝箱类的小游戏,以下是游戏的基本规则说明:\n1、打开宝箱,按照游戏指引进行「出售」或「装备」操作。\n2、请持续推进游戏进程。\n3、屏幕底部的黑白按钮不要进行点击操作。\n\n请严格按照以上游戏规则,开始游戏\n",
|
"params": "跃动小子是一款开宝箱类的小游戏,以下是游戏的基本规则说明:\n1、打开宝箱,按照游戏指引进行「出售」或「装备」操作。\n2、请持续推进游戏进程。\n3、游戏界面底部的黑白按钮不要进行点击操作。\n\n请严格按照以上游戏规则,开始游戏\n",
|
||||||
"options": {
|
"options": {
|
||||||
"time_limit": 300,
|
"time_limit": 300,
|
||||||
"pre_mark_operation": true
|
"pre_mark_operation": true
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import (
|
|||||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGameZhuadaE(t *testing.T) {
|
func TestGameYuedongxiaozi(t *testing.T) {
|
||||||
userInstruction := `跃动小子是一款开宝箱类的小游戏,以下是游戏的基本规则说明:
|
userInstruction := `跃动小子是一款开宝箱类的小游戏,以下是游戏的基本规则说明:
|
||||||
1、打开宝箱,按照游戏指引进行「出售」或「装备」操作。
|
1、打开宝箱,按照游戏指引进行「出售」或「装备」操作。
|
||||||
2、请持续推进游戏进程。
|
2、请持续推进游戏进程。
|
||||||
3、屏幕底部的黑白按钮不要进行点击操作。
|
3、游戏界面底部的黑白按钮不要进行点击操作。
|
||||||
|
|
||||||
请严格按照以上游戏规则,开始游戏
|
请严格按照以上游戏规则,开始游戏
|
||||||
`
|
`
|
||||||
@@ -25,18 +25,25 @@ func TestGameZhuadaE(t *testing.T) {
|
|||||||
"package_name": "com.ss.android.ugc.aweme",
|
"package_name": "com.ss.android.ugc.aweme",
|
||||||
}),
|
}),
|
||||||
TestSteps: []hrp.IStep{
|
TestSteps: []hrp.IStep{
|
||||||
hrp.NewStep("启动抖音 app").
|
// hrp.NewStep("启动抖音 app").
|
||||||
Android().
|
// Android().
|
||||||
AppLaunch("$package_name").
|
// AppLaunch("$package_name").
|
||||||
Sleep(5).
|
// Sleep(5).
|
||||||
Validate().
|
// Validate().
|
||||||
AssertAppInForeground("$package_name"),
|
// AssertAppInForeground("$package_name"),
|
||||||
|
// hrp.NewStep("启动「跃动小子」小游戏").
|
||||||
|
// Android().
|
||||||
|
// StartToGoal("搜索「跃动小子」,点击「小游戏」tab,进入小游戏",
|
||||||
|
// option.WithPreMarkOperation(true)).
|
||||||
|
// Validate().
|
||||||
|
// AssertAI("当前在小游戏页面"),
|
||||||
hrp.NewStep("启动「跃动小子」小游戏").
|
hrp.NewStep("启动「跃动小子」小游戏").
|
||||||
Android().
|
Android().
|
||||||
StartToGoal("搜索「跃动小子」,启动小游戏",
|
Home().
|
||||||
|
StartToGoal("在手机桌面点击「跃动小子」启动小游戏,等待游戏加载完成",
|
||||||
option.WithPreMarkOperation(true)).
|
option.WithPreMarkOperation(true)).
|
||||||
Validate().
|
Validate().
|
||||||
AssertAI("当前页面底部包含「领地」「试炼」按钮"),
|
AssertAI("当前在小游戏页面"),
|
||||||
hrp.NewStep("开始游戏").
|
hrp.NewStep("开始游戏").
|
||||||
Android().
|
Android().
|
||||||
StartToGoal(userInstruction,
|
StartToGoal(userInstruction,
|
||||||
|
|||||||
@@ -9,41 +9,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"teststeps": [
|
"teststeps": [
|
||||||
{
|
|
||||||
"name": "启动抖音 app",
|
|
||||||
"android": {
|
|
||||||
"os_type": "android",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"method": "app_launch",
|
|
||||||
"params": "$package_name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "sleep",
|
|
||||||
"params": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"validate": [
|
|
||||||
{
|
|
||||||
"check": "ui_foreground_app",
|
|
||||||
"assert": "equal",
|
|
||||||
"expect": "$package_name",
|
|
||||||
"msg": "app [$package_name] should be in foreground"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "启动「抓大鹅」小游戏",
|
"name": "启动「抓大鹅」小游戏",
|
||||||
"android": {
|
"android": {
|
||||||
"os_type": "android",
|
"os_type": "android",
|
||||||
"actions": [
|
"actions": [
|
||||||
|
{
|
||||||
|
"method": "home"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"method": "start_to_goal",
|
"method": "start_to_goal",
|
||||||
"params": "搜索「抓大鹅」,启动小游戏",
|
"params": "在手机桌面点击「抓大鹅」启动小游戏,处理弹窗,等待游戏加载完成",
|
||||||
"options": {
|
"options": {
|
||||||
"pre_mark_operation": true
|
"pre_mark_operation": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "sleep",
|
||||||
|
"params": 10
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -51,8 +34,8 @@
|
|||||||
{
|
{
|
||||||
"check": "ui_ai",
|
"check": "ui_ai",
|
||||||
"assert": "ai_assert",
|
"assert": "ai_assert",
|
||||||
"expect": "当前页面底部包含「抓大鹅」按钮",
|
"expect": "当前页面底部包含「抓大鹅」",
|
||||||
"msg": "assert ai prompt [当前页面底部包含「抓大鹅」按钮] failed"
|
"msg": "assert ai prompt [当前页面底部包含「抓大鹅」] failed"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -67,6 +50,10 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"pre_mark_operation": true
|
"pre_mark_operation": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "sleep",
|
||||||
|
"params": 10
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,22 +28,32 @@ func TestGameZhuadaE(t *testing.T) {
|
|||||||
"package_name": "com.ss.android.ugc.aweme",
|
"package_name": "com.ss.android.ugc.aweme",
|
||||||
}),
|
}),
|
||||||
TestSteps: []hrp.IStep{
|
TestSteps: []hrp.IStep{
|
||||||
hrp.NewStep("启动抖音 app").
|
// hrp.NewStep("启动抖音 app").
|
||||||
Android().
|
// Android().
|
||||||
AppLaunch("$package_name").
|
// AppLaunch("$package_name").
|
||||||
Sleep(5).
|
// Sleep(5).
|
||||||
Validate().
|
// Validate().
|
||||||
AssertAppInForeground("$package_name"),
|
// AssertAppInForeground("$package_name"),
|
||||||
|
// hrp.NewStep("启动「抓大鹅」小游戏").
|
||||||
|
// Android().
|
||||||
|
// StartToGoal("搜索「抓大鹅」,启动小游戏",
|
||||||
|
// option.WithPreMarkOperation(true)).
|
||||||
|
// Sleep(10).
|
||||||
|
// Validate().
|
||||||
|
// AssertAI("当前页面底部包含「抓大鹅」"),
|
||||||
hrp.NewStep("启动「抓大鹅」小游戏").
|
hrp.NewStep("启动「抓大鹅」小游戏").
|
||||||
Android().
|
Android().
|
||||||
StartToGoal("搜索「抓大鹅」,启动小游戏",
|
Home().
|
||||||
|
StartToGoal("在手机桌面点击「抓大鹅」启动小游戏,处理弹窗,等待游戏加载完成",
|
||||||
option.WithPreMarkOperation(true)).
|
option.WithPreMarkOperation(true)).
|
||||||
|
Sleep(10).
|
||||||
Validate().
|
Validate().
|
||||||
AssertAI("当前页面底部包含「抓大鹅」按钮"),
|
AssertAI("当前页面底部包含「抓大鹅」"),
|
||||||
hrp.NewStep("进入「抓大鹅」小游戏").
|
hrp.NewStep("进入「抓大鹅」小游戏").
|
||||||
Android().
|
Android().
|
||||||
StartToGoal("点击「抓大鹅」,进入小游戏",
|
StartToGoal("点击「抓大鹅」,进入小游戏",
|
||||||
option.WithPreMarkOperation(true)).
|
option.WithPreMarkOperation(true)).
|
||||||
|
Sleep(10).
|
||||||
Validate().
|
Validate().
|
||||||
AssertAI("当前页面底部包含「移出」「凑齐」「打乱」按钮"),
|
AssertAI("当前页面底部包含「移出」「凑齐」「打乱」按钮"),
|
||||||
hrp.NewStep("开始游戏").
|
hrp.NewStep("开始游戏").
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v5.0.0-250704
|
v5.0.0-250709
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/httprunner/funplugin"
|
||||||
|
"github.com/httprunner/funplugin/fungo"
|
||||||
"github.com/maja42/goval"
|
"github.com/maja42/goval"
|
||||||
"github.com/mark3labs/mcp-go/mcp"
|
"github.com/mark3labs/mcp-go/mcp"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/funplugin"
|
|
||||||
"github.com/httprunner/funplugin/fungo"
|
|
||||||
"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/mcphost"
|
"github.com/httprunner/httprunner/v5/mcphost"
|
||||||
@@ -238,7 +238,7 @@ func (p *Parser) ParseString(raw string, variablesMapping map[string]interface{}
|
|||||||
log.Debug().
|
log.Debug().
|
||||||
Str("parsedString", parsedString).
|
Str("parsedString", parsedString).
|
||||||
Int("matchStartPosition", matchStartPosition).
|
Int("matchStartPosition", matchStartPosition).
|
||||||
Msg("[parseString] parse function")
|
Msg("parse string function")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ func (p *Parser) ParseString(raw string, variablesMapping map[string]interface{}
|
|||||||
log.Debug().
|
log.Debug().
|
||||||
Str("parsedString", parsedString).
|
Str("parsedString", parsedString).
|
||||||
Int("matchStartPosition", matchStartPosition).
|
Int("matchStartPosition", matchStartPosition).
|
||||||
Msg("[parseString] parse variable")
|
Msg("parse string variable")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +306,8 @@ func (p *Parser) CallFunc(funcName string, arguments ...interface{}) (interface{
|
|||||||
|
|
||||||
// CallMCPTool calls a MCP tool on a specific MCP server
|
// CallMCPTool calls a MCP tool on a specific MCP server
|
||||||
func (p *Parser) CallMCPTool(ctx context.Context, serverName,
|
func (p *Parser) CallMCPTool(ctx context.Context, serverName,
|
||||||
funcName string, arguments map[string]interface{}) (interface{}, error) {
|
funcName string, arguments map[string]interface{},
|
||||||
|
) (interface{}, error) {
|
||||||
if p.MCPHost == nil {
|
if p.MCPHost == nil {
|
||||||
return nil, fmt.Errorf("mcphost is not initialized")
|
return nil, fmt.Errorf("mcphost is not initialized")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1838,6 +1838,101 @@ const htmlTemplate = `<!DOCTYPE html>
|
|||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AI Assertion Styles */
|
||||||
|
.ai-assertion-section {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 15px;
|
||||||
|
background: linear-gradient(135deg, #f0f8ff 0%, #f5f5ff 100%);
|
||||||
|
border: 2px solid #4169e1;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 8px rgba(65, 105, 225, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-assertion-section h5 {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
color: #4169e1;
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-screenshot-container {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-screenshot {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-screenshot img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-screenshot img:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-analysis-container {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-analysis-content {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought {
|
||||||
|
background: linear-gradient(135deg, #e8f4fd 0%, #f0f8ff 100%);
|
||||||
|
border: 1px solid #4169e1;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-thought .thought-content {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-style: italic;
|
||||||
|
color: #34495e;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-raw-response {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-raw-response .response-content {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
background: white;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.validator-ai-layout {
|
.validator-ai-layout {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -2950,6 +3045,60 @@ const htmlTemplate = `<!DOCTYPE html>
|
|||||||
{{if and $validator.msg (ne $validator.check_result "pass")}}
|
{{if and $validator.msg (ne $validator.check_result "pass")}}
|
||||||
<div class="validator-message">{{$validator.msg}}</div>
|
<div class="validator-message">{{$validator.msg}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<!-- AI Assertion Results -->
|
||||||
|
{{if $validator.ai_result}}
|
||||||
|
<div class="ai-assertion-section">
|
||||||
|
<h5>🤖 AI Assertion Details</h5>
|
||||||
|
|
||||||
|
<!-- AI Assertion Screenshot -->
|
||||||
|
{{if $validator.ai_result.image_path}}
|
||||||
|
<div class="ai-screenshot-container">
|
||||||
|
<span class="step-name">📸 AI Assertion Screenshot</span>
|
||||||
|
{{if $validator.ai_result.screenshot_elapsed}}
|
||||||
|
<span class="duration">{{formatDuration $validator.ai_result.screenshot_elapsed}}</span>
|
||||||
|
{{end}}
|
||||||
|
<div class="ai-screenshot">
|
||||||
|
{{$base64Image := encodeImageBase64 $validator.ai_result.image_path}}
|
||||||
|
{{if $base64Image}}
|
||||||
|
<img src="data:image/jpeg;base64,{{$base64Image}}" alt="AI Assertion Screenshot" onclick="openImageModal(this.src)" />
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- AI Model Analysis -->
|
||||||
|
<div class="ai-analysis-container">
|
||||||
|
<span class="step-name">🧠 AI Model Analysis</span>
|
||||||
|
{{if $validator.ai_result.model_call_elapsed}}
|
||||||
|
<span class="duration">{{formatDuration $validator.ai_result.model_call_elapsed}}</span>
|
||||||
|
{{end}}
|
||||||
|
<div class="ai-analysis-content">
|
||||||
|
{{if $validator.ai_result.assertion_result.model_name}}
|
||||||
|
<div class="model-info">🤖 Model: {{$validator.ai_result.assertion_result.model_name}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{if $validator.ai_result.assertion_result.usage}}
|
||||||
|
<div class="usage-info">📊 Tokens: {{$validator.ai_result.assertion_result.usage.PromptTokens}} in / {{$validator.ai_result.assertion_result.usage.CompletionTokens}} out / {{$validator.ai_result.assertion_result.usage.TotalTokens}} total</div>
|
||||||
|
{{end}}
|
||||||
|
{{if $validator.ai_result.resolution}}
|
||||||
|
<div class="model-info">📐 Resolution: {{$validator.ai_result.resolution.Width}}x{{$validator.ai_result.resolution.Height}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{if $validator.ai_result.assertion_result.thought}}
|
||||||
|
<div class="ai-thought">
|
||||||
|
<strong>💭 AI Reasoning:</strong>
|
||||||
|
<div class="thought-content">{{$validator.ai_result.assertion_result.thought}}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if $validator.ai_result.assertion_result.content}}
|
||||||
|
<div class="ai-raw-response">
|
||||||
|
<strong>📝 Raw Model Response:</strong>
|
||||||
|
<div class="response-content">{{$validator.ai_result.assertion_result.content}}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-1
@@ -1042,7 +1042,8 @@ func validateUI(ud *uixt.XTDriver, iValidators []interface{}, parser *Parser, st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform validation
|
// Perform validation
|
||||||
err = ud.DoValidation(validator.Check, validator.Assert, expected, validator.Message)
|
validationResult.AIResult, err = ud.DoValidation(
|
||||||
|
validator.Check, validator.Assert, expected, validator.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Add the failed validation result to the list before returning error
|
// Add the failed validation result to the list before returning error
|
||||||
validateResults = append(validateResults, validationResult)
|
validateResults = append(validateResults, validationResult)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"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/internal/version"
|
"github.com/httprunner/httprunner/v5/internal/version"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -235,4 +236,5 @@ type ValidationResult struct {
|
|||||||
Validator
|
Validator
|
||||||
CheckValue interface{} `json:"check_value" yaml:"check_value"`
|
CheckValue interface{} `json:"check_value" yaml:"check_value"`
|
||||||
CheckResult string `json:"check_result" yaml:"check_result"`
|
CheckResult string `json:"check_result" yaml:"check_result"`
|
||||||
|
AIResult *uixt.AIExecutionResult `json:"ai_result,omitempty" yaml:"ai_result,omitempty"` // store AI assertion result for displaying in report
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type AssertOptions struct {
|
|||||||
type AssertionResult struct {
|
type AssertionResult struct {
|
||||||
Pass bool `json:"pass"`
|
Pass bool `json:"pass"`
|
||||||
Thought string `json:"thought"`
|
Thought string `json:"thought"`
|
||||||
|
Content string `json:"content,omitempty"` // raw response content
|
||||||
ModelName string `json:"model_name"` // model name used for assertion
|
ModelName string `json:"model_name"` // model name used for assertion
|
||||||
Usage *schema.TokenUsage `json:"usage,omitempty"` // token usage statistics
|
Usage *schema.TokenUsage `json:"usage,omitempty"` // token usage statistics
|
||||||
}
|
}
|
||||||
@@ -180,5 +181,6 @@ func parseAssertionResult(content string, modelType option.LLMServiceType) (*Ass
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.ModelName = string(modelType)
|
result.ModelName = string(modelType)
|
||||||
|
result.Content = content // Store the original response content
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -492,9 +492,10 @@ func (dExt *XTDriver) AIAssert(assertion string, opts ...option.ActionOption) (*
|
|||||||
return assertResult, errors.Wrap(err, "AI assertion failed")
|
return assertResult, errors.Wrap(err, "AI assertion failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For assertion failure, we should still return success but mark the assertion as failed
|
||||||
|
// This ensures that the AIResult (including screenshot and thought) is properly saved and displayed
|
||||||
if !result.Pass {
|
if !result.Pass {
|
||||||
assertResult.Error = result.Thought
|
assertResult.Error = result.Thought // Store the failure reason for reporting
|
||||||
return assertResult, errors.New(result.Thought)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return assertResult, nil
|
return assertResult, nil
|
||||||
|
|||||||
+24
-11
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/json"
|
"github.com/httprunner/httprunner/v5/internal/json"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Attachments map[string]interface{}
|
type Attachments map[string]interface{}
|
||||||
@@ -151,32 +152,38 @@ func (s *DriverSession) buildURL(urlStr string) (string, error) {
|
|||||||
return baseURL.ResolveReference(relativeURL).String(), nil
|
return baseURL.ResolveReference(relativeURL).String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DriverSession) GET(urlStr string) (rawResp DriverRawResponse, err error) {
|
func (s *DriverSession) GET(urlStr string, opts ...option.ActionOption) (rawResp DriverRawResponse, err error) {
|
||||||
return s.RequestWithRetry(http.MethodGet, urlStr, nil)
|
return s.RequestWithRetry(http.MethodGet, urlStr, nil, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DriverSession) POST(data interface{}, urlStr string) (rawResp DriverRawResponse, err error) {
|
func (s *DriverSession) POST(data interface{}, urlStr string, opts ...option.ActionOption) (rawResp DriverRawResponse, err error) {
|
||||||
var bsJSON []byte = nil
|
var bsJSON []byte = nil
|
||||||
if data != nil {
|
if data != nil {
|
||||||
if bsJSON, err = json.Marshal(data); err != nil {
|
if bsJSON, err = json.Marshal(data); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.RequestWithRetry(http.MethodPost, urlStr, bsJSON)
|
return s.RequestWithRetry(http.MethodPost, urlStr, bsJSON, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DriverSession) DELETE(urlStr string) (rawResp DriverRawResponse, err error) {
|
func (s *DriverSession) DELETE(urlStr string, opts ...option.ActionOption) (rawResp DriverRawResponse, err error) {
|
||||||
return s.RequestWithRetry(http.MethodDelete, urlStr, nil)
|
return s.RequestWithRetry(http.MethodDelete, urlStr, nil, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DriverSession) RequestWithRetry(method string, urlStr string, rawBody []byte) (
|
func (s *DriverSession) RequestWithRetry(method string, urlStr string, rawBody []byte, opts ...option.ActionOption) (
|
||||||
rawResp DriverRawResponse, err error,
|
rawResp DriverRawResponse, err error,
|
||||||
) {
|
) {
|
||||||
var lastError error
|
var lastError error
|
||||||
|
|
||||||
for attempt := 1; attempt <= s.maxRetry; attempt++ {
|
maxRetry := s.maxRetry
|
||||||
|
options := option.NewActionOptions(opts...)
|
||||||
|
if options.MaxRetryTimes > 0 {
|
||||||
|
maxRetry = options.MaxRetryTimes
|
||||||
|
}
|
||||||
|
|
||||||
|
for attempt := 1; attempt <= maxRetry; attempt++ {
|
||||||
// Execute the request
|
// Execute the request
|
||||||
rawResp, err = s.Request(method, urlStr, rawBody)
|
rawResp, err = s.Request(method, urlStr, rawBody, opts...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if attempt > 1 {
|
if attempt > 1 {
|
||||||
log.Info().Msgf("request succeeded after %d attempts", attempt)
|
log.Info().Msgf("request succeeded after %d attempts", attempt)
|
||||||
@@ -210,9 +217,15 @@ func (s *DriverSession) RequestWithRetry(method string, urlStr string, rawBody [
|
|||||||
return nil, lastError
|
return nil, lastError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DriverSession) Request(method string, urlStr string, rawBody []byte) (
|
func (s *DriverSession) Request(method string, urlStr string, rawBody []byte, opts ...option.ActionOption) (
|
||||||
rawResp DriverRawResponse, err error,
|
rawResp DriverRawResponse, err error,
|
||||||
) {
|
) {
|
||||||
|
timeout := s.timeout
|
||||||
|
options := option.NewActionOptions(opts...)
|
||||||
|
if options.Timeout > 0 {
|
||||||
|
timeout = time.Duration(options.Timeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
// build final URL
|
// build final URL
|
||||||
rawURL, err := s.buildURL(urlStr)
|
rawURL, err := s.buildURL(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -252,7 +265,7 @@ func (s *DriverSession) Request(method string, urlStr string, rawBody []byte) (
|
|||||||
logger.Msg("request uixt driver")
|
logger.Msg("request uixt driver")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(s.ctx, s.timeout)
|
ctx, cancel := context.WithTimeout(s.ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, rawURL, bytes.NewBuffer(rawBody))
|
req, err := http.NewRequestWithContext(ctx, method, rawURL, bytes.NewBuffer(rawBody))
|
||||||
|
|||||||
+19
-8
@@ -65,8 +65,8 @@ func convertToAbsolutePoint(driver IDriver, x, y float64) (absX, absY float64, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func convertToAbsoluteCoordinates(driver IDriver, fromX, fromY, toX, toY float64) (
|
func convertToAbsoluteCoordinates(driver IDriver, fromX, fromY, toX, toY float64) (
|
||||||
absFromX, absFromY, absToX, absToY float64, err error) {
|
absFromX, absFromY, absToX, absToY float64, err error,
|
||||||
|
) {
|
||||||
// absolute coordinates
|
// absolute coordinates
|
||||||
if fromX > 1 || toX > 1 || fromY > 1 || toY > 1 {
|
if fromX > 1 || toX > 1 || fromY > 1 || toY > 1 {
|
||||||
return fromX, fromY, toX, toY, nil
|
return fromX, fromY, toX, toY, nil
|
||||||
@@ -190,32 +190,43 @@ func (dExt *XTDriver) assertSelector(selector, assert string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dExt *XTDriver) DoValidation(check, assert, expected string, message ...string) (err error) {
|
func (dExt *XTDriver) DoValidation(check, assert, expected string, message ...string) (aiResult *AIExecutionResult, err error) {
|
||||||
switch check {
|
switch check {
|
||||||
case option.SelectorOCR:
|
case option.SelectorOCR:
|
||||||
err = dExt.assertOCR(expected, assert)
|
err = dExt.assertOCR(expected, assert)
|
||||||
case option.SelectorAI:
|
case option.SelectorAI:
|
||||||
_, err = dExt.AIAssert(expected)
|
aiResult, err = dExt.AIAssert(expected)
|
||||||
case option.SelectorForegroundApp:
|
case option.SelectorForegroundApp:
|
||||||
err = dExt.assertForegroundApp(expected, assert)
|
err = dExt.assertForegroundApp(expected, assert)
|
||||||
case option.SelectorSelector:
|
case option.SelectorSelector:
|
||||||
err = dExt.assertSelector(expected, assert)
|
err = dExt.assertSelector(expected, assert)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("validator %s not implemented", check)
|
return nil, fmt.Errorf("validator %s not implemented", check)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Technical error (not assertion failure)
|
||||||
if message == nil {
|
if message == nil {
|
||||||
message = []string{""}
|
message = []string{""}
|
||||||
}
|
}
|
||||||
log.Error().Err(err).Str("assert", assert).Str("expect", expected).
|
log.Error().Err(err).Str("assert", assert).Str("expect", expected).
|
||||||
Str("msg", message[0]).Msg("validate failed")
|
Str("msg", message[0]).Msg("validate failed")
|
||||||
return err
|
return nil, err
|
||||||
|
} else if aiResult != nil {
|
||||||
|
// Check assertion result instead of relying on error
|
||||||
|
if !aiResult.AssertionResult.Pass {
|
||||||
|
return aiResult, errors.New(aiResult.AssertionResult.Thought)
|
||||||
}
|
}
|
||||||
|
log.Info().Str("check", check).Str("assert", assert).
|
||||||
|
Str("expect", expected).
|
||||||
|
Interface("ai_assertion_result", aiResult.AssertionResult).
|
||||||
|
Msg("ai assertion passed")
|
||||||
|
return aiResult, nil
|
||||||
|
} else {
|
||||||
log.Info().Str("check", check).Str("assert", assert).
|
log.Info().Str("check", check).Str("assert", assert).
|
||||||
Str("expect", expected).Msg("validate success")
|
Str("expect", expected).Msg("validate success")
|
||||||
return nil
|
return nil, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SleepConfig struct {
|
type SleepConfig struct {
|
||||||
|
|||||||
+5
-5
@@ -160,22 +160,22 @@ func (dev *IOSDevice) Setup() error {
|
|||||||
if version.GreaterThan(semver.MustParse("17.4.0")) {
|
if version.GreaterThan(semver.MustParse("17.4.0")) {
|
||||||
info, err := tunnel.TunnelInfoForDevice(dev.DeviceEntry.Properties.SerialNumber, ios.HttpApiHost(), ios.HttpApiPort())
|
info, err := tunnel.TunnelInfoForDevice(dev.DeviceEntry.Properties.SerialNumber, ios.HttpApiHost(), ios.HttpApiPort())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(code.DeviceConnectionError, err.Error())
|
||||||
}
|
}
|
||||||
dev.DeviceEntry.UserspaceTUNPort = info.UserspaceTUNPort
|
dev.DeviceEntry.UserspaceTUNPort = info.UserspaceTUNPort
|
||||||
dev.DeviceEntry.UserspaceTUN = info.UserspaceTUN
|
dev.DeviceEntry.UserspaceTUN = info.UserspaceTUN
|
||||||
rsdService, err := ios.NewWithAddrPortDevice(info.Address, info.RsdPort, dev.DeviceEntry)
|
rsdService, err := ios.NewWithAddrPortDevice(info.Address, info.RsdPort, dev.DeviceEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(code.DeviceConnectionError, err.Error())
|
||||||
}
|
}
|
||||||
defer rsdService.Close()
|
defer rsdService.Close()
|
||||||
rsdProvider, err := rsdService.Handshake()
|
rsdProvider, err := rsdService.Handshake()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(code.DeviceConnectionError, err.Error())
|
||||||
}
|
}
|
||||||
device, err := ios.GetDeviceWithAddress(dev.DeviceEntry.Properties.SerialNumber, info.Address, rsdProvider)
|
device, err := ios.GetDeviceWithAddress(dev.DeviceEntry.Properties.SerialNumber, info.Address, rsdProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Wrap(code.DeviceConnectionError, err.Error())
|
||||||
}
|
}
|
||||||
device.UserspaceTUN = dev.DeviceEntry.UserspaceTUN
|
device.UserspaceTUN = dev.DeviceEntry.UserspaceTUN
|
||||||
device.UserspaceTUNPort = dev.DeviceEntry.UserspaceTUNPort
|
device.UserspaceTUNPort = dev.DeviceEntry.UserspaceTUNPort
|
||||||
@@ -470,7 +470,7 @@ func (dev *IOSDevice) getVersion() (version *semver.Version, err error) {
|
|||||||
version, err = ios.GetProductVersion(dev.DeviceEntry)
|
version, err = ios.GetProductVersion(dev.DeviceEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("failed to get version")
|
log.Error().Err(err).Msg("failed to get version")
|
||||||
return nil, err
|
return nil, errors.Wrap(code.DeviceGetInfoError, err.Error())
|
||||||
}
|
}
|
||||||
log.Info().Str("version", version.String()).Msg("get ios device version")
|
log.Info().Str("version", version.String()).Msg("get ios device version")
|
||||||
return version, nil
|
return version, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user