mobile: harden wireless adb smoke

This commit is contained in:
2026-05-25 23:18:28 +08:00
parent 6ed1e7ea58
commit 51d760d6d8
6 changed files with 169 additions and 69 deletions
+2
View File
@@ -54,6 +54,7 @@ powershell -ExecutionPolicy Bypass -File D:\ai-contract-platform\mobile\tools\re
That smoke helper will:
- call the reconnect helper and rediscover the latest wireless-debug port
- wait for the target serial to reach `adb` state `device` before launching the app
- optionally reinstall `build/app/outputs/flutter-apk/app-debug.apk` when it exists
- verify `8001/health` and `3003/`
- wake the phone, relaunch the app, and capture:
@@ -92,6 +93,7 @@ Current blocker meanings:
- `wireless_adb_unavailable`
- the workstation cannot discover or connect to the phone's wireless ADB endpoint
- re-enable or re-pair Wireless debugging on the phone, then rerun the smoke command
- if `adb devices -l` shows the serial as `offline`, wait for the phone to finish reconnecting or toggle Wireless debugging off/on once
- `device_locked`
- the phone is still behind the OEM lockscreen / pattern unlock surface
- `login_required`
+4
View File
@@ -106,9 +106,13 @@ Created on 2026-04-18.
- Android true-device validation continued on the wireless X60PRO:
- connected serial: `192.168.3.93:44421`
- `tools/real-device-smoke.ps1` now supports `-DeviceSerial` for known wireless-debug ports
- reconnect and smoke helpers now wait for the wireless serial to reach `adb` state `device`, so a transient `offline` session is reported as a wireless ADB blocker instead of an app-surface failure
- `documents-workspace` now passes through signed-in shell -> AI tools -> legal-document workspace -> type list -> builder
- legal-document type cards, builder primary actions, and draft actions now expose stable semantic labels for accessibility and automation
- `tools/real-device-smoke.ps1 -DeviceSerial 192.168.3.93:44421 -ConfigureDevice -SkipInstall -SmokeDepth documents-workspace -LoginAccount <member-account> -LoginPassword <member-password>` returned `SMOKE_OK=True`
- artifact bundle: `mobile/qa-artifacts/real-device-smoke/2026-05-25T21-26-28-369/`
- current wireless-debug serial rotated again to `192.168.3.93:38491`; the first rerun hit `adb` `offline`, so the reconnect/smoke helpers were hardened to wait for the serial to reach `device` before continuing
- the latest `documents-workspace` retry on `192.168.3.93:38491` now exits cleanly with `SMOKE_BLOCKER=wireless_adb_unavailable` instead of an app-surface misfire, which makes the remaining issue explicit
## Phase 0/1 mobile structure progress
@@ -532,49 +532,15 @@ class _LegalDocumentBuilderScreenState
spacing: 12,
runSpacing: 12,
children: [
OutlinedButton.icon(
onPressed: isValidating
? null
: _validateDraft,
icon: isValidating
? const SizedBox(
width: 16,
height: 16,
child:
CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(
Icons.verified_outlined),
label: Text(
isValidating ? '校验中...' : '校验要素',
),
),
OutlinedButton.icon(
onPressed: isSaving
? null
: () => _saveDraft(),
icon: isSaving
? const SizedBox(
width: 16,
height: 16,
child:
CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(Icons.save_outlined),
label: Text(
isSaving ? '保存中...' : '保存草稿'),
),
Tooltip(
message: 'generate-legal-document',
child: FilledButton.icon(
onPressed: isGenerating
Semantics(
label:
'validate-legal-document-draft',
button: true,
child: OutlinedButton.icon(
onPressed: isValidating
? null
: () => _generateDraft(),
icon: isGenerating
: _validateDraft,
icon: isValidating
? const SizedBox(
width: 16,
height: 16,
@@ -584,9 +550,59 @@ class _LegalDocumentBuilderScreenState
),
)
: const Icon(
Icons.auto_awesome),
Icons.verified_outlined),
label: Text(
isGenerating ? '生成中...' : '生成文书',
isValidating ? '校验中...' : '校验要素',
),
),
),
Semantics(
label: 'save-legal-document-draft',
button: true,
child: OutlinedButton.icon(
onPressed: isSaving
? null
: () => _saveDraft(),
icon: isSaving
? const SizedBox(
width: 16,
height: 16,
child:
CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(
Icons.save_outlined),
label: Text(
isSaving ? '保存中...' : '保存草稿'),
),
),
Tooltip(
message: 'generate-legal-document',
child: Semantics(
label: 'generate-legal-document',
button: true,
child: FilledButton.icon(
onPressed: isGenerating
? null
: () => _generateDraft(),
icon: isGenerating
? const SizedBox(
width: 16,
height: 16,
child:
CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(
Icons.auto_awesome),
label: Text(
isGenerating
? '生成中...'
: '生成文书',
),
),
),
),
@@ -881,20 +897,32 @@ class _DraftCard extends StatelessWidget {
spacing: 12,
runSpacing: 12,
children: [
OutlinedButton.icon(
onPressed: onRestore,
icon: const Icon(Icons.restore_outlined),
label: const Text('恢复'),
Semantics(
label: 'restore-legal-document-draft-${draft.id}',
button: true,
child: OutlinedButton.icon(
onPressed: onRestore,
icon: const Icon(Icons.restore_outlined),
label: const Text('恢复'),
),
),
OutlinedButton.icon(
onPressed: onDelete,
icon: const Icon(Icons.delete_outline),
label: const Text('删除'),
Semantics(
label: 'delete-legal-document-draft-${draft.id}',
button: true,
child: OutlinedButton.icon(
onPressed: onDelete,
icon: const Icon(Icons.delete_outline),
label: const Text('删除'),
),
),
FilledButton.icon(
onPressed: onGenerate,
icon: const Icon(Icons.auto_awesome),
label: const Text('生成'),
Semantics(
label: 'generate-saved-legal-document-draft-${draft.id}',
button: true,
child: FilledButton.icon(
onPressed: onGenerate,
icon: const Icon(Icons.auto_awesome),
label: const Text('生成'),
),
),
],
),
@@ -442,10 +442,14 @@ class _DocumentTypeCard extends StatelessWidget {
color: legalDocumentRiskColor(item.riskLevel),
),
const SizedBox(height: 10),
FilledButton.icon(
onPressed: onOpen,
icon: const Icon(Icons.edit_note_outlined),
label: const Text('开始起草'),
Semantics(
label: 'open-legal-document-${item.code}',
button: true,
child: FilledButton.icon(
onPressed: onOpen,
icon: const Icon(Icons.edit_note_outlined),
label: const Text('开始起草'),
),
),
],
),
+37 -7
View File
@@ -80,6 +80,34 @@ function Get-ConnectedSerial {
return $serialLine
}
function Wait-ForAdbDeviceOnline {
param(
[string]$Serial,
[int]$RetryCount = 10,
[int]$DelaySeconds = 2,
[switch]$RepairOffline
)
$repairAttempted = $false
for ($attempt = 1; $attempt -le $RetryCount; $attempt++) {
$state = ((& $script:adb -s $Serial get-state 2>&1) -join [Environment]::NewLine).Trim()
if ($state -eq "device") {
return $true
}
if ($RepairOffline -and -not $repairAttempted -and $state -match 'offline') {
& $script:adb reconnect offline | Out-Null
$repairAttempted = $true
}
if ($attempt -lt $RetryCount) {
Start-Sleep -Seconds $DelaySeconds
}
}
return $false
}
function Get-AppSurface {
param([string]$UiDump)
@@ -260,6 +288,10 @@ function Get-SmokeState {
[string]$PackageName
)
if (-not (Wait-ForAdbDeviceOnline -Serial $Serial -RetryCount 3 -DelaySeconds 1)) {
throw "Wireless ADB device '$Serial' is not online."
}
$topText = (& $script:adb -s $Serial shell dumpsys activity top) -join [Environment]::NewLine
$topPath = Join-Path $ArtifactDir "$Label-activity-top.txt"
Save-TextArtifact -Path $topPath -Text $topText
@@ -613,13 +645,8 @@ try {
$serial = $DeviceSerial.Trim()
$connectOutput = & $adb connect $serial 2>&1
$connectOutput | ForEach-Object { Write-Host $_ }
$connectedDevices = & $adb devices 2>$null
$isConnected = $connectedDevices |
ForEach-Object { $_.ToString().Trim() } |
Where-Object { $_ -match "^$([regex]::Escape($serial))\s+device$" } |
Select-Object -First 1
if ($LASTEXITCODE -ne 0 -or -not $isConnected) {
throw "Failed to connect to specified device serial '$serial'."
if (-not (Wait-ForAdbDeviceOnline -Serial $serial -RetryCount 8 -DelaySeconds 2 -RepairOffline)) {
throw "Failed to connect specified device serial '$serial' into adb 'device' state."
}
if ($ConfigureDevice) {
& $adb -s $serial shell settings put system screen_off_timeout 1800000 | Out-Null
@@ -627,6 +654,9 @@ try {
}
} else {
$serial = Get-ConnectedSerial -ReconnectScript $reconnectScript -ExpectedIpPrefix $ExpectedIp -ShouldConfigureDevice:$ConfigureDevice.IsPresent
if (-not (Wait-ForAdbDeviceOnline -Serial $serial -RetryCount 8 -DelaySeconds 2 -RepairOffline)) {
throw "Failed to connect discovered device serial '$serial' into adb 'device' state."
}
}
} catch {
$connectionError = ($_.Exception.Message -replace '\s+', ' ').Trim()
+34 -2
View File
@@ -11,6 +11,34 @@ if (-not (Test-Path $adb)) {
throw "adb.exe not found at $adb"
}
function Wait-ForDeviceState {
param(
[string]$Serial,
[int]$Attempts,
[int]$DelaySeconds,
[switch]$RepairOffline
)
$repairAttempted = $false
for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
$state = ((& $adb -s $Serial get-state 2>&1) -join [Environment]::NewLine).Trim()
if ($state -eq "device") {
return $true
}
if ($RepairOffline -and -not $repairAttempted -and $state -match 'offline') {
& $adb reconnect offline | Out-Null
$repairAttempted = $true
}
if ($attempt -lt $Attempts) {
Start-Sleep -Seconds $DelaySeconds
}
}
return $false
}
$pattern = '^(?<name>\S+)\s+_adb-tls-connect\._tcp\s+(?<host>\d+\.\d+\.\d+\.\d+):(?<port>\d+)$'
$candidates = @()
@@ -48,15 +76,19 @@ $connectedSerial = $null
foreach ($candidate in $candidates) {
$serial = "$($candidate.Host):$($candidate.Port)"
$connectOutput = & $adb connect $serial 2>&1
if ($LASTEXITCODE -eq 0) {
$connectOutput | ForEach-Object { Write-Host $_ }
if (Wait-ForDeviceState -Serial $serial -Attempts $RetryCount -DelaySeconds $DelaySeconds -RepairOffline) {
$connectedSerial = $serial
break
}
& $adb disconnect $serial | Out-Null
}
if (-not $connectedSerial) {
$candidateList = ($candidates | ForEach-Object { "$($_.Host):$($_.Port)" }) -join ", "
throw "Failed to connect to any discovered wireless device. Candidates: $candidateList"
throw "Failed to connect any discovered wireless device into adb 'device' state. Candidates: $candidateList"
}
if ($ConfigureDevice) {