From 31c66b8cc06c3b8d9784cfc649b12074397968d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Wed, 16 Jul 2025 17:08:57 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=9F=BA?= =?UTF-8?q?=E4=BA=8EtouchEvent=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../uitest/android_touch_simulator_test.go | 240 ++++++++++++++++++ go.mod | 2 +- uixt/android_driver_uia2.go | 106 ++++++++ uixt/types/device.go | 17 ++ 4 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 examples/uitest/android_touch_simulator_test.go diff --git a/examples/uitest/android_touch_simulator_test.go b/examples/uitest/android_touch_simulator_test.go new file mode 100644 index 00000000..ddc690cf --- /dev/null +++ b/examples/uitest/android_touch_simulator_test.go @@ -0,0 +1,240 @@ +package uitest + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/httprunner/httprunner/v5/uixt" + "github.com/httprunner/httprunner/v5/uixt/option" + "github.com/httprunner/httprunner/v5/uixt/types" +) + +// ParseTouchEvents parses touch event data from comma-separated string format +func ParseTouchEvents(data string) ([]types.TouchEvent, error) { + lines := strings.Split(strings.TrimSpace(data), "\n") + events := make([]types.TouchEvent, 0, len(lines)) + + for _, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + + parts := strings.Split(line, ",") + if len(parts) != 13 { + return nil, fmt.Errorf("invalid touch event data format: expected 13 fields, got %d", len(parts)) + } + + event := types.TouchEvent{} + var err error + + // Parse each field + if event.Timestamp, err = strconv.ParseInt(parts[0], 10, 64); err != nil { + return nil, fmt.Errorf("invalid timestamp: %v", err) + } + if event.X, err = strconv.ParseFloat(parts[1], 64); err != nil { + return nil, fmt.Errorf("invalid x coordinate: %v", err) + } + if event.Y, err = strconv.ParseFloat(parts[2], 64); err != nil { + return nil, fmt.Errorf("invalid y coordinate: %v", err) + } + if event.DeviceID, err = strconv.Atoi(parts[3]); err != nil { + return nil, fmt.Errorf("invalid device id: %v", err) + } + if event.Pressure, err = strconv.ParseFloat(parts[4], 64); err != nil { + return nil, fmt.Errorf("invalid pressure: %v", err) + } + if event.Size, err = strconv.ParseFloat(parts[5], 64); err != nil { + return nil, fmt.Errorf("invalid size: %v", err) + } + if event.RawX, err = strconv.ParseFloat(parts[6], 64); err != nil { + return nil, fmt.Errorf("invalid raw x: %v", err) + } + if event.RawY, err = strconv.ParseFloat(parts[7], 64); err != nil { + return nil, fmt.Errorf("invalid raw y: %v", err) + } + if event.DownTime, err = strconv.ParseInt(parts[8], 10, 64); err != nil { + return nil, fmt.Errorf("invalid down time: %v", err) + } + if event.EventTime, err = strconv.ParseInt(parts[9], 10, 64); err != nil { + return nil, fmt.Errorf("invalid event time: %v", err) + } + if event.ToolType, err = strconv.Atoi(parts[10]); err != nil { + return nil, fmt.Errorf("invalid tool type: %v", err) + } + if event.Flag, err = strconv.Atoi(parts[11]); err != nil { + return nil, fmt.Errorf("invalid flag: %v", err) + } + 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 := uixt.NewAndroidDevice( + option.WithSerialNumber(""), + ) + if err != nil { + t.Fatal(err) + } + + driver, err := uixt.NewUIA2Driver(device) + if err != nil { + t.Fatal(err) + } + defer driver.TearDown() + + // 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.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" + + events, err := ParseTouchEvents(singleEventData) + if err != nil { + t.Fatalf("ParseTouchEvents failed: %v", err) + } + + if len(events) != 1 { + t.Fatalf("Expected 1 event, got %d", len(events)) + } + + event := events[0] + if event.Timestamp != 1752646457403 { + t.Errorf("Expected timestamp 1752646457403, got %d", event.Timestamp) + } + if event.X != 456.78418 { + t.Errorf("Expected X 456.78418, got %f", event.X) + } + if event.Y != 1574.0195 { + t.Errorf("Expected Y 1574.0195, got %f", event.Y) + } + if event.Action != 0 { + t.Errorf("Expected Action 0, got %d", event.Action) + } + if event.Pressure != 1.0 { + t.Errorf("Expected Pressure 1.0, got %f", event.Pressure) + } + if event.Size != 0.016666668 { + t.Errorf("Expected Size 0.016666668, got %f", event.Size) + } +} + +func TestTouchEventParsingInvalidData(t *testing.T) { + // Test with invalid data + testCases := []struct { + name string + data string + }{ + { + name: "too few fields", + data: "1752646457403,456.78418,1574.0195,7,1.0", + }, + { + name: "invalid timestamp", + data: "invalid,456.78418,1574.0195,7,1.0,0.016666668,504.78418,1721.0195,924451292,924451292,1,0,0", + }, + { + name: "invalid x coordinate", + data: "1752646457403,invalid,1574.0195,7,1.0,0.016666668,504.78418,1721.0195,924451292,924451292,1,0,0", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := ParseTouchEvents(tc.data) + if err == nil { + t.Errorf("Expected error for invalid data, but got none") + } + }) + } +} + +func TestTouchEventSequenceValidation(t *testing.T) { + // Test a complete touch sequence: DOWN -> MOVE -> MOVE -> UP + sequenceData := `1752646457403,100.0,100.0,7,1.0,0.016666668,100.0,100.0,924451292,924451292,1,0,0 +1752646457420,120.0,120.0,7,1.0,0.022058824,120.0,120.0,924451292,924451335,1,0,2 +1752646457440,140.0,140.0,7,1.0,0.022058824,140.0,140.0,924451292,924451351,1,0,2 +1752646457460,160.0,160.0,7,1.0,0.012254903,160.0,160.0,924451292,924451619,1,0,1` + + events, err := ParseTouchEvents(sequenceData) + if err != nil { + t.Fatalf("ParseTouchEvents failed: %v", err) + } + + if len(events) != 4 { + t.Fatalf("Expected 4 events, got %d", len(events)) + } + + // Validate sequence: DOWN -> MOVE -> MOVE -> UP + expectedActions := []int{0, 2, 2, 1} // ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_UP + for i, event := range events { + if event.Action != expectedActions[i] { + t.Errorf("Event %d: expected action %d, got %d", i, expectedActions[i], event.Action) + } + } + + // Validate timestamps are in increasing order + for i := 1; i < len(events); i++ { + if events[i].Timestamp <= events[i-1].Timestamp { + t.Errorf("Event %d: timestamp should be greater than previous event", i) + } + } + + t.Logf("Touch sequence validation passed: %d events with correct action sequence", len(events)) +} diff --git a/go.mod b/go.mod index ca680fc9..3abfe4f7 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/gin-gonic/gin v1.10.0 github.com/go-openapi/spec v0.20.7 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 + github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/httprunner/funplugin v0.5.5 github.com/jinzhu/copier v0.3.5 @@ -82,7 +83,6 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/goph/emperror v0.17.2 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/grandcat/zeroconf v1.0.0 // indirect diff --git a/uixt/android_driver_uia2.go b/uixt/android_driver_uia2.go index 7c1751ae..f8afac1a 100644 --- a/uixt/android_driver_uia2.go +++ b/uixt/android_driver_uia2.go @@ -428,6 +428,112 @@ func (ud *UIA2Driver) Swipe(fromX, fromY, toX, toY float64, opts ...option.Actio return err } +// TouchByEvents performs a complex swipe using a sequence of touch events with pressure and size data +func (ud *UIA2Driver) TouchByEvents(events []types.TouchEvent, opts ...option.ActionOption) error { + log.Info().Int("eventCount", len(events)).Msg("UIA2Driver.SwipeSimulator") + + if len(events) == 0 { + return fmt.Errorf("no touch events provided") + } + + actionOptions := option.NewActionOptions(opts...) + + // Apply pre-handlers for the first and last events (start and end coordinates) + firstEvent := events[0] + lastEvent := events[len(events)-1] + fromX, fromY, toX, toY, err := preHandler_Swipe(ud, option.ACTION_SwipeCoordinate, actionOptions, + firstEvent.X, firstEvent.Y, lastEvent.X, lastEvent.Y) + if err != nil { + return err + } + defer postHandler(ud, option.ACTION_SwipeCoordinate, actionOptions) + + var actions []interface{} + var prevTimestamp int64 + + for i, event := range events { + var duration float64 + if i > 0 { + // Calculate duration from previous event in milliseconds + duration = float64(event.Timestamp - prevTimestamp) + } + prevTimestamp = event.Timestamp + + // Apply coordinate transformation if it's the first or last event + x, y := event.X, event.Y + if i == 0 { + x, y = fromX, fromY + } else if i == len(events)-1 { + x, y = toX, toY + } + + var actionMap map[string]interface{} + + switch event.Action { + case 0: // ACTION_DOWN + actionMap = map[string]interface{}{ + "type": "pointerDown", + "duration": 0, + "button": 0, + "pressure": event.Pressure, + "size": event.Size, + } + // Add initial move to position before down + if i == 0 { + moveAction := map[string]interface{}{ + "type": "pointerMove", + "duration": 0, + "x": x, + "y": y, + "origin": "viewport", + "pressure": event.Pressure, + "size": event.Size, + } + actions = append(actions, moveAction) + } + case 1: // ACTION_UP + actionMap = map[string]interface{}{ + "type": "pointerUp", + "duration": 0, + "button": 0, + "pressure": event.Pressure, + "size": event.Size, + } + case 2: // ACTION_MOVE + actionMap = map[string]interface{}{ + "type": "pointerMove", + "duration": duration, + "x": x, + "y": y, + "origin": "viewport", + "pressure": event.Pressure, + "size": event.Size, + } + default: + log.Warn().Int("action", event.Action).Msg("Unknown action type, skipping") + continue + } + + actions = append(actions, actionMap) + } + + data := map[string]interface{}{ + "actions": []interface{}{ + map[string]interface{}{ + "type": "pointer", + "parameters": map[string]string{"pointerType": "touch"}, + "id": "touch", + "actions": actions, + }, + }, + } + option.MergeOptions(data, opts...) + + urlStr := fmt.Sprintf("/session/%s/actions/swipe", ud.Session.ID) + _, err = ud.Session.POST(data, urlStr) + return err +} + func (ud *UIA2Driver) SetPasteboard(contentType types.PasteboardType, content string) (err error) { log.Info().Str("contentType", string(contentType)). Str("content", content).Msg("UIA2Driver.SetPasteboard") diff --git a/uixt/types/device.go b/uixt/types/device.go index f46b8b8e..85d17ddc 100644 --- a/uixt/types/device.go +++ b/uixt/types/device.go @@ -225,3 +225,20 @@ const ( DirectionLeft Direction = "left" DirectionRight Direction = "right" ) + +// TouchEvent represents a single touch event with all its properties +type TouchEvent struct { + Timestamp int64 `json:"timestamp"` + X float64 `json:"x"` + Y float64 `json:"y"` + DeviceID int `json:"deviceId"` + Pressure float64 `json:"pressure"` + Size float64 `json:"size"` + RawX float64 `json:"rawX"` + RawY float64 `json:"rawY"` + DownTime int64 `json:"downTime"` + EventTime int64 `json:"eventTime"` + ToolType int `json:"toolType"` + Flag int `json:"flag"` + Action int `json:"action"` +} From ed5fba55407749217c674b5aa7063f5773266de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Wed, 16 Jul 2025 17:41:56 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81touchByEvent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/uitest/demo_touch_by_events_test.go | 311 +++++++++++++++++++ uixt/android_driver_uia2.go | 31 +- uixt/types/device.go | 1 - 3 files changed, 336 insertions(+), 7 deletions(-) create mode 100644 examples/uitest/demo_touch_by_events_test.go diff --git a/examples/uitest/demo_touch_by_events_test.go b/examples/uitest/demo_touch_by_events_test.go new file mode 100644 index 00000000..f18c96e0 --- /dev/null +++ b/examples/uitest/demo_touch_by_events_test.go @@ -0,0 +1,311 @@ +package uitest + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/httprunner/httprunner/v5/uixt" + "github.com/httprunner/httprunner/v5/uixt/option" + "github.com/httprunner/httprunner/v5/uixt/types" +) + +// ParseTouchEventsOptimized parses touch event data using EventTime and DownTime +func ParseTouchEventsOptimized(data string) ([]types.TouchEvent, error) { + lines := strings.Split(strings.TrimSpace(data), "\n") + events := make([]types.TouchEvent, 0, len(lines)) + + for lineNum, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + + parts := strings.Split(line, ",") + event := types.TouchEvent{} + var err error + + if len(parts) == 13 { + // Legacy format: first field is EventTime for backward compatibility + if event.EventTime, err = strconv.ParseInt(parts[0], 10, 64); err != nil { + return nil, fmt.Errorf("line %d: invalid eventTime: %v", lineNum+1, err) + } + if event.X, err = strconv.ParseFloat(parts[1], 64); err != nil { + return nil, fmt.Errorf("line %d: invalid x: %v", lineNum+1, err) + } + if event.Y, err = strconv.ParseFloat(parts[2], 64); err != nil { + return nil, fmt.Errorf("line %d: invalid y: %v", lineNum+1, err) + } + if event.DeviceID, err = strconv.Atoi(parts[3]); err != nil { + return nil, fmt.Errorf("line %d: invalid deviceId: %v", lineNum+1, err) + } + if event.Pressure, err = strconv.ParseFloat(parts[4], 64); err != nil { + return nil, fmt.Errorf("line %d: invalid pressure: %v", lineNum+1, err) + } + if event.Size, err = strconv.ParseFloat(parts[5], 64); err != nil { + return nil, fmt.Errorf("line %d: invalid size: %v", lineNum+1, err) + } + if event.RawX, err = strconv.ParseFloat(parts[6], 64); err != nil { + return nil, fmt.Errorf("line %d: invalid rawX: %v", lineNum+1, err) + } + if event.RawY, err = strconv.ParseFloat(parts[7], 64); err != nil { + return nil, fmt.Errorf("line %d: invalid rawY: %v", lineNum+1, err) + } + if event.DownTime, err = strconv.ParseInt(parts[8], 10, 64); err != nil { + return nil, fmt.Errorf("line %d: invalid downTime: %v", lineNum+1, err) + } + // Skip parts[9] (duplicate EventTime) + if event.ToolType, err = strconv.Atoi(parts[10]); err != nil { + return nil, fmt.Errorf("line %d: invalid toolType: %v", lineNum+1, err) + } + if event.Flag, err = strconv.Atoi(parts[11]); err != nil { + return nil, fmt.Errorf("line %d: invalid flag: %v", lineNum+1, err) + } + if event.Action, err = strconv.Atoi(parts[12]); err != nil { + return nil, fmt.Errorf("line %d: invalid action: %v", lineNum+1, err) + } + } else { + return nil, fmt.Errorf("line %d: expected 13 fields, got %d", lineNum+1, len(parts)) + } + + events = append(events, event) + } + + return events, nil +} + +// ValidateGestureSequence validates the touch event sequence +func ValidateGestureSequence(events []types.TouchEvent) error { + if len(events) == 0 { + return fmt.Errorf("empty event sequence") + } + + downTime := events[0].DownTime + prevEventTime := int64(0) + + for i, event := range events { + // Check DownTime consistency + if event.DownTime != downTime { + return fmt.Errorf("event %d: DownTime mismatch", i) + } + + // Check EventTime ordering + if event.EventTime < prevEventTime { + return fmt.Errorf("event %d: EventTime should be increasing", i) + } + prevEventTime = event.EventTime + + // Validate action sequence + if i == 0 && event.Action != 0 { + return fmt.Errorf("first event should be ACTION_DOWN") + } + if i == len(events)-1 && event.Action != 1 { + return fmt.Errorf("last event should be ACTION_UP") + } + } + + return nil +} + +func TestTouchByEventsWithOptimizedTimeSystem(t *testing.T) { + device, err := uixt.NewAndroidDevice( + option.WithSerialNumber(""), + ) + if err != nil { + t.Skip("Android device not available") + } + + driver, err := uixt.NewUIA2Driver(device) + if err != nil { + t.Skip("UIA2 driver not available") + } + defer driver.TearDown() + + // Optimized touch event data using EventTime and DownTime + // This represents a swipe gesture from top to bottom + touchEventData := `111586196,401.20703,1191.3164,2,1.0,0.03529412,457.20703,1359.3164,111586196,111586196,1,0,0 +111586236,402.913,1185.0792,2,1.0,0.039215688,458.913,1353.0792,111586196,111586236,1,0,2 +111586250,410.60825,1164.3806,2,1.0,0.03529412,466.60825,1332.3806,111586196,111586250,1,0,2 +111586270,437.7335,1093.1417,2,1.0,0.039215688,493.7335,1261.1417,111586196,111586270,1,0,2 +111586287,463.5786,1018.01746,2,1.0,0.039215688,519.5786,1186.0175,111586196,111586287,1,0,2 +111586304,487.56482,948.9773,2,1.0,0.03529412,543.5648,1116.9773,111586196,111586304,1,0,2 +111586320,511.81476,881.6183,2,1.0,0.039215688,567.81476,1049.6183,111586196,111586320,1,0,2 +111586337,543.4369,811.4982,2,1.0,0.03529412,599.4369,979.4982,111586196,111586337,1,0,2 +111586354,577.1632,747.4512,2,1.0,0.039215688,633.1632,915.4512,111586196,111586354,1,0,2 +111586370,610.1538,691.72034,2,1.0,0.03529412,666.1538,859.72034,111586196,111586370,1,0,2 +111586387,639.1683,642.6914,2,1.0,0.03529412,695.1683,810.6914,111586196,111586387,1,0,2 +111586404,658.9832,605.90857,2,1.0,0.03529412,714.9832,773.90857,111586196,111586404,1,0,2 +111586420,672.21954,581.1634,2,1.0,0.03529412,728.21954,749.1634,111586196,111586420,1,0,2 +111586434,680.7687,566.1778,2,1.0,0.03529412,736.7687,734.1778,111586196,111586434,1,0,2 +111586450,688.0894,554.2295,2,1.0,0.03529412,744.0894,722.2295,111586196,111586450,1,0,2 +111586466,694.542,544.7783,2,1.0,0.03529412,750.542,712.7783,111586196,111586466,1,0,2 +111586483,700.60645,537.2637,2,1.0,0.039215688,756.60645,705.2637,111586196,111586483,1,0,2 +111586500,705.08887,531.1406,2,1.0,0.039215688,761.08887,699.1406,111586196,111586500,1,0,2 +111586517,708.1211,527.8008,2,1.0,0.039215688,764.1211,695.8008,111586196,111586517,1,0,2 +111586533,709.43945,524.46094,2,1.0,0.039215688,765.43945,692.46094,111586196,111586533,1,0,2 +111586537,709.1758,523.34766,2,1.0,0.03529412,765.1758,691.34766,111586196,111586537,1,33554432,2 +111586546,709.1758,523.34766,2,1.0,0.03529412,765.1758,691.34766,111586196,111586546,1,0,1` + + // Parse touch events + events, err := ParseTouchEventsOptimized(touchEventData) + if err != nil { + t.Fatalf("ParseTouchEventsOptimized failed: %v", err) + } + + t.Logf("Parsed %d touch events", len(events)) + + // Validate gesture sequence + if err := ValidateGestureSequence(events); err != nil { + t.Fatalf("Gesture validation failed: %v", err) + } + + // Analyze gesture timing + analyzeGestureTiming(t, events) + + // Execute touch events using optimized time system + err = driver.TouchByEvents(events) + if err != nil { + t.Fatalf("TouchByEvents failed: %v", err) + } + + t.Logf("Successfully executed %d touch events", len(events)) +} + +func TestEventTimeCalculations(t *testing.T) { + // Test data with clear timing relationships + testData := `111586000,100.0,100.0,2,1.0,0.5,100.0,100.0,111586000,111586000,1,0,0 +111586050,120.0,120.0,2,1.0,0.5,120.0,120.0,111586000,111586050,1,0,2 +111586100,140.0,140.0,2,1.0,0.5,140.0,140.0,111586000,111586100,1,0,2 +111586150,160.0,160.0,2,1.0,0.5,160.0,160.0,111586000,111586150,1,0,1` + + events, err := ParseTouchEventsOptimized(testData) + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + if len(events) != 4 { + t.Fatalf("Expected 4 events, got %d", len(events)) + } + + // Test timing calculations + gestureDuration := events[3].EventTime - events[0].DownTime + expectedDuration := int64(150) // 111586150 - 111586000 + if gestureDuration != expectedDuration { + t.Errorf("Gesture duration: expected %d, got %d", expectedDuration, gestureDuration) + } + + // Test event intervals + expectedIntervals := []int64{50, 50, 50} // Each event is 50ms apart + for i := 1; i < len(events); i++ { + interval := events[i].EventTime - events[i-1].EventTime + if interval != expectedIntervals[i-1] { + t.Errorf("Event %d interval: expected %d, got %d", i, expectedIntervals[i-1], interval) + } + } + + t.Logf("Timing calculations validated successfully") +} + +func TestDownTimeConsistency(t *testing.T) { + // Test data with consistent DownTime + testData := `100000,10.0,10.0,1,1.0,0.5,10.0,10.0,100000,100000,1,0,0 +100020,20.0,20.0,1,1.0,0.5,20.0,20.0,100000,100020,1,0,2 +100040,30.0,30.0,1,1.0,0.5,30.0,30.0,100000,100040,1,0,1` + + events, err := ParseTouchEventsOptimized(testData) + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Verify DownTime consistency + expectedDownTime := int64(100000) + for i, event := range events { + if event.DownTime != expectedDownTime { + t.Errorf("Event %d: DownTime expected %d, got %d", i, expectedDownTime, event.DownTime) + } + } + + // Verify EventTime progression + for i := 1; i < len(events); i++ { + if events[i].EventTime <= events[i-1].EventTime { + t.Errorf("Event %d: EventTime should be greater than previous event", i) + } + } + + t.Logf("DownTime consistency validated successfully") +} + +func TestCoordinateSystem(t *testing.T) { + // Test coordinate priority: rawX/rawY should be used preferentially + testData := `100000,50.0,50.0,1,1.0,0.5,100.0,200.0,100000,100000,1,0,0 +100010,60.0,60.0,1,1.0,0.5,110.0,210.0,100000,100010,1,0,1` + + events, err := ParseTouchEventsOptimized(testData) + if err != nil { + t.Fatalf("Parse failed: %v", err) + } + + // Check that rawX/rawY values are parsed correctly + if events[0].RawX != 100.0 || events[0].RawY != 200.0 { + t.Errorf("Event 0: expected rawX=100.0, rawY=200.0, got rawX=%.1f, rawY=%.1f", + events[0].RawX, events[0].RawY) + } + + if events[1].RawX != 110.0 || events[1].RawY != 210.0 { + t.Errorf("Event 1: expected rawX=110.0, rawY=210.0, got rawX=%.1f, rawY=%.1f", + events[1].RawX, events[1].RawY) + } + + t.Logf("Coordinate system validation passed") +} + +func analyzeGestureTiming(t *testing.T, events []types.TouchEvent) { + if len(events) == 0 { + return + } + + t.Logf("=== Gesture Timing Analysis ===") + t.Logf("Total events: %d", len(events)) + t.Logf("DownTime: %d", events[0].DownTime) + t.Logf("First EventTime: %d", events[0].EventTime) + t.Logf("Last EventTime: %d", events[len(events)-1].EventTime) + + // Calculate total gesture duration using DownTime and final EventTime + totalDuration := events[len(events)-1].EventTime - events[0].DownTime + t.Logf("Total gesture duration: %dms", totalDuration) + + // Calculate average interval between events + if len(events) > 1 { + totalInterval := events[len(events)-1].EventTime - events[0].EventTime + avgInterval := totalInterval / int64(len(events)-1) + t.Logf("Average event interval: %dms", avgInterval) + } + + // Verify DownTime consistency + downTime := events[0].DownTime + consistent := true + for i, event := range events { + if event.DownTime != downTime { + t.Logf("Warning: Event %d has different DownTime: %d vs %d", i, event.DownTime, downTime) + consistent = false + } + } + t.Logf("DownTime consistency: %t", consistent) + + // Action sequence analysis + actions := make([]string, len(events)) + for i, event := range events { + switch event.Action { + case 0: + actions[i] = "DOWN" + case 1: + actions[i] = "UP" + case 2: + actions[i] = "MOVE" + default: + actions[i] = fmt.Sprintf("UNK(%d)", event.Action) + } + } + t.Logf("Action sequence: %v", actions) + t.Logf("================================") +} diff --git a/uixt/android_driver_uia2.go b/uixt/android_driver_uia2.go index f8afac1a..70390860 100644 --- a/uixt/android_driver_uia2.go +++ b/uixt/android_driver_uia2.go @@ -441,26 +441,45 @@ func (ud *UIA2Driver) TouchByEvents(events []types.TouchEvent, opts ...option.Ac // Apply pre-handlers for the first and last events (start and end coordinates) firstEvent := events[0] lastEvent := events[len(events)-1] + + // Use rawX/rawY if available, otherwise fallback to X/Y for first event + startX, startY := firstEvent.RawX, firstEvent.RawY + if startX == 0 && startY == 0 { + startX, startY = firstEvent.X, firstEvent.Y + } + + // Use rawX/rawY if available, otherwise fallback to X/Y for last event + endX, endY := lastEvent.RawX, lastEvent.RawY + if endX == 0 && endY == 0 { + endX, endY = lastEvent.X, lastEvent.Y + } + fromX, fromY, toX, toY, err := preHandler_Swipe(ud, option.ACTION_SwipeCoordinate, actionOptions, - firstEvent.X, firstEvent.Y, lastEvent.X, lastEvent.Y) + startX, startY, endX, endY) if err != nil { return err } defer postHandler(ud, option.ACTION_SwipeCoordinate, actionOptions) var actions []interface{} - var prevTimestamp int64 + var prevEventTime int64 for i, event := range events { var duration float64 if i > 0 { - // Calculate duration from previous event in milliseconds - duration = float64(event.Timestamp - prevTimestamp) + // Calculate duration from previous event using EventTime (milliseconds) + duration = float64(event.EventTime - prevEventTime) + } + prevEventTime = event.EventTime + + // Use rawX/rawY if available, otherwise fallback to X/Y + x, y := event.RawX, event.RawY + if x == 0 && y == 0 { + // Fallback to X/Y if rawX/rawY are not set + x, y = event.X, event.Y } - prevTimestamp = event.Timestamp // Apply coordinate transformation if it's the first or last event - x, y := event.X, event.Y if i == 0 { x, y = fromX, fromY } else if i == len(events)-1 { diff --git a/uixt/types/device.go b/uixt/types/device.go index 85d17ddc..f908c0c9 100644 --- a/uixt/types/device.go +++ b/uixt/types/device.go @@ -228,7 +228,6 @@ const ( // TouchEvent represents a single touch event with all its properties type TouchEvent struct { - Timestamp int64 `json:"timestamp"` X float64 `json:"x"` Y float64 `json:"y"` DeviceID int `json:"deviceId"` From 76d60cb7d0a5f26e83eb1b1967ca2f12cb4154b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Thu, 17 Jul 2025 11:55:05 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=8E=A2=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uixt/android_device.go | 8 ++++++++ uixt/device.go | 2 ++ uixt/harmony_device.go | 4 ++++ uixt/ios_device.go | 12 ++++++++++++ 4 files changed, 26 insertions(+) diff --git a/uixt/android_device.go b/uixt/android_device.go index e7cc774c..efb243e8 100644 --- a/uixt/android_device.go +++ b/uixt/android_device.go @@ -114,6 +114,14 @@ func (dev *AndroidDevice) Setup() error { return nil } +func (dev *AndroidDevice) IsHealthy() (bool, error) { + state, err := dev.Device.State() + if err != nil { + return false, err + } + return state == gadb.StateOnline, nil +} + func (dev *AndroidDevice) Teardown() error { return nil } diff --git a/uixt/device.go b/uixt/device.go index 3ced174a..06aeab60 100644 --- a/uixt/device.go +++ b/uixt/device.go @@ -12,6 +12,8 @@ type IDevice interface { UUID() string NewDriver() (driver IDriver, err error) + IsHealthy() (bool, error) + Setup() error Teardown() error diff --git a/uixt/harmony_device.go b/uixt/harmony_device.go index 8725f471..1742f101 100644 --- a/uixt/harmony_device.go +++ b/uixt/harmony_device.go @@ -75,6 +75,10 @@ func (dev *HarmonyDevice) Setup() error { return nil } +func (dev *HarmonyDevice) IsHealthy() (bool, error) { + return true, nil +} + func (dev *HarmonyDevice) Teardown() error { return nil } diff --git a/uixt/ios_device.go b/uixt/ios_device.go index ba16502f..fda57fc8 100644 --- a/uixt/ios_device.go +++ b/uixt/ios_device.go @@ -184,6 +184,18 @@ func (dev *IOSDevice) Setup() error { return nil } +func (dev *IOSDevice) IsHealthy() (bool, error) { + startTimestamp := time.Now() + lockdown, err := ios.ConnectLockdownWithSession(dev.DeviceEntry) + if err != nil { + return false, err + } + defer lockdown.Close() + elapsed := time.Since(startTimestamp) + log.Info().Dur("elapsed", elapsed).Msg("connect lockdown") + return true, nil +} + func (dev *IOSDevice) Teardown() error { for _, listener := range dev.listeners { _ = listener.Close() From b200fd40199002b38187df5b34aa74c15d777bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Thu, 17 Jul 2025 11:59:25 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=8E=A2=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uixt/browser_device.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/uixt/browser_device.go b/uixt/browser_device.go index 8e0231a5..8b7cdf3a 100644 --- a/uixt/browser_device.go +++ b/uixt/browser_device.go @@ -45,6 +45,10 @@ func (dev *BrowserDevice) Setup() error { return nil } +func (dev *BrowserDevice) IsHealthy() (bool, error) { + return true, nil +} + func (dev *BrowserDevice) LogEnabled() bool { return dev.Options.LogOn } From e1b2f727c7f8f2fd579def9aa52d99f437be406d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Thu, 17 Jul 2025 14:10:18 +0800 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/uitest/demo_touch_by_events_test.go | 311 ------------------- 1 file changed, 311 deletions(-) delete mode 100644 examples/uitest/demo_touch_by_events_test.go diff --git a/examples/uitest/demo_touch_by_events_test.go b/examples/uitest/demo_touch_by_events_test.go deleted file mode 100644 index f18c96e0..00000000 --- a/examples/uitest/demo_touch_by_events_test.go +++ /dev/null @@ -1,311 +0,0 @@ -package uitest - -import ( - "fmt" - "strconv" - "strings" - "testing" - - "github.com/httprunner/httprunner/v5/uixt" - "github.com/httprunner/httprunner/v5/uixt/option" - "github.com/httprunner/httprunner/v5/uixt/types" -) - -// ParseTouchEventsOptimized parses touch event data using EventTime and DownTime -func ParseTouchEventsOptimized(data string) ([]types.TouchEvent, error) { - lines := strings.Split(strings.TrimSpace(data), "\n") - events := make([]types.TouchEvent, 0, len(lines)) - - for lineNum, line := range lines { - if strings.TrimSpace(line) == "" { - continue - } - - parts := strings.Split(line, ",") - event := types.TouchEvent{} - var err error - - if len(parts) == 13 { - // Legacy format: first field is EventTime for backward compatibility - if event.EventTime, err = strconv.ParseInt(parts[0], 10, 64); err != nil { - return nil, fmt.Errorf("line %d: invalid eventTime: %v", lineNum+1, err) - } - if event.X, err = strconv.ParseFloat(parts[1], 64); err != nil { - return nil, fmt.Errorf("line %d: invalid x: %v", lineNum+1, err) - } - if event.Y, err = strconv.ParseFloat(parts[2], 64); err != nil { - return nil, fmt.Errorf("line %d: invalid y: %v", lineNum+1, err) - } - if event.DeviceID, err = strconv.Atoi(parts[3]); err != nil { - return nil, fmt.Errorf("line %d: invalid deviceId: %v", lineNum+1, err) - } - if event.Pressure, err = strconv.ParseFloat(parts[4], 64); err != nil { - return nil, fmt.Errorf("line %d: invalid pressure: %v", lineNum+1, err) - } - if event.Size, err = strconv.ParseFloat(parts[5], 64); err != nil { - return nil, fmt.Errorf("line %d: invalid size: %v", lineNum+1, err) - } - if event.RawX, err = strconv.ParseFloat(parts[6], 64); err != nil { - return nil, fmt.Errorf("line %d: invalid rawX: %v", lineNum+1, err) - } - if event.RawY, err = strconv.ParseFloat(parts[7], 64); err != nil { - return nil, fmt.Errorf("line %d: invalid rawY: %v", lineNum+1, err) - } - if event.DownTime, err = strconv.ParseInt(parts[8], 10, 64); err != nil { - return nil, fmt.Errorf("line %d: invalid downTime: %v", lineNum+1, err) - } - // Skip parts[9] (duplicate EventTime) - if event.ToolType, err = strconv.Atoi(parts[10]); err != nil { - return nil, fmt.Errorf("line %d: invalid toolType: %v", lineNum+1, err) - } - if event.Flag, err = strconv.Atoi(parts[11]); err != nil { - return nil, fmt.Errorf("line %d: invalid flag: %v", lineNum+1, err) - } - if event.Action, err = strconv.Atoi(parts[12]); err != nil { - return nil, fmt.Errorf("line %d: invalid action: %v", lineNum+1, err) - } - } else { - return nil, fmt.Errorf("line %d: expected 13 fields, got %d", lineNum+1, len(parts)) - } - - events = append(events, event) - } - - return events, nil -} - -// ValidateGestureSequence validates the touch event sequence -func ValidateGestureSequence(events []types.TouchEvent) error { - if len(events) == 0 { - return fmt.Errorf("empty event sequence") - } - - downTime := events[0].DownTime - prevEventTime := int64(0) - - for i, event := range events { - // Check DownTime consistency - if event.DownTime != downTime { - return fmt.Errorf("event %d: DownTime mismatch", i) - } - - // Check EventTime ordering - if event.EventTime < prevEventTime { - return fmt.Errorf("event %d: EventTime should be increasing", i) - } - prevEventTime = event.EventTime - - // Validate action sequence - if i == 0 && event.Action != 0 { - return fmt.Errorf("first event should be ACTION_DOWN") - } - if i == len(events)-1 && event.Action != 1 { - return fmt.Errorf("last event should be ACTION_UP") - } - } - - return nil -} - -func TestTouchByEventsWithOptimizedTimeSystem(t *testing.T) { - device, err := uixt.NewAndroidDevice( - option.WithSerialNumber(""), - ) - if err != nil { - t.Skip("Android device not available") - } - - driver, err := uixt.NewUIA2Driver(device) - if err != nil { - t.Skip("UIA2 driver not available") - } - defer driver.TearDown() - - // Optimized touch event data using EventTime and DownTime - // This represents a swipe gesture from top to bottom - touchEventData := `111586196,401.20703,1191.3164,2,1.0,0.03529412,457.20703,1359.3164,111586196,111586196,1,0,0 -111586236,402.913,1185.0792,2,1.0,0.039215688,458.913,1353.0792,111586196,111586236,1,0,2 -111586250,410.60825,1164.3806,2,1.0,0.03529412,466.60825,1332.3806,111586196,111586250,1,0,2 -111586270,437.7335,1093.1417,2,1.0,0.039215688,493.7335,1261.1417,111586196,111586270,1,0,2 -111586287,463.5786,1018.01746,2,1.0,0.039215688,519.5786,1186.0175,111586196,111586287,1,0,2 -111586304,487.56482,948.9773,2,1.0,0.03529412,543.5648,1116.9773,111586196,111586304,1,0,2 -111586320,511.81476,881.6183,2,1.0,0.039215688,567.81476,1049.6183,111586196,111586320,1,0,2 -111586337,543.4369,811.4982,2,1.0,0.03529412,599.4369,979.4982,111586196,111586337,1,0,2 -111586354,577.1632,747.4512,2,1.0,0.039215688,633.1632,915.4512,111586196,111586354,1,0,2 -111586370,610.1538,691.72034,2,1.0,0.03529412,666.1538,859.72034,111586196,111586370,1,0,2 -111586387,639.1683,642.6914,2,1.0,0.03529412,695.1683,810.6914,111586196,111586387,1,0,2 -111586404,658.9832,605.90857,2,1.0,0.03529412,714.9832,773.90857,111586196,111586404,1,0,2 -111586420,672.21954,581.1634,2,1.0,0.03529412,728.21954,749.1634,111586196,111586420,1,0,2 -111586434,680.7687,566.1778,2,1.0,0.03529412,736.7687,734.1778,111586196,111586434,1,0,2 -111586450,688.0894,554.2295,2,1.0,0.03529412,744.0894,722.2295,111586196,111586450,1,0,2 -111586466,694.542,544.7783,2,1.0,0.03529412,750.542,712.7783,111586196,111586466,1,0,2 -111586483,700.60645,537.2637,2,1.0,0.039215688,756.60645,705.2637,111586196,111586483,1,0,2 -111586500,705.08887,531.1406,2,1.0,0.039215688,761.08887,699.1406,111586196,111586500,1,0,2 -111586517,708.1211,527.8008,2,1.0,0.039215688,764.1211,695.8008,111586196,111586517,1,0,2 -111586533,709.43945,524.46094,2,1.0,0.039215688,765.43945,692.46094,111586196,111586533,1,0,2 -111586537,709.1758,523.34766,2,1.0,0.03529412,765.1758,691.34766,111586196,111586537,1,33554432,2 -111586546,709.1758,523.34766,2,1.0,0.03529412,765.1758,691.34766,111586196,111586546,1,0,1` - - // Parse touch events - events, err := ParseTouchEventsOptimized(touchEventData) - if err != nil { - t.Fatalf("ParseTouchEventsOptimized failed: %v", err) - } - - t.Logf("Parsed %d touch events", len(events)) - - // Validate gesture sequence - if err := ValidateGestureSequence(events); err != nil { - t.Fatalf("Gesture validation failed: %v", err) - } - - // Analyze gesture timing - analyzeGestureTiming(t, events) - - // Execute touch events using optimized time system - err = driver.TouchByEvents(events) - if err != nil { - t.Fatalf("TouchByEvents failed: %v", err) - } - - t.Logf("Successfully executed %d touch events", len(events)) -} - -func TestEventTimeCalculations(t *testing.T) { - // Test data with clear timing relationships - testData := `111586000,100.0,100.0,2,1.0,0.5,100.0,100.0,111586000,111586000,1,0,0 -111586050,120.0,120.0,2,1.0,0.5,120.0,120.0,111586000,111586050,1,0,2 -111586100,140.0,140.0,2,1.0,0.5,140.0,140.0,111586000,111586100,1,0,2 -111586150,160.0,160.0,2,1.0,0.5,160.0,160.0,111586000,111586150,1,0,1` - - events, err := ParseTouchEventsOptimized(testData) - if err != nil { - t.Fatalf("Parse failed: %v", err) - } - - if len(events) != 4 { - t.Fatalf("Expected 4 events, got %d", len(events)) - } - - // Test timing calculations - gestureDuration := events[3].EventTime - events[0].DownTime - expectedDuration := int64(150) // 111586150 - 111586000 - if gestureDuration != expectedDuration { - t.Errorf("Gesture duration: expected %d, got %d", expectedDuration, gestureDuration) - } - - // Test event intervals - expectedIntervals := []int64{50, 50, 50} // Each event is 50ms apart - for i := 1; i < len(events); i++ { - interval := events[i].EventTime - events[i-1].EventTime - if interval != expectedIntervals[i-1] { - t.Errorf("Event %d interval: expected %d, got %d", i, expectedIntervals[i-1], interval) - } - } - - t.Logf("Timing calculations validated successfully") -} - -func TestDownTimeConsistency(t *testing.T) { - // Test data with consistent DownTime - testData := `100000,10.0,10.0,1,1.0,0.5,10.0,10.0,100000,100000,1,0,0 -100020,20.0,20.0,1,1.0,0.5,20.0,20.0,100000,100020,1,0,2 -100040,30.0,30.0,1,1.0,0.5,30.0,30.0,100000,100040,1,0,1` - - events, err := ParseTouchEventsOptimized(testData) - if err != nil { - t.Fatalf("Parse failed: %v", err) - } - - // Verify DownTime consistency - expectedDownTime := int64(100000) - for i, event := range events { - if event.DownTime != expectedDownTime { - t.Errorf("Event %d: DownTime expected %d, got %d", i, expectedDownTime, event.DownTime) - } - } - - // Verify EventTime progression - for i := 1; i < len(events); i++ { - if events[i].EventTime <= events[i-1].EventTime { - t.Errorf("Event %d: EventTime should be greater than previous event", i) - } - } - - t.Logf("DownTime consistency validated successfully") -} - -func TestCoordinateSystem(t *testing.T) { - // Test coordinate priority: rawX/rawY should be used preferentially - testData := `100000,50.0,50.0,1,1.0,0.5,100.0,200.0,100000,100000,1,0,0 -100010,60.0,60.0,1,1.0,0.5,110.0,210.0,100000,100010,1,0,1` - - events, err := ParseTouchEventsOptimized(testData) - if err != nil { - t.Fatalf("Parse failed: %v", err) - } - - // Check that rawX/rawY values are parsed correctly - if events[0].RawX != 100.0 || events[0].RawY != 200.0 { - t.Errorf("Event 0: expected rawX=100.0, rawY=200.0, got rawX=%.1f, rawY=%.1f", - events[0].RawX, events[0].RawY) - } - - if events[1].RawX != 110.0 || events[1].RawY != 210.0 { - t.Errorf("Event 1: expected rawX=110.0, rawY=210.0, got rawX=%.1f, rawY=%.1f", - events[1].RawX, events[1].RawY) - } - - t.Logf("Coordinate system validation passed") -} - -func analyzeGestureTiming(t *testing.T, events []types.TouchEvent) { - if len(events) == 0 { - return - } - - t.Logf("=== Gesture Timing Analysis ===") - t.Logf("Total events: %d", len(events)) - t.Logf("DownTime: %d", events[0].DownTime) - t.Logf("First EventTime: %d", events[0].EventTime) - t.Logf("Last EventTime: %d", events[len(events)-1].EventTime) - - // Calculate total gesture duration using DownTime and final EventTime - totalDuration := events[len(events)-1].EventTime - events[0].DownTime - t.Logf("Total gesture duration: %dms", totalDuration) - - // Calculate average interval between events - if len(events) > 1 { - totalInterval := events[len(events)-1].EventTime - events[0].EventTime - avgInterval := totalInterval / int64(len(events)-1) - t.Logf("Average event interval: %dms", avgInterval) - } - - // Verify DownTime consistency - downTime := events[0].DownTime - consistent := true - for i, event := range events { - if event.DownTime != downTime { - t.Logf("Warning: Event %d has different DownTime: %d vs %d", i, event.DownTime, downTime) - consistent = false - } - } - t.Logf("DownTime consistency: %t", consistent) - - // Action sequence analysis - actions := make([]string, len(events)) - for i, event := range events { - switch event.Action { - case 0: - actions[i] = "DOWN" - case 1: - actions[i] = "UP" - case 2: - actions[i] = "MOVE" - default: - actions[i] = fmt.Sprintf("UNK(%d)", event.Action) - } - } - t.Logf("Action sequence: %v", actions) - t.Logf("================================") -} From dd52ecbe6ad6481828085ede6b1cf77342ed6e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=B3=93=E9=93=AE?= Date: Thu, 17 Jul 2025 14:10:29 +0800 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81wings=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uixt/ai/wings_service.go | 41 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/uixt/ai/wings_service.go b/uixt/ai/wings_service.go index 5457457d..9c0615ee 100644 --- a/uixt/ai/wings_service.go +++ b/uixt/ai/wings_service.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "os" "strings" "time" @@ -14,19 +15,41 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v5/internal/builtin" ) // WingsService implements ILLMService interface using external Wings API type WingsService struct { - apiURL string - bizId string + apiURL string + bizId string + isExternal bool + accessKey string + secretKey string } // NewWingsService creates a new Wings service instance func NewWingsService() ILLMService { + // Check for environment variables for external API access + accessKey := "" + secretKey := "" + isExternal := false + apiURL := "https://vedem-algorithm.bytedance.net/algorithm/StepActionDecision" + + // If environment variables are set, use external API with authentication + if ak, sk := os.Getenv("VEDEM_WINGS_AK"), os.Getenv("VEDEM_WINGS_SK"); ak != "" && sk != "" { + accessKey = ak + secretKey = sk + isExternal = true + apiURL = "https://vedem-algorithm.zijieapi.com/algorithm/StepActionDecision" + } + return &WingsService{ - apiURL: "https://vedem-algorithm.bytedance.net/algorithm/StepActionDecision", - bizId: "489fdae44de048e0922a32834ea668af", + apiURL: apiURL, + bizId: "489fdae44de048e0922a32834ea668af", + isExternal: isExternal, + accessKey: accessKey, + secretKey: secretKey, } } @@ -384,6 +407,16 @@ func (w *WingsService) callWingsAPI(ctx context.Context, request WingsActionRequ httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Accept", "application/json") + // Add authentication headers if using external API + if w.isExternal { + signToken := "UNSIGNED-PAYLOAD" + token := builtin.Sign("auth-v2", w.accessKey, w.secretKey, []byte(signToken)) + + httpReq.Header.Add("Agw-Auth", token) + httpReq.Header.Add("Agw-Auth-Content", signToken) + httpReq.Header.Add("Content-Type", "application/json") + } + // Execute HTTP request client := &http.Client{ Timeout: 30 * time.Second,