docs: import wiki at afc651f75a3a
+317
@@ -0,0 +1,317 @@
|
|||||||
|
<!-- docs-wiki-sync:docs/tasks/T-271.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
|
||||||
|
> 同步来源:[`docs/tasks/T-271.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-271.md) · commit `afc651f75a3a`
|
||||||
|
|
||||||
|
---
|
||||||
|
id: T-271
|
||||||
|
title: 放宽候选卡片的图片条件避免图未加载即否决
|
||||||
|
phase: 2
|
||||||
|
deps:
|
||||||
|
- T-269
|
||||||
|
status: DOING
|
||||||
|
created: 2026-08-01
|
||||||
|
context_ref: 1286ee3
|
||||||
|
work_branch: null
|
||||||
|
write_paths:
|
||||||
|
- docs/tasks/T-271.md
|
||||||
|
- docs/current-state.md
|
||||||
|
- android-buyer/app/src/main/**
|
||||||
|
- android-buyer/app/src/test/**
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题 / 背景
|
||||||
|
|
||||||
|
真机任务三轮全部 `CANDIDATE_RESULTS_NOT_READY`,`cards=0`,且三轮数字完全一致:
|
||||||
|
|
||||||
|
```
|
||||||
|
containers=550 rejClass=250 rejHeight=100 rejImage=200
|
||||||
|
```
|
||||||
|
|
||||||
|
### 算术还原
|
||||||
|
|
||||||
|
`awaitCandidateCards` 最多轮询 `candidateReadyPollLimit = 50` 次。
|
||||||
|
|
||||||
|
```
|
||||||
|
550 ÷ 50 = 11 每次读取 recycler 有 11 个子节点
|
||||||
|
250 ÷ 50 = 5 每次 5 个因 className 被拒
|
||||||
|
100 ÷ 50 = 2 每次 2 个因高度被拒
|
||||||
|
200 ÷ 50 = 4 每次 4 个因缺少图片被拒
|
||||||
|
5 + 2 + 4 = 11 ✓ 全部被拒,零卡片,轮询 50 次(10 秒)后超时
|
||||||
|
```
|
||||||
|
|
||||||
|
### 真机对照:那 4 个正是真实商品卡
|
||||||
|
|
||||||
|
在图片搜索结果页做 `uiautomator dump`(126 节点,全页仅一个可滚动
|
||||||
|
`RecyclerView`,8 个子节点,占满屏),逐个核对九道过滤条件:
|
||||||
|
|
||||||
|
| # | 类型 | 尺寸 | class | 宽度 | 高度 | 图片 | 价格 |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| 0 | LinearLayout | 1080×168 | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||||
|
| 1 | RecyclerView | 1080×120 | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||||
|
| **2** | FrameLayout | **535×764** | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| **3** | FrameLayout | **535×764** | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| **4** | FrameLayout | **535×764** | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| **5** | FrameLayout | **535×764** | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
|
| 6 | FrameLayout | 535×182 | ✓ | ✓ | ✗ | ✓ | ✗ |
|
||||||
|
| 7 | FrameLayout | 535×182 | ✓ | ✓ | ✗ | ✓ | ✗ |
|
||||||
|
|
||||||
|
四个 `535×764` 的容器带有商品标题、券后价、收藏数,是真实商品卡,**在页面加载完成
|
||||||
|
后能通过全部九道过滤**。
|
||||||
|
|
||||||
|
失败轮次的 `rejImage=4`(每次读取)与此处的 4 个真实商品卡**数量吻合**:说明那 4 张卡
|
||||||
|
当时确实存在且文本已可读,**唯独死在图片条件上**。
|
||||||
|
|
||||||
|
### 直接原因
|
||||||
|
|
||||||
|
`BuyerAccessibilityService` 候选卡片过滤的第 6 个条件:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val hasImage = descendants.any { descendant ->
|
||||||
|
descendant.isVisibleToUser &&
|
||||||
|
descendant.isEnabled &&
|
||||||
|
descendant.className?.toString()?.endsWith("ImageView") == true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
商品图尚未加载完成时,卡片内的 `ImageView` 还不满足 `isVisibleToUser && isEnabled`,
|
||||||
|
整张卡被否决。就绪轮询窗口仅 `50 × 200ms = 10` 秒,**图片未在该窗口内加载完即三轮
|
||||||
|
全败**。
|
||||||
|
|
||||||
|
### 该条件本身缺乏价值
|
||||||
|
|
||||||
|
一个宽度占 recycler 35–60%、高度不低于屏幕 1/8、可见面积达标、拥有多条语义文本、
|
||||||
|
含价格语义且能找到安全点击目标的容器,**已经足以判定为商品卡**。额外要求「必须存在
|
||||||
|
已加载的 ImageView」不增加任何安全性,只把结果绑定到网络速度和图片加载时机上。
|
||||||
|
|
||||||
|
### 这是同类问题第三次出现
|
||||||
|
|
||||||
|
| 次序 | 症状 | 真因 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1 | 支付边界误报、详情页认不出(T-262/T-269) | `clickable` 取在了错误的节点层级 |
|
||||||
|
| 2 | 规格入口 100% 缺失(T-264/T-265) | 移除了当前版本唯一的入口 |
|
||||||
|
| 3 | **卡片零通过(本任务)** | **图片加载完成前即否决** |
|
||||||
|
|
||||||
|
三次都表现为「需要更强的感知能力」,实际都是单条规则细节。
|
||||||
|
|
||||||
|
## 关联需求与交互
|
||||||
|
|
||||||
|
- 功能:F-005
|
||||||
|
- 用户故事:US-004
|
||||||
|
- 交互:不新增采购操作
|
||||||
|
- 架构/API:沿用既有诊断机制,**后端零改动**
|
||||||
|
|
||||||
|
## 行为契约
|
||||||
|
|
||||||
|
### 一、图片降为加分项而非硬性条件
|
||||||
|
|
||||||
|
候选卡片过滤中,`hasImage` **不再作为否决条件**。卡片判定改为要求以下全部成立:
|
||||||
|
|
||||||
|
1. className 为 `FrameLayout` / `ViewGroup`(不变);
|
||||||
|
2. 宽度占 recycler 的 `MIN_CARD_WIDTH_PERCENT`–`MAX_CARD_WIDTH_PERCENT`(不变);
|
||||||
|
3. 高度不低于屏幕 1/8(不变);
|
||||||
|
4. 可见面积达标(不变);
|
||||||
|
5. 语义文本数不低于 `MIN_CANDIDATE_TEXTS`(不变);
|
||||||
|
6. 存在价格语义(不变);
|
||||||
|
7. 找得到安全点击目标(不变);
|
||||||
|
8. 能算出稳定签名(不变)。
|
||||||
|
|
||||||
|
**其余七个条件的判定逻辑与阈值常量一律不改**,仅移除图片这一条的否决权。
|
||||||
|
|
||||||
|
### 二、图片状态仍需记录
|
||||||
|
|
||||||
|
`PinduoduoCandidateCard` 保留 `hasImage` 字段,如实记录该卡当时是否已有可见图片,
|
||||||
|
供后续评估与证据审计使用。
|
||||||
|
|
||||||
|
诊断计数调整:`rejImage` 语义改为「**因缺少图片而被降级但未被否决**的卡片数」,
|
||||||
|
即变为观测项而非拒绝项。恒等式相应调整为:
|
||||||
|
|
||||||
|
```
|
||||||
|
containers - (rejClass + rejWidth + rejHeight + rejVisible + rejTexts + rejPrice + rejClick + rejSignature) = 返回卡片数
|
||||||
|
```
|
||||||
|
|
||||||
|
`rejImage` 不再计入该等式,必须有单测保证新恒等式成立。
|
||||||
|
|
||||||
|
### 三、不得因此放宽后续校验
|
||||||
|
|
||||||
|
放宽的只是「进入候选列表」的门槛。**后续所有校验一律不变**:
|
||||||
|
|
||||||
|
- 颜色、尺码硬约束判定不变;
|
||||||
|
- 商品身份解析(goods_id / 规范链接)不变;
|
||||||
|
- 双证据截图(`DETAIL` + `SPECIFICATION`)仍为强制,缺一不可;
|
||||||
|
- 支付边界、订单确认页、登录、验证码、风控的判定与优先级不变;
|
||||||
|
- T-265 落到结算页的跳过逻辑不变。
|
||||||
|
|
||||||
|
即:图片未加载的卡片可以被**打开检查**,但**能否成为合格候选仍由既有硬约束决定**。
|
||||||
|
|
||||||
|
### 四、既有诊断不变
|
||||||
|
|
||||||
|
T-254/T-255/T-257/T-260/T-261/T-262/T-263/T-264/T-265/T-268/T-269 既有诊断键的顺序与
|
||||||
|
名称**保持不变**;仅 `rejImage` 的语义按第二节调整,须在本任务文档与
|
||||||
|
`docs/current-state.md` 中写明该口径变更。
|
||||||
|
|
||||||
|
## 方案
|
||||||
|
|
||||||
|
1. 候选卡片过滤移除 `hasImage` 的否决权,保留其取值并记入 `PinduoduoCandidateCard`。
|
||||||
|
2. `rejImage` 改为「降级但未否决」计数,调整恒等式与相关单测。
|
||||||
|
3. 单测覆盖:真机 dump 的四张 `535×764` 商品卡在**无图片**时仍被接受;两个
|
||||||
|
`535×182` 的矮容器仍因高度被拒;`LinearLayout` / `RecyclerView` 仍因 className
|
||||||
|
被拒;新恒等式成立;`hasImage` 取值如实记录;其余七个条件的拒绝行为逐项不变。
|
||||||
|
|
||||||
|
## 验收要点
|
||||||
|
|
||||||
|
- [ ] 图片未加载的商品卡仍被接受为候选,有基于真机 dump 特征的单测。
|
||||||
|
- [ ] 其余七个过滤条件的判定逻辑、阈值常量与拒绝行为逐项不变,有回归测试。
|
||||||
|
- [ ] 新恒等式 `containers - Σ(除 rejImage 外的拒绝) = 返回卡片数` 有单测保证。
|
||||||
|
- [ ] `rejImage` 如实记录「缺图但未否决」的数量。
|
||||||
|
- [ ] `PinduoduoCandidateCard.hasImage` 取值正确。
|
||||||
|
- [ ] 颜色/尺码硬约束、身份解析、双证据、支付边界与订单确认页判定均未放宽,有回归测试。
|
||||||
|
- [ ] 既有诊断键顺序与名称不变;`rejImage` 口径变更已写入任务文档与 `current-state`。
|
||||||
|
- [ ] Android Debug/Release 单测、`lintDebug`、`assembleDebug`、`assembleRelease` 全部通过。
|
||||||
|
- [ ] 真机验证:在图片尚未完全加载的图搜结果页跑任务,`cards` 不再为 0,能进入候选
|
||||||
|
采集;从 Admin 读取 `cards` / `distinct` / `rejImage` 记入执行记录。
|
||||||
|
|
||||||
|
## 边界
|
||||||
|
|
||||||
|
- **只移除图片这一条的否决权**,其余八个条件(含签名)的逻辑与阈值一律不动。
|
||||||
|
- 不改 `MIN_CARD_WIDTH_PERCENT`、`MAX_CARD_WIDTH_PERCENT`、`MIN_VISIBLE_CARD_PERCENT`、
|
||||||
|
`MIN_CANDIDATE_TEXTS`、`MIN_MAIN_RECYCLER_NODES`、`MIN_MAIN_RECYCLER_WIDTH_PERCENT`
|
||||||
|
或最小高度算式。
|
||||||
|
- 不改 `candidateReadyPollLimit` 与就绪等待时长——本任务通过降低误否决解决问题,
|
||||||
|
不靠延长等待。
|
||||||
|
- 不改颜色/尺码硬约束、身份解析、双证据要求、跨轮去重或三轮预算。
|
||||||
|
- 不改支付边界、订单确认页、登录、验证码、风控的任何判定与优先级。
|
||||||
|
- 不改 T-265 规格入口优先级、T-268 门控诊断、T-269 详情页识别与归位策略。
|
||||||
|
- 不改后端代码、数据库、API 契约或 Admin 模板。
|
||||||
|
- 不处理价格解析(前缀与节点拆分)问题,另行开任务。
|
||||||
|
- 不引入 VLM 或截图判断——本任务证明当前失败是规则问题,不是感知能力问题。
|
||||||
|
|
||||||
|
## 执行记录
|
||||||
|
|
||||||
|
开工后记录修改、命令、结果、环境、决策和 blocker。
|
||||||
|
|
||||||
|
### 2026-08-01 实现
|
||||||
|
|
||||||
|
**改动文件**
|
||||||
|
|
||||||
|
- `android-buyer/app/src/main/java/com/roubao/autopilot/pinduoduo/PinduoduoCandidateContainerFilter.kt`
|
||||||
|
(新增):把候选容器"进入候选列表"的八项判定抽成不依赖
|
||||||
|
`AccessibilityNodeInfo` 的纯函数 `PinduoduoCandidateContainerFilter.evaluate()`,
|
||||||
|
短路顺序为 CLASS → WIDTH/HEIGHT → VISIBLE → TEXTS → PRICE → CLICK →
|
||||||
|
SIGNATURE(`hasImage` 不再出现在否决链条里,只用于填充返回卡片的
|
||||||
|
`hasImage` 字段)。抽取的目的:`candidateNodes()` 直接操作
|
||||||
|
`AccessibilityNodeInfo`,仓库现有测试栈没有 Robolectric/Mockito,无法在
|
||||||
|
JVM 单测里构造该类型的夹具;抽成纯函数后可以直接用真机 dump 的数值
|
||||||
|
(类名、宽高、语义文本、价格/点击/签名布尔量)构造第 8 节要求的回归测试,
|
||||||
|
同时不引入 VLM/截图,也不改变任何判定逻辑本身。
|
||||||
|
- `android-buyer/app/src/main/java/com/roubao/autopilot/accessibility/BuyerAccessibilityService.kt`:
|
||||||
|
`candidateNodes()` 改为从无障碍树读出原始度量后调用上面的纯函数,把返回的
|
||||||
|
`Accepted`/`Rejected` 落回既有 `rejected*` 计数与候选列表;`rejectedImage`
|
||||||
|
语义改为「已接受但 hasImage=false」的观测计数(在 `Accepted` 分支里判断
|
||||||
|
`!verdict.card.hasImage` 才 +1,不再 `continue`);`PinduoduoCandidateCard`
|
||||||
|
的 `hasImage` 字段改为如实传入 `hasImage`(此前放宽前该分支恒为
|
||||||
|
`true`,因为不满足就已经被拒绝跳过了)。className/宽度/高度/可见面积/
|
||||||
|
语义文本数/价格语义/安全点击目标/稳定签名的判定逻辑、
|
||||||
|
`MIN_CARD_WIDTH_PERCENT`/`MAX_CARD_WIDTH_PERCENT`/`MIN_VISIBLE_CARD_PERCENT`/
|
||||||
|
`MIN_CANDIDATE_TEXTS`/`MIN_MAIN_RECYCLER_NODES`/`MIN_MAIN_RECYCLER_WIDTH_PERCENT`
|
||||||
|
和最小高度算式(`rootBounds.height() / 8`)字面量与位置均未改动,
|
||||||
|
只是把判定语句搬到了纯函数里、原样传参调用。
|
||||||
|
- `android-buyer/app/src/main/java/com/roubao/autopilot/pinduoduo/PinduoduoCandidateModels.kt`:
|
||||||
|
`PinduoduoCandidateCardRead` 的 `init` 恒等式移除 `rejectedImage`,新恒等式
|
||||||
|
为 `containers - (rejClass+rejWidth+rejHeight+rejVisible+rejTexts+rejPrice+
|
||||||
|
rejClick+rejSignature) = cards.size`;非负校验列表里保留 `rejectedImage`
|
||||||
|
(仍需 ≥0)。字段旁追加口径变更注释。
|
||||||
|
- `android-buyer/app/src/main/java/com/roubao/autopilot/procurement/CandidateSearchDiagnostics.kt`:
|
||||||
|
`rejectedImage` 字段旁追加口径变更注释(键名、位置、`auditSuffix()` 输出
|
||||||
|
格式未改,`CandidateSearchDiagnosticsTest` 的既有断言未改也仍然通过)。
|
||||||
|
- `android-buyer/app/src/test/java/com/roubao/autopilot/pinduoduo/PinduoduoCandidateContainerFilterTest.kt`
|
||||||
|
(新增):基于真机图搜结果页 dump(`RecyclerView` bounds `[0,132][1080,2328]`,
|
||||||
|
宽 1080,根节点高 2376,最小卡片高 297)构造 8 个子节点夹具,覆盖:
|
||||||
|
- 4 张 `535×764` 真实商品卡在 `hasImage=false` 时仍被接受(含四个不同标题
|
||||||
|
算出四个互不相同的稳定签名);
|
||||||
|
- 2 个 `535×182` 矮容器仍因 HEIGHT 被拒;
|
||||||
|
- `LinearLayout`/`RecyclerView` 两个非卡片容器仍因 CLASS 被拒;
|
||||||
|
- 汇总全部 8 个节点复刻 `containers=8 rejClass=2 rejHeight=2 cards=4
|
||||||
|
rejImage=4`,并用这份数据构造 `PinduoduoCandidateCardRead` 验证新恒等式
|
||||||
|
成立;
|
||||||
|
- 另外 11 个独立回归测试:基线(缺图仍接受)、宽度过窄、宽度过宽、高度
|
||||||
|
不足、可见面积不足、与 recycler 不相交、文本不足、缺价格语义、缺安全
|
||||||
|
点击目标、缺稳定签名的拒绝行为,以及 `hasImage=true` 时卡片如实记录
|
||||||
|
`true`。
|
||||||
|
- `android-buyer/app/src/test/java/com/roubao/autopilot/pinduoduo/PinduoduoCandidateCardReadTest.kt`:
|
||||||
|
`each rejection bucket is one short circuit attribution` 移除
|
||||||
|
`rejectedImage` 分支(它不再是一个「否决桶」);把原先的
|
||||||
|
`container identity leaves exactly the returned card count` 改造为
|
||||||
|
`... excluding rejectedImage`,构造 2 张卡片(一张有图一张无图)验证
|
||||||
|
`containers=10` 减去其余八个拒绝计数各 1 后剩下 2 张;新增
|
||||||
|
`rejectedImage counts an accepted card that lacked an image` 单独验证该
|
||||||
|
计数与「已接受但缺图」的对应关系。
|
||||||
|
- `docs/tasks/T-271.md`:status 从 `TODO` 改为 `DOING`;本节。
|
||||||
|
- `docs/current-state.md`:阶段列表追加 T-271 DOING;追加 T-271 叙述段落
|
||||||
|
(含 `rejImage` 口径变更说明);任务表追加 T-271 行。
|
||||||
|
|
||||||
|
**未改动(按边界要求核对)**:`backend-api/**` 未碰;
|
||||||
|
`candidateReadyPollLimit`(仍为 50)与轮询间隔(仍为 200ms)未改;
|
||||||
|
颜色/尺码硬约束、商品身份解析、双证据、支付边界、订单确认页、登录/验证码/
|
||||||
|
风控判定与 T-265 规格入口优先级/落到结算页跳过逻辑/T-268 门控诊断/T-269
|
||||||
|
详情页识别与归位策略均未触碰;价格解析(前缀/节点拆分)未处理。
|
||||||
|
|
||||||
|
**命令与结果(均在 Windows 侧执行,WSL 无 Android SDK/JDK)**
|
||||||
|
|
||||||
|
```
|
||||||
|
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat testDebugUnitTest --no-daemon --console=plain"
|
||||||
|
BUILD SUCCESSFUL;Debug 单测 433 项,0 failures / 0 errors
|
||||||
|
(含新增 PinduoduoCandidateContainerFilterTest 15 项、
|
||||||
|
PinduoduoCandidateCardReadTest 由 3 项增至 4 项)
|
||||||
|
|
||||||
|
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat testReleaseUnitTest --no-daemon --console=plain"
|
||||||
|
BUILD SUCCESSFUL;Release 单测 433 项,0 failures / 0 errors
|
||||||
|
|
||||||
|
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat lintDebug --no-daemon --console=plain"
|
||||||
|
BUILD SUCCESSFUL
|
||||||
|
|
||||||
|
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat assembleDebug --no-daemon --console=plain"
|
||||||
|
BUILD SUCCESSFUL;app-debug.apk 已生成
|
||||||
|
|
||||||
|
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat assembleRelease --no-daemon --console=plain"
|
||||||
|
BUILD SUCCESSFUL;app-release-unsigned.apk 已生成
|
||||||
|
```
|
||||||
|
|
||||||
|
**八个子节点夹具断言结果**(`PinduoduoCandidateContainerFilterTest`):
|
||||||
|
|
||||||
|
| # | 类型 | 尺寸 | 断言结果 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 0 | LinearLayout | 1080×168 | `Rejected(CLASS)` |
|
||||||
|
| 1 | RecyclerView | 1080×120 | `Rejected(CLASS)` |
|
||||||
|
| 2..5 | FrameLayout | 535×764,`hasImage=false` | `Accepted`,`card.hasImage=false`,四张签名互不相同 |
|
||||||
|
| 6..7 | FrameLayout | 535×182 | `Rejected(HEIGHT)` |
|
||||||
|
|
||||||
|
汇总:`containers=8 rejClass=2 rejHeight=2 rejImage=4 cards=4`,代入新恒等式
|
||||||
|
`containers - (rejClass+rejWidth+rejHeight+rejVisible+rejTexts+rejPrice+
|
||||||
|
rejClick+rejSignature) = cards.size` 即 `8-2-2=4`,与 `cards.size=4` 一致
|
||||||
|
(`rejImage=4` 不参与该式)。
|
||||||
|
|
||||||
|
**待人工完成(不在本次改动范围内)**:真机验证——在图片尚未完全加载的
|
||||||
|
图搜结果页跑任务,确认 `cards` 不再为 0、能进入候选采集,并从 Admin 读取
|
||||||
|
`cards`/`distinct`/`rejImage` 记入本节;完成前 status 保持 `DOING`。
|
||||||
|
|
||||||
|
**偏离任务文档之处及理由**:
|
||||||
|
|
||||||
|
1. 任务「方案」一节未显式要求把候选过滤逻辑抽成独立纯函数,只说"移除
|
||||||
|
`hasImage` 的否决权"。之所以新增
|
||||||
|
`PinduoduoCandidateContainerFilter`,是因为验收要点明确要求「有基于真机
|
||||||
|
dump 特征的单测」,而 `candidateNodes()` 直接操作
|
||||||
|
`android.view.accessibility.AccessibilityNodeInfo`,仓库 `app/build.gradle.kts`
|
||||||
|
的 `testImplementation` 里没有 Robolectric、也没有 Mockito/MockK,无法在
|
||||||
|
JVM 单测里构造该框架类型的夹具或调用私有方法。抽成不依赖
|
||||||
|
`AccessibilityNodeInfo` 的纯函数是在不新增测试框架依赖、不引入
|
||||||
|
Robolectric(会改变构建配置和体积)的前提下,唯一能让"真机 dump 数值"
|
||||||
|
直接驱动单测的办法。抽取只是**照抄原判定语句的顺序和阈值参数**,未改变
|
||||||
|
任何逻辑、常量或短路顺序,`candidateNodes()` 里的其余代码(recycler
|
||||||
|
查找、`collectNodes`/`collectSemanticTexts`/`findSafeCandidateClickTarget`/
|
||||||
|
点击目标查找等)未受影响。
|
||||||
|
2. 新纯函数里的宽度/高度/可见面积等阈值百分比作为参数从
|
||||||
|
`BuyerAccessibilityService` 的私有 `companion object` 常量原样传入,而不是
|
||||||
|
在新文件里重新声明同名常量,避免两处定义漂移;测试文件里为了独立验证
|
||||||
|
边界值,用私有 `companion object` 字面量复刻了 `35`/`60`/`80`/`2` 四个数值
|
||||||
|
(因为它们在生产代码里是 `private const val`,测试模块无法直接
|
||||||
|
import)——这是任务文档本身在“真机 dump 数据”一节已经写明的已知阈值,
|
||||||
|
不是本任务新引入的数值。
|
||||||
Reference in New Issue
Block a user