mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-03 06:49:38 +08:00
Merge branch 'simulation_input_dev' into 'master'
add simulation See merge request iesqa/httprunner!138
This commit is contained in:
@@ -256,35 +256,35 @@ func TestSwipeWithDirection(t *testing.T) {
|
|||||||
minDistance: 100.0,
|
minDistance: 100.0,
|
||||||
maxDistance: 500.0,
|
maxDistance: 500.0,
|
||||||
},
|
},
|
||||||
//{
|
{
|
||||||
// name: "随机距离下滑",
|
name: "随机距离下滑",
|
||||||
// direction: "down",
|
direction: "down",
|
||||||
// startX: 0.5,
|
startX: 0.5,
|
||||||
// startY: 0.5,
|
startY: 0.5,
|
||||||
// minDistance: 150.0,
|
minDistance: 150.0,
|
||||||
// maxDistance: 350.0, // 范围内随机
|
maxDistance: 350.0, // 范围内随机
|
||||||
//},
|
},
|
||||||
//{
|
{
|
||||||
// name: "固定距离左滑",
|
name: "固定距离左滑",
|
||||||
// direction: "left",
|
direction: "left",
|
||||||
// startX: 0.5,
|
startX: 0.5,
|
||||||
// startY: 0.5,
|
startY: 0.5,
|
||||||
// minDistance: 300.0,
|
minDistance: 300.0,
|
||||||
// maxDistance: 300.0,
|
maxDistance: 300.0,
|
||||||
//},
|
},
|
||||||
//{
|
{
|
||||||
// name: "随机距离右滑",
|
name: "随机距离右滑",
|
||||||
// direction: "right",
|
direction: "right",
|
||||||
// startX: 0.6,
|
startX: 0.6,
|
||||||
// startY: 0.5,
|
startY: 0.5,
|
||||||
// minDistance: 100.0,
|
minDistance: 100.0,
|
||||||
// maxDistance: 250.0,
|
maxDistance: 250.0,
|
||||||
//},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err := driver.SwipeWithDirection(
|
err := driver.SIMSwipeWithDirection(
|
||||||
tc.direction,
|
tc.direction,
|
||||||
tc.startX,
|
tc.startX,
|
||||||
tc.startY,
|
tc.startY,
|
||||||
@@ -316,19 +316,19 @@ func TestSwipeWithDirectionInvalidInputs(t *testing.T) {
|
|||||||
defer driver.TearDown()
|
defer driver.TearDown()
|
||||||
|
|
||||||
// Test invalid direction
|
// Test invalid direction
|
||||||
err = driver.SwipeWithDirection("invalid", 500.0, 500.0, 100.0, 200.0)
|
err = driver.SIMSwipeWithDirection("invalid", 500.0, 500.0, 100.0, 200.0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error for invalid direction, but got none")
|
t.Error("Expected error for invalid direction, but got none")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test invalid distance range (max < min)
|
// Test invalid distance range (max < min)
|
||||||
err = driver.SwipeWithDirection("up", 500.0, 500.0, 200.0, 100.0)
|
err = driver.SIMSwipeWithDirection("up", 500.0, 500.0, 200.0, 100.0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error for invalid distance range, but got none")
|
t.Error("Expected error for invalid distance range, but got none")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test zero distance
|
// Test zero distance
|
||||||
err = driver.SwipeWithDirection("up", 500.0, 500.0, 0.0, 0.0)
|
err = driver.SIMSwipeWithDirection("up", 500.0, 500.0, 0.0, 0.0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error for zero distance, but got none")
|
t.Error("Expected error for zero distance, but got none")
|
||||||
}
|
}
|
||||||
@@ -376,7 +376,7 @@ func TestSwipeInArea(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err := driver.SwipeInArea(
|
err := driver.SIMSwipeInArea(
|
||||||
tc.direction,
|
tc.direction,
|
||||||
tc.areaStartX,
|
tc.areaStartX,
|
||||||
tc.areaStartY,
|
tc.areaStartY,
|
||||||
@@ -429,7 +429,7 @@ func TestSwipeFromPointToPoint(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err := driver.SwipeFromPointToPoint(
|
err := driver.SIMSwipeFromPointToPoint(
|
||||||
tc.startX,
|
tc.startX,
|
||||||
tc.startY,
|
tc.startY,
|
||||||
tc.endX,
|
tc.endX,
|
||||||
@@ -460,13 +460,13 @@ func TestSwipeFromPointToPointInvalidInputs(t *testing.T) {
|
|||||||
defer driver.TearDown()
|
defer driver.TearDown()
|
||||||
|
|
||||||
// Test same start and end point
|
// Test same start and end point
|
||||||
err = driver.SwipeFromPointToPoint(0.5, 0.5, 0.5, 0.5)
|
err = driver.SIMSwipeFromPointToPoint(0.5, 0.5, 0.5, 0.5)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error for same start and end point, but got none")
|
t.Error("Expected error for same start and end point, but got none")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test very close points (should result in distance too short)
|
// Test very close points (should result in distance too short)
|
||||||
err = driver.SwipeFromPointToPoint(0.5, 0.5, 0.501, 0.501)
|
err = driver.SIMSwipeFromPointToPoint(0.5, 0.5, 0.501, 0.501)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error for very close points, but got none")
|
t.Error("Expected error for very close points, but got none")
|
||||||
}
|
}
|
||||||
@@ -503,7 +503,7 @@ func TestClickAtPoint(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err := driver.ClickAtPoint(tc.x, tc.y)
|
err := driver.SIMClickAtPoint(tc.x, tc.y)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("ClickAtPoint failed: %v", err)
|
t.Errorf("ClickAtPoint failed: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@@ -529,21 +529,91 @@ func TestClickAtPointInvalidInputs(t *testing.T) {
|
|||||||
defer driver.TearDown()
|
defer driver.TearDown()
|
||||||
|
|
||||||
// Test negative coordinates
|
// Test negative coordinates
|
||||||
err = driver.ClickAtPoint(-0.1, 0.5)
|
err = driver.SIMClickAtPoint(-0.1, 0.5)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error for negative x coordinate, but got none")
|
t.Error("Expected error for negative x coordinate, but got none")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = driver.ClickAtPoint(0.5, -0.1)
|
err = driver.SIMClickAtPoint(0.5, -0.1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error for negative y coordinate, but got none")
|
t.Error("Expected error for negative y coordinate, but got none")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test coordinates out of range (though these should be handled by convertToAbsolutePoint)
|
// Test coordinates out of range (though these should be handled by convertToAbsolutePoint)
|
||||||
err = driver.ClickAtPoint(1.5, 0.5)
|
err = driver.SIMClickAtPoint(1.5, 0.5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf("Out of range coordinates handled properly: %v", err)
|
t.Logf("Out of range coordinates handled properly: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log("Click invalid input validation tests passed")
|
t.Log("Click invalid input validation tests passed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSIMInput(t *testing.T) {
|
||||||
|
device, err := uixt.NewAndroidDevice(
|
||||||
|
option.WithSerialNumber(""),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := uixt.NewUIA2Driver(device)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer driver.TearDown()
|
||||||
|
|
||||||
|
// Test cases for different text inputs
|
||||||
|
testCases := []struct {
|
||||||
|
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: "This is a very long text to test the performance of SIMInput function. 这是一个很长的文本用来测试SIMInput函数的性能。1234567890!@#$%^&*()英語の長い文",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := driver.SIMInput(tc.text)
|
||||||
|
// err := driver.Input(tc.text)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("SIMInput failed: %v", err)
|
||||||
|
} else {
|
||||||
|
t.Logf("Successfully executed SIMInput: %s with text '%s'", tc.name, tc.text)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
287
internal/simulation/input_api.go
Normal file
287
internal/simulation/input_api.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package simulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InputRequest 输入请求参数
|
||||||
|
type InputRequest struct {
|
||||||
|
Text string `json:"text"` // 输入文本
|
||||||
|
MinSegmentLen int `json:"min_segment"` // 最小分割长度
|
||||||
|
MaxSegmentLen int `json:"max_segment"` // 最大分割长度
|
||||||
|
MinDelayMs int `json:"min_delay_ms"` // 最小延迟时间(毫秒)
|
||||||
|
MaxDelayMs int `json:"max_delay_ms"` // 最大延迟时间(毫秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputResponse 输入响应结果
|
||||||
|
type InputResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Segments []InputSegment `json:"segments"`
|
||||||
|
Metrics InputMetrics `json:"metrics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputSegment 输入片段
|
||||||
|
type InputSegment struct {
|
||||||
|
Index int `json:"index"` // 片段索引
|
||||||
|
Text string `json:"text"` // 片段文本
|
||||||
|
DelayMs int `json:"delay_ms"` // 该片段后的延迟时间(毫秒)
|
||||||
|
CharLen int `json:"char_len"` // 字符长度
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputMetrics 输入指标
|
||||||
|
type InputMetrics struct {
|
||||||
|
TotalSegments int `json:"total_segments"` // 总片段数
|
||||||
|
TotalDelayMs int `json:"total_delay_ms"` // 总延迟时间
|
||||||
|
EstimatedTimeMs int `json:"estimated_time_ms"` // 预估总耗时
|
||||||
|
OriginalCharLen int `json:"original_char_len"` // 原始字符长度
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputConfig 输入配置参数
|
||||||
|
type InputConfig struct {
|
||||||
|
MinSegmentLen int // 最小分割长度(字符数)
|
||||||
|
MaxSegmentLen int // 最大分割长度(字符数)
|
||||||
|
MinDelayMs int // 最小延迟时间(毫秒)
|
||||||
|
MaxDelayMs int // 最大延迟时间(毫秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInputConfig 默认输入配置
|
||||||
|
var DefaultInputConfig = InputConfig{
|
||||||
|
MinSegmentLen: 1, // 1个字符
|
||||||
|
MaxSegmentLen: 4, // 4个字符
|
||||||
|
MinDelayMs: 50, // 50毫秒
|
||||||
|
MaxDelayMs: 200, // 200毫秒
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputSimulatorAPI 输入仿真API
|
||||||
|
type InputSimulatorAPI struct {
|
||||||
|
rand *rand.Rand
|
||||||
|
config InputConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInputSimulatorAPI 创建新的输入仿真API
|
||||||
|
func NewInputSimulatorAPI(config *InputConfig) *InputSimulatorAPI {
|
||||||
|
if config == nil {
|
||||||
|
config = &DefaultInputConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
return &InputSimulatorAPI{
|
||||||
|
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
config: *config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateInputSegments 生成输入片段序列
|
||||||
|
func (api *InputSimulatorAPI) GenerateInputSegments(req InputRequest) InputResponse {
|
||||||
|
// 验证输入参数
|
||||||
|
if err := api.validateRequest(req); err != nil {
|
||||||
|
return InputResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果文本为空,直接返回
|
||||||
|
if req.Text == "" {
|
||||||
|
return InputResponse{
|
||||||
|
Success: true,
|
||||||
|
Segments: []InputSegment{},
|
||||||
|
Metrics: InputMetrics{
|
||||||
|
TotalSegments: 0,
|
||||||
|
TotalDelayMs: 0,
|
||||||
|
EstimatedTimeMs: 0,
|
||||||
|
OriginalCharLen: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成分割片段
|
||||||
|
segments := api.splitTextIntelligently(req.Text, req.MinSegmentLen, req.MaxSegmentLen)
|
||||||
|
|
||||||
|
// 生成延迟时间
|
||||||
|
inputSegments := make([]InputSegment, len(segments))
|
||||||
|
totalDelayMs := 0
|
||||||
|
|
||||||
|
for i, segment := range segments {
|
||||||
|
var delayMs int
|
||||||
|
// 最后一个片段不需要延迟
|
||||||
|
if i < len(segments)-1 {
|
||||||
|
delayMs = api.generateRandomDelay(req.MinDelayMs, req.MaxDelayMs)
|
||||||
|
totalDelayMs += delayMs
|
||||||
|
}
|
||||||
|
|
||||||
|
inputSegments[i] = InputSegment{
|
||||||
|
Index: i,
|
||||||
|
Text: segment,
|
||||||
|
DelayMs: delayMs,
|
||||||
|
CharLen: len([]rune(segment)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算指标
|
||||||
|
metrics := InputMetrics{
|
||||||
|
TotalSegments: len(segments),
|
||||||
|
TotalDelayMs: totalDelayMs,
|
||||||
|
EstimatedTimeMs: totalDelayMs, // 简化计算,实际输入时间可能更长
|
||||||
|
OriginalCharLen: len([]rune(req.Text)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return InputResponse{
|
||||||
|
Success: true,
|
||||||
|
Segments: inputSegments,
|
||||||
|
Metrics: metrics,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateRequest 验证请求参数
|
||||||
|
func (api *InputSimulatorAPI) validateRequest(req InputRequest) error {
|
||||||
|
// 使用配置中的默认值填充请求参数
|
||||||
|
if req.MinSegmentLen <= 0 {
|
||||||
|
req.MinSegmentLen = api.config.MinSegmentLen
|
||||||
|
}
|
||||||
|
if req.MaxSegmentLen <= 0 {
|
||||||
|
req.MaxSegmentLen = api.config.MaxSegmentLen
|
||||||
|
}
|
||||||
|
if req.MinDelayMs < 0 {
|
||||||
|
req.MinDelayMs = api.config.MinDelayMs
|
||||||
|
}
|
||||||
|
if req.MaxDelayMs < 0 {
|
||||||
|
req.MaxDelayMs = api.config.MaxDelayMs
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitTextIntelligently 智能分割文本
|
||||||
|
// 规则:
|
||||||
|
// 1. 先分解成基础单元:中文每个字符一个单元,英文/数字连续的作为一个单元,其他字符各自一个单元
|
||||||
|
// 2. 按MinSegmentLen到MaxSegmentLen的随机值组合基础单元
|
||||||
|
func (api *InputSimulatorAPI) splitTextIntelligently(text string, minLen, maxLen int) []string {
|
||||||
|
if minLen <= 0 {
|
||||||
|
minLen = api.config.MinSegmentLen
|
||||||
|
}
|
||||||
|
if maxLen <= 0 {
|
||||||
|
maxLen = api.config.MaxSegmentLen
|
||||||
|
}
|
||||||
|
if maxLen < minLen {
|
||||||
|
maxLen = minLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一步:分解成基础单元
|
||||||
|
baseUnits := api.splitIntoBaseUnits(text)
|
||||||
|
|
||||||
|
// 第二步:按随机数组合基础单元
|
||||||
|
var segments []string
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for i < len(baseUnits) {
|
||||||
|
remainingUnits := len(baseUnits) - i
|
||||||
|
|
||||||
|
var unitCount int
|
||||||
|
// 如果剩余单元数少于minLen,就把剩余的全部作为一个片段
|
||||||
|
if remainingUnits < minLen {
|
||||||
|
unitCount = remainingUnits
|
||||||
|
} else {
|
||||||
|
// 随机决定本次要组合的单元数量(在minLen到maxLen之间)
|
||||||
|
unitCount = minLen
|
||||||
|
if maxLen > minLen {
|
||||||
|
// 确保unitCount不超过剩余单元数
|
||||||
|
maxPossibleCount := maxLen
|
||||||
|
if maxPossibleCount > remainingUnits {
|
||||||
|
maxPossibleCount = remainingUnits
|
||||||
|
}
|
||||||
|
unitCount = minLen + api.rand.Intn(maxPossibleCount-minLen+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组合unitCount个基础单元成一个片段
|
||||||
|
segment := ""
|
||||||
|
for j := 0; j < unitCount; j++ {
|
||||||
|
segment += baseUnits[i+j]
|
||||||
|
}
|
||||||
|
segments = append(segments, segment)
|
||||||
|
i += unitCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitIntoBaseUnits 将文本分解成基础单元
|
||||||
|
func (api *InputSimulatorAPI) splitIntoBaseUnits(text string) []string {
|
||||||
|
var units []string
|
||||||
|
runes := []rune(text)
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for i < len(runes) {
|
||||||
|
// 处理中文字符:每个字符一个单元
|
||||||
|
if api.isChinese(runes[i]) {
|
||||||
|
units = append(units, string(runes[i]))
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理连续英文字母:作为一个单元
|
||||||
|
if unicode.IsLetter(runes[i]) && runes[i] <= 127 {
|
||||||
|
start := i
|
||||||
|
for i < len(runes) && unicode.IsLetter(runes[i]) && runes[i] <= 127 {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
word := string(runes[start:i])
|
||||||
|
units = append(units, word)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理连续数字:作为一个单元
|
||||||
|
if unicode.IsDigit(runes[i]) {
|
||||||
|
start := i
|
||||||
|
for i < len(runes) && unicode.IsDigit(runes[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
number := string(runes[start:i])
|
||||||
|
units = append(units, number)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理其他字符(空格、标点等):每个字符一个单元
|
||||||
|
units = append(units, string(runes[i]))
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return units
|
||||||
|
}
|
||||||
|
|
||||||
|
// isChinese 判断字符是否为中文
|
||||||
|
func (api *InputSimulatorAPI) isChinese(r rune) bool {
|
||||||
|
return unicode.Is(unicode.Scripts["Han"], r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateRandomDelay 生成随机延迟时间
|
||||||
|
func (api *InputSimulatorAPI) generateRandomDelay(minDelayMs, maxDelayMs int) int {
|
||||||
|
if minDelayMs < 0 {
|
||||||
|
minDelayMs = api.config.MinDelayMs
|
||||||
|
}
|
||||||
|
if maxDelayMs < 0 {
|
||||||
|
maxDelayMs = api.config.MaxDelayMs
|
||||||
|
}
|
||||||
|
if maxDelayMs < minDelayMs {
|
||||||
|
maxDelayMs = minDelayMs
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxDelayMs == minDelayMs {
|
||||||
|
return minDelayMs
|
||||||
|
}
|
||||||
|
|
||||||
|
return minDelayMs + api.rand.Intn(maxDelayMs-minDelayMs+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitText 公开的文本分割函数(使用智能分割)
|
||||||
|
func (api *InputSimulatorAPI) SplitText(text string) []string {
|
||||||
|
return api.splitTextIntelligently(text, api.config.MinSegmentLen, api.config.MaxSegmentLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateDelay 公开的延迟生成函数
|
||||||
|
func (api *InputSimulatorAPI) GenerateDelay() int {
|
||||||
|
return api.generateRandomDelay(api.config.MinDelayMs, api.config.MaxDelayMs)
|
||||||
|
}
|
||||||
@@ -558,7 +558,7 @@ func (ud *UIA2Driver) TouchByEvents(events []types.TouchEvent, opts ...option.Ac
|
|||||||
// direction: 滑动方向 ("up", "down", "left", "right")
|
// direction: 滑动方向 ("up", "down", "left", "right")
|
||||||
// startX, startY: 起始坐标
|
// startX, startY: 起始坐标
|
||||||
// minDistance, maxDistance: 距离范围,如果相等则为固定距离,否则为随机距离
|
// minDistance, maxDistance: 距离范围,如果相等则为固定距离,否则为随机距离
|
||||||
func (ud *UIA2Driver) SwipeWithDirection(direction string, startX, startY, minDistance, maxDistance float64, opts ...option.ActionOption) error {
|
func (ud *UIA2Driver) SIMSwipeWithDirection(direction string, startX, startY, minDistance, maxDistance float64, opts ...option.ActionOption) error {
|
||||||
absStartX, absStartY, err := convertToAbsolutePoint(ud, startX, startY)
|
absStartX, absStartY, err := convertToAbsolutePoint(ud, startX, startY)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -610,7 +610,7 @@ func (ud *UIA2Driver) SwipeWithDirection(direction string, startX, startY, minDi
|
|||||||
// direction: 滑动方向 ("up", "down", "left", "right")
|
// direction: 滑动方向 ("up", "down", "left", "right")
|
||||||
// areaStartX, areaStartY, areaEndX, areaEndY: 区域范围(相对坐标)
|
// areaStartX, areaStartY, areaEndX, areaEndY: 区域范围(相对坐标)
|
||||||
// minDistance, maxDistance: 距离范围,如果相等则为固定距离,否则为随机距离
|
// minDistance, maxDistance: 距离范围,如果相等则为固定距离,否则为随机距离
|
||||||
func (ud *UIA2Driver) SwipeInArea(direction string, areaStartX, areaStartY, areaEndX, areaEndY, minDistance, maxDistance float64, opts ...option.ActionOption) error {
|
func (ud *UIA2Driver) SIMSwipeInArea(direction string, areaStartX, areaStartY, areaEndX, areaEndY, minDistance, maxDistance float64, opts ...option.ActionOption) error {
|
||||||
// 转换区域坐标为绝对坐标
|
// 转换区域坐标为绝对坐标
|
||||||
absAreaStartX, absAreaStartY, err := convertToAbsolutePoint(ud, areaStartX, areaStartY)
|
absAreaStartX, absAreaStartY, err := convertToAbsolutePoint(ud, areaStartX, areaStartY)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -677,7 +677,7 @@ func (ud *UIA2Driver) SwipeInArea(direction string, areaStartX, areaStartY, area
|
|||||||
// SwipeFromPointToPoint 指定起始点和结束点进行滑动
|
// SwipeFromPointToPoint 指定起始点和结束点进行滑动
|
||||||
// startX, startY: 起始坐标(相对坐标)
|
// startX, startY: 起始坐标(相对坐标)
|
||||||
// endX, endY: 结束坐标(相对坐标)
|
// endX, endY: 结束坐标(相对坐标)
|
||||||
func (ud *UIA2Driver) SwipeFromPointToPoint(startX, startY, endX, endY float64, opts ...option.ActionOption) error {
|
func (ud *UIA2Driver) SIMSwipeFromPointToPoint(startX, startY, endX, endY float64, opts ...option.ActionOption) error {
|
||||||
// 转换起始点和结束点为绝对坐标
|
// 转换起始点和结束点为绝对坐标
|
||||||
absStartX, absStartY, err := convertToAbsolutePoint(ud, startX, startY)
|
absStartX, absStartY, err := convertToAbsolutePoint(ud, startX, startY)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -717,7 +717,7 @@ func (ud *UIA2Driver) SwipeFromPointToPoint(startX, startY, endX, endY float64,
|
|||||||
|
|
||||||
// ClickAtPoint 点击相对坐标
|
// ClickAtPoint 点击相对坐标
|
||||||
// x, y: 点击坐标(相对坐标)
|
// x, y: 点击坐标(相对坐标)
|
||||||
func (ud *UIA2Driver) ClickAtPoint(x, y float64, opts ...option.ActionOption) error {
|
func (ud *UIA2Driver) SIMClickAtPoint(x, y float64, opts ...option.ActionOption) error {
|
||||||
// 转换为绝对坐标
|
// 转换为绝对坐标
|
||||||
absX, absY, err := convertToAbsolutePoint(ud, x, y)
|
absX, absY, err := convertToAbsolutePoint(ud, x, y)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -789,6 +789,72 @@ func (ud *UIA2Driver) Input(text string, opts ...option.ActionOption) (err error
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SIMInput 仿真输入函数,模拟人类分批输入行为
|
||||||
|
// 将文本智能分割,英文单词和数字保持完整,中文按1-2个字符分割
|
||||||
|
func (ud *UIA2Driver) SIMInput(text string, opts ...option.ActionOption) error {
|
||||||
|
log.Info().Str("text", text).Msg("UIA2Driver.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 {
|
||||||
|
// 使用SendUnicodeKeys进行输入(内部已包含Session.POST请求)
|
||||||
|
segmentErr := ud.SendUnicodeKeys(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{}{
|
||||||
|
"text": text,
|
||||||
|
}
|
||||||
|
option.MergeOptions(data, opts...)
|
||||||
|
urlStr := fmt.Sprintf("/session/%s/keys", ud.Session.ID)
|
||||||
|
_, err := ud.Session.POST(data, urlStr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info().Int("totalSegments", response.Metrics.TotalSegments).
|
||||||
|
Int("actualDelayMs", response.Metrics.TotalDelayMs).
|
||||||
|
Msg("SIMInput completed successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ud *UIA2Driver) SendUnicodeKeys(text string, opts ...option.ActionOption) (err error) {
|
func (ud *UIA2Driver) SendUnicodeKeys(text string, opts ...option.ActionOption) (err error) {
|
||||||
log.Info().Str("text", text).Msg("UIA2Driver.SendUnicodeKeys")
|
log.Info().Str("text", text).Msg("UIA2Driver.SendUnicodeKeys")
|
||||||
// If the Unicode IME is not installed, fall back to the old interface.
|
// If the Unicode IME is not installed, fall back to the old interface.
|
||||||
|
|||||||
Reference in New Issue
Block a user