Files
httprunner/uixt/option/request.go
lilong.129 77f5683f9a fix: remove unnecessary IgnoreNotFoundError and MaxRetryTimes from coordinate-based tap tools
- Removed IgnoreNotFoundError and MaxRetryTimes parameters from TapRequest, TapAbsXYRequest, and DoubleTapXYRequest structures
- Updated corresponding tool implementations to remove references to these non-existent fields
- These parameters are not applicable to coordinate-based operations as they don't involve element searching
- Only OCR/CV-based operations need these error handling parameters

This ensures that only relevant tools have the ignore_NotFoundError functionality,
making the API more consistent and avoiding confusion.
2025-05-26 22:10:08 +08:00

290 lines
11 KiB
Go

package option
import (
"reflect"
"strings"
"github.com/httprunner/httprunner/v5/uixt/types"
"github.com/mark3labs/mcp-go/mcp"
"github.com/rs/zerolog/log"
)
type TargetDeviceRequest struct {
Platform string `json:"platform" binding:"required" desc:"Device platform: android/ios/browser"`
Serial string `json:"serial" binding:"required" desc:"Device serial/udid/browser id"`
}
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)"`
}
type DragRequest struct {
TargetDeviceRequest
FromX float64 `json:"from_x" binding:"required" desc:"Starting X-coordinate (percentage, 0.0 to 1.0)"`
FromY float64 `json:"from_y" binding:"required" desc:"Starting Y-coordinate (percentage, 0.0 to 1.0)"`
ToX float64 `json:"to_x" binding:"required" desc:"Ending X-coordinate (percentage, 0.0 to 1.0)"`
ToY float64 `json:"to_y" binding:"required" desc:"Ending Y-coordinate (percentage, 0.0 to 1.0)"`
Duration float64 `json:"duration" desc:"Swipe duration in milliseconds (optional)"`
PressDuration float64 `json:"press_duration" desc:"Press duration in milliseconds (optional)"`
}
type SwipeRequest struct {
TargetDeviceRequest
Direction string `json:"direction" binding:"required" desc:"The direction of the swipe. Supported directions: up, down, left, right"`
Duration float64 `json:"duration" desc:"Swipe duration in milliseconds (optional)"`
PressDuration float64 `json:"press_duration" desc:"Press duration in milliseconds (optional)"`
}
type InputRequest struct {
TargetDeviceRequest
Text string `json:"text" binding:"required"`
Frequency int `json:"frequency"` // only iOS
}
type DeleteRequest struct {
TargetDeviceRequest
Count int `json:"count" binding:"required"`
}
type KeycodeRequest struct {
TargetDeviceRequest
Keycode int `json:"keycode" binding:"required"`
}
type AppInstallRequest struct {
TargetDeviceRequest
AppUrl string `json:"appUrl" binding:"required"`
MappingUrl string `json:"mappingUrl"`
ResourceMappingUrl string `json:"resourceMappingUrl"`
PackageName string `json:"packageName"`
}
type AppInfoRequest struct {
TargetDeviceRequest
PackageName string `form:"packageName" binding:"required"`
}
type AppUninstallRequest struct {
TargetDeviceRequest
PackageName string `json:"packageName" binding:"required"`
}
type AppClearRequest struct {
TargetDeviceRequest
PackageName string `json:"packageName" binding:"required"`
}
type AppLaunchRequest struct {
TargetDeviceRequest
PackageName string `json:"packageName" binding:"required" desc:"The package name of the app to launch"`
}
type AppTerminateRequest struct {
TargetDeviceRequest
PackageName string `json:"packageName" binding:"required" desc:"The package name of the app to terminate"`
}
type PressButtonRequest struct {
TargetDeviceRequest
Button types.DeviceButton `json:"button" binding:"required" desc:"The button to press. Supported buttons: BACK (android only), HOME, VOLUME_UP, VOLUME_DOWN, ENTER."`
}
// Additional requests for missing actions
type WebLoginNoneUIRequest struct {
TargetDeviceRequest
PackageName string `json:"packageName" binding:"required" desc:"Package name for the app to login"`
PhoneNumber string `json:"phoneNumber" binding:"required" desc:"Phone number for login"`
Captcha string `json:"captcha" binding:"required" desc:"Captcha code"`
Password string `json:"password" binding:"required" desc:"Password for login"`
}
type SwipeToTapAppRequest struct {
TargetDeviceRequest
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"`
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"`
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 {
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)"`
}
type SelectorRequest struct {
TargetDeviceRequest
Selector string `json:"selector" binding:"required" desc:"CSS or XPath selector"`
}
type WebCloseTabRequest struct {
TargetDeviceRequest
TabIndex int `json:"tabIndex" binding:"required" desc:"Index of the tab to close"`
}
type SetImeRequest struct {
TargetDeviceRequest
Ime string `json:"ime" binding:"required" desc:"IME package name to set"`
}
type GetSourceRequest struct {
TargetDeviceRequest
PackageName string `json:"packageName" binding:"required" desc:"Package name to get source from"`
}
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)"`
}
type TapByOCRRequest struct {
TargetDeviceRequest
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"`
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)"`
}
type SwipeAdvancedRequest struct {
TargetDeviceRequest
FromX float64 `json:"fromX" binding:"required" desc:"Starting X coordinate"`
FromY float64 `json:"fromY" binding:"required" desc:"Starting Y coordinate"`
ToX float64 `json:"toX" binding:"required" desc:"Ending X coordinate"`
ToY float64 `json:"toY" binding:"required" desc:"Ending Y coordinate"`
Duration float64 `json:"duration" desc:"Swipe duration in seconds (optional)"`
PressDuration float64 `json:"pressDuration" desc:"Press duration in seconds (optional)"`
}
type SleepMSRequest struct {
TargetDeviceRequest
Milliseconds int64 `json:"milliseconds" binding:"required" desc:"Sleep duration in milliseconds"`
}
type SleepRandomRequest struct {
TargetDeviceRequest
Params []float64 `json:"params" binding:"required" desc:"Random sleep parameters [min, max] or [min1, max1, weight1, ...]"`
}
type CallFunctionRequest struct {
TargetDeviceRequest
Description string `json:"description" binding:"required" desc:"Function description"`
}
type AIActionRequest struct {
TargetDeviceRequest
Prompt string `json:"prompt" binding:"required" desc:"AI action prompt"`
}
type FinishedRequest struct {
Content string `json:"content" binding:"required" desc:"Completion message for finished reason"`
}
// NewMCPOptions generates mcp.NewTool parameters from a struct type.
// It automatically generates mcp.NewTool parameters based on the struct fields and their desc tags.
func NewMCPOptions(t interface{}) (options []mcp.ToolOption) {
tType := reflect.TypeOf(t)
// Handle pointer type by getting the element type
if tType.Kind() == reflect.Ptr {
tType = tType.Elem()
}
// Ensure we have a struct type
if tType.Kind() != reflect.Struct {
log.Warn().Str("type", tType.String()).Msg("NewMCPOptions expects a struct or pointer to struct")
return options
}
for i := 0; i < tType.NumField(); i++ {
field := tType.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == "" || jsonTag == "-" {
continue
}
name := strings.Split(jsonTag, ",")[0]
binding := field.Tag.Get("binding")
required := strings.Contains(binding, "required")
desc := field.Tag.Get("desc")
switch field.Type.Kind() {
case reflect.Float64, reflect.Float32, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if required {
options = append(options, mcp.WithNumber(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithNumber(name, mcp.Description(desc)))
}
case reflect.String:
if required {
options = append(options, mcp.WithString(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithString(name, mcp.Description(desc)))
}
case reflect.Bool:
if required {
options = append(options, mcp.WithBoolean(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithBoolean(name, mcp.Description(desc)))
}
case reflect.Slice:
// Handle slice types, especially []string and []float64
if field.Type.Elem().Kind() == reflect.String {
// Array of strings
if required {
options = append(options, mcp.WithArray(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithArray(name, mcp.Description(desc)))
}
} else if field.Type.Elem().Kind() == reflect.Float64 {
// Array of numbers
if required {
options = append(options, mcp.WithArray(name, mcp.Required(), mcp.Description(desc)))
} else {
options = append(options, mcp.WithArray(name, mcp.Description(desc)))
}
}
default:
log.Warn().Str("field_type", field.Type.String()).Msg("Unsupported field type")
}
}
return options
}