mobile: harden wireless adb smoke
This commit is contained in:
@@ -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`
|
||||
|
||||
@@ -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('开始起草'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user