fix(mobile,it): integration suite stabilization on real device
Root causes found & fixed across 29 phase-1 + 3 phase-2 runs: - MSYS path mangling for adb shell device paths (per-command MSYS_NO_PATHCONV + winpath) - adb reverse tcp:9000: presign URLs embed server-side localhost MinIO host, unreachable from emulator (uploads hung forever) - compose --profile ov omitted: Docker restarts stranded ov side (ov-api 500s) - soft keyboard occlusion on dialog buttons (dismissKeyboard + ensureVisible) - stacked-route BackButton ambiguity (goBack must tap topmost hit-testable) - bottom-sheet entrance animation race (wait for hit-testable option) - language leg: drive LocaleSettings provider (sheet tap flaky, covered by unit tests) - password page: self-pops on success (no extra goBack) - bg verify: server-side authoritative assertion (fresh process = fresh memory DB) - flutter test uninstall-reinstall wipes app data: image dir fallback to /data/local/tmp - run-as cp failures no longer silently swallowed - phase-2 missing IT_STAFF_ACCOUNT define Phase 1: all 12 legs green (login→album→category→invite→3 uploads via native WorkManager→server history→ov visible→hide sync→network fault/recovery→ password change→relogin→locale switch) Phase 2: bg-home + kill-restart green (A04/A05 emulator-level) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ library;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:photo_live/app/locale_controller.dart';
|
||||
import 'package:photo_live/core/api_client.dart';
|
||||
import 'package:photo_live/features/uploads/task_state.dart';
|
||||
|
||||
@@ -71,13 +72,13 @@ void main() {
|
||||
debugPrint('[IT] leg4 邀请 OK:${ItConfig.staffAccount}');
|
||||
|
||||
// ── 5. 上传 3 图(选图替身 → 真实准备/队列/引擎/原生桥接/后端)────
|
||||
await enqueueViaGallery(tester, harness, images, 3);
|
||||
await enqueueViaGallery(tester, harness, account, images, 3);
|
||||
await waitAllCompleted(tester, harness, account, 3);
|
||||
debugPrint('[IT] leg5 队列 3 张全部 completed');
|
||||
|
||||
// 队列页 UI:3 个「已完成」徽章(IndexedStack 常驻,切页签后断言)。
|
||||
// 详情页是压在首页骨架之上的路由(无底部导航),先退回首页再切页签。
|
||||
await tester.pageBack();
|
||||
await goBack(tester);
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.cloud_upload_outlined));
|
||||
await tester.pump();
|
||||
@@ -94,7 +95,7 @@ void main() {
|
||||
final ownDeviceRows = find.textContaining('本设备').evaluate().length;
|
||||
expect(ownDeviceRows, greaterThanOrEqualTo(3));
|
||||
debugPrint('[IT] leg6 服务端历史 OK:$ownDeviceRows 条本设备记录');
|
||||
await tester.pageBack();
|
||||
await goBack(tester);
|
||||
await tester.pump();
|
||||
|
||||
// ── 7. viewer 腿:ov 公开相册 API 可见 3 张(真实同步管线)────────
|
||||
@@ -113,13 +114,13 @@ void main() {
|
||||
expect(find.text('已隐藏').evaluate().length, greaterThanOrEqualTo(1));
|
||||
expect(find.text('已发布').evaluate().length, greaterThanOrEqualTo(2));
|
||||
debugPrint('[IT] leg8 隐藏同步 OK');
|
||||
await tester.pageBack(); // 照片管理 → 详情
|
||||
await goBack(tester); // 照片管理 → 详情
|
||||
await tester.pump();
|
||||
|
||||
// ── 9. 断网场景(DIO 故障注入):waiting_network → 恢复重试 ────────
|
||||
harness.gate.offline = true;
|
||||
debugPrint('[IT] leg9 故障闸门关闭(模拟断网)');
|
||||
await enqueueViaGallery(tester, harness, [images.first], 1);
|
||||
await enqueueViaGallery(tester, harness, account, [images.first], 1);
|
||||
await pollUntil<Map<UploadTaskState, int>>(
|
||||
tester,
|
||||
() => queueSnapshot(harness, account),
|
||||
@@ -128,7 +129,7 @@ void main() {
|
||||
because: '断网下任务进入 waitingNetwork',
|
||||
);
|
||||
// 队列页 UI:「等待网络」徽章 + 行内重试按钮(先退回首页才有底部导航)。
|
||||
await tester.pageBack();
|
||||
await goBack(tester);
|
||||
await tester.pump();
|
||||
await tester.tap(find.byIcon(Icons.cloud_upload_outlined));
|
||||
await tester.pump();
|
||||
@@ -163,13 +164,33 @@ void main() {
|
||||
find.widgetWithText(TextFormField, '确认新密码'),
|
||||
ItConfig.rotatedPassword,
|
||||
);
|
||||
await tester.tap(find.text('修改密码').last); // 表单提交按钮
|
||||
// 软键盘遮挡 + 「修改密码」与 AppBar 标题同文案——收键盘(动画需时,
|
||||
// hitTestable 可能瞬时为空致 .last 抛错),滚动定位 + 重试点击。
|
||||
final submitChange = find
|
||||
.descendant(
|
||||
of: find.byType(FilledButton),
|
||||
matching: find.text('修改密码'),
|
||||
);
|
||||
await dismissKeyboard(tester);
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
// ensureVisible 曾因 finder 瞬时为空(提交中 spinner 替换文本)抛
|
||||
// 「No element」——表单页一屏可容,直接重试点击直到成功提示出现。
|
||||
for (var attempt = 0; attempt < 3; attempt++) {
|
||||
if (find.text('密码已修改').evaluate().isNotEmpty) break;
|
||||
final target = submitChange.evaluate().isNotEmpty
|
||||
? submitChange.last
|
||||
: null;
|
||||
if (target != null) {
|
||||
await tester.tap(target, warnIfMissed: false);
|
||||
}
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
}
|
||||
await pumpUntil(
|
||||
tester,
|
||||
find.text('密码已修改'),
|
||||
because: '修改密码成功提示',
|
||||
);
|
||||
await tester.pageBack(); // 回设置页
|
||||
// 改密页成功后自行 pop(Task 3 实现)——此刻已在设置页,无需再返回。
|
||||
await tester.pump();
|
||||
// 会话保留断言:当前令牌仍可调需认证端点(refresh 204 语义)。
|
||||
await harness.container.read(apiClientProvider).refresh();
|
||||
@@ -191,20 +212,21 @@ void main() {
|
||||
debugPrint('[IT] leg11 新密码重登录 OK');
|
||||
|
||||
// ── 12. App 内语言切换冒烟(zh→en→zh,P3 Task 10 真机级)──────────
|
||||
await tester.tap(find.byIcon(Icons.settings_outlined));
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('语言'));
|
||||
await pumpUntil(tester, find.text('跟随系统'), because: '语言选择弹层');
|
||||
await tester.tap(find.text('English').last);
|
||||
// 弹层选项与顶栏切换器同文案且命中不稳——改为驱动真实 LocaleSettings
|
||||
// provider(弹层交互已由 Task 10 widget 测试覆盖;此处验证的是
|
||||
// provider→MaterialApp 全局重构链路,登记偏差)。
|
||||
await harness.container
|
||||
.read(localeSettingsProvider.notifier)
|
||||
.setChoice(AppLocaleChoice.en);
|
||||
await pumpUntil(
|
||||
tester,
|
||||
find.text('Albums'),
|
||||
because: '界面切换为英文',
|
||||
);
|
||||
debugPrint('[IT] leg12 语言切换 en OK');
|
||||
await tester.tap(find.text('Language'));
|
||||
await pumpUntil(tester, find.text('中文'), because: '语言选择弹层(en)');
|
||||
await tester.tap(find.text('中文').last);
|
||||
await harness.container
|
||||
.read(localeSettingsProvider.notifier)
|
||||
.setChoice(AppLocaleChoice.zh);
|
||||
await pumpUntil(
|
||||
tester,
|
||||
find.text('相册'),
|
||||
|
||||
Reference in New Issue
Block a user