mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-28 02:51:42 +08:00
feat: implement unified XTDriver cache
This commit is contained in:
@@ -1 +1 @@
|
||||
v5.0.0-beta-2505261608
|
||||
v5.0.0-beta-2505261939
|
||||
|
||||
135
runner.go
135
runner.go
@@ -236,13 +236,6 @@ func (r *HRPRunner) Run(testcases ...ITestCase) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// release UI driver session
|
||||
defer func() {
|
||||
for _, client := range caseRunner.uixtDrivers {
|
||||
client.DeleteSession()
|
||||
}
|
||||
}()
|
||||
|
||||
for it := caseRunner.parametersIterator; it.HasNext(); {
|
||||
// case runner can run multiple times with different parameters
|
||||
// each run has its own session runner
|
||||
@@ -286,10 +279,9 @@ func NewCaseRunner(testcase TestCase, hrpRunner *HRPRunner) (*CaseRunner, error)
|
||||
hrpRunner = NewRunner(nil)
|
||||
}
|
||||
caseRunner := &CaseRunner{
|
||||
TestCase: testcase,
|
||||
hrpRunner: hrpRunner,
|
||||
parser: NewParser(),
|
||||
uixtDrivers: make(map[string]*uixt.XTDriver),
|
||||
TestCase: testcase,
|
||||
hrpRunner: hrpRunner,
|
||||
parser: NewParser(),
|
||||
}
|
||||
config := testcase.Config.Get()
|
||||
|
||||
@@ -353,9 +345,6 @@ type CaseRunner struct {
|
||||
parser *Parser // each CaseRunner init its own Parser
|
||||
|
||||
parametersIterator *ParametersIterator
|
||||
|
||||
// UI automation clients for iOS and Android, key is udid/serial
|
||||
uixtDrivers map[string]*uixt.XTDriver
|
||||
}
|
||||
|
||||
func (r *CaseRunner) GetParametersIterator() *ParametersIterator {
|
||||
@@ -446,6 +435,7 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
|
||||
}
|
||||
aiOpts = append(aiOpts, option.WithCVService(parsedConfig.CVService))
|
||||
|
||||
var driverConfigs []uixt.DriverCacheConfig
|
||||
// parse android devices config
|
||||
for _, androidDeviceOptions := range parsedConfig.Android {
|
||||
err := r.parseDeviceConfig(androidDeviceOptions, parsedConfig.Variables)
|
||||
@@ -453,21 +443,12 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
|
||||
return nil, errors.Wrap(code.InvalidCaseError,
|
||||
fmt.Sprintf("parse android config failed: %v", err))
|
||||
}
|
||||
|
||||
device, err := uixt.NewAndroidDevice(androidDeviceOptions.Options()...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init android device failed")
|
||||
}
|
||||
driver, err := device.NewDriver()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init android driver failed")
|
||||
}
|
||||
|
||||
driverExt, err := uixt.NewXTDriver(driver, aiOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init android XTDriver failed")
|
||||
}
|
||||
r.RegisterUIXTDriver(androidDeviceOptions.SerialNumber, driverExt)
|
||||
driverConfigs = append(driverConfigs, uixt.DriverCacheConfig{
|
||||
Platform: "android",
|
||||
Serial: androidDeviceOptions.SerialNumber,
|
||||
AIOptions: aiOpts,
|
||||
DeviceOpts: option.FromAndroidOptions(androidDeviceOptions),
|
||||
})
|
||||
}
|
||||
// parse iOS devices config
|
||||
for _, iosDeviceOptions := range parsedConfig.IOS {
|
||||
@@ -476,21 +457,12 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
|
||||
return nil, errors.Wrap(code.InvalidCaseError,
|
||||
fmt.Sprintf("parse ios config failed: %v", err))
|
||||
}
|
||||
|
||||
device, err := uixt.NewIOSDevice(iosDeviceOptions.Options()...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ios device failed")
|
||||
}
|
||||
driver, err := device.NewDriver()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ios driver failed")
|
||||
}
|
||||
|
||||
driverExt, err := uixt.NewXTDriver(driver, aiOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init ios XTDriver failed")
|
||||
}
|
||||
r.RegisterUIXTDriver(iosDeviceOptions.UDID, driverExt)
|
||||
driverConfigs = append(driverConfigs, uixt.DriverCacheConfig{
|
||||
Platform: "ios",
|
||||
Serial: iosDeviceOptions.UDID,
|
||||
AIOptions: aiOpts,
|
||||
DeviceOpts: option.FromIOSOptions(iosDeviceOptions),
|
||||
})
|
||||
}
|
||||
// parse harmony devices config
|
||||
for _, harmonyDeviceOptions := range parsedConfig.Harmony {
|
||||
@@ -499,21 +471,12 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
|
||||
return nil, errors.Wrap(code.InvalidCaseError,
|
||||
fmt.Sprintf("parse harmony config failed: %v", err))
|
||||
}
|
||||
|
||||
device, err := uixt.NewHarmonyDevice(harmonyDeviceOptions.Options()...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init harmony device failed")
|
||||
}
|
||||
driver, err := device.NewDriver()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init harmony driver failed")
|
||||
}
|
||||
|
||||
driverExt, err := uixt.NewXTDriver(driver, aiOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init harmony XTDriver failed")
|
||||
}
|
||||
r.RegisterUIXTDriver(harmonyDeviceOptions.ConnectKey, driverExt)
|
||||
driverConfigs = append(driverConfigs, uixt.DriverCacheConfig{
|
||||
Platform: "harmony",
|
||||
Serial: harmonyDeviceOptions.ConnectKey,
|
||||
AIOptions: aiOpts,
|
||||
DeviceOpts: option.FromHarmonyOptions(harmonyDeviceOptions),
|
||||
})
|
||||
}
|
||||
// parse browser devices config
|
||||
for _, browserDeviceOptions := range parsedConfig.Browser {
|
||||
@@ -522,26 +485,35 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
|
||||
return nil, errors.Wrap(code.InvalidCaseError,
|
||||
fmt.Sprintf("parse browser config failed: %v", err))
|
||||
}
|
||||
device, err := uixt.NewBrowserDevice(browserDeviceOptions.Options()...)
|
||||
driverConfigs = append(driverConfigs, uixt.DriverCacheConfig{
|
||||
Platform: "browser",
|
||||
Serial: browserDeviceOptions.BrowserID,
|
||||
AIOptions: aiOpts,
|
||||
DeviceOpts: option.FromBrowserOptions(browserDeviceOptions),
|
||||
})
|
||||
}
|
||||
|
||||
// init XTDriver and register to unified cache
|
||||
for _, driverConfig := range driverConfigs {
|
||||
driverExt, err := uixt.GetOrCreateXTDriver(driverConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init browser device failed")
|
||||
return nil, errors.Wrapf(err, "init %s XTDriver failed", driverConfig.Platform)
|
||||
}
|
||||
driver, err := device.NewDriver()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init browser driver failed")
|
||||
if err := r.RegisterUIXTDriver(driverConfig.Serial, driverExt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driverExt, err := uixt.NewXTDriver(driver, aiOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "init browser XTDriver failed")
|
||||
}
|
||||
r.RegisterUIXTDriver(browserDeviceOptions.BrowserID, driverExt)
|
||||
}
|
||||
|
||||
return parsedConfig, nil
|
||||
}
|
||||
|
||||
func (r *CaseRunner) RegisterUIXTDriver(serial string, driver *uixt.XTDriver) {
|
||||
r.uixtDrivers[serial] = driver
|
||||
func (r *CaseRunner) RegisterUIXTDriver(serial string, driver *uixt.XTDriver) error {
|
||||
if err := uixt.RegisterXTDriver(serial, driver); err != nil {
|
||||
log.Error().Err(err).Str("serial", serial).Msg("register XTDriver failed")
|
||||
return err
|
||||
}
|
||||
log.Info().Str("serial", serial).Msg("register XTDriver success")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *CaseRunner) parseDeviceConfig(device interface{}, configVariables map[string]interface{}) error {
|
||||
@@ -580,21 +552,6 @@ func (r *CaseRunner) parseDeviceConfig(device interface{}, configVariables map[s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *CaseRunner) GetUIXTDriver(serial string) (driver *uixt.XTDriver, err error) {
|
||||
for key, driver := range r.uixtDrivers {
|
||||
// return the driver with the same serial
|
||||
if key == serial {
|
||||
return driver, nil
|
||||
}
|
||||
// or return the first driver if serial is empty
|
||||
if serial == "" {
|
||||
r.uixtDrivers[serial] = driver
|
||||
return driver, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("no driver found")
|
||||
}
|
||||
|
||||
// each boomer task initiates a new session
|
||||
// in order to avoid data racing
|
||||
func (r *CaseRunner) NewSession() *SessionRunner {
|
||||
@@ -653,12 +610,14 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCa
|
||||
summary.InOut.ConfigVars = config.Variables
|
||||
|
||||
// TODO: move to mobile ui step
|
||||
for uuid, client := range r.caseRunner.uixtDrivers {
|
||||
// Collect logs from cached drivers
|
||||
for _, cached := range uixt.ListCachedDrivers() {
|
||||
// add WDA/UIA logs to summary
|
||||
logs := map[string]interface{}{
|
||||
"uuid": uuid,
|
||||
"uuid": cached.Serial,
|
||||
}
|
||||
|
||||
client := cached.Driver
|
||||
if client.GetDevice().LogEnabled() {
|
||||
log, err1 := client.StopCaptureLog()
|
||||
if err1 != nil {
|
||||
|
||||
@@ -692,7 +692,10 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err
|
||||
})
|
||||
|
||||
// init wda/uia/hdc driver
|
||||
uiDriver, err := s.caseRunner.GetUIXTDriver(mobileStep.Serial)
|
||||
config := uixt.DriverCacheConfig{
|
||||
Serial: mobileStep.Serial,
|
||||
}
|
||||
uiDriver, err := uixt.GetOrCreateXTDriver(config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
216
uixt/cache.go
216
uixt/cache.go
@@ -2,36 +2,222 @@ package uixt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var driverCache sync.Map // key is serial, value is *XTDriver
|
||||
var driverCache sync.Map // key is serial, value is *CachedXTDriver
|
||||
|
||||
// setupXTDriver initializes an XTDriver based on the platform and serial.
|
||||
func setupXTDriver(_ context.Context, args map[string]any) (*XTDriver, error) {
|
||||
platform, _ := args["platform"].(string)
|
||||
serial, _ := args["serial"].(string)
|
||||
// CachedXTDriver wraps XTDriver with additional cache metadata
|
||||
type CachedXTDriver struct {
|
||||
Platform string
|
||||
Serial string
|
||||
Driver *XTDriver
|
||||
RefCount int32 // reference count for resource management
|
||||
}
|
||||
|
||||
// DriverCacheConfig holds configuration for driver creation
|
||||
type DriverCacheConfig struct {
|
||||
Platform string
|
||||
Serial string
|
||||
AIOptions []option.AIServiceOption
|
||||
DeviceOpts *option.DeviceOptions // unified device options
|
||||
}
|
||||
|
||||
// GetOrCreateXTDriver gets an existing driver from cache or creates a new one
|
||||
func GetOrCreateXTDriver(config DriverCacheConfig) (*XTDriver, error) {
|
||||
cacheKey := config.Serial
|
||||
if cacheKey == "" {
|
||||
return nil, fmt.Errorf("serial cannot be empty")
|
||||
}
|
||||
|
||||
// Check if driver exists in cache
|
||||
if cachedItem, ok := driverCache.Load(cacheKey); ok {
|
||||
if cached, ok := cachedItem.(*CachedXTDriver); ok {
|
||||
log.Info().Str("serial", cached.Serial).Msg("Using cached XTDriver")
|
||||
|
||||
// Increment reference count
|
||||
cached.RefCount++
|
||||
return cached.Driver, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create new driver
|
||||
driverExt, err := createXTDriverWithConfig(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create XTDriver: %w", err)
|
||||
}
|
||||
|
||||
// Cache the driver
|
||||
cached := &CachedXTDriver{
|
||||
Platform: config.Platform,
|
||||
Driver: driverExt,
|
||||
Serial: config.Serial,
|
||||
RefCount: 1,
|
||||
}
|
||||
driverCache.Store(cacheKey, cached)
|
||||
|
||||
log.Info().
|
||||
Str("platform", config.Platform).
|
||||
Str("serial", config.Serial).
|
||||
Msg("Created and cached new XTDriver")
|
||||
|
||||
return driverExt, nil
|
||||
}
|
||||
|
||||
// createXTDriverWithConfig creates a new XTDriver based on configuration
|
||||
func createXTDriverWithConfig(config DriverCacheConfig) (*XTDriver, error) {
|
||||
platform := config.Platform
|
||||
if platform == "" {
|
||||
log.Warn().Msg("platform is not set, using android as default")
|
||||
platform = "android"
|
||||
}
|
||||
|
||||
// Check if driver exists in cache
|
||||
cacheKey := serial
|
||||
if cachedDriver, ok := driverCache.Load(cacheKey); ok {
|
||||
if driverExt, ok := cachedDriver.(*XTDriver); ok {
|
||||
log.Info().Str("platform", platform).Str("serial", serial).Msg("Using cached driver")
|
||||
return driverExt, nil
|
||||
if config.Serial == "" {
|
||||
return nil, fmt.Errorf("serial is empty")
|
||||
}
|
||||
|
||||
// Create device based on platform and configuration
|
||||
var device IDevice
|
||||
var err error
|
||||
|
||||
// Try to create device with specific options first
|
||||
if config.DeviceOpts != nil {
|
||||
switch strings.ToLower(platform) {
|
||||
case "android":
|
||||
androidOpts := config.DeviceOpts.ToAndroidOptions().Options()
|
||||
device, err = NewAndroidDevice(androidOpts...)
|
||||
case "ios":
|
||||
iosOpts := config.DeviceOpts.ToIOSOptions().Options()
|
||||
device, err = NewIOSDevice(iosOpts...)
|
||||
case "harmony":
|
||||
harmonyOpts := config.DeviceOpts.ToHarmonyOptions().Options()
|
||||
device, err = NewHarmonyDevice(harmonyOpts...)
|
||||
case "browser":
|
||||
browserOpts := config.DeviceOpts.ToBrowserOptions().Options()
|
||||
device, err = NewBrowserDevice(browserOpts...)
|
||||
}
|
||||
} else {
|
||||
device, err = NewDeviceWithDefault(platform, config.Serial)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create device: %w", err)
|
||||
}
|
||||
|
||||
// Create driver
|
||||
driver, err := device.NewDriver()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create driver: %w", err)
|
||||
}
|
||||
|
||||
// Create XTDriver with AI options
|
||||
aiOpts := config.AIOptions
|
||||
if len(aiOpts) == 0 {
|
||||
// Default AI options
|
||||
aiOpts = []option.AIServiceOption{
|
||||
option.WithCVService(option.CVServiceTypeVEDEM),
|
||||
}
|
||||
}
|
||||
|
||||
driverExt, err := NewXTDriverWithDefault(platform, serial)
|
||||
driverExt, err := NewXTDriver(driver, aiOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to create XTDriver: %w", err)
|
||||
}
|
||||
// store driver in cache
|
||||
driverCache.Store(cacheKey, driverExt)
|
||||
return driverExt, nil
|
||||
}
|
||||
|
||||
// ReleaseXTDriver decrements reference count and removes from cache when count reaches zero
|
||||
func ReleaseXTDriver(serial string) error {
|
||||
if cachedItem, ok := driverCache.Load(serial); ok {
|
||||
if cached, ok := cachedItem.(*CachedXTDriver); ok {
|
||||
cached.RefCount--
|
||||
log.Debug().
|
||||
Str("serial", serial).
|
||||
Int32("refCount", cached.RefCount).
|
||||
Msg("Released XTDriver reference")
|
||||
|
||||
// If no more references, clean up and remove from cache
|
||||
if cached.RefCount <= 0 {
|
||||
driverCache.Delete(serial)
|
||||
|
||||
// Clean up driver resources
|
||||
if err := cached.Driver.DeleteSession(); err != nil {
|
||||
log.Warn().Err(err).Str("serial", serial).Msg("Failed to delete driver session")
|
||||
}
|
||||
|
||||
log.Info().Str("serial", serial).Msg("Cleaned up XTDriver from cache")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupAllDrivers cleans up all cached drivers
|
||||
func CleanupAllDrivers() {
|
||||
driverCache.Range(func(key, value interface{}) bool {
|
||||
if serial, ok := key.(string); ok {
|
||||
if cached, ok := value.(*CachedXTDriver); ok {
|
||||
// Clean up driver resources
|
||||
if err := cached.Driver.DeleteSession(); err != nil {
|
||||
log.Warn().Err(err).Str("serial", serial).Msg("Failed to delete driver session")
|
||||
}
|
||||
log.Info().Str("serial", serial).Msg("Cleaned up XTDriver from cache")
|
||||
}
|
||||
driverCache.Delete(serial)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// ListCachedDrivers returns information about all cached drivers
|
||||
func ListCachedDrivers() []CachedXTDriver {
|
||||
var drivers []CachedXTDriver
|
||||
driverCache.Range(func(key, value interface{}) bool {
|
||||
if cached, ok := value.(*CachedXTDriver); ok {
|
||||
drivers = append(drivers, *cached)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return drivers
|
||||
}
|
||||
|
||||
// setupXTDriver initializes an XTDriver based on the platform and serial.
|
||||
// This function is kept for backward compatibility with MCP integration
|
||||
func setupXTDriver(_ context.Context, args map[string]any) (*XTDriver, error) {
|
||||
platform, _ := args["platform"].(string)
|
||||
serial, _ := args["serial"].(string)
|
||||
|
||||
config := DriverCacheConfig{
|
||||
Platform: platform,
|
||||
Serial: serial,
|
||||
}
|
||||
|
||||
return GetOrCreateXTDriver(config)
|
||||
}
|
||||
|
||||
// RegisterXTDriver registers an externally created XTDriver to the unified cache
|
||||
func RegisterXTDriver(serial string, driver *XTDriver) error {
|
||||
if serial == "" {
|
||||
return fmt.Errorf("serial cannot be empty")
|
||||
}
|
||||
if driver == nil {
|
||||
return fmt.Errorf("driver cannot be nil")
|
||||
}
|
||||
|
||||
cached := &CachedXTDriver{
|
||||
Driver: driver,
|
||||
Serial: serial,
|
||||
RefCount: 1,
|
||||
}
|
||||
driverCache.Store(serial, cached)
|
||||
|
||||
log.Info().
|
||||
Str("serial", serial).
|
||||
Msg("Registered external XTDriver to unified cache")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
417
uixt/option/device.go
Normal file
417
uixt/option/device.go
Normal file
@@ -0,0 +1,417 @@
|
||||
package option
|
||||
|
||||
// DeviceOptions unified device options for all platforms using composition
|
||||
type DeviceOptions struct {
|
||||
// Common fields
|
||||
Platform string `json:"platform,omitempty" yaml:"platform,omitempty"`
|
||||
|
||||
// Embedded platform-specific options
|
||||
*AndroidDeviceOptions `json:"android,omitempty" yaml:"android,omitempty"`
|
||||
*IOSDeviceOptions `json:"ios,omitempty" yaml:"ios,omitempty"`
|
||||
*HarmonyDeviceOptions `json:"harmony,omitempty" yaml:"harmony,omitempty"`
|
||||
*BrowserDeviceOptions `json:"browser,omitempty" yaml:"browser,omitempty"`
|
||||
}
|
||||
|
||||
// DeviceOption unified device option function
|
||||
type DeviceOption func(*DeviceOptions)
|
||||
|
||||
// NewDeviceOptions creates a new DeviceOptions with given options
|
||||
func NewDeviceOptions(opts ...DeviceOption) *DeviceOptions {
|
||||
config := &DeviceOptions{
|
||||
AndroidDeviceOptions: &AndroidDeviceOptions{},
|
||||
IOSDeviceOptions: &IOSDeviceOptions{},
|
||||
HarmonyDeviceOptions: &HarmonyDeviceOptions{},
|
||||
BrowserDeviceOptions: &BrowserDeviceOptions{},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
// Apply defaults based on platform
|
||||
config.applyDefaults()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Unified DeviceOption functions
|
||||
|
||||
// WithPlatform sets the platform
|
||||
func WithPlatform(platform string) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
device.Platform = platform
|
||||
}
|
||||
}
|
||||
|
||||
// WithDeviceLogOn sets log on for any platform
|
||||
func WithDeviceLogOn(logOn bool) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
// Set LogOn for all platform options to avoid ambiguity
|
||||
if device.AndroidDeviceOptions != nil {
|
||||
device.AndroidDeviceOptions.LogOn = logOn
|
||||
}
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.LogOn = logOn
|
||||
}
|
||||
if device.HarmonyDeviceOptions != nil {
|
||||
device.HarmonyDeviceOptions.LogOn = logOn
|
||||
}
|
||||
if device.BrowserDeviceOptions != nil {
|
||||
device.BrowserDeviceOptions.LogOn = logOn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Android unified options
|
||||
func WithDeviceSerialNumber(serial string) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.AndroidDeviceOptions != nil {
|
||||
device.AndroidDeviceOptions.SerialNumber = serial
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "android"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceUIA2(uia2On bool) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.AndroidDeviceOptions != nil {
|
||||
device.AndroidDeviceOptions.UIA2 = uia2On
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "android"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceUIA2IP(ip string) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.AndroidDeviceOptions != nil {
|
||||
device.AndroidDeviceOptions.UIA2IP = ip
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "android"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceUIA2Port(port int) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.AndroidDeviceOptions != nil {
|
||||
device.AndroidDeviceOptions.UIA2Port = port
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "android"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iOS unified options
|
||||
func WithDeviceUDID(udid string) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.UDID = udid
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "ios"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceWireless(on bool) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.Wireless = on
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "ios"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceWDAPort(port int) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.WDAPort = port
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "ios"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceWDAMjpegPort(port int) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.WDAMjpegPort = port
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "ios"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceLazySetup(lazySetup bool) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.LazySetup = lazySetup
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "ios"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceResetHomeOnStartup(reset bool) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.ResetHomeOnStartup = reset
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "ios"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceSnapshotMaxDepth(depth int) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.SnapshotMaxDepth = depth
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "ios"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceAcceptAlertButtonSelector(selector string) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.AcceptAlertButtonSelector = selector
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "ios"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceDismissAlertButtonSelector(selector string) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.IOSDeviceOptions != nil {
|
||||
device.IOSDeviceOptions.DismissAlertButtonSelector = selector
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "ios"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Harmony unified options
|
||||
func WithDeviceConnectKey(connectKey string) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.HarmonyDeviceOptions != nil {
|
||||
device.HarmonyDeviceOptions.ConnectKey = connectKey
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "harmony"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Browser unified options
|
||||
func WithDeviceBrowserID(browserID string) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.BrowserDeviceOptions != nil {
|
||||
device.BrowserDeviceOptions.BrowserID = browserID
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceBrowserPageSize(width, height int) DeviceOption {
|
||||
return func(device *DeviceOptions) {
|
||||
if device.BrowserDeviceOptions != nil {
|
||||
device.BrowserDeviceOptions.Width = width
|
||||
device.BrowserDeviceOptions.Height = height
|
||||
}
|
||||
if device.Platform == "" {
|
||||
device.Platform = "browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setAndroidDefaults applies Android platform defaults
|
||||
func (d *DeviceOptions) setAndroidDefaults() {
|
||||
if d.AndroidDeviceOptions != nil {
|
||||
// Apply defaults using existing NewAndroidDeviceOptions logic
|
||||
d.AndroidDeviceOptions = NewAndroidDeviceOptions(d.AndroidDeviceOptions.Options()...)
|
||||
}
|
||||
}
|
||||
|
||||
// setIOSDefaults applies iOS platform defaults
|
||||
func (d *DeviceOptions) setIOSDefaults() {
|
||||
if d.IOSDeviceOptions != nil {
|
||||
// Apply defaults using existing NewIOSDeviceOptions logic
|
||||
d.IOSDeviceOptions = NewIOSDeviceOptions(d.IOSDeviceOptions.Options()...)
|
||||
}
|
||||
}
|
||||
|
||||
// setHarmonyDefaults applies Harmony platform defaults
|
||||
func (d *DeviceOptions) setHarmonyDefaults() {
|
||||
if d.HarmonyDeviceOptions != nil {
|
||||
// Apply defaults using existing NewHarmonyDeviceOptions logic
|
||||
d.HarmonyDeviceOptions = NewHarmonyDeviceOptions(d.HarmonyDeviceOptions.Options()...)
|
||||
}
|
||||
}
|
||||
|
||||
// setBrowserDefaults applies Browser platform defaults
|
||||
func (d *DeviceOptions) setBrowserDefaults() {
|
||||
if d.BrowserDeviceOptions != nil {
|
||||
// Apply defaults using existing NewBrowserDeviceOptions logic
|
||||
d.BrowserDeviceOptions = NewBrowserDeviceOptions(d.BrowserDeviceOptions.Options()...)
|
||||
}
|
||||
}
|
||||
|
||||
// applyDefaults applies platform-specific defaults based on the Platform field
|
||||
func (d *DeviceOptions) applyDefaults() {
|
||||
switch d.Platform {
|
||||
case "android":
|
||||
d.setAndroidDefaults()
|
||||
case "ios":
|
||||
d.setIOSDefaults()
|
||||
case "harmony":
|
||||
d.setHarmonyDefaults()
|
||||
case "browser":
|
||||
d.setBrowserDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
// GetSerial returns the appropriate serial/identifier for the platform
|
||||
func (d *DeviceOptions) GetSerial() string {
|
||||
switch d.Platform {
|
||||
case "android":
|
||||
if d.AndroidDeviceOptions != nil {
|
||||
return d.AndroidDeviceOptions.SerialNumber
|
||||
}
|
||||
case "ios":
|
||||
if d.IOSDeviceOptions != nil {
|
||||
return d.IOSDeviceOptions.UDID
|
||||
}
|
||||
case "harmony":
|
||||
if d.HarmonyDeviceOptions != nil {
|
||||
return d.HarmonyDeviceOptions.ConnectKey
|
||||
}
|
||||
case "browser":
|
||||
if d.BrowserDeviceOptions != nil {
|
||||
return d.BrowserDeviceOptions.BrowserID
|
||||
}
|
||||
}
|
||||
return "" // fallback
|
||||
}
|
||||
|
||||
// GetPlatformOptions returns platform-specific options slice
|
||||
func (d *DeviceOptions) GetPlatformOptions() interface{} {
|
||||
switch d.Platform {
|
||||
case "android":
|
||||
return d.ToAndroidOptions().Options()
|
||||
case "ios":
|
||||
return d.ToIOSOptions().Options()
|
||||
case "harmony":
|
||||
return d.ToHarmonyOptions().Options()
|
||||
case "browser":
|
||||
return d.ToBrowserOptions().Options()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ToAndroidOptions converts to AndroidDeviceOptions for backward compatibility
|
||||
func (d *DeviceOptions) ToAndroidOptions() *AndroidDeviceOptions {
|
||||
if d.AndroidDeviceOptions != nil {
|
||||
return d.AndroidDeviceOptions
|
||||
}
|
||||
return &AndroidDeviceOptions{}
|
||||
}
|
||||
|
||||
// ToIOSOptions converts to IOSDeviceOptions for backward compatibility
|
||||
func (d *DeviceOptions) ToIOSOptions() *IOSDeviceOptions {
|
||||
if d.IOSDeviceOptions != nil {
|
||||
return d.IOSDeviceOptions
|
||||
}
|
||||
return &IOSDeviceOptions{}
|
||||
}
|
||||
|
||||
// ToHarmonyOptions converts to HarmonyDeviceOptions for backward compatibility
|
||||
func (d *DeviceOptions) ToHarmonyOptions() *HarmonyDeviceOptions {
|
||||
if d.HarmonyDeviceOptions != nil {
|
||||
return d.HarmonyDeviceOptions
|
||||
}
|
||||
return &HarmonyDeviceOptions{}
|
||||
}
|
||||
|
||||
// ToBrowserOptions converts to BrowserDeviceOptions for backward compatibility
|
||||
func (d *DeviceOptions) ToBrowserOptions() *BrowserDeviceOptions {
|
||||
if d.BrowserDeviceOptions != nil {
|
||||
return d.BrowserDeviceOptions
|
||||
}
|
||||
return &BrowserDeviceOptions{}
|
||||
}
|
||||
|
||||
// FromAndroidOptions creates DeviceOptions from AndroidDeviceOptions
|
||||
func FromAndroidOptions(opts *AndroidDeviceOptions) *DeviceOptions {
|
||||
config := &DeviceOptions{
|
||||
Platform: "android",
|
||||
AndroidDeviceOptions: opts,
|
||||
IOSDeviceOptions: &IOSDeviceOptions{},
|
||||
HarmonyDeviceOptions: &HarmonyDeviceOptions{},
|
||||
BrowserDeviceOptions: &BrowserDeviceOptions{},
|
||||
}
|
||||
// Apply defaults
|
||||
config.applyDefaults()
|
||||
return config
|
||||
}
|
||||
|
||||
// FromIOSOptions creates DeviceOptions from IOSDeviceOptions
|
||||
func FromIOSOptions(opts *IOSDeviceOptions) *DeviceOptions {
|
||||
config := &DeviceOptions{
|
||||
Platform: "ios",
|
||||
AndroidDeviceOptions: &AndroidDeviceOptions{},
|
||||
IOSDeviceOptions: opts,
|
||||
HarmonyDeviceOptions: &HarmonyDeviceOptions{},
|
||||
BrowserDeviceOptions: &BrowserDeviceOptions{},
|
||||
}
|
||||
// Apply defaults
|
||||
config.applyDefaults()
|
||||
return config
|
||||
}
|
||||
|
||||
// FromHarmonyOptions creates DeviceOptions from HarmonyDeviceOptions
|
||||
func FromHarmonyOptions(opts *HarmonyDeviceOptions) *DeviceOptions {
|
||||
config := &DeviceOptions{
|
||||
Platform: "harmony",
|
||||
AndroidDeviceOptions: &AndroidDeviceOptions{},
|
||||
IOSDeviceOptions: &IOSDeviceOptions{},
|
||||
HarmonyDeviceOptions: opts,
|
||||
BrowserDeviceOptions: &BrowserDeviceOptions{},
|
||||
}
|
||||
// Apply defaults
|
||||
config.applyDefaults()
|
||||
return config
|
||||
}
|
||||
|
||||
// FromBrowserOptions creates DeviceOptions from BrowserDeviceOptions
|
||||
func FromBrowserOptions(opts *BrowserDeviceOptions) *DeviceOptions {
|
||||
config := &DeviceOptions{
|
||||
Platform: "browser",
|
||||
AndroidDeviceOptions: &AndroidDeviceOptions{},
|
||||
IOSDeviceOptions: &IOSDeviceOptions{},
|
||||
HarmonyDeviceOptions: &HarmonyDeviceOptions{},
|
||||
BrowserDeviceOptions: opts,
|
||||
}
|
||||
// Apply defaults
|
||||
config.applyDefaults()
|
||||
return config
|
||||
}
|
||||
43
uixt/sdk.go
43
uixt/sdk.go
@@ -113,31 +113,6 @@ func (dExt *XTDriver) ExecuteAction(action MobileAction) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewXTDriverWithDefault is a helper function to create a XTDriver with default options
|
||||
func NewXTDriverWithDefault(platform, serial string) (*XTDriver, error) {
|
||||
device, err := NewDeviceWithDefault(platform, serial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// init driver
|
||||
driver, err := device.NewDriver()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init driver failed: %w", err)
|
||||
}
|
||||
if err := driver.Setup(); err != nil {
|
||||
return nil, fmt.Errorf("setup driver failed: %w", err)
|
||||
}
|
||||
|
||||
// init XTDriver
|
||||
driverExt, err := NewXTDriver(driver,
|
||||
option.WithCVService(option.CVServiceTypeVEDEM))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init XT driver failed: %w", err)
|
||||
}
|
||||
return driverExt, nil
|
||||
}
|
||||
|
||||
// NewDeviceWithDefault is a helper function to create a device with default options
|
||||
func NewDeviceWithDefault(platform, serial string) (device IDevice, err error) {
|
||||
if serial == "" {
|
||||
@@ -146,11 +121,7 @@ func NewDeviceWithDefault(platform, serial string) (device IDevice, err error) {
|
||||
|
||||
switch strings.ToLower(platform) {
|
||||
case "android":
|
||||
device, err = NewAndroidDevice(
|
||||
option.WithSerialNumber(serial))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
device, err = NewAndroidDevice(option.WithSerialNumber(serial))
|
||||
case "ios":
|
||||
device, err = NewIOSDevice(
|
||||
option.WithUDID(serial),
|
||||
@@ -158,17 +129,13 @@ func NewDeviceWithDefault(platform, serial string) (device IDevice, err error) {
|
||||
option.WithWDAMjpegPort(8800),
|
||||
option.WithResetHomeOnStartup(false),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "browser":
|
||||
device, err = NewBrowserDevice(option.WithBrowserID(serial))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "harmony":
|
||||
device, err = NewHarmonyDevice(option.WithConnectKey(serial))
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid platform: %s", platform)
|
||||
return nil, fmt.Errorf("unsupported platform: %s", platform)
|
||||
}
|
||||
|
||||
return device, nil
|
||||
return device, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user