mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-05 15:59:33 +08:00
fix: MCP server ignore_NotFoundError option not working
- Fixed TapByOCR and TapByCV tools to properly handle ignore_NotFoundError option - Added option parameters to all MCP tool request structures - Fixed ConvertActionToCallToolRequest methods to extract action options - Added extractActionOptionsToArguments helper function for consistent option handling - Extended fix to all MCP tools: SwipeToTapApp, SwipeToTapText, SwipeToTapTexts, TapXY, TapAbsXY - Added comprehensive tests for option parameter handling - Updated test expectations to match actual registered tools This ensures that when ignore_NotFoundError is set to true, OCR/CV operations will return nil instead of throwing errors when target elements are not found, allowing tests to continue execution as expected.
This commit is contained in:
@@ -313,12 +313,27 @@ func (t *ToolTapXY) Implement() server.ToolHandlerFunc {
|
||||
return nil, fmt.Errorf("parse parameters error: %w", err)
|
||||
}
|
||||
|
||||
// Build action options from request structure
|
||||
var opts []option.ActionOption
|
||||
|
||||
// Add boolean options
|
||||
if tapReq.IgnoreNotFoundError {
|
||||
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
||||
}
|
||||
|
||||
// Add numeric options
|
||||
if tapReq.Duration > 0 {
|
||||
opts = append(opts, option.WithDuration(tapReq.Duration))
|
||||
}
|
||||
if tapReq.MaxRetryTimes > 0 {
|
||||
opts = append(opts, option.WithMaxRetryTimes(tapReq.MaxRetryTimes))
|
||||
}
|
||||
|
||||
// Add default options
|
||||
opts = append(opts, option.WithPreMarkOperation(true))
|
||||
|
||||
// Tap action logic
|
||||
log.Info().Float64("x", tapReq.X).Float64("y", tapReq.Y).Msg("tapping at coordinates")
|
||||
opts := []option.ActionOption{
|
||||
option.WithDuration(tapReq.Duration),
|
||||
option.WithPreMarkOperation(true),
|
||||
}
|
||||
|
||||
err = driverExt.TapXY(tapReq.X, tapReq.Y, opts...)
|
||||
if err != nil {
|
||||
@@ -340,6 +355,10 @@ func (t *ToolTapXY) ConvertActionToCallToolRequest(action MobileAction) (mcp.Cal
|
||||
if duration := action.ActionOptions.Duration; duration > 0 {
|
||||
arguments["duration"] = duration
|
||||
}
|
||||
|
||||
// Extract options to arguments
|
||||
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
||||
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap params: %v", action.Params)
|
||||
@@ -372,12 +391,24 @@ func (t *ToolTapAbsXY) Implement() server.ToolHandlerFunc {
|
||||
return nil, fmt.Errorf("parse parameters error: %w", err)
|
||||
}
|
||||
|
||||
// Tap absolute XY action logic
|
||||
log.Info().Float64("x", tapAbsReq.X).Float64("y", tapAbsReq.Y).Msg("tapping at absolute coordinates")
|
||||
opts := []option.ActionOption{}
|
||||
// Build action options from request structure
|
||||
var opts []option.ActionOption
|
||||
|
||||
// Add boolean options
|
||||
if tapAbsReq.IgnoreNotFoundError {
|
||||
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
||||
}
|
||||
|
||||
// Add numeric options
|
||||
if tapAbsReq.Duration > 0 {
|
||||
opts = append(opts, option.WithDuration(tapAbsReq.Duration))
|
||||
}
|
||||
if tapAbsReq.MaxRetryTimes > 0 {
|
||||
opts = append(opts, option.WithMaxRetryTimes(tapAbsReq.MaxRetryTimes))
|
||||
}
|
||||
|
||||
// Tap absolute XY action logic
|
||||
log.Info().Float64("x", tapAbsReq.X).Float64("y", tapAbsReq.Y).Msg("tapping at absolute coordinates")
|
||||
|
||||
err = driverExt.TapAbsXY(tapAbsReq.X, tapAbsReq.Y, opts...)
|
||||
if err != nil {
|
||||
@@ -399,12 +430,16 @@ func (t *ToolTapAbsXY) ConvertActionToCallToolRequest(action MobileAction) (mcp.
|
||||
if duration := action.ActionOptions.Duration; duration > 0 {
|
||||
arguments["duration"] = duration
|
||||
}
|
||||
|
||||
// Extract options to arguments
|
||||
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
||||
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap abs params: %v", action.Params)
|
||||
}
|
||||
|
||||
// ToolTapByOCR implements the tap_by_ocr tool call.
|
||||
// ToolTapByOCR implements the tap_ocr tool call.
|
||||
type ToolTapByOCR struct{}
|
||||
|
||||
func (t *ToolTapByOCR) Name() option.ActionMethod {
|
||||
@@ -431,9 +466,31 @@ func (t *ToolTapByOCR) Implement() server.ToolHandlerFunc {
|
||||
return nil, fmt.Errorf("parse parameters error: %w", err)
|
||||
}
|
||||
|
||||
// Build action options from request structure
|
||||
var opts []option.ActionOption
|
||||
|
||||
// Add boolean options
|
||||
if ocrReq.IgnoreNotFoundError {
|
||||
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
||||
}
|
||||
if ocrReq.Regex {
|
||||
opts = append(opts, option.WithRegex(true))
|
||||
}
|
||||
if ocrReq.TapRandomRect {
|
||||
opts = append(opts, option.WithTapRandomRect(true))
|
||||
}
|
||||
|
||||
// Add numeric options
|
||||
if ocrReq.MaxRetryTimes > 0 {
|
||||
opts = append(opts, option.WithMaxRetryTimes(ocrReq.MaxRetryTimes))
|
||||
}
|
||||
if ocrReq.Index > 0 {
|
||||
opts = append(opts, option.WithIndex(ocrReq.Index))
|
||||
}
|
||||
|
||||
// Tap by OCR action logic
|
||||
log.Info().Str("text", ocrReq.Text).Msg("tapping by OCR")
|
||||
err = driverExt.TapByOCR(ocrReq.Text)
|
||||
err = driverExt.TapByOCR(ocrReq.Text, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Tap by OCR failed: %s", err.Error())), nil
|
||||
}
|
||||
@@ -447,12 +504,16 @@ func (t *ToolTapByOCR) ConvertActionToCallToolRequest(action MobileAction) (mcp.
|
||||
arguments := map[string]any{
|
||||
"text": text,
|
||||
}
|
||||
|
||||
// Extract options to arguments
|
||||
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
||||
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid tap by OCR params: %v", action.Params)
|
||||
}
|
||||
|
||||
// ToolTapByCV implements the tap_by_cv tool call.
|
||||
// ToolTapByCV implements the tap_cv tool call.
|
||||
type ToolTapByCV struct{}
|
||||
|
||||
func (t *ToolTapByCV) Name() option.ActionMethod {
|
||||
@@ -479,13 +540,32 @@ func (t *ToolTapByCV) Implement() server.ToolHandlerFunc {
|
||||
return nil, fmt.Errorf("parse parameters error: %w", err)
|
||||
}
|
||||
|
||||
// Build action options from request structure
|
||||
var opts []option.ActionOption
|
||||
|
||||
// Add boolean options
|
||||
if cvReq.IgnoreNotFoundError {
|
||||
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
||||
}
|
||||
if cvReq.TapRandomRect {
|
||||
opts = append(opts, option.WithTapRandomRect(true))
|
||||
}
|
||||
|
||||
// Add numeric options
|
||||
if cvReq.MaxRetryTimes > 0 {
|
||||
opts = append(opts, option.WithMaxRetryTimes(cvReq.MaxRetryTimes))
|
||||
}
|
||||
if cvReq.Index > 0 {
|
||||
opts = append(opts, option.WithIndex(cvReq.Index))
|
||||
}
|
||||
|
||||
// Tap by CV action logic
|
||||
log.Info().Str("imagePath", cvReq.ImagePath).Msg("tapping by CV")
|
||||
|
||||
// For TapByCV, we need to check if there are UI types in the options
|
||||
// In the original DoAction, it requires ScreenShotWithUITypes to be set
|
||||
// We'll add a basic implementation that triggers CV recognition
|
||||
err = driverExt.TapByCV()
|
||||
err = driverExt.TapByCV(opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Tap by CV failed: %s", err.Error())), nil
|
||||
}
|
||||
@@ -499,6 +579,10 @@ func (t *ToolTapByCV) ConvertActionToCallToolRequest(action MobileAction) (mcp.C
|
||||
arguments := map[string]any{
|
||||
"imagePath": "", // Will be handled by the tool based on UI types
|
||||
}
|
||||
|
||||
// Extract options to arguments
|
||||
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
||||
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
|
||||
@@ -1002,9 +1086,25 @@ func (t *ToolSwipeToTapApp) Implement() server.ToolHandlerFunc {
|
||||
return nil, fmt.Errorf("parse parameters error: %w", err)
|
||||
}
|
||||
|
||||
// Build action options from request structure
|
||||
var opts []option.ActionOption
|
||||
|
||||
// Add boolean options
|
||||
if swipeAppReq.IgnoreNotFoundError {
|
||||
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
||||
}
|
||||
|
||||
// Add numeric options
|
||||
if swipeAppReq.MaxRetryTimes > 0 {
|
||||
opts = append(opts, option.WithMaxRetryTimes(swipeAppReq.MaxRetryTimes))
|
||||
}
|
||||
if swipeAppReq.Index > 0 {
|
||||
opts = append(opts, option.WithIndex(swipeAppReq.Index))
|
||||
}
|
||||
|
||||
// Swipe to tap app action logic
|
||||
log.Info().Str("appName", swipeAppReq.AppName).Msg("swipe to tap app")
|
||||
err = driverExt.SwipeToTapApp(swipeAppReq.AppName)
|
||||
err = driverExt.SwipeToTapApp(swipeAppReq.AppName, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Swipe to tap app failed: %s", err.Error())), nil
|
||||
}
|
||||
@@ -1018,6 +1118,10 @@ func (t *ToolSwipeToTapApp) ConvertActionToCallToolRequest(action MobileAction)
|
||||
arguments := map[string]any{
|
||||
"appName": appName,
|
||||
}
|
||||
|
||||
// Extract options to arguments
|
||||
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
||||
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe to tap app params: %v", action.Params)
|
||||
@@ -1050,9 +1154,28 @@ func (t *ToolSwipeToTapText) Implement() server.ToolHandlerFunc {
|
||||
return nil, fmt.Errorf("parse parameters error: %w", err)
|
||||
}
|
||||
|
||||
// Build action options from request structure
|
||||
var opts []option.ActionOption
|
||||
|
||||
// Add boolean options
|
||||
if swipeTextReq.IgnoreNotFoundError {
|
||||
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
||||
}
|
||||
if swipeTextReq.Regex {
|
||||
opts = append(opts, option.WithRegex(true))
|
||||
}
|
||||
|
||||
// Add numeric options
|
||||
if swipeTextReq.MaxRetryTimes > 0 {
|
||||
opts = append(opts, option.WithMaxRetryTimes(swipeTextReq.MaxRetryTimes))
|
||||
}
|
||||
if swipeTextReq.Index > 0 {
|
||||
opts = append(opts, option.WithIndex(swipeTextReq.Index))
|
||||
}
|
||||
|
||||
// Swipe to tap text action logic
|
||||
log.Info().Str("text", swipeTextReq.Text).Msg("swipe to tap text")
|
||||
err = driverExt.SwipeToTapTexts([]string{swipeTextReq.Text})
|
||||
err = driverExt.SwipeToTapTexts([]string{swipeTextReq.Text}, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Swipe to tap text failed: %s", err.Error())), nil
|
||||
}
|
||||
@@ -1066,6 +1189,10 @@ func (t *ToolSwipeToTapText) ConvertActionToCallToolRequest(action MobileAction)
|
||||
arguments := map[string]any{
|
||||
"text": text,
|
||||
}
|
||||
|
||||
// Extract options to arguments
|
||||
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
||||
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid swipe to tap text params: %v", action.Params)
|
||||
@@ -1098,9 +1225,28 @@ func (t *ToolSwipeToTapTexts) Implement() server.ToolHandlerFunc {
|
||||
return nil, fmt.Errorf("parse parameters error: %w", err)
|
||||
}
|
||||
|
||||
// Build action options from request structure
|
||||
var opts []option.ActionOption
|
||||
|
||||
// Add boolean options
|
||||
if swipeTextsReq.IgnoreNotFoundError {
|
||||
opts = append(opts, option.WithIgnoreNotFoundError(true))
|
||||
}
|
||||
if swipeTextsReq.Regex {
|
||||
opts = append(opts, option.WithRegex(true))
|
||||
}
|
||||
|
||||
// Add numeric options
|
||||
if swipeTextsReq.MaxRetryTimes > 0 {
|
||||
opts = append(opts, option.WithMaxRetryTimes(swipeTextsReq.MaxRetryTimes))
|
||||
}
|
||||
if swipeTextsReq.Index > 0 {
|
||||
opts = append(opts, option.WithIndex(swipeTextsReq.Index))
|
||||
}
|
||||
|
||||
// Swipe to tap texts action logic
|
||||
log.Info().Strs("texts", swipeTextsReq.Texts).Msg("swipe to tap texts")
|
||||
err = driverExt.SwipeToTapTexts(swipeTextsReq.Texts)
|
||||
err = driverExt.SwipeToTapTexts(swipeTextsReq.Texts, opts...)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("Swipe to tap texts failed: %s", err.Error())), nil
|
||||
}
|
||||
@@ -1121,6 +1267,10 @@ func (t *ToolSwipeToTapTexts) ConvertActionToCallToolRequest(action MobileAction
|
||||
arguments := map[string]any{
|
||||
"texts": texts,
|
||||
}
|
||||
|
||||
// Extract options to arguments
|
||||
extractActionOptionsToArguments(action.GetOptions(), arguments)
|
||||
|
||||
return buildMCPCallToolRequest(t.Name(), arguments), nil
|
||||
}
|
||||
|
||||
@@ -1198,6 +1348,48 @@ func mapToStruct(m map[string]any, out interface{}) error {
|
||||
return json.Unmarshal(b, out)
|
||||
}
|
||||
|
||||
// extractActionOptionsToArguments extracts action options and adds them to arguments map
|
||||
// This is a generic helper that can be used by multiple tools
|
||||
func extractActionOptionsToArguments(actionOptions []option.ActionOption, arguments map[string]any) {
|
||||
if len(actionOptions) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply all options to a temporary ActionOptions to extract values
|
||||
tempOptions := &option.ActionOptions{}
|
||||
for _, opt := range actionOptions {
|
||||
opt(tempOptions)
|
||||
}
|
||||
|
||||
// Define option mappings for common boolean options
|
||||
booleanOptions := map[string]bool{
|
||||
"ignore_NotFoundError": tempOptions.IgnoreNotFoundError,
|
||||
"regex": tempOptions.Regex,
|
||||
"tap_random_rect": tempOptions.TapRandomRect,
|
||||
}
|
||||
|
||||
// Add boolean options only if they are true
|
||||
for key, value := range booleanOptions {
|
||||
if value {
|
||||
arguments[key] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Add numeric options only if they have meaningful values
|
||||
if tempOptions.MaxRetryTimes > 0 {
|
||||
arguments["max_retry_times"] = tempOptions.MaxRetryTimes
|
||||
}
|
||||
if tempOptions.Index != 0 {
|
||||
arguments["index"] = tempOptions.Index
|
||||
}
|
||||
if tempOptions.Duration > 0 {
|
||||
arguments["duration"] = tempOptions.Duration
|
||||
}
|
||||
if tempOptions.PressDuration > 0 {
|
||||
arguments["press_duration"] = tempOptions.PressDuration
|
||||
}
|
||||
}
|
||||
|
||||
// ToolHome implements the home tool call.
|
||||
type ToolHome struct{}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package uixt
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -18,19 +19,43 @@ func TestNewMCPServer(t *testing.T) {
|
||||
expectedTools := []string{
|
||||
"list_available_devices",
|
||||
"select_device",
|
||||
"tap_xy",
|
||||
"tap_abs_xy",
|
||||
"tap_ocr",
|
||||
"tap_cv",
|
||||
"double_tap_xy",
|
||||
"swipe_direction",
|
||||
"swipe_coordinate",
|
||||
"swipe_to_tap_app",
|
||||
"swipe_to_tap_text",
|
||||
"swipe_to_tap_texts",
|
||||
"drag",
|
||||
"input",
|
||||
"screenshot",
|
||||
"get_screen_size",
|
||||
"press_button",
|
||||
"home",
|
||||
"back",
|
||||
"list_packages",
|
||||
"app_launch",
|
||||
"app_terminate",
|
||||
"get_screen_size",
|
||||
"press_button",
|
||||
"tap_xy",
|
||||
"swipe",
|
||||
"drag",
|
||||
"screenshot",
|
||||
"home",
|
||||
"back",
|
||||
"input",
|
||||
"app_install",
|
||||
"app_uninstall",
|
||||
"app_clear",
|
||||
"sleep",
|
||||
"sleep_ms",
|
||||
"sleep_random",
|
||||
"set_ime",
|
||||
"get_source",
|
||||
"close_popups",
|
||||
"web_login_none_ui",
|
||||
"secondary_click",
|
||||
"hover_by_selector",
|
||||
"tap_by_selector",
|
||||
"secondary_click_by_selector",
|
||||
"web_close_tab",
|
||||
"ai_action",
|
||||
"finished",
|
||||
}
|
||||
|
||||
registeredToolNames := make(map[string]bool)
|
||||
@@ -48,19 +73,43 @@ func TestToolInterfaces(t *testing.T) {
|
||||
tools := []ActionTool{
|
||||
&ToolListAvailableDevices{},
|
||||
&ToolSelectDevice{},
|
||||
&ToolTapXY{},
|
||||
&ToolTapAbsXY{},
|
||||
&ToolTapByOCR{},
|
||||
&ToolTapByCV{},
|
||||
&ToolDoubleTapXY{},
|
||||
&ToolSwipeDirection{},
|
||||
&ToolSwipeCoordinate{},
|
||||
&ToolSwipeToTapApp{},
|
||||
&ToolSwipeToTapText{},
|
||||
&ToolSwipeToTapTexts{},
|
||||
&ToolDrag{},
|
||||
&ToolInput{},
|
||||
&ToolScreenShot{},
|
||||
&ToolGetScreenSize{},
|
||||
&ToolPressButton{},
|
||||
&ToolHome{},
|
||||
&ToolBack{},
|
||||
&ToolListPackages{},
|
||||
&ToolLaunchApp{},
|
||||
&ToolTerminateApp{},
|
||||
&ToolGetScreenSize{},
|
||||
&ToolPressButton{},
|
||||
&ToolTapXY{},
|
||||
&ToolSwipeDirection{},
|
||||
&ToolDrag{},
|
||||
&ToolScreenShot{},
|
||||
&ToolHome{},
|
||||
&ToolBack{},
|
||||
&ToolInput{},
|
||||
&ToolAppInstall{},
|
||||
&ToolAppUninstall{},
|
||||
&ToolAppClear{},
|
||||
&ToolSleep{},
|
||||
&ToolSleepMS{},
|
||||
&ToolSleepRandom{},
|
||||
&ToolSetIme{},
|
||||
&ToolGetSource{},
|
||||
&ToolClosePopups{},
|
||||
&ToolWebLoginNoneUI{},
|
||||
&ToolSecondaryClick{},
|
||||
&ToolHoverBySelector{},
|
||||
&ToolTapBySelector{},
|
||||
&ToolSecondaryClickBySelector{},
|
||||
&ToolWebCloseTab{},
|
||||
&ToolAIAction{},
|
||||
&ToolFinished{},
|
||||
}
|
||||
|
||||
for _, tool := range tools {
|
||||
@@ -70,3 +119,65 @@ func TestToolInterfaces(t *testing.T) {
|
||||
assert.NotNil(t, tool.Implement(), "Tool implementation should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreNotFoundErrorOption(t *testing.T) {
|
||||
// Test that ignore_NotFoundError option is properly extracted and applied
|
||||
server := NewMCPServer()
|
||||
|
||||
// Test TapByOCR tool
|
||||
tapOCRTool := server.GetToolByAction(option.ACTION_TapByOCR)
|
||||
assert.NotNil(t, tapOCRTool, "TapByOCR tool should be available")
|
||||
|
||||
// Create a mock action with ignore_NotFoundError option
|
||||
actionOptions := option.NewActionOptions(
|
||||
option.WithIgnoreNotFoundError(true),
|
||||
option.WithMaxRetryTimes(2),
|
||||
option.WithIndex(1),
|
||||
option.WithRegex(true),
|
||||
option.WithTapRandomRect(true),
|
||||
)
|
||||
action := MobileAction{
|
||||
Method: option.ACTION_TapByOCR,
|
||||
Params: "test_text",
|
||||
ActionOptions: *actionOptions,
|
||||
}
|
||||
|
||||
// Convert action to MCP call tool request
|
||||
request, err := tapOCRTool.ConvertActionToCallToolRequest(action)
|
||||
assert.NoError(t, err, "Should convert action to request without error")
|
||||
|
||||
// Verify that ignore_NotFoundError option is included in arguments
|
||||
args := request.Params.Arguments
|
||||
assert.Equal(t, true, args["ignore_NotFoundError"], "ignore_NotFoundError should be true")
|
||||
assert.Equal(t, 2, args["max_retry_times"], "max_retry_times should be 2")
|
||||
assert.Equal(t, 1, args["index"], "index should be 1")
|
||||
assert.Equal(t, true, args["regex"], "regex should be true")
|
||||
assert.Equal(t, true, args["tap_random_rect"], "tap_random_rect should be true")
|
||||
assert.Equal(t, "test_text", args["text"], "text should be test_text")
|
||||
}
|
||||
|
||||
func TestExtractActionOptionsToArguments(t *testing.T) {
|
||||
// Test the extractActionOptionsToArguments helper function
|
||||
actionOptions := []option.ActionOption{
|
||||
option.WithIgnoreNotFoundError(true),
|
||||
option.WithMaxRetryTimes(3),
|
||||
option.WithIndex(2),
|
||||
option.WithRegex(true),
|
||||
option.WithTapRandomRect(false), // false should not be included
|
||||
option.WithDuration(1.5),
|
||||
}
|
||||
|
||||
arguments := make(map[string]any)
|
||||
extractActionOptionsToArguments(actionOptions, arguments)
|
||||
|
||||
// Verify extracted options
|
||||
assert.Equal(t, true, arguments["ignore_NotFoundError"], "ignore_NotFoundError should be extracted")
|
||||
assert.Equal(t, 3, arguments["max_retry_times"], "max_retry_times should be extracted")
|
||||
assert.Equal(t, 2, arguments["index"], "index should be extracted")
|
||||
assert.Equal(t, true, arguments["regex"], "regex should be extracted")
|
||||
assert.Equal(t, 1.5, arguments["duration"], "duration should be extracted")
|
||||
|
||||
// tap_random_rect should not be included since it's false
|
||||
_, exists := arguments["tap_random_rect"]
|
||||
assert.False(t, exists, "tap_random_rect should not be included when false")
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@ type TargetDeviceRequest struct {
|
||||
|
||||
type TapRequest struct {
|
||||
TargetDeviceRequest
|
||||
X float64 `json:"x" binding:"required" desc:"X coordinate (0.0~1.0 for percent, or absolute pixel value)"`
|
||||
Y float64 `json:"y" binding:"required" desc:"Y coordinate (0.0~1.0 for percent, or absolute pixel value)"`
|
||||
Duration float64 `json:"duration" desc:"Tap duration in seconds (optional)"`
|
||||
X float64 `json:"x" binding:"required" desc:"X coordinate (0.0~1.0 for percent, or absolute pixel value)"`
|
||||
Y float64 `json:"y" binding:"required" desc:"Y coordinate (0.0~1.0 for percent, or absolute pixel value)"`
|
||||
Duration float64 `json:"duration" desc:"Tap duration in seconds (optional)"`
|
||||
IgnoreNotFoundError bool `json:"ignore_NotFoundError" desc:"Ignore error if target element not found"`
|
||||
MaxRetryTimes int `json:"max_retry_times" desc:"Maximum retry times for the tap action"`
|
||||
}
|
||||
|
||||
type DragRequest struct {
|
||||
@@ -103,17 +105,28 @@ type WebLoginNoneUIRequest struct {
|
||||
|
||||
type SwipeToTapAppRequest struct {
|
||||
TargetDeviceRequest
|
||||
AppName string `json:"appName" binding:"required" desc:"App name to find and tap"`
|
||||
AppName string `json:"appName" binding:"required" desc:"App name to find and tap"`
|
||||
IgnoreNotFoundError bool `json:"ignore_NotFoundError" desc:"Ignore error if target element not found"`
|
||||
MaxRetryTimes int `json:"max_retry_times" desc:"Maximum retry times for finding the app"`
|
||||
Index int `json:"index" desc:"Index of the target element when multiple matches found"`
|
||||
}
|
||||
|
||||
type SwipeToTapTextRequest struct {
|
||||
TargetDeviceRequest
|
||||
Text string `json:"text" binding:"required" desc:"Text to find and tap"`
|
||||
Text string `json:"text" binding:"required" desc:"Text to find and tap"`
|
||||
IgnoreNotFoundError bool `json:"ignore_NotFoundError" desc:"Ignore error if target element not found"`
|
||||
MaxRetryTimes int `json:"max_retry_times" desc:"Maximum retry times for finding the text"`
|
||||
Index int `json:"index" desc:"Index of the target element when multiple matches found"`
|
||||
Regex bool `json:"regex" desc:"Use regex to match text"`
|
||||
}
|
||||
|
||||
type SwipeToTapTextsRequest struct {
|
||||
TargetDeviceRequest
|
||||
Texts []string `json:"texts" binding:"required" desc:"List of texts to find and tap"`
|
||||
Texts []string `json:"texts" binding:"required" desc:"List of texts to find and tap"`
|
||||
IgnoreNotFoundError bool `json:"ignore_NotFoundError" desc:"Ignore error if target element not found"`
|
||||
MaxRetryTimes int `json:"max_retry_times" desc:"Maximum retry times for finding the texts"`
|
||||
Index int `json:"index" desc:"Index of the target element when multiple matches found"`
|
||||
Regex bool `json:"regex" desc:"Use regex to match text"`
|
||||
}
|
||||
|
||||
type SecondaryClickRequest struct {
|
||||
@@ -144,25 +157,38 @@ type GetSourceRequest struct {
|
||||
|
||||
type TapAbsXYRequest struct {
|
||||
TargetDeviceRequest
|
||||
X float64 `json:"x" binding:"required" desc:"Absolute X coordinate in pixels"`
|
||||
Y float64 `json:"y" binding:"required" desc:"Absolute Y coordinate in pixels"`
|
||||
Duration float64 `json:"duration" desc:"Tap duration in seconds (optional)"`
|
||||
X float64 `json:"x" binding:"required" desc:"Absolute X coordinate in pixels"`
|
||||
Y float64 `json:"y" binding:"required" desc:"Absolute Y coordinate in pixels"`
|
||||
Duration float64 `json:"duration" desc:"Tap duration in seconds (optional)"`
|
||||
IgnoreNotFoundError bool `json:"ignore_NotFoundError" desc:"Ignore error if target element not found"`
|
||||
MaxRetryTimes int `json:"max_retry_times" desc:"Maximum retry times for the tap action"`
|
||||
}
|
||||
|
||||
type TapByOCRRequest struct {
|
||||
TargetDeviceRequest
|
||||
Text string `json:"text" binding:"required" desc:"OCR text to find and tap"`
|
||||
Text string `json:"text" binding:"required" desc:"OCR text to find and tap"`
|
||||
IgnoreNotFoundError bool `json:"ignore_NotFoundError" desc:"Ignore error if target element not found"`
|
||||
MaxRetryTimes int `json:"max_retry_times" desc:"Maximum retry times for finding the text"`
|
||||
Index int `json:"index" desc:"Index of the target element when multiple matches found"`
|
||||
Regex bool `json:"regex" desc:"Use regex to match text"`
|
||||
TapRandomRect bool `json:"tap_random_rect" desc:"Tap random point in text rectangle"`
|
||||
}
|
||||
|
||||
type TapByCVRequest struct {
|
||||
TargetDeviceRequest
|
||||
ImagePath string `json:"imagePath" desc:"Path to reference image for CV recognition"`
|
||||
ImagePath string `json:"imagePath" desc:"Path to reference image for CV recognition"`
|
||||
IgnoreNotFoundError bool `json:"ignore_NotFoundError" desc:"Ignore error if target element not found"`
|
||||
MaxRetryTimes int `json:"max_retry_times" desc:"Maximum retry times for finding the image"`
|
||||
Index int `json:"index" desc:"Index of the target element when multiple matches found"`
|
||||
TapRandomRect bool `json:"tap_random_rect" desc:"Tap random point in image rectangle"`
|
||||
}
|
||||
|
||||
type DoubleTapXYRequest struct {
|
||||
TargetDeviceRequest
|
||||
X float64 `json:"x" binding:"required" desc:"X coordinate (0.0~1.0 for percent, or absolute pixel value)"`
|
||||
Y float64 `json:"y" binding:"required" desc:"Y coordinate (0.0~1.0 for percent, or absolute pixel value)"`
|
||||
X float64 `json:"x" binding:"required" desc:"X coordinate (0.0~1.0 for percent, or absolute pixel value)"`
|
||||
Y float64 `json:"y" binding:"required" desc:"Y coordinate (0.0~1.0 for percent, or absolute pixel value)"`
|
||||
IgnoreNotFoundError bool `json:"ignore_NotFoundError" desc:"Ignore error if target element not found"`
|
||||
MaxRetryTimes int `json:"max_retry_times" desc:"Maximum retry times for the tap action"`
|
||||
}
|
||||
|
||||
type SwipeAdvancedRequest struct {
|
||||
|
||||
Reference in New Issue
Block a user