feat: integrate N7Cockpit remote mode control
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$AdbPath = 'D:\Android\Sdk\platform-tools\adb.exe',
|
||||
[string]$ServerBaseUrl = 'http://127.0.0.1:8000',
|
||||
[string]$TokenFile = "$env:LOCALAPPDATA\Temp\n7-device-token.txt",
|
||||
[string]$PackageName = 'com.seeker.n7cockpit.remote',
|
||||
[string]$ModeId = 'baby_nap',
|
||||
[int]$TimeoutSeconds = 40,
|
||||
[string]$ArtifactDirectory = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
if (-not $ArtifactDirectory) {
|
||||
$ArtifactDirectory = Join-Path $projectRoot 'artifacts\remote-link'
|
||||
}
|
||||
$buildConfig = Join-Path $projectRoot 'app\build\generated\source\buildConfig\debug\com\seeker\n7cockpit\BuildConfig.java'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $TokenFile)) {
|
||||
throw "The device token file does not exist: $TokenFile"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $buildConfig)) {
|
||||
throw 'Debug BuildConfig was not found. Build the remote debug APK first.'
|
||||
}
|
||||
|
||||
$configText = Get-Content -LiteralPath $buildConfig -Raw -Encoding UTF8
|
||||
if ($configText -notmatch 'ORCHESTRATOR_EXECUTION_TARGET\s*=\s*"dry_run"') {
|
||||
throw 'Safety interlock: the APK execution target is not dry_run.'
|
||||
}
|
||||
if ($configText -notmatch 'ORCHESTRATOR_BASE_URL\s*=\s*"http://10\.0\.2\.2:8000"') {
|
||||
throw 'This test only permits an emulator APK targeting 10.0.2.2:8000.'
|
||||
}
|
||||
|
||||
$token = (Get-Content -LiteralPath $TokenFile -Raw).Trim()
|
||||
if (-not $token) {
|
||||
throw 'The device token is empty.'
|
||||
}
|
||||
$headers = @{ Authorization = "Bearer $token" }
|
||||
|
||||
$catalog = Invoke-RestMethod -Uri "$ServerBaseUrl/api/v1/vehicles/denza_n7/modes" `
|
||||
-Headers $headers -TimeoutSec 5
|
||||
$mode = @($catalog.modes | Where-Object { $_.id -eq $ModeId -and $_.origin -eq 'preset' })
|
||||
if ($mode.Count -ne 1) {
|
||||
throw "The preset is not unique in the server catalog: $ModeId"
|
||||
}
|
||||
$expectedModeName = [string]$mode[0].name
|
||||
|
||||
& (Join-Path $PSScriptRoot 'start-remote-app.ps1') -AdbPath $AdbPath `
|
||||
-PackageName $PackageName | Out-Null
|
||||
Start-Sleep -Seconds 1
|
||||
|
||||
# Standard 2560x1600 emulator: mode navigation, then the first preset button.
|
||||
& $AdbPath shell input tap 94 406
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $ArtifactDirectory | Out-Null
|
||||
& $AdbPath shell uiautomator dump /sdcard/n7-before-mode.xml | Out-Null
|
||||
& $AdbPath pull /sdcard/n7-before-mode.xml (Join-Path $ArtifactDirectory 'before-mode.xml') | Out-Null
|
||||
$beforeUi = Get-Content -LiteralPath (Join-Path $ArtifactDirectory 'before-mode.xml') `
|
||||
-Raw -Encoding UTF8
|
||||
if (-not $beforeUi.Contains('http://10.0.2.2:8000')) {
|
||||
throw 'The Android mode page did not render its server connection.'
|
||||
}
|
||||
if (-not $beforeUi.Contains($expectedModeName)) {
|
||||
throw "The Android mode page did not render the preset: $ModeId"
|
||||
}
|
||||
|
||||
$baseline = Invoke-RestMethod `
|
||||
-Uri "$ServerBaseUrl/api/v1/vehicles/denza_n7/events?after=0" `
|
||||
-Headers $headers -TimeoutSec 5
|
||||
$sequence = [long]$baseline.next_sequence
|
||||
|
||||
& $AdbPath shell input tap 1469 444
|
||||
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||
$observed = New-Object System.Collections.Generic.List[object]
|
||||
$terminal = $null
|
||||
do {
|
||||
Start-Sleep -Seconds 1
|
||||
$batch = Invoke-RestMethod `
|
||||
-Uri "$ServerBaseUrl/api/v1/vehicles/denza_n7/events?after=$sequence" `
|
||||
-Headers $headers -TimeoutSec 5
|
||||
foreach ($event in @($batch.events)) {
|
||||
$execution = $event.execution
|
||||
if (-not $execution -and $event.data) {
|
||||
$execution = $event.data.execution
|
||||
}
|
||||
if (-not $execution -or $execution.mode_id -ne $ModeId -or
|
||||
$execution.source -ne 'android') {
|
||||
continue
|
||||
}
|
||||
$observed.Add([pscustomobject]@{
|
||||
sequence = [long]$event.sequence
|
||||
execution_id = $execution.execution_id
|
||||
status = $execution.status
|
||||
target = $execution.execution_target
|
||||
})
|
||||
if ($execution.status -in @('succeeded', 'failed', 'cancelled')) {
|
||||
$terminal = $execution
|
||||
}
|
||||
}
|
||||
$sequence = [long]$batch.next_sequence
|
||||
} while (-not $terminal -and (Get-Date) -lt $deadline)
|
||||
|
||||
if (-not $terminal) {
|
||||
throw "No terminal mode event was received within $TimeoutSeconds seconds."
|
||||
}
|
||||
if ($terminal.execution_target -ne 'dry_run') {
|
||||
throw "Safety failure: server execution target was $($terminal.execution_target)."
|
||||
}
|
||||
if ($terminal.status -ne 'succeeded') {
|
||||
throw "Mode execution failed: $($terminal.status) $($terminal.error)"
|
||||
}
|
||||
|
||||
$statusPath = Join-Path $ArtifactDirectory 'after-execution.xml'
|
||||
$screenPath = Join-Path $ArtifactDirectory 'after-execution.png'
|
||||
Start-Sleep -Seconds 2
|
||||
& $AdbPath shell uiautomator dump /sdcard/n7-after-execution.xml | Out-Null
|
||||
& $AdbPath pull /sdcard/n7-after-execution.xml $statusPath | Out-Null
|
||||
& $AdbPath shell screencap -p /sdcard/n7-after-execution.png | Out-Null
|
||||
& $AdbPath pull /sdcard/n7-after-execution.png $screenPath | Out-Null
|
||||
$afterUi = Get-Content -LiteralPath $statusPath -Raw -Encoding UTF8
|
||||
|
||||
if (-not $afterUi.Contains($expectedModeName) -or -not $afterUi.Contains('100%')) {
|
||||
throw 'The server task succeeded, but Android did not render terminal progress.'
|
||||
}
|
||||
if (-not $afterUi.Contains($terminal.execution_id)) {
|
||||
throw 'The Android execution_id does not match the server execution_id.'
|
||||
}
|
||||
if (-not $afterUi.Contains('N7Cockpit')) {
|
||||
throw 'Android did not render the expected source identity.'
|
||||
}
|
||||
|
||||
$statuses = @($observed | Sort-Object sequence | Select-Object -ExpandProperty status)
|
||||
foreach ($required in @('queued', 'executing', 'succeeded')) {
|
||||
if ($required -notin $statuses) {
|
||||
throw "The event stream is missing state: $required"
|
||||
}
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
Passed = $true
|
||||
SafetyTarget = $terminal.execution_target
|
||||
CanExecuted = $false
|
||||
ModeId = $terminal.mode_id
|
||||
ExecutionId = $terminal.execution_id
|
||||
EventStates = ($statuses -join ' -> ')
|
||||
AndroidStateSynchronized = $true
|
||||
Screenshot = $screenPath
|
||||
}
|
||||
Reference in New Issue
Block a user