From 2569670c7f10b26123f3b686d6b889d0537e386b Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Mon, 26 May 2025 19:39:46 +0800 Subject: [PATCH] feat: implement unified XTDriver cache --- internal/version/VERSION | 2 +- runner.go | 135 +++++-------- step_ui.go | 5 +- uixt/cache.go | 216 ++++++++++++++++++-- uixt/option/device.go | 417 +++++++++++++++++++++++++++++++++++++++ uixt/sdk.go | 43 +--- 6 files changed, 675 insertions(+), 143 deletions(-) create mode 100644 uixt/option/device.go diff --git a/internal/version/VERSION b/internal/version/VERSION index 34009e02..b584c029 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-beta-2505261608 +v5.0.0-beta-2505261939 diff --git a/runner.go b/runner.go index fbb9a027..97dbf94a 100644 --- a/runner.go +++ b/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 { diff --git a/step_ui.go b/step_ui.go index b9d04fc3..a54fc40f 100644 --- a/step_ui.go +++ b/step_ui.go @@ -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 } diff --git a/uixt/cache.go b/uixt/cache.go index 0d4bbb98..91582fa6 100644 --- a/uixt/cache.go +++ b/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 +} diff --git a/uixt/option/device.go b/uixt/option/device.go new file mode 100644 index 00000000..1b1257ae --- /dev/null +++ b/uixt/option/device.go @@ -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 +} diff --git a/uixt/sdk.go b/uixt/sdk.go index caa6b209..f3440f87 100644 --- a/uixt/sdk.go +++ b/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 }