mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-09 22:44:05 +08:00
Merge branch 'session_refactor' into 'master'
Session refactor See merge request iesqa/httprunner!128
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -428,6 +428,131 @@ 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]
|
||||
|
||||
// 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,
|
||||
startX, startY, endX, endY)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer postHandler(ud, option.ACTION_SwipeCoordinate, actionOptions)
|
||||
|
||||
var actions []interface{}
|
||||
var prevEventTime int64
|
||||
|
||||
for i, event := range events {
|
||||
var duration float64
|
||||
if i > 0 {
|
||||
// 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
|
||||
}
|
||||
|
||||
// Apply coordinate transformation if it's the first or last event
|
||||
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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ type IDevice interface {
|
||||
UUID() string
|
||||
NewDriver() (driver IDriver, err error)
|
||||
|
||||
IsHealthy() (bool, error)
|
||||
|
||||
Setup() error
|
||||
Teardown() error
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -225,3 +225,19 @@ const (
|
||||
DirectionLeft Direction = "left"
|
||||
DirectionRight Direction = "right"
|
||||
)
|
||||
|
||||
// TouchEvent represents a single touch event with all its properties
|
||||
type TouchEvent struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user