# 端到端验证手册 目标:在**真实接口**下把手机端走通,并确认平板端没退化。只看编译通过是不够的—— 本次适配里"筛选条件被静默清空"和"后端不认新参数"两个问题,都只有跑真机才暴露。 ## 环境 ```bash ~/Library/Android/sdk/emulator/emulator -list-avds # Medium_Phone_API_36.1(手机) / Aerologic_Tablet(平板) adb devices -l adb -s shell wm size && adb -s shell wm density ``` 判定形态:手机 1080×2400@420dpi → `PHONE(sw=411dp)`;平板 1280×800@160dpi → `TABLET(sw=800dp)`。 `ui_probe.sh` 封装了常用操作,比手写 adb 省事且更可靠: ```bash P=.claude/skills/phone-adapt/scripts/ui_probe.sh $P form # 打印形态识别结果 $P texts # 当前界面所有可见文字(做断言用) $P bounds "已交接" # 元素 bounds + 中心点 $P tap-text "确认" # 按文字点击(自动取中心点) $P wait-text "待交接" # 等页面就绪 $P wait-gone "请稍候……" # 等加载弹窗消失 $P shot /tmp/a.png # 截图 $P req pageQuery # 抓请求/响应体 $P crash # 崩溃次数 + 栈 ``` ## 构建安装 ```bash ./gradlew assembleDebug 2>&1 | grep -E "^(BUILD|FAILURE)|error:" adb -s install -r -d app/build/outputs/apk/debug/app-debug.apk ``` 注意 gradle 输出里大量 `e: 注: ARouter::Compiler …` 是注解处理器的**提示**被误标成 `e:`, 不是错误;只看 `BUILD SUCCESSFUL` / `error:` 就行。 ## 进入目标页面的两条路 ### 正规路径(优先):登录 → 首页菜单 ```bash adb -s shell monkey -p com.lukouguoji.aerologic -c android.intent.category.LAUNCHER 1 ``` 登录页账号密码需要用户提供。填表用 `input text`,注意 `#` 等字符要转义(`Aa123456\#`)。 底部有「首页 / 国内 / 国际 / 我的」Tab,国际业务页找对应入口。 **首页菜单是按权限过滤的**:当前账号角色没有该权限时,入口根本不显示(本次 ADMIN 登录后 国际业务只有「板箱过磅」「进港理货」,没有「出库交接」)。这时走下面的直启。 ### 直启(权限缺失时的替代):冷启动 + am start `app/src/debug/AndroidManifest.xml` 里把调试页临时 `exported="true"`,就能直接拉起。 **必须冷启动**(先 force-stop),否则会撞上既有的 LoadingModel 崩溃: ```bash adb -s shell am force-stop com.lukouguoji.aerologic sleep 2 adb -s shell am start -n com.lukouguoji.aerologic/<完整类名> ``` 登录态存在 SharedPreference 里,冷启动直启同样带 token,接口能正常返回真实数据。 > 先经首页再 `am start` 会崩在 `LoadingModel.showLoading` → XPopup `popupInfo is null`, > 这是既有缺陷(未改动的页面也复现),不是本次改动引入的。 ## 点击前先等就绪 加载弹窗("请稍候……")会盖住整屏并**吞掉 `input tap`**,表现为"点了没反应"。 本次验证就因此白跑了几轮。所以: ```bash $P wait-text "待交接" # 等目标元素出现 $P tap-text "已交接" $P texts | grep -c "全选" # 用文字变化确认点击确实生效 ``` 点完**一定要断言效果**(用 `texts` 或截图),别假设点中了。 ## 逐项走查清单 每一步做完都跑一次 `crash`(必须为 0): - [ ] 列表加载:卡片字段、各种状态形态(如待处理 / 已完成两种卡片) - [ ] Tab 切换:指示条位置、角标数字、底部条按设计显隐 - [ ] 筛选弹层:打开、各控件取值、重置、确认后重新查询 - [ ] 下拉字典:真实取到数据(不是空列表) - [ ] 多选:单选、全选联动、"已选 N 项"计数 - [ ] 详情页/二级页:字段绑定、条件显示的区块 - [ ] 扫码入口能拉起 ## 核对请求参数是否真的生效 新增的查询字段要确认两件事:客户端**发出去了**,服务端**认**。 ```bash $P req pageQuery ``` OkHttp 日志是多行 pretty JSON。看到请求体里有你的字段(如 `"hoState": "0"`)只说明客户端对了。 **服务端是否生效要用行为验证**:切到另一个 Tab,看返回数据是否真的变了。 本次实测:请求正确携带 `hoState:"0"/"1"`,但两个 Tab 返回同一批数据 → 后端没做过滤。 这种情况**必须在交付说明里如实标注**,不能让它看起来是通的。 ## 无数据时怎么核对 UI 服务端当天可能没数据(列表空)。放宽条件(清空日期)再查;仍为空时,临时注入样例 Bean: ```kotlin // 临时:覆盖各种形态各一条,验证后删除 binding.rv.postDelayed({ binding.rv.commonAdapter()?.refresh(listOf(/* 待处理 / 危险品 / 已完成 各一条 */)) }, 2500) ``` 截图核对完**必须删除**,然后重新构建 + grep 确认无残留: ```bash grep -rn "injectMockForVisualCheck\|TODO 临时" module_base/src module_gjc/src | wc -l # 应为 0 ``` ## 屏幕方向验证 新增/修改 Activity 后,确认手机端不会「先横屏再转竖屏」: ```bash # 冷启动时连续采样:应全程 mRotation=0 adb -s shell am force-stop com.lukouguoji.aerologic; sleep 2 adb -s shell monkey -p com.lukouguoji.aerologic -c android.intent.category.LAUNCHER 1 for i in $(seq 1 10); do adb -s shell dumpsys window | grep -m1 -oE "mRotation=[0-9]+"; sleep 0.4; done ``` 平板则应首屏就是横屏(截图宽 > 高)。若手机出现横屏帧,检查该 Activity 的 Manifest 是否漏写 `android:screenOrientation="@integer/screen_orientation"`。 ## Pad 端回归(必做) ```bash adb -s install -r -d app/build/outputs/apk/debug/app-debug.apk adb -s shell am start -n com.lukouguoji.aerologic/<同一个类名> $P shot /tmp/tablet.png $P form # 应为 TABLET(sw=800dp…) ``` 对比改造前后:搜索条数量与顺序、日期默认值、按钮(平板可能比手机多按钮)、底部统计文案。 改过 `module_base` 里的公共基类或 Pad 系组件时,这一步不能省,且要多看几个页面。 ## 定位"值被神秘修改"类问题 在 setter 里打带堆栈的日志,一次就能看到调用来源: ```kotlin android.util.Log.d("DBG", "value '$field' -> '$v'", Throwable("trace")) ``` ```bash adb -s logcat -d -s DBG | grep -A14 "'旧值' -> ''" | grep "at com.lukouguoji\|at android" ``` 本次靠这招定位到 `TextView.onRestoreInstanceState → EditText.setText → doOnTextChanged`, 即复合自定义 View 的 id 串档(见 `components.md`)。**排完删日志。**