diff --git a/app-runner/Private/DeviceProviders/AdbProvider.ps1 b/app-runner/Private/DeviceProviders/AdbProvider.ps1 index 3d6ce28..b5ea42c 100644 --- a/app-runner/Private/DeviceProviders/AdbProvider.ps1 +++ b/app-runner/Private/DeviceProviders/AdbProvider.ps1 @@ -420,12 +420,12 @@ class AdbProvider : DeviceProvider { return $this.DeviceSerial } - [void] StartDevice() { - Write-Warning "$($this.Platform): StartDevice is not supported for ADB devices" + [DevicePowerResult] StartDevice() { + return [DevicePowerResult]::NotSupported } - [void] StopDevice() { - Write-Warning "$($this.Platform): StopDevice is not supported for ADB devices" + [DevicePowerResult] StopDevice() { + return [DevicePowerResult]::NotSupported } [void] RestartDevice() { diff --git a/app-runner/Private/DeviceProviders/DeviceProvider.ps1 b/app-runner/Private/DeviceProviders/DeviceProvider.ps1 index 92e27b4..ad4f165 100644 --- a/app-runner/Private/DeviceProviders/DeviceProvider.ps1 +++ b/app-runner/Private/DeviceProviders/DeviceProvider.ps1 @@ -1,6 +1,12 @@ # Device Provider Base Class # Provides shared functionality for all device providers +enum DevicePowerResult { + NotSupported + PoweredOn + PoweredOff + Failed +} <# .SYNOPSIS @@ -234,8 +240,11 @@ class DeviceProvider { } [object] InvokeCommand([string]$action, [object[]]$parameters) { - # Build command once and check if it's a no-op $builtCommand = $this.BuildCommand($action, $parameters) + return $this.InvokeBuiltCommand($action, $builtCommand) + } + + [object] InvokeBuiltCommand([string]$action, [BuiltCommand]$builtCommand) { if ($builtCommand.IsNoOp()) { return $null } @@ -348,16 +357,32 @@ class DeviceProvider { } # Device lifecycle management (shared implementation) - [void] StartDevice() { + [DevicePowerResult] StartDevice() { Write-Debug "$($this.Platform): Starting device" - $this.InvokeCommand('poweron', @()) + if ($this.Commands.ContainsKey('poweron') -and $null -eq $this.Commands['poweron']) { + return [DevicePowerResult]::NotSupported + } + $command = $this.BuildCommand('poweron', @()) + if ($command.IsNoOp()) { + return [DevicePowerResult]::NotSupported + } + $this.InvokeBuiltCommand('poweron', $command) + return [DevicePowerResult]::PoweredOn } - [void] StopDevice() { + [DevicePowerResult] StopDevice() { Write-Debug "$($this.Platform): Stopping device" - Write-Output $this.InvokeCommand('poweroff', @()) + if ($this.Commands.ContainsKey('poweroff') -and $null -eq $this.Commands['poweroff']) { + return [DevicePowerResult]::NotSupported + } + $command = $this.BuildCommand('poweroff', @()) + if ($command.IsNoOp()) { + return [DevicePowerResult]::NotSupported + } + $this.InvokeBuiltCommand('poweroff', $command) + return [DevicePowerResult]::PoweredOff } [void] RestartDevice() { diff --git a/app-runner/Private/DeviceProviders/MockDeviceProvider.ps1 b/app-runner/Private/DeviceProviders/MockDeviceProvider.ps1 index 40c4033..f93fff6 100644 --- a/app-runner/Private/DeviceProviders/MockDeviceProvider.ps1 +++ b/app-runner/Private/DeviceProviders/MockDeviceProvider.ps1 @@ -137,15 +137,17 @@ class MockDeviceProvider : DeviceProvider { return $this.IsConnected } - [void] StartDevice() { + [DevicePowerResult] StartDevice() { Write-Debug "Mock: Starting device" $this.MockConfig.PowerState = "On" + return [DevicePowerResult]::PoweredOn } - [void] StopDevice() { + [DevicePowerResult] StopDevice() { Write-Debug "Mock: Stopping device" $this.MockConfig.PowerState = "Off" $this.MockConfig.AppRunning = $false + return [DevicePowerResult]::PoweredOff } [void] RestartDevice() { diff --git a/app-runner/Private/DeviceProviders/SauceLabsProvider.ps1 b/app-runner/Private/DeviceProviders/SauceLabsProvider.ps1 index 59f27f8..e9939c7 100644 --- a/app-runner/Private/DeviceProviders/SauceLabsProvider.ps1 +++ b/app-runner/Private/DeviceProviders/SauceLabsProvider.ps1 @@ -639,12 +639,12 @@ class SauceLabsProvider : DeviceProvider { return $this.DeviceName } - [void] StartDevice() { - Write-Warning "$($this.Platform): StartDevice is not applicable for SauceLabs cloud devices" + [DevicePowerResult] StartDevice() { + return [DevicePowerResult]::NotSupported } - [void] StopDevice() { - Write-Warning "$($this.Platform): StopDevice is not applicable for SauceLabs cloud devices" + [DevicePowerResult] StopDevice() { + return [DevicePowerResult]::NotSupported } [void] RestartDevice() { diff --git a/app-runner/Private/DeviceProviders/SwitchProvider.ps1 b/app-runner/Private/DeviceProviders/SwitchProvider.ps1 index 5e0c819..796e654 100644 --- a/app-runner/Private/DeviceProviders/SwitchProvider.ps1 +++ b/app-runner/Private/DeviceProviders/SwitchProvider.ps1 @@ -137,9 +137,12 @@ class SwitchProvider : DeviceProvider { } # override StopDevice because it exits immediately but actually takes a while to finish - [void] StopDevice() { - ([DeviceProvider] $this).StopDevice(); - Start-Sleep -Seconds 3 + [DevicePowerResult] StopDevice() { + $result = ([DeviceProvider] $this).StopDevice() + if ($result -eq [DevicePowerResult]::PoweredOff) { + Start-Sleep -Seconds 3 + } + return $result } # Override TakeScreenshot to pass --target when a target is specified diff --git a/app-runner/Private/DeviceProviders/XboxProvider.ps1 b/app-runner/Private/DeviceProviders/XboxProvider.ps1 index 321a1e8..e887b9f 100644 --- a/app-runner/Private/DeviceProviders/XboxProvider.ps1 +++ b/app-runner/Private/DeviceProviders/XboxProvider.ps1 @@ -91,18 +91,20 @@ class XboxProvider : DeviceProvider { } # Override StartDevice to support connected standby - [void] StartDevice() { + [DevicePowerResult] StartDevice() { Write-Debug "$($this.Platform): Starting device" $this.InvokePowerOn() + return [DevicePowerResult]::PoweredOn } # Override StopDevice to support connected standby - [void] StopDevice() { + [DevicePowerResult] StopDevice() { $powerState = $this.InvokeCommand('powerState', @()) if ($powerState -match 'Allows Instant On') { $this.InvokeCommand('sleep', @()) + return [DevicePowerResult]::PoweredOff } else { - $this.InvokeCommand('poweroff', @()) + return ([DeviceProvider] $this).StopDevice() } } diff --git a/app-runner/Private/DeviceProviders/iOSSimulatorProvider.ps1 b/app-runner/Private/DeviceProviders/iOSSimulatorProvider.ps1 index 7c676bd..06bb1d4 100644 --- a/app-runner/Private/DeviceProviders/iOSSimulatorProvider.ps1 +++ b/app-runner/Private/DeviceProviders/iOSSimulatorProvider.ps1 @@ -398,26 +398,32 @@ class iOSSimulatorProvider : DeviceProvider { return $this.SimulatorUUID } - [void] StartDevice() { + [DevicePowerResult] StartDevice() { Write-Debug "$($this.Platform): Starting simulator" if ($this.SimulatorUUID) { $this.BootSimulator() + return [DevicePowerResult]::PoweredOn } else { Write-Warning "$($this.Platform): No simulator selected. Call Connect() first." + return [DevicePowerResult]::Failed } } - [void] StopDevice() { + [DevicePowerResult] StopDevice() { Write-Debug "$($this.Platform): Stopping simulator" if ($this.SimulatorUUID) { try { $this.InvokeCommand('shutdown', @($this.SimulatorUUID)) + return [DevicePowerResult]::PoweredOff } catch { Write-Warning "Failed to shutdown simulator: $_" } + } else { + Write-Warning "$($this.Platform): No simulator selected. Call Connect() first." } + return [DevicePowerResult]::Failed } [void] RestartDevice() { diff --git a/app-runner/Public/Disconnect-Device.ps1 b/app-runner/Public/Disconnect-Device.ps1 index 9a2c406..9baacbf 100644 --- a/app-runner/Public/Disconnect-Device.ps1 +++ b/app-runner/Public/Disconnect-Device.ps1 @@ -37,8 +37,11 @@ function Disconnect-Device { # Power off device first if requested if ($PowerOff) { Write-Debug "Powering off device before disconnect" - $provider.StopDevice() - Write-Output "Device powered off" + switch ($provider.StopDevice()) { + ([DevicePowerResult]::PoweredOff) { Write-Output "Device powered off" } + ([DevicePowerResult]::NotSupported) { Write-Output "Device power off not supported. Skipped." } + ([DevicePowerResult]::Failed) { Write-Warning "Device power off failed." } + } } $provider.Disconnect() } finally { @@ -56,4 +59,4 @@ function Disconnect-Device { Write-Debug "Successfully disconnected from $platform device" Write-Output "Disconnected from device" } -} \ No newline at end of file +} diff --git a/app-runner/Public/Start-Device.ps1 b/app-runner/Public/Start-Device.ps1 index f73bd42..5128890 100644 --- a/app-runner/Public/Start-Device.ps1 +++ b/app-runner/Public/Start-Device.ps1 @@ -21,7 +21,9 @@ function Start-Device { # Use the provider to start the device $provider = $script:CurrentSession.Provider - $provider.StartDevice() - - Write-Output "Device started successfully" -} \ No newline at end of file + switch ($provider.StartDevice()) { + ([DevicePowerResult]::PoweredOn) { Write-Output "Device started successfully" } + ([DevicePowerResult]::NotSupported) { Write-Output "Device power on not supported. Skipped." } + ([DevicePowerResult]::Failed) { Write-Warning "Device power on failed." } + } +} diff --git a/app-runner/Tests/PowerReporting.Tests.ps1 b/app-runner/Tests/PowerReporting.Tests.ps1 new file mode 100644 index 0000000..28faa59 --- /dev/null +++ b/app-runner/Tests/PowerReporting.Tests.ps1 @@ -0,0 +1,76 @@ +$ErrorActionPreference = 'Stop' + +BeforeAll { + Import-Module "$PSScriptRoot\..\SentryAppRunner.psd1" -Force +} + +AfterAll { + Remove-Module SentryAppRunner -Force -ErrorAction SilentlyContinue +} + +Describe 'Device power reporting' -Tag 'Unit' { + BeforeEach { + $script:provider = [PSCustomObject]@{ + StartResult = $null + StopResult = $null + Disconnected = $false + } + $script:provider | Add-Member ScriptMethod StartDevice { return $this.StartResult } + $script:provider | Add-Member ScriptMethod StopDevice { return $this.StopResult } + $script:provider | Add-Member ScriptMethod TestConnection { return $true } + $script:provider | Add-Member ScriptMethod Disconnect { $this.Disconnected = $true } + + & (Get-Module SentryAppRunner) { + param($provider) + $script:CurrentSession = @{ + Provider = $provider + Platform = 'Test' + Identifier = 'test device' + Mutex = $null + } + } $script:provider + } + + AfterEach { + & (Get-Module SentryAppRunner) { $script:CurrentSession = $null } + } + + It 'Reports power operations' -TestCases @( + @{ + Name = 'failed'; StartResult = 'Failed'; StopResult = 'Failed' + StartMessage = 'Device power on failed.'; StopMessage = 'Device power off failed.' + OutputType = [System.Management.Automation.WarningRecord] + } + @{ + Name = 'unsupported'; StartResult = 'NotSupported'; StopResult = 'NotSupported' + StartMessage = 'Device power on not supported. Skipped.'; StopMessage = 'Device power off not supported. Skipped.' + OutputType = [string] + } + @{ + Name = 'successful'; StartResult = 'PoweredOn'; StopResult = 'PoweredOff' + StartMessage = 'Device started successfully'; StopMessage = 'Device powered off' + OutputType = [string] + } + ) { + param($StartResult, $StopResult, $StartMessage, $StopMessage, $OutputType) + + & (Get-Module SentryAppRunner) { + param($StartResult, $StopResult) + $script:CurrentSession.Provider.StartResult = [DevicePowerResult]$StartResult + $script:CurrentSession.Provider.StopResult = [DevicePowerResult]$StopResult + } $StartResult $StopResult + + $startOutput = @(Start-Device 3>&1) + $startOutput.Count | Should -Be 1 + $startOutput[0] | Should -BeOfType $OutputType + "$($startOutput[0])" | Should -Be $StartMessage + + $stopOutput = @(Disconnect-Device -PowerOff 3>&1) + $stopOutput.Count | Should -Be 2 + $stopOutput[0] | Should -BeOfType $OutputType + "$($stopOutput[0])" | Should -Be $StopMessage + $stopOutput[1] | Should -Be 'Disconnected from device' + $script:provider.Disconnected | Should -BeTrue + Get-DeviceSession | Should -BeNullOrEmpty + } +}