mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
- 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.
290 lines
11 KiB
Go
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
|
|
}
|