fix: merge

This commit is contained in:
huangbin.beal
2025-08-11 21:41:41 +08:00
10 changed files with 819 additions and 145 deletions

View File

@@ -706,17 +706,16 @@ func (ad *ADBDriver) StopCaptureLog() (result interface{}, err error) {
log.Error().Err(err).Msg("failed to close adb log writer")
}
pointRes := ConvertPoints(ad.Device.Logcat.logs)
// 没有解析到打点日志,走兜底逻辑
if len(pointRes) == 0 {
log.Info().Msg("action log is null, use action file >>>")
actionLogDirPath := config.GetConfig().ActionLogDirPath()
logFilePathPrefix := fmt.Sprintf("%v/data", actionLogDirPath)
files := []string{}
ad.Device.RunShellCommand("pull", config.DeviceActionLogFilePath, actionLogDirPath)
actionLogRegStr := `.*data_\d+\.txt`
ad.Device.PullFolder(config.DeviceActionLogFilePath, actionLogDirPath)
err = filepath.Walk(actionLogDirPath, func(path string, info fs.FileInfo, err error) error {
// 只是需要日志文件
if ok := strings.Contains(path, logFilePathPrefix); ok {
if ok, _ := regexp.MatchString(actionLogRegStr, path); ok {
files = append(files, path)
}
return nil

View File

@@ -18,6 +18,7 @@ var (
// Ensure drivers implement SIMSupport interface
_ SIMSupport = (*UIA2Driver)(nil)
_ SIMSupport = (*WDADriver)(nil)
)
// current implemeted driver: ADBDriver, UIA2Driver, WDADriver, HDCDriver

View File

@@ -284,8 +284,9 @@ func getSimulationDuration(params []float64) (milliseconds int64) {
return 0
}
// sleepStrict sleeps strict duration with given params
// startTime is used to correct sleep duration caused by process time
// sleepStrict sleeps for strict duration with optional start time correction
// If startTime is zero, acts as normal context-aware sleep
// If startTime is provided, corrects sleep duration by subtracting elapsed time
// ctx allows for cancellation during sleep
func sleepStrict(ctx context.Context, startTime time.Time, strictMilliseconds int64) {
var elapsed int64

View File

@@ -24,6 +24,7 @@ import (
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/internal/config"
"github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/internal/simulation"
"github.com/httprunner/httprunner/v5/uixt/option"
"github.com/httprunner/httprunner/v5/uixt/types"
)
@@ -678,6 +679,13 @@ func (wd *WDADriver) TouchByEvents(events []types.TouchEvent, opts ...option.Act
x, y = toX, toY
}
if x, err = wd.toScale(x); err != nil {
return err
}
if y, err = wd.toScale(y); err != nil {
return err
}
var actionMap map[string]interface{}
switch event.Action {
@@ -743,6 +751,201 @@ func (wd *WDADriver) TouchByEvents(events []types.TouchEvent, opts ...option.Act
return err
}
// SIMSwipeWithDirection 向指定方向滑动任意距离
// direction: 滑动方向 ("up", "down", "left", "right")
// fromX, fromY: 起始坐标
// simMinDistance, simMaxDistance: 距离范围,如果相等则为固定距离,否则为随机距离
func (wd *WDADriver) SIMSwipeWithDirection(direction string, fromX, fromY, simMinDistance, simMaxDistance float64, opts ...option.ActionOption) error {
absStartX, absStartY, err := convertToAbsolutePoint(wd, fromX, fromY)
if err != nil {
return err
}
// 获取设备型号和配置参数
deviceModel := "iphone"
deviceParams := simulation.GetRandomDeviceParams(deviceModel)
log.Info().Str("direction", direction).
Float64("startX", absStartX).Float64("startY", absStartY).
Float64("minDistance", simMinDistance).Float64("maxDistance", simMaxDistance).
Str("deviceModel", deviceModel).
Int("deviceID", deviceParams.DeviceID).
Float64("pressure", deviceParams.Pressure).
Float64("size", deviceParams.Size).
Msg("WDADriver.SIMSwipeWithDirection")
// 导入滑动仿真库
simulator := simulation.NewSlideSimulatorAPI(nil)
// 转换方向字符串为Direction类型
var slideDirection simulation.Direction
switch direction {
case "up":
slideDirection = simulation.Up
case "down":
slideDirection = simulation.Down
case "left":
slideDirection = simulation.Left
case "right":
slideDirection = simulation.Right
default:
return fmt.Errorf("invalid direction: %s, must be one of: up, down, left, right", direction)
}
// 使用滑动仿真算法生成触摸事件序列
events, err := simulator.GenerateSlideWithRandomDistance(
absStartX, absStartY, slideDirection, simMinDistance, simMaxDistance,
deviceParams.DeviceID, deviceParams.Pressure, deviceParams.Size)
if err != nil {
return fmt.Errorf("generate slide events failed: %v", err)
}
// 执行触摸事件序列
return wd.TouchByEvents(events, opts...)
}
// SIMSwipeInArea 在指定区域内向指定方向滑动任意距离
// direction: 滑动方向 ("up", "down", "left", "right")
// simAreaStartX, simAreaStartY, simAreaEndX, simAreaEndY: 区域范围(相对坐标)
// simMinDistance, simMaxDistance: 距离范围,如果相等则为固定距离,否则为随机距离
func (wd *WDADriver) SIMSwipeInArea(direction string, simAreaStartX, simAreaStartY, simAreaEndX, simAreaEndY, simMinDistance, simMaxDistance float64, opts ...option.ActionOption) error {
// 转换区域坐标为绝对坐标
absAreaStartX, absAreaStartY, err := convertToAbsolutePoint(wd, simAreaStartX, simAreaStartY)
if err != nil {
return err
}
absAreaEndX, absAreaEndY, err := convertToAbsolutePoint(wd, simAreaEndX, simAreaEndY)
if err != nil {
return err
}
// 确保区域坐标正确(start应该小于等于end)
if absAreaStartX > absAreaEndX {
absAreaStartX, absAreaEndX = absAreaEndX, absAreaStartX
}
if absAreaStartY > absAreaEndY {
absAreaStartY, absAreaEndY = absAreaEndY, absAreaStartY
}
// 获取设备型号和配置参数
deviceModel := "iphone"
deviceParams := simulation.GetRandomDeviceParams(deviceModel)
log.Info().Str("direction", direction).
Float64("areaStartX", absAreaStartX).Float64("areaStartY", absAreaStartY).
Float64("areaEndX", absAreaEndX).Float64("areaEndY", absAreaEndY).
Float64("minDistance", simMinDistance).Float64("maxDistance", simMaxDistance).
Str("deviceModel", deviceModel).
Int("deviceID", deviceParams.DeviceID).
Float64("pressure", deviceParams.Pressure).
Float64("size", deviceParams.Size).
Msg("WDADriver.SIMSwipeInArea")
// 导入滑动仿真库
simulator := simulation.NewSlideSimulatorAPI(nil)
// 转换方向字符串为Direction类型
var slideDirection simulation.Direction
switch direction {
case "up":
slideDirection = simulation.Up
case "down":
slideDirection = simulation.Down
case "left":
slideDirection = simulation.Left
case "right":
slideDirection = simulation.Right
default:
return fmt.Errorf("invalid direction: %s, must be one of: up, down, left, right", direction)
}
// 使用滑动仿真算法生成区域内滑动的触摸事件序列
events, err := simulator.GenerateSlideInArea(
absAreaStartX, absAreaStartY, absAreaEndX, absAreaEndY,
slideDirection, simMinDistance, simMaxDistance,
deviceParams.DeviceID, deviceParams.Pressure, deviceParams.Size)
if err != nil {
return fmt.Errorf("generate slide in area events failed: %v", err)
}
// 执行触摸事件序列
return wd.TouchByEvents(events, opts...)
}
// SIMSwipeFromPointToPoint 指定起始点和结束点进行滑动
// fromX, fromY: 起始坐标(相对坐标)
// toX, toY: 结束坐标(相对坐标)
func (wd *WDADriver) SIMSwipeFromPointToPoint(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error {
// 转换起始点和结束点为绝对坐标
absStartX, absStartY, err := convertToAbsolutePoint(wd, fromX, fromY)
if err != nil {
return err
}
absEndX, absEndY, err := convertToAbsolutePoint(wd, toX, toY)
if err != nil {
return err
}
// 获取设备型号和配置参数
deviceModel := "iphone"
deviceParams := simulation.GetRandomDeviceParams(deviceModel)
log.Info().Float64("startX", absStartX).Float64("startY", absStartY).
Float64("endX", absEndX).Float64("endY", absEndY).
Str("deviceModel", deviceModel).
Int("deviceID", deviceParams.DeviceID).
Float64("pressure", deviceParams.Pressure).
Float64("size", deviceParams.Size).
Msg("WDADriver.SIMSwipeFromPointToPoint")
// 导入滑动仿真库
simulator := simulation.NewSlideSimulatorAPI(nil)
// 使用滑动仿真算法生成点对点滑动的触摸事件序列
events, err := simulator.GeneratePointToPointSlideEvents(
absStartX, absStartY, absEndX, absEndY,
deviceParams.DeviceID, deviceParams.Pressure, deviceParams.Size)
if err != nil {
return fmt.Errorf("generate point to point slide events failed: %v", err)
}
// 执行触摸事件序列
return wd.TouchByEvents(events, opts...)
}
// SIMClickAtPoint 点击相对坐标
// x, y: 点击坐标(相对坐标)
func (wd *WDADriver) SIMClickAtPoint(x, y float64, opts ...option.ActionOption) error {
// 转换为绝对坐标
absX, absY, err := convertToAbsolutePoint(wd, x, y)
if err != nil {
return err
}
// 获取设备型号和配置参数
deviceModel := "iphone"
deviceParams := simulation.GetRandomDeviceParams(deviceModel)
log.Info().Float64("x", absX).Float64("y", absY).
Str("deviceModel", deviceModel).
Int("deviceID", deviceParams.DeviceID).
Float64("pressure", deviceParams.Pressure).
Float64("size", deviceParams.Size).
Msg("WDADriver.SIMClickAtPoint")
// 导入点击仿真库
clickSimulator := simulation.NewClickSimulatorAPI(nil)
// 使用点击仿真算法生成触摸事件序列
events, err := clickSimulator.GenerateClickEvents(
absX, absY, deviceParams.DeviceID, deviceParams.Pressure, deviceParams.Size)
if err != nil {
return fmt.Errorf("generate click events failed: %v", err)
}
// 执行触摸事件序列
return wd.TouchByEvents(events, opts...)
}
func (wd *WDADriver) SetPasteboard(contentType types.PasteboardType, content string) (err error) {
// [[FBRoute POST:@"/wda/setPasteboard"] respondWithTarget:self action:@selector(handleSetPasteboard:)]
data := map[string]interface{}{
@@ -784,6 +987,69 @@ func (wd *WDADriver) Input(text string, opts ...option.ActionOption) (err error)
return
}
// SIMInput 仿真输入函数,模拟人类分批输入行为
// 将文本智能分割英文单词和数字保持完整中文按1-2个字符分割
func (wd *WDADriver) SIMInput(text string, opts ...option.ActionOption) error {
log.Info().Str("text", text).Msg("WDADriver.SIMInput")
if text == "" {
return nil
}
// 创建输入仿真器(使用默认配置)
inputSimulator := simulation.NewInputSimulatorAPI(nil)
// 生成输入片段(使用智能分割算法,所有参数使用默认值)
inputReq := simulation.InputRequest{
Text: text,
// MinSegmentLen, MaxSegmentLen, MinDelayMs, MaxDelayMs 使用默认值
}
response := inputSimulator.GenerateInputSegments(inputReq)
if !response.Success {
return fmt.Errorf("failed to generate input segments: %s", response.Message)
}
log.Info().Int("segments", response.Metrics.TotalSegments).
Int("totalDelayMs", response.Metrics.TotalDelayMs).
Int("estimatedTimeMs", response.Metrics.EstimatedTimeMs).
Msg("Input segments generated")
// 逐个输入每个片段
var segmentErrCnt int
for _, segment := range response.Segments {
// 使用Input进行输入内部已包含Session.POST请求
segmentErr := wd.Input(segment.Text, opts...)
if segmentErr != nil {
segmentErrCnt++
log.Info().Err(segmentErr).Int("segmentErrCnt", segmentErrCnt).
Msg("segments err")
}
log.Debug().Str("segment", segment.Text).Int("index", segment.Index).
Int("charLen", segment.CharLen).Msg("Successfully input segment")
// 如果有延迟时间,则等待
if segment.DelayMs > 0 {
time.Sleep(time.Duration(segment.DelayMs) * time.Millisecond)
log.Debug().Int("delayMs", segment.DelayMs).
Msg("Delay between input segments")
}
}
if segmentErrCnt > 0 {
data := map[string]interface{}{"value": strings.Split(text, "")}
option.MergeOptions(data, opts...)
_, err := wd.Session.POST(data, "/wings/interaction/keys")
return err
}
log.Info().Int("totalSegments", response.Metrics.TotalSegments).
Int("actualDelayMs", response.Metrics.TotalDelayMs).
Msg("SIMInput completed successfully")
return nil
}
func (wd *WDADriver) Backspace(count int, opts ...option.ActionOption) (err error) {
log.Info().Int("count", count).Msg("WDADriver.Backspace")
if count == 0 {

View File

@@ -15,7 +15,29 @@ import (
"github.com/httprunner/httprunner/v5/uixt/option"
)
// ToolSleep implements the sleep tool call.
// extractStartTimeMs extracts start_time_ms from MCP request arguments
// Returns time.Time (zero if not provided) and any conversion error
func extractStartTimeMs(request mcp.CallToolRequest) (time.Time, error) {
startTimeMs, ok := request.GetArguments()["start_time_ms"]
if !ok || startTimeMs == nil {
return time.Time{}, nil // Return zero time for normal sleep
}
var ms int64
switch v := startTimeMs.(type) {
case float64:
ms = int64(v)
case int64:
ms = v
case int:
ms = int64(v)
default:
return time.Time{}, fmt.Errorf("invalid start_time_ms type: %T", v)
}
return time.UnixMilli(ms), nil
}
type ToolSleep struct {
// Return data fields - these define the structure of data returned by this tool
Seconds float64 `json:"seconds" desc:"Duration in seconds that was slept"`
@@ -33,6 +55,7 @@ func (t *ToolSleep) Description() string {
func (t *ToolSleep) Options() []mcp.ToolOption {
return []mcp.ToolOption{
mcp.WithNumber("seconds", mcp.Description("Number of seconds to sleep")),
mcp.WithNumber("start_time_ms", mcp.Description("Start time as Unix milliseconds for strict sleep calculation")),
}
}
@@ -70,16 +93,15 @@ func (t *ToolSleep) Implement() server.ToolHandlerFunc {
return nil, fmt.Errorf("unsupported sleep duration type: %T", v)
}
// Use context-aware sleep instead of blocking time.Sleep
select {
case <-time.After(duration):
// Normal completion
case <-ctx.Done():
// Interrupted by context cancellation (interrupt signal, timeout, time limit)
log.Info().Msg("sleep interrupted by context cancellation")
// Don't return error - let the upper layer handle timeout/time limit logic
// Extract start_time_ms and use sleepStrict for unified sleep logic
startTime, err := extractStartTimeMs(request)
if err != nil {
return nil, err
}
milliseconds := int64(actualSeconds * 1000)
sleepStrict(ctx, startTime, milliseconds)
message := fmt.Sprintf("Successfully slept for %v seconds", actualSeconds)
returnData := ToolSleep{
Seconds: actualSeconds,
@@ -91,9 +113,24 @@ func (t *ToolSleep) Implement() server.ToolHandlerFunc {
}
func (t *ToolSleep) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
arguments := map[string]any{
"seconds": action.Params,
arguments := map[string]any{}
var seconds float64
if param, ok := action.Params.(json.Number); ok {
seconds, _ = param.Float64()
arguments["seconds"] = seconds
} else if param, ok := action.Params.(int64); ok {
seconds = float64(param)
arguments["seconds"] = seconds
} else if sleepConfig, ok := action.Params.(SleepConfig); ok {
// When startTime is provided, pass both seconds and startTime
seconds = sleepConfig.Seconds
arguments["seconds"] = seconds
arguments["start_time_ms"] = sleepConfig.StartTime.UnixMilli()
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep params: %v", action.Params)
}
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
}
@@ -115,6 +152,7 @@ func (t *ToolSleepMS) Description() string {
func (t *ToolSleepMS) Options() []mcp.ToolOption {
return []mcp.ToolOption{
mcp.WithNumber("milliseconds", mcp.Description("Number of milliseconds to sleep")),
mcp.WithNumber("start_time_ms", mcp.Description("Start time as Unix milliseconds for strict sleep calculation")),
}
}
@@ -152,16 +190,14 @@ func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
return nil, fmt.Errorf("unsupported sleep duration type: %T", v)
}
// Use context-aware sleep instead of blocking time.Sleep
select {
case <-time.After(duration):
// Normal completion
case <-ctx.Done():
// Interrupted by context cancellation (interrupt signal, timeout, time limit)
log.Info().Msg("sleep interrupted by context cancellation")
// Don't return error - let the upper layer handle timeout/time limit logic
// Extract start_time_ms and use sleepStrict for unified sleep logic
startTime, err := extractStartTimeMs(request)
if err != nil {
return nil, err
}
sleepStrict(ctx, startTime, actualMilliseconds)
message := fmt.Sprintf("Successfully slept for %d milliseconds", actualMilliseconds)
returnData := ToolSleepMS{
Milliseconds: actualMilliseconds,
@@ -173,17 +209,24 @@ func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
}
func (t *ToolSleepMS) ConvertActionToCallToolRequest(action option.MobileAction) (mcp.CallToolRequest, error) {
arguments := map[string]any{}
var milliseconds int64
if param, ok := action.Params.(json.Number); ok {
milliseconds, _ = param.Int64()
arguments["milliseconds"] = milliseconds
} else if param, ok := action.Params.(int64); ok {
milliseconds = param
arguments["milliseconds"] = milliseconds
} else if sleepConfig, ok := action.Params.(SleepConfig); ok {
// When startTime is provided, pass both milliseconds and startTime
milliseconds = sleepConfig.Milliseconds
arguments["milliseconds"] = milliseconds
arguments["start_time_ms"] = sleepConfig.StartTime.UnixMilli()
} else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep ms params: %v", action.Params)
}
arguments := map[string]any{
"milliseconds": milliseconds,
}
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
}

View File

@@ -0,0 +1,240 @@
package uixt
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/httprunner/httprunner/v5/uixt/option"
)
func TestToolSleep_ConvertActionToCallToolRequest(t *testing.T) {
tool := &ToolSleep{}
tests := []struct {
name string
action option.MobileAction
expectedArgs map[string]any
shouldError bool
}{
{
name: "json.Number parameter",
action: option.MobileAction{
Method: option.ACTION_Sleep,
Params: json.Number("3.5"),
},
expectedArgs: map[string]any{"seconds": float64(3.5)},
shouldError: false,
},
{
name: "int64 parameter",
action: option.MobileAction{
Method: option.ACTION_Sleep,
Params: int64(5),
},
expectedArgs: map[string]any{"seconds": float64(5)},
shouldError: false,
},
{
name: "SleepConfig with startTime",
action: option.MobileAction{
Method: option.ACTION_Sleep,
Params: SleepConfig{
StartTime: time.UnixMilli(1691234567890),
Seconds: 2.5,
},
},
expectedArgs: map[string]any{
"seconds": 2.5,
"start_time_ms": int64(1691234567890),
},
shouldError: false,
},
{
name: "invalid parameter type",
action: option.MobileAction{
Method: option.ACTION_Sleep,
Params: "invalid",
},
expectedArgs: nil,
shouldError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
request, err := tool.ConvertActionToCallToolRequest(tt.action)
if tt.shouldError {
assert.Error(t, err)
} else {
require.NoError(t, err)
args := request.GetArguments()
for key, expectedValue := range tt.expectedArgs {
assert.Equal(t, expectedValue, args[key], "Argument %s mismatch", key)
}
}
})
}
}
func TestToolSleepMS_ConvertActionToCallToolRequest(t *testing.T) {
tool := &ToolSleepMS{}
tests := []struct {
name string
action option.MobileAction
expectedArgs map[string]any
shouldError bool
}{
{
name: "json.Number parameter",
action: option.MobileAction{
Method: option.ACTION_SleepMS,
Params: json.Number("1500"),
},
expectedArgs: map[string]any{"milliseconds": int64(1500)},
shouldError: false,
},
{
name: "int64 parameter",
action: option.MobileAction{
Method: option.ACTION_SleepMS,
Params: int64(2000),
},
expectedArgs: map[string]any{"milliseconds": int64(2000)},
shouldError: false,
},
{
name: "SleepConfig with startTime",
action: option.MobileAction{
Method: option.ACTION_SleepMS,
Params: SleepConfig{
StartTime: time.UnixMilli(1691234567890),
Milliseconds: 3000,
},
},
expectedArgs: map[string]any{
"milliseconds": int64(3000),
"start_time_ms": int64(1691234567890),
},
shouldError: false,
},
{
name: "invalid parameter type",
action: option.MobileAction{
Method: option.ACTION_SleepMS,
Params: "invalid",
},
expectedArgs: nil,
shouldError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
request, err := tool.ConvertActionToCallToolRequest(tt.action)
if tt.shouldError {
assert.Error(t, err)
} else {
require.NoError(t, err)
args := request.GetArguments()
for key, expectedValue := range tt.expectedArgs {
assert.Equal(t, expectedValue, args[key], "Argument %s mismatch", key)
}
}
})
}
}
func TestSleepStrictTiming(t *testing.T) {
// Test that strict sleep properly adjusts for elapsed time
startTime := time.Now()
// Simulate some processing time
time.Sleep(50 * time.Millisecond)
ctx := context.Background()
// Test sleepStrict with the start time
testStart := time.Now()
sleepStrict(ctx, startTime, 200) // 200ms total duration
actualElapsed := time.Since(testStart)
// Should sleep approximately 150ms (200ms - 50ms already elapsed)
// Allow some tolerance for timing variations
expectedSleep := 150 * time.Millisecond
assert.Greater(t, actualElapsed, expectedSleep/2, "Sleep too short")
assert.Less(t, actualElapsed, expectedSleep*2, "Sleep too long")
}
func TestSleepCancellation(t *testing.T) {
// Test that sleep respects context cancellation
ctx, cancel := context.WithCancel(context.Background())
// Cancel after 50ms
go func() {
time.Sleep(50 * time.Millisecond)
cancel()
}()
start := time.Now()
sleepStrict(ctx, time.Time{}, 500) // Try to sleep 500ms
elapsed := time.Since(start)
// Should be cancelled after ~50ms, not sleep full 500ms
assert.Less(t, elapsed, 200*time.Millisecond, "Sleep was not properly cancelled")
}
func TestSleepStrictWithZeroTime(t *testing.T) {
// Test sleepStrict behaves like normal sleep when startTime is zero
ctx := context.Background()
start := time.Now()
sleepStrict(ctx, time.Time{}, 100) // 100ms, no start time
elapsed := time.Since(start)
// Should sleep full duration
expectedSleep := 100 * time.Millisecond
assert.Greater(t, elapsed, expectedSleep/2, "Sleep too short")
assert.Less(t, elapsed, expectedSleep*2, "Sleep too long")
}
func TestSleepStrictWithPastStartTime(t *testing.T) {
// Test sleepStrict skips sleep when elapsed time exceeds duration
startTime := time.Now().Add(-300 * time.Millisecond) // 300ms ago
ctx := context.Background()
start := time.Now()
sleepStrict(ctx, startTime, 200) // Want 200ms total, but 300ms already elapsed
elapsed := time.Since(start)
// Should skip sleep entirely
assert.Less(t, elapsed, 50*time.Millisecond, "Should have skipped sleep")
}
func TestJsonNumberHandling(t *testing.T) {
// Test that json.Number is correctly handled in different scenarios
// Test float json.Number
floatNumber := json.Number("3.14")
floatVal, err := floatNumber.Float64()
assert.NoError(t, err)
assert.Equal(t, 3.14, floatVal)
// Test int json.Number
intNumber := json.Number("1500")
intVal, err := intNumber.Int64()
assert.NoError(t, err)
assert.Equal(t, int64(1500), intVal)
// Test invalid json.Number
invalidNumber := json.Number("invalid")
_, err = invalidNumber.Float64()
assert.Error(t, err)
}

View File

@@ -66,22 +66,21 @@ func ParseTouchEvents(data string) ([]types.TouchEvent, error) {
if event.Action, err = strconv.Atoi(parts[12]); err != nil {
return nil, fmt.Errorf("invalid action: %v", err)
}
events = append(events, event)
}
return events, nil
}
func TestAndroidTouchByEvents(t *testing.T) {
device, err := NewAndroidDevice(
option.WithSerialNumber(""),
func TestIOSTouchByEvents(t *testing.T) {
device, err := NewIOSDevice(
option.WithUDID(""),
)
if err != nil {
t.Fatal(err)
}
driver, err := NewUIA2Driver(device)
driver, err := NewWDADriver(device)
if err != nil {
t.Fatal(err)
}
@@ -138,60 +137,6 @@ func TestAndroidTouchByEvents(t *testing.T) {
t.Logf("Successfully executed touch events: %d events processed", len(events))
}
func TestIOSTouchByEvents(t *testing.T) {
driver := setupWDADriverExt(t)
// Example touch event data as provided
touchEventData := `1752649131556,401.20703,1191.3164,2,1.0,0.03529412,457.20703,1359.3164,111586196,111586196,1,0,0
1752649131595,402.913,1185.0792,2,1.0,0.039215688,458.913,1353.0792,111586196,111586236,1,0,2
1752649131612,410.60825,1164.3806,2,1.0,0.03529412,466.60825,1332.3806,111586196,111586250,1,0,2
1752649131629,437.7335,1093.1417,2,1.0,0.039215688,493.7335,1261.1417,111586196,111586270,1,0,2
1752649131646,463.5786,1018.01746,2,1.0,0.039215688,519.5786,1186.0175,111586196,111586287,1,0,2
1752649131662,487.56482,948.9773,2,1.0,0.03529412,543.5648,1116.9773,111586196,111586304,1,0,2
1752649131679,511.81476,881.6183,2,1.0,0.039215688,567.81476,1049.6183,111586196,111586320,1,0,2
1752649131696,543.4369,811.4982,2,1.0,0.03529412,599.4369,979.4982,111586196,111586337,1,0,2
1752649131713,577.1632,747.4512,2,1.0,0.039215688,633.1632,915.4512,111586196,111586354,1,0,2
1752649131729,610.1538,691.72034,2,1.0,0.03529412,666.1538,859.72034,111586196,111586370,1,0,2
1752649131746,639.1683,642.6914,2,1.0,0.03529412,695.1683,810.6914,111586196,111586387,1,0,2
1752649131763,658.9832,605.90857,2,1.0,0.03529412,714.9832,773.90857,111586196,111586404,1,0,2
1752649131779,672.21954,581.1634,2,1.0,0.03529412,728.21954,749.1634,111586196,111586420,1,0,2
1752649131796,680.7687,566.1778,2,1.0,0.03529412,736.7687,734.1778,111586196,111586434,1,0,2
1752649131814,688.0894,554.2295,2,1.0,0.03529412,744.0894,722.2295,111586196,111586450,1,0,2
1752649131830,694.542,544.7783,2,1.0,0.03529412,750.542,712.7783,111586196,111586466,1,0,2
1752649131847,700.60645,537.2637,2,1.0,0.039215688,756.60645,705.2637,111586196,111586483,1,0,2
1752649131863,705.08887,531.1406,2,1.0,0.039215688,761.08887,699.1406,111586196,111586500,1,0,2
1752649131880,708.1211,527.8008,2,1.0,0.039215688,764.1211,695.8008,111586196,111586517,1,0,2
1752649131897,709.43945,524.46094,2,1.0,0.039215688,765.43945,692.46094,111586196,111586533,1,0,2
1752649131902,709.1758,523.34766,2,1.0,0.03529412,765.1758,691.34766,111586196,111586537,1,33554432,2
1752649131907,709.1758,523.34766,2,1.0,0.03529412,765.1758,691.34766,111586196,111586546,1,0,1`
// Parse touch events
events, err := ParseTouchEvents(touchEventData)
if err != nil {
t.Fatalf("ParseTouchEvents failed: %v", err)
}
// Check first event
firstEvent := events[0]
if firstEvent.Action != 0 { // ACTION_DOWN
t.Errorf("Expected first event action to be 0 (ACTION_DOWN), got %d", firstEvent.Action)
}
// Check last event
lastEvent := events[len(events)-1]
if lastEvent.Action != 1 { // ACTION_UP
t.Errorf("Expected last event action to be 1 (ACTION_UP), got %d", lastEvent.Action)
}
// Use TouchByEvents with parsed events
err = driver.IDriver.(*WDADriver).TouchByEvents(events)
if err != nil {
t.Fatalf("TouchByEvents failed: %v", err)
}
t.Logf("Successfully executed touch events: %d events processed", len(events))
}
func TestTouchEventParsing(t *testing.T) {
// Test single touch event parsing
singleEventData := "1752646457403,456.78418,1574.0195,7,1.0,0.016666668,504.78418,1721.0195,924451292,924451292,1,0,0"
@@ -281,14 +226,14 @@ func TestTouchEventSequenceValidation(t *testing.T) {
}
func TestSwipeWithDirection(t *testing.T) {
device, err := NewAndroidDevice(
option.WithSerialNumber(""),
device, err := NewIOSDevice(
option.WithUDID(""),
)
if err != nil {
t.Fatal(err)
}
driver, err := NewUIA2Driver(device)
driver, err := NewWDADriver(device)
if err != nil {
t.Fatal(err)
}
@@ -308,7 +253,7 @@ func TestSwipeWithDirection(t *testing.T) {
direction: "up",
startX: 0.5,
startY: 0.5,
minDistance: 100.0,
minDistance: 500.0,
maxDistance: 500.0,
},
}
@@ -332,50 +277,15 @@ func TestSwipeWithDirection(t *testing.T) {
}
}
func TestSwipeWithDirectionInvalidInputs(t *testing.T) {
device, err := NewAndroidDevice(
option.WithSerialNumber(""),
)
if err != nil {
t.Fatal(err)
}
driver, err := NewUIA2Driver(device)
if err != nil {
t.Fatal(err)
}
defer driver.TearDown()
// Test invalid direction
err = driver.SIMSwipeWithDirection("invalid", 500.0, 500.0, 100.0, 200.0)
if err == nil {
t.Error("Expected error for invalid direction, but got none")
}
// Test invalid distance range (max < min)
err = driver.SIMSwipeWithDirection("up", 500.0, 500.0, 200.0, 100.0)
if err == nil {
t.Error("Expected error for invalid distance range, but got none")
}
// Test zero distance
err = driver.SIMSwipeWithDirection("up", 500.0, 500.0, 0.0, 0.0)
if err == nil {
t.Error("Expected error for zero distance, but got none")
}
t.Log("Invalid input validation tests passed")
}
func TestSwipeInArea(t *testing.T) {
device, err := NewAndroidDevice(
option.WithSerialNumber(""),
device, err := NewIOSDevice(
option.WithUDID(""),
)
if err != nil {
t.Fatal(err)
}
driver, err := NewUIA2Driver(device)
driver, err := NewWDADriver(device)
if err != nil {
t.Fatal(err)
}
@@ -428,14 +338,14 @@ func TestSwipeInArea(t *testing.T) {
}
func TestSwipeFromPointToPoint(t *testing.T) {
device, err := NewAndroidDevice(
option.WithSerialNumber(""),
device, err := NewIOSDevice(
option.WithUDID(""),
)
if err != nil {
t.Fatal(err)
}
driver, err := NewUIA2Driver(device)
driver, err := NewWDADriver(device)
if err != nil {
t.Fatal(err)
}
@@ -477,14 +387,14 @@ func TestSwipeFromPointToPoint(t *testing.T) {
}
func TestSwipeFromPointToPointInvalidInputs(t *testing.T) {
device, err := NewAndroidDevice(
option.WithSerialNumber(""),
device, err := NewIOSDevice(
option.WithUDID(""),
)
if err != nil {
t.Fatal(err)
}
driver, err := NewUIA2Driver(device)
driver, err := NewWDADriver(device)
if err != nil {
t.Fatal(err)
}
@@ -506,14 +416,14 @@ func TestSwipeFromPointToPointInvalidInputs(t *testing.T) {
}
func TestClickAtPoint(t *testing.T) {
device, err := NewAndroidDevice(
option.WithSerialNumber(""),
device, err := NewIOSDevice(
option.WithUDID(""),
)
if err != nil {
t.Fatal(err)
}
driver, err := NewUIA2Driver(device)
driver, err := NewWDADriver(device)
if err != nil {
t.Fatal(err)
}
@@ -546,14 +456,14 @@ func TestClickAtPoint(t *testing.T) {
}
func TestClickAtPointInvalidInputs(t *testing.T) {
device, err := NewAndroidDevice(
option.WithSerialNumber(""),
device, err := NewIOSDevice(
option.WithUDID(""),
)
if err != nil {
t.Fatal(err)
}
driver, err := NewUIA2Driver(device)
driver, err := NewWDADriver(device)
if err != nil {
t.Fatal(err)
}
@@ -580,14 +490,14 @@ func TestClickAtPointInvalidInputs(t *testing.T) {
}
func TestSIMInput(t *testing.T) {
device, err := NewAndroidDevice(
option.WithSerialNumber(""),
device, err := NewIOSDevice(
option.WithUDID(""),
)
if err != nil {
t.Fatal(err)
}
driver, err := NewUIA2Driver(device)
driver, err := NewWDADriver(device)
if err != nil {
t.Fatal(err)
}