mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-25 17:44:02 +08:00
add simulation
This commit is contained in:
@@ -256,30 +256,30 @@ func TestSwipeWithDirection(t *testing.T) {
|
||||
minDistance: 100.0,
|
||||
maxDistance: 500.0,
|
||||
},
|
||||
{
|
||||
name: "随机距离下滑",
|
||||
direction: "down",
|
||||
startX: 0.5,
|
||||
startY: 0.5,
|
||||
minDistance: 150.0,
|
||||
maxDistance: 350.0, // 范围内随机
|
||||
},
|
||||
{
|
||||
name: "固定距离左滑",
|
||||
direction: "left",
|
||||
startX: 0.5,
|
||||
startY: 0.5,
|
||||
minDistance: 300.0,
|
||||
maxDistance: 300.0,
|
||||
},
|
||||
{
|
||||
name: "随机距离右滑",
|
||||
direction: "right",
|
||||
startX: 0.6,
|
||||
startY: 0.5,
|
||||
minDistance: 100.0,
|
||||
maxDistance: 250.0,
|
||||
},
|
||||
//{
|
||||
// name: "随机距离下滑",
|
||||
// direction: "down",
|
||||
// startX: 0.5,
|
||||
// startY: 0.5,
|
||||
// minDistance: 150.0,
|
||||
// maxDistance: 350.0, // 范围内随机
|
||||
//},
|
||||
//{
|
||||
// name: "固定距离左滑",
|
||||
// direction: "left",
|
||||
// startX: 0.5,
|
||||
// startY: 0.5,
|
||||
// minDistance: 300.0,
|
||||
// maxDistance: 300.0,
|
||||
//},
|
||||
//{
|
||||
// name: "随机距离右滑",
|
||||
// direction: "right",
|
||||
// startX: 0.6,
|
||||
// startY: 0.5,
|
||||
// minDistance: 100.0,
|
||||
// maxDistance: 250.0,
|
||||
//},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -567,41 +567,41 @@ func TestSIMInput(t *testing.T) {
|
||||
name string
|
||||
text string
|
||||
}{
|
||||
{
|
||||
name: "英文短文本",
|
||||
text: "Hello",
|
||||
},
|
||||
{
|
||||
name: "英文长文本",
|
||||
text: "Hello World! This is a test message.",
|
||||
},
|
||||
{
|
||||
name: "日文文本",
|
||||
text: "英語の長い文字",
|
||||
},
|
||||
{
|
||||
name: "混合文本",
|
||||
text: "Hello你好123",
|
||||
},
|
||||
{
|
||||
name: "特殊字符",
|
||||
text: "!@#$%^&*()",
|
||||
},
|
||||
{
|
||||
name: "数字文本",
|
||||
text: "1234567890",
|
||||
},
|
||||
{
|
||||
name: "空文本",
|
||||
text: "",
|
||||
},
|
||||
{
|
||||
name: "单个字符",
|
||||
text: "A",
|
||||
},
|
||||
//{
|
||||
// name: "英文短文本",
|
||||
// text: "Hello",
|
||||
//},
|
||||
//{
|
||||
// name: "英文长文本",
|
||||
// text: "Hello World! This is a test message.",
|
||||
//},
|
||||
//{
|
||||
// name: "日文文本",
|
||||
// text: "英語の長い文字",
|
||||
//},
|
||||
//{
|
||||
// name: "混合文本",
|
||||
// text: "Hello你好123",
|
||||
//},
|
||||
//{
|
||||
// name: "特殊字符",
|
||||
// text: "!@#$%^&*()",
|
||||
//},
|
||||
//{
|
||||
// name: "数字文本",
|
||||
// text: "1234567890",
|
||||
//},
|
||||
//{
|
||||
// name: "空文本",
|
||||
// text: "",
|
||||
//},
|
||||
//{
|
||||
// name: "单个字符",
|
||||
// text: "A",
|
||||
//},
|
||||
{
|
||||
name: "长文本",
|
||||
text: "This is a very long text to test the performance of SIMInput function. 这是一个很长的文本用来测试SIMInput函数的性能。1234567890!@#$%^&*()英語の長い文",
|
||||
text: "This is a very long text to test the performance of SIMInput function. 这是一个很长的文本用来测试SIMInput函数的性能。1234567890!@#$%^&*()英語の長い文字",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
v5.0.0-250728
|
||||
v5.0.0-250729
|
||||
|
||||
94
step_ui.go
94
step_ui.go
@@ -290,6 +290,100 @@ func (s *StepMobile) SwipeRight(opts ...option.ActionOption) *StepMobile {
|
||||
return s
|
||||
}
|
||||
|
||||
// SIMSwipeWithDirection performs simulated swipe in specified direction with random distance
|
||||
func (s *StepMobile) SIMSwipeWithDirection(direction string, startX, startY, minDistance, maxDistance float64, opts ...option.ActionOption) *StepMobile {
|
||||
// Create params map for SIMSwipeWithDirection
|
||||
params := map[string]interface{}{
|
||||
"direction": direction,
|
||||
"start_x": startX,
|
||||
"start_y": startY,
|
||||
"min_distance": minDistance,
|
||||
"max_distance": maxDistance,
|
||||
}
|
||||
|
||||
action := option.MobileAction{
|
||||
Method: option.ACTION_SIMSwipeDirection,
|
||||
Params: params,
|
||||
Options: option.NewActionOptions(opts...),
|
||||
}
|
||||
|
||||
s.obj().Actions = append(s.obj().Actions, action)
|
||||
return s
|
||||
}
|
||||
|
||||
// SIMSwipeInArea performs simulated swipe in specified area with direction and random distance
|
||||
func (s *StepMobile) SIMSwipeInArea(direction string, areaStartX, areaStartY, areaEndX, areaEndY, minDistance, maxDistance float64, opts ...option.ActionOption) *StepMobile {
|
||||
// Create params map for SIMSwipeInArea
|
||||
params := map[string]interface{}{
|
||||
"direction": direction,
|
||||
"area_start_x": areaStartX,
|
||||
"area_start_y": areaStartY,
|
||||
"area_end_x": areaEndX,
|
||||
"area_end_y": areaEndY,
|
||||
"min_distance": minDistance,
|
||||
"max_distance": maxDistance,
|
||||
}
|
||||
|
||||
action := option.MobileAction{
|
||||
Method: option.ACTION_SIMSwipeInArea,
|
||||
Params: params,
|
||||
Options: option.NewActionOptions(opts...),
|
||||
}
|
||||
|
||||
s.obj().Actions = append(s.obj().Actions, action)
|
||||
return s
|
||||
}
|
||||
|
||||
// SIMSwipeFromPointToPoint performs simulated swipe from point to point
|
||||
func (s *StepMobile) SIMSwipeFromPointToPoint(startX, startY, endX, endY float64, opts ...option.ActionOption) *StepMobile {
|
||||
// Create params map for SIMSwipeFromPointToPoint
|
||||
params := map[string]interface{}{
|
||||
"start_x": startX,
|
||||
"start_y": startY,
|
||||
"end_x": endX,
|
||||
"end_y": endY,
|
||||
}
|
||||
|
||||
action := option.MobileAction{
|
||||
Method: option.ACTION_SIMSwipeFromPointToPoint,
|
||||
Params: params,
|
||||
Options: option.NewActionOptions(opts...),
|
||||
}
|
||||
|
||||
s.obj().Actions = append(s.obj().Actions, action)
|
||||
return s
|
||||
}
|
||||
|
||||
// SIMClickAtPoint performs simulated click at specified point
|
||||
func (s *StepMobile) SIMClickAtPoint(x, y float64, opts ...option.ActionOption) *StepMobile {
|
||||
// Create params map for SIMClickAtPoint
|
||||
params := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
|
||||
action := option.MobileAction{
|
||||
Method: option.ACTION_SIMClickAtPoint,
|
||||
Params: params,
|
||||
Options: option.NewActionOptions(opts...),
|
||||
}
|
||||
|
||||
s.obj().Actions = append(s.obj().Actions, action)
|
||||
return s
|
||||
}
|
||||
|
||||
// SIMInput performs simulated text input with intelligent segmentation
|
||||
func (s *StepMobile) SIMInput(text string, opts ...option.ActionOption) *StepMobile {
|
||||
action := option.MobileAction{
|
||||
Method: option.ACTION_SIMInput,
|
||||
Params: text,
|
||||
Options: option.NewActionOptions(opts...),
|
||||
}
|
||||
|
||||
s.obj().Actions = append(s.obj().Actions, action)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *StepMobile) SwipeToTapApp(appName string, opts ...option.ActionOption) *StepMobile {
|
||||
action := option.MobileAction{
|
||||
Method: option.ACTION_SwipeToTapApp,
|
||||
|
||||
@@ -87,23 +87,28 @@ func (s *MCPServer4XTDriver) registerTools() {
|
||||
s.registerTool(&ToolSelectDevice{}) // SelectDevice
|
||||
|
||||
// Touch Tools
|
||||
s.registerTool(&ToolTapXY{}) // tap xy
|
||||
s.registerTool(&ToolTapAbsXY{}) // tap abs xy
|
||||
s.registerTool(&ToolTapByOCR{}) // tap by OCR
|
||||
s.registerTool(&ToolTapByCV{}) // tap by CV
|
||||
s.registerTool(&ToolDoubleTapXY{}) // double tap xy
|
||||
s.registerTool(&ToolTapXY{}) // tap xy
|
||||
s.registerTool(&ToolTapAbsXY{}) // tap abs xy
|
||||
s.registerTool(&ToolTapByOCR{}) // tap by OCR
|
||||
s.registerTool(&ToolTapByCV{}) // tap by CV
|
||||
s.registerTool(&ToolDoubleTapXY{}) // double tap xy
|
||||
s.registerTool(&ToolSIMClickAtPoint{}) // simulated click at point
|
||||
|
||||
// Swipe Tools
|
||||
s.registerTool(&ToolSwipe{}) // generic swipe, auto-detect direction or coordinate
|
||||
s.registerTool(&ToolSwipeDirection{}) // swipe direction, up/down/left/right
|
||||
s.registerTool(&ToolSwipeCoordinate{}) // swipe coordinate, [fromX, fromY, toX, toY]
|
||||
s.registerTool(&ToolSwipe{}) // generic swipe, auto-detect direction or coordinate
|
||||
s.registerTool(&ToolSwipeDirection{}) // swipe direction, up/down/left/right
|
||||
s.registerTool(&ToolSwipeCoordinate{}) // swipe coordinate, [fromX, fromY, toX, toY]
|
||||
s.registerTool(&ToolSIMSwipeDirection{}) // simulated swipe direction with random distance
|
||||
s.registerTool(&ToolSIMSwipeInArea{}) // simulated swipe in area with direction and distance
|
||||
s.registerTool(&ToolSIMSwipeFromPointToPoint{}) // simulated swipe from point to point
|
||||
s.registerTool(&ToolSwipeToTapApp{})
|
||||
s.registerTool(&ToolSwipeToTapText{})
|
||||
s.registerTool(&ToolSwipeToTapTexts{})
|
||||
s.registerTool(&ToolDrag{})
|
||||
|
||||
// Input Tools
|
||||
s.registerTool(&ToolInput{})
|
||||
s.registerTool(&ToolInput{}) // regular input
|
||||
s.registerTool(&ToolSIMInput{}) // simulated input with intelligent segmentation
|
||||
s.registerTool(&ToolBackspace{})
|
||||
s.registerTool(&ToolSetIme{})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
)
|
||||
@@ -192,3 +193,83 @@ func (t *ToolBackspace) ConvertActionToCallToolRequest(action option.MobileActio
|
||||
}
|
||||
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
||||
}
|
||||
|
||||
// ToolSIMInput implements the sim_input tool call.
|
||||
type ToolSIMInput struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Text string `json:"text" desc:"Text that was input with simulation"`
|
||||
Segments int `json:"segments" desc:"Number of segments the text was split into"`
|
||||
}
|
||||
|
||||
func (t *ToolSIMInput) Name() option.ActionName {
|
||||
return option.ACTION_SIMInput
|
||||
}
|
||||
|
||||
func (t *ToolSIMInput) Description() string {
|
||||
return "Input text with intelligent segmentation and human-like typing patterns"
|
||||
}
|
||||
|
||||
func (t *ToolSIMInput) Options() []mcp.ToolOption {
|
||||
unifiedReq := &option.ActionOptions{}
|
||||
return unifiedReq.GetMCPOptions(option.ACTION_SIMInput)
|
||||
}
|
||||
|
||||
func (t *ToolSIMInput) Implement() server.ToolHandlerFunc {
|
||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
arguments := request.GetArguments()
|
||||
driverExt, err := setupXTDriver(ctx, arguments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setup driver failed: %w", err)
|
||||
}
|
||||
|
||||
unifiedReq, err := parseActionOptions(arguments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if unifiedReq.Text == "" {
|
||||
return nil, fmt.Errorf("text is required")
|
||||
}
|
||||
|
||||
text := unifiedReq.Text
|
||||
|
||||
log.Info().
|
||||
Str("text", text).
|
||||
Int("textLength", len(text)).
|
||||
Msg("performing simulated input")
|
||||
|
||||
opts := unifiedReq.Options()
|
||||
|
||||
// Call the underlying SIMInput method (Android UIA2 specific)
|
||||
if uia2Driver, ok := driverExt.IDriver.(*UIA2Driver); ok {
|
||||
err = uia2Driver.SIMInput(text, opts...)
|
||||
if err != nil {
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Simulated input failed: %s", err.Error())), err
|
||||
}
|
||||
} else {
|
||||
return NewMCPErrorResponse("SIMInput is only supported on Android UIA2 driver"), fmt.Errorf("unsupported driver type for SIMInput")
|
||||
}
|
||||
|
||||
// Estimate segments count (this is approximate since the actual segmentation happens in the driver)
|
||||
estimatedSegments := len([]rune(text))/2 + 1
|
||||
if estimatedSegments < 1 {
|
||||
estimatedSegments = 1
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Successfully performed simulated input: %s", text)
|
||||
returnData := ToolSIMInput{
|
||||
Text: text,
|
||||
Segments: estimatedSegments,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ToolSIMInput) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
text := fmt.Sprintf("%v", action.Params)
|
||||
arguments := map[string]any{
|
||||
"text": text,
|
||||
}
|
||||
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
||||
}
|
||||
|
||||
@@ -547,3 +547,412 @@ func (t *ToolDrag) ConvertActionToCallToolRequest(action option.MobileAction) (m
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid drag parameters: %v", action.Params)
|
||||
}
|
||||
|
||||
// ToolSIMSwipeDirection implements the sim_swipe_direction tool call.
|
||||
type ToolSIMSwipeDirection struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Direction string `json:"direction" desc:"Direction that was swiped (up/down/left/right)"`
|
||||
StartX float64 `json:"startX" desc:"Starting X coordinate of the simulated swipe"`
|
||||
StartY float64 `json:"startY" desc:"Starting Y coordinate of the simulated swipe"`
|
||||
MinDistance float64 `json:"minDistance" desc:"Minimum distance of the simulated swipe"`
|
||||
MaxDistance float64 `json:"maxDistance" desc:"Maximum distance of the simulated swipe"`
|
||||
ActualDistance float64 `json:"actualDistance" desc:"Actual distance of the simulated swipe"`
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeDirection) Name() option.ActionName {
|
||||
return option.ACTION_SIMSwipeDirection
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeDirection) Description() string {
|
||||
return "Perform simulated swipe in specified direction with random distance and human-like touch patterns"
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeDirection) Options() []mcp.ToolOption {
|
||||
unifiedReq := &option.ActionOptions{}
|
||||
return unifiedReq.GetMCPOptions(option.ACTION_SIMSwipeDirection)
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeDirection) Implement() server.ToolHandlerFunc {
|
||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
arguments := request.GetArguments()
|
||||
driverExt, err := setupXTDriver(ctx, arguments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setup driver failed: %w", err)
|
||||
}
|
||||
|
||||
unifiedReq, err := parseActionOptions(arguments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if unifiedReq.Direction == nil {
|
||||
return nil, fmt.Errorf("direction parameter is required")
|
||||
}
|
||||
direction, ok := unifiedReq.Direction.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("direction must be a string")
|
||||
}
|
||||
|
||||
// Validate direction
|
||||
validDirections := []string{"up", "down", "left", "right"}
|
||||
if !slices.Contains(validDirections, direction) {
|
||||
return nil, fmt.Errorf("invalid swipe direction: %s, expected one of: %v",
|
||||
direction, validDirections)
|
||||
}
|
||||
|
||||
// Default values if not provided
|
||||
startX := unifiedReq.StartX
|
||||
startY := unifiedReq.StartY
|
||||
minDistance := unifiedReq.MinDistance
|
||||
maxDistance := unifiedReq.MaxDistance
|
||||
|
||||
if startX == 0 {
|
||||
startX = 0.5 // default to center
|
||||
}
|
||||
if startY == 0 {
|
||||
startY = 0.5 // default to center
|
||||
}
|
||||
if minDistance == 0 {
|
||||
minDistance = 100 // default minimum distance
|
||||
}
|
||||
if maxDistance == 0 {
|
||||
maxDistance = 300 // default maximum distance
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("direction", direction).
|
||||
Float64("startX", startX).
|
||||
Float64("startY", startY).
|
||||
Float64("minDistance", minDistance).
|
||||
Float64("maxDistance", maxDistance).
|
||||
Msg("performing simulated swipe with direction")
|
||||
|
||||
// Build all options from request arguments
|
||||
opts := unifiedReq.Options()
|
||||
|
||||
// Call the underlying SIMSwipeWithDirection method (Android UIA2 specific)
|
||||
if uia2Driver, ok := driverExt.IDriver.(*UIA2Driver); ok {
|
||||
err = uia2Driver.SIMSwipeWithDirection(direction, startX, startY, minDistance, maxDistance, opts...)
|
||||
if err != nil {
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Simulated swipe failed: %s", err.Error())), err
|
||||
}
|
||||
} else {
|
||||
return NewMCPErrorResponse("SIMSwipeWithDirection is only supported on Android UIA2 driver"), fmt.Errorf("unsupported driver type for SIMSwipeWithDirection")
|
||||
}
|
||||
|
||||
// Calculate actual distance for response (approximate)
|
||||
actualDistance := minDistance
|
||||
if maxDistance > minDistance {
|
||||
actualDistance = minDistance + (maxDistance-minDistance)*0.5 // approximate middle value
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Successfully performed simulated swipe %s from (%.2f, %.2f) with distance %.2f",
|
||||
direction, startX, startY, actualDistance)
|
||||
returnData := ToolSIMSwipeDirection{
|
||||
Direction: direction,
|
||||
StartX: startX,
|
||||
StartY: startY,
|
||||
MinDistance: minDistance,
|
||||
MaxDistance: maxDistance,
|
||||
ActualDistance: actualDistance,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeDirection) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
// Handle params as map[string]interface{}
|
||||
if paramsMap, ok := action.Params.(map[string]interface{}); ok {
|
||||
arguments := map[string]any{}
|
||||
|
||||
// Extract direction
|
||||
if direction, exists := paramsMap["direction"]; exists {
|
||||
arguments["direction"] = direction
|
||||
}
|
||||
|
||||
// Extract coordinates and distances
|
||||
if startX, exists := paramsMap["start_x"]; exists {
|
||||
arguments["start_x"] = startX
|
||||
}
|
||||
if startY, exists := paramsMap["start_y"]; exists {
|
||||
arguments["start_y"] = startY
|
||||
}
|
||||
if minDistance, exists := paramsMap["min_distance"]; exists {
|
||||
arguments["min_distance"] = minDistance
|
||||
}
|
||||
if maxDistance, exists := paramsMap["max_distance"]; exists {
|
||||
arguments["max_distance"] = maxDistance
|
||||
}
|
||||
|
||||
// Add duration and press duration from options
|
||||
if duration := action.ActionOptions.Duration; duration > 0 {
|
||||
arguments["duration"] = duration
|
||||
}
|
||||
if pressDuration := action.ActionOptions.PressDuration; pressDuration > 0 {
|
||||
arguments["pressDuration"] = pressDuration
|
||||
}
|
||||
|
||||
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid SIM swipe direction params: %v", action.Params)
|
||||
}
|
||||
|
||||
// ToolSIMSwipeInArea implements the sim_swipe_in_area tool call.
|
||||
type ToolSIMSwipeInArea struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
Direction string `json:"direction" desc:"Direction that was swiped (up/down/left/right)"`
|
||||
AreaStartX float64 `json:"areaStartX" desc:"Area starting X coordinate"`
|
||||
AreaStartY float64 `json:"areaStartY" desc:"Area starting Y coordinate"`
|
||||
AreaEndX float64 `json:"areaEndX" desc:"Area ending X coordinate"`
|
||||
AreaEndY float64 `json:"areaEndY" desc:"Area ending Y coordinate"`
|
||||
MinDistance float64 `json:"minDistance" desc:"Minimum distance of the simulated swipe"`
|
||||
MaxDistance float64 `json:"maxDistance" desc:"Maximum distance of the simulated swipe"`
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeInArea) Name() option.ActionName {
|
||||
return option.ACTION_SIMSwipeInArea
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeInArea) Description() string {
|
||||
return "Perform simulated swipe in specified area with direction and random distance"
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeInArea) Options() []mcp.ToolOption {
|
||||
unifiedReq := &option.ActionOptions{}
|
||||
return unifiedReq.GetMCPOptions(option.ACTION_SIMSwipeInArea)
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeInArea) Implement() server.ToolHandlerFunc {
|
||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
arguments := request.GetArguments()
|
||||
driverExt, err := setupXTDriver(ctx, arguments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setup driver failed: %w", err)
|
||||
}
|
||||
|
||||
unifiedReq, err := parseActionOptions(arguments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if unifiedReq.Direction == nil {
|
||||
return nil, fmt.Errorf("direction parameter is required")
|
||||
}
|
||||
direction, ok := unifiedReq.Direction.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("direction must be a string")
|
||||
}
|
||||
|
||||
// Validate direction
|
||||
validDirections := []string{"up", "down", "left", "right"}
|
||||
if !slices.Contains(validDirections, direction) {
|
||||
return nil, fmt.Errorf("invalid swipe direction: %s, expected one of: %v",
|
||||
direction, validDirections)
|
||||
}
|
||||
|
||||
// Get area coordinates
|
||||
areaStartX := unifiedReq.AreaStartX
|
||||
areaStartY := unifiedReq.AreaStartY
|
||||
areaEndX := unifiedReq.AreaEndX
|
||||
areaEndY := unifiedReq.AreaEndY
|
||||
minDistance := unifiedReq.MinDistance
|
||||
maxDistance := unifiedReq.MaxDistance
|
||||
|
||||
// Default values
|
||||
if minDistance == 0 {
|
||||
minDistance = 100
|
||||
}
|
||||
if maxDistance == 0 {
|
||||
maxDistance = 300
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("direction", direction).
|
||||
Float64("areaStartX", areaStartX).
|
||||
Float64("areaStartY", areaStartY).
|
||||
Float64("areaEndX", areaEndX).
|
||||
Float64("areaEndY", areaEndY).
|
||||
Float64("minDistance", minDistance).
|
||||
Float64("maxDistance", maxDistance).
|
||||
Msg("performing simulated swipe in area")
|
||||
|
||||
// Build all options from request arguments
|
||||
opts := unifiedReq.Options()
|
||||
|
||||
// Call the underlying SIMSwipeInArea method (Android UIA2 specific)
|
||||
if uia2Driver, ok := driverExt.IDriver.(*UIA2Driver); ok {
|
||||
err = uia2Driver.SIMSwipeInArea(direction, areaStartX, areaStartY, areaEndX, areaEndY, minDistance, maxDistance, opts...)
|
||||
if err != nil {
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Simulated swipe in area failed: %s", err.Error())), err
|
||||
}
|
||||
} else {
|
||||
return NewMCPErrorResponse("SIMSwipeInArea is only supported on Android UIA2 driver"), fmt.Errorf("unsupported driver type for SIMSwipeInArea")
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Successfully performed simulated swipe %s in area (%.2f,%.2f)-(%.2f,%.2f)",
|
||||
direction, areaStartX, areaStartY, areaEndX, areaEndY)
|
||||
returnData := ToolSIMSwipeInArea{
|
||||
Direction: direction,
|
||||
AreaStartX: areaStartX,
|
||||
AreaStartY: areaStartY,
|
||||
AreaEndX: areaEndX,
|
||||
AreaEndY: areaEndY,
|
||||
MinDistance: minDistance,
|
||||
MaxDistance: maxDistance,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeInArea) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
// Handle params as map[string]interface{}
|
||||
if paramsMap, ok := action.Params.(map[string]interface{}); ok {
|
||||
arguments := map[string]any{}
|
||||
|
||||
// Extract direction
|
||||
if direction, exists := paramsMap["direction"]; exists {
|
||||
arguments["direction"] = direction
|
||||
}
|
||||
|
||||
// Extract area coordinates and distances
|
||||
if areaStartX, exists := paramsMap["area_start_x"]; exists {
|
||||
arguments["area_start_x"] = areaStartX
|
||||
}
|
||||
if areaStartY, exists := paramsMap["area_start_y"]; exists {
|
||||
arguments["area_start_y"] = areaStartY
|
||||
}
|
||||
if areaEndX, exists := paramsMap["area_end_x"]; exists {
|
||||
arguments["area_end_x"] = areaEndX
|
||||
}
|
||||
if areaEndY, exists := paramsMap["area_end_y"]; exists {
|
||||
arguments["area_end_y"] = areaEndY
|
||||
}
|
||||
if minDistance, exists := paramsMap["min_distance"]; exists {
|
||||
arguments["min_distance"] = minDistance
|
||||
}
|
||||
if maxDistance, exists := paramsMap["max_distance"]; exists {
|
||||
arguments["max_distance"] = maxDistance
|
||||
}
|
||||
|
||||
// Add duration and press duration from options
|
||||
if duration := action.ActionOptions.Duration; duration > 0 {
|
||||
arguments["duration"] = duration
|
||||
}
|
||||
if pressDuration := action.ActionOptions.PressDuration; pressDuration > 0 {
|
||||
arguments["pressDuration"] = pressDuration
|
||||
}
|
||||
|
||||
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid SIM swipe in area params: %v", action.Params)
|
||||
}
|
||||
|
||||
// ToolSIMSwipeFromPointToPoint implements the sim_swipe_point_to_point tool call.
|
||||
type ToolSIMSwipeFromPointToPoint struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
StartX float64 `json:"startX" desc:"Starting X coordinate"`
|
||||
StartY float64 `json:"startY" desc:"Starting Y coordinate"`
|
||||
EndX float64 `json:"endX" desc:"Ending X coordinate"`
|
||||
EndY float64 `json:"endY" desc:"Ending Y coordinate"`
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeFromPointToPoint) Name() option.ActionName {
|
||||
return option.ACTION_SIMSwipeFromPointToPoint
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeFromPointToPoint) Description() string {
|
||||
return "Perform simulated swipe from point to point with human-like touch patterns"
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeFromPointToPoint) Options() []mcp.ToolOption {
|
||||
unifiedReq := &option.ActionOptions{}
|
||||
return unifiedReq.GetMCPOptions(option.ACTION_SIMSwipeFromPointToPoint)
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeFromPointToPoint) Implement() server.ToolHandlerFunc {
|
||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
arguments := request.GetArguments()
|
||||
driverExt, err := setupXTDriver(ctx, arguments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setup driver failed: %w", err)
|
||||
}
|
||||
|
||||
unifiedReq, err := parseActionOptions(arguments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get coordinates from arguments
|
||||
startX := unifiedReq.StartX
|
||||
startY := unifiedReq.StartY
|
||||
endX := unifiedReq.ToX // Using existing ToX field
|
||||
endY := unifiedReq.ToY // Using existing ToY field
|
||||
|
||||
log.Info().
|
||||
Float64("startX", startX).
|
||||
Float64("startY", startY).
|
||||
Float64("endX", endX).
|
||||
Float64("endY", endY).
|
||||
Msg("performing simulated point to point swipe")
|
||||
|
||||
// Build all options from request arguments
|
||||
opts := unifiedReq.Options()
|
||||
|
||||
// Call the underlying SIMSwipeFromPointToPoint method (Android UIA2 specific)
|
||||
if uia2Driver, ok := driverExt.IDriver.(*UIA2Driver); ok {
|
||||
err = uia2Driver.SIMSwipeFromPointToPoint(startX, startY, endX, endY, opts...)
|
||||
if err != nil {
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Simulated point to point swipe failed: %s", err.Error())), err
|
||||
}
|
||||
} else {
|
||||
return NewMCPErrorResponse("SIMSwipeFromPointToPoint is only supported on Android UIA2 driver"), fmt.Errorf("unsupported driver type for SIMSwipeFromPointToPoint")
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Successfully performed simulated swipe from (%.2f,%.2f) to (%.2f,%.2f)",
|
||||
startX, startY, endX, endY)
|
||||
returnData := ToolSIMSwipeFromPointToPoint{
|
||||
StartX: startX,
|
||||
StartY: startY,
|
||||
EndX: endX,
|
||||
EndY: endY,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ToolSIMSwipeFromPointToPoint) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
// Handle params as map[string]interface{}
|
||||
if paramsMap, ok := action.Params.(map[string]interface{}); ok {
|
||||
arguments := map[string]any{}
|
||||
|
||||
// Extract coordinates
|
||||
if startX, exists := paramsMap["start_x"]; exists {
|
||||
arguments["start_x"] = startX
|
||||
}
|
||||
if startY, exists := paramsMap["start_y"]; exists {
|
||||
arguments["start_y"] = startY
|
||||
}
|
||||
if endX, exists := paramsMap["end_x"]; exists {
|
||||
arguments["to_x"] = endX // Map to existing ToX field
|
||||
}
|
||||
if endY, exists := paramsMap["end_y"]; exists {
|
||||
arguments["to_y"] = endY // Map to existing ToY field
|
||||
}
|
||||
|
||||
// Add duration and press duration from options
|
||||
if duration := action.ActionOptions.Duration; duration > 0 {
|
||||
arguments["duration"] = duration
|
||||
}
|
||||
if pressDuration := action.ActionOptions.PressDuration; pressDuration > 0 {
|
||||
arguments["pressDuration"] = pressDuration
|
||||
}
|
||||
|
||||
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid SIM swipe point to point params: %v", action.Params)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
@@ -341,3 +342,95 @@ func (t *ToolDoubleTapXY) ConvertActionToCallToolRequest(action option.MobileAct
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid double tap params: %v", action.Params)
|
||||
}
|
||||
|
||||
// ToolSIMClickAtPoint implements the sim_click_at_point tool call.
|
||||
type ToolSIMClickAtPoint struct {
|
||||
// Return data fields - these define the structure of data returned by this tool
|
||||
X float64 `json:"x" desc:"X coordinate where simulated click was performed"`
|
||||
Y float64 `json:"y" desc:"Y coordinate where simulated click was performed"`
|
||||
}
|
||||
|
||||
func (t *ToolSIMClickAtPoint) Name() option.ActionName {
|
||||
return option.ACTION_SIMClickAtPoint
|
||||
}
|
||||
|
||||
func (t *ToolSIMClickAtPoint) Description() string {
|
||||
return "Perform simulated click at specified point with human-like touch patterns"
|
||||
}
|
||||
|
||||
func (t *ToolSIMClickAtPoint) Options() []mcp.ToolOption {
|
||||
unifiedReq := &option.ActionOptions{}
|
||||
return unifiedReq.GetMCPOptions(option.ACTION_SIMClickAtPoint)
|
||||
}
|
||||
|
||||
func (t *ToolSIMClickAtPoint) Implement() server.ToolHandlerFunc {
|
||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
arguments := request.GetArguments()
|
||||
driverExt, err := setupXTDriver(ctx, arguments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setup driver failed: %w", err)
|
||||
}
|
||||
|
||||
unifiedReq, err := parseActionOptions(arguments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if unifiedReq.X == 0 || unifiedReq.Y == 0 {
|
||||
return nil, fmt.Errorf("x and y coordinates are required")
|
||||
}
|
||||
|
||||
x := unifiedReq.X
|
||||
y := unifiedReq.Y
|
||||
|
||||
log.Info().
|
||||
Float64("x", x).
|
||||
Float64("y", y).
|
||||
Msg("performing simulated click at point")
|
||||
|
||||
// Build all options from request arguments
|
||||
opts := unifiedReq.Options()
|
||||
|
||||
// Call the underlying SIMClickAtPoint method (Android UIA2 specific)
|
||||
if uia2Driver, ok := driverExt.IDriver.(*UIA2Driver); ok {
|
||||
err = uia2Driver.SIMClickAtPoint(x, y, opts...)
|
||||
if err != nil {
|
||||
return NewMCPErrorResponse(fmt.Sprintf("Simulated click failed: %s", err.Error())), err
|
||||
}
|
||||
} else {
|
||||
return NewMCPErrorResponse("SIMClickAtPoint is only supported on Android UIA2 driver"), fmt.Errorf("unsupported driver type for SIMClickAtPoint")
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Successfully performed simulated click at (%.2f, %.2f)", x, y)
|
||||
returnData := ToolSIMClickAtPoint{
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
|
||||
return NewMCPSuccessResponse(message, &returnData), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ToolSIMClickAtPoint) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
|
||||
// Handle params as map[string]interface{}
|
||||
if paramsMap, ok := action.Params.(map[string]interface{}); ok {
|
||||
arguments := map[string]any{}
|
||||
|
||||
// Extract coordinates
|
||||
if x, exists := paramsMap["x"]; exists {
|
||||
arguments["x"] = x
|
||||
}
|
||||
if y, exists := paramsMap["y"]; exists {
|
||||
arguments["y"] = y
|
||||
}
|
||||
|
||||
// Add duration from options
|
||||
if duration := action.ActionOptions.Duration; duration > 0 {
|
||||
arguments["duration"] = duration
|
||||
}
|
||||
|
||||
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
|
||||
}
|
||||
return mcp.CallToolRequest{}, fmt.Errorf("invalid SIM click at point params: %v", action.Params)
|
||||
}
|
||||
|
||||
@@ -66,9 +66,14 @@ const (
|
||||
ACTION_TapByCV ActionName = "tap_cv"
|
||||
ACTION_DoubleTap ActionName = "double_tap" // generic double tap action
|
||||
ACTION_DoubleTapXY ActionName = "double_tap_xy"
|
||||
ACTION_Swipe ActionName = "swipe" // swipe by direction or coordinates
|
||||
ACTION_SwipeDirection ActionName = "swipe_direction" // swipe by direction (up, down, left, right)
|
||||
ACTION_SwipeCoordinate ActionName = "swipe_coordinate" // swipe by coordinates (fromX, fromY, toX, toY)
|
||||
ACTION_Swipe ActionName = "swipe" // swipe by direction or coordinates
|
||||
ACTION_SwipeDirection ActionName = "swipe_direction" // swipe by direction (up, down, left, right)
|
||||
ACTION_SwipeCoordinate ActionName = "swipe_coordinate" // swipe by coordinates (fromX, fromY, toX, toY)
|
||||
ACTION_SIMSwipeDirection ActionName = "sim_swipe_direction" // simulated swipe by direction with random distance
|
||||
ACTION_SIMSwipeInArea ActionName = "sim_swipe_in_area" // simulated swipe in area with direction and distance
|
||||
ACTION_SIMSwipeFromPointToPoint ActionName = "sim_swipe_point_to_point" // simulated swipe from point to point
|
||||
ACTION_SIMClickAtPoint ActionName = "sim_click_at_point" // simulated click at point
|
||||
ACTION_SIMInput ActionName = "sim_input" // simulated text input with segments
|
||||
ACTION_Drag ActionName = "drag"
|
||||
ACTION_Input ActionName = "input"
|
||||
ACTION_PressButton ActionName = "press_button"
|
||||
@@ -201,6 +206,14 @@ type ActionOptions struct {
|
||||
PressDuration float64 `json:"press_duration,omitempty" yaml:"press_duration,omitempty" desc:"Press duration in seconds"`
|
||||
Steps int `json:"steps,omitempty" yaml:"steps,omitempty" desc:"Number of steps for action"`
|
||||
Direction interface{} `json:"direction,omitempty" yaml:"direction,omitempty" desc:"Direction for swipe operations or custom coordinates"`
|
||||
StartX float64 `json:"start_x,omitempty" yaml:"start_x,omitempty" desc:"Starting X coordinate for simulated swipe"`
|
||||
StartY float64 `json:"start_y,omitempty" yaml:"start_y,omitempty" desc:"Starting Y coordinate for simulated swipe"`
|
||||
MinDistance float64 `json:"min_distance,omitempty" yaml:"min_distance,omitempty" desc:"Minimum distance for simulated swipe"`
|
||||
MaxDistance float64 `json:"max_distance,omitempty" yaml:"max_distance,omitempty" desc:"Maximum distance for simulated swipe"`
|
||||
AreaStartX float64 `json:"area_start_x,omitempty" yaml:"area_start_x,omitempty" desc:"Area starting X coordinate for simulated swipe"`
|
||||
AreaStartY float64 `json:"area_start_y,omitempty" yaml:"area_start_y,omitempty" desc:"Area starting Y coordinate for simulated swipe"`
|
||||
AreaEndX float64 `json:"area_end_x,omitempty" yaml:"area_end_x,omitempty" desc:"Area ending X coordinate for simulated swipe"`
|
||||
AreaEndY float64 `json:"area_end_y,omitempty" yaml:"area_end_y,omitempty" desc:"Area ending Y coordinate for simulated swipe"`
|
||||
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty" desc:"Timeout in seconds for action execution"`
|
||||
TimeLimit int `json:"time_limit,omitempty" yaml:"time_limit,omitempty" desc:"Time limit in seconds for action execution, stops gracefully when reached"`
|
||||
Frequency int `json:"frequency,omitempty" yaml:"frequency,omitempty" desc:"Action frequency"`
|
||||
|
||||
Reference in New Issue
Block a user