Closed
fix(tests): 修复3个测试文件的mock问题 #14
gyangeAdd on Aug 29Closed at Aug 29
fix(tests): 修复3个测试文件的mock问题 #14
Closed
gyangeAdd on Aug 29Closed at Aug 29
Total: 475 files changed+102423-13450
@@ -83,6 +83,76 @@ export const PLATFORM_REGISTRY: Record<Platform, PlatformMeta> = {
83- **TypeScript strict mode**: All code must pass `tsc --noEmit`83- **TypeScript strict mode**: All code must pass `tsc --noEmit`
84- **Async preferred**: Use async/await, avoid sync APIs except in tests84- **Async preferred**: Use async/await, avoid sync APIs except in tests
85 85 
86+## Concurrency Safety Strategy
87+ 
88+CPTF 在异步并发场景下采用三层防护策略,**大部分全局状态无需加锁**。
89+ 
90+### 模式隔离原则(v0.3.x)
91+ 
92+模式(native/external)构造时定型,业务代码无 `if (isNativeMode)` 分支:
93+- 启动期 1 次决策:`DriverModeArbiter.selectMode()` 决定整 run 模式
94+- 内部重试:L2 重连 / L3 action retry / L4 崩溃恢复
95+- 禁止操作期静默切实现(破坏隔离、报告难归因)
96+- 详见 `cptf-native-driver/SKILL.md` 4 级降级策略
97+ 
98+### Node.js 单线程原子性
99+ 
100+Node.js 的事件循环是单线程的,这意味着:
101+- **同步 Map 操作是原子的**:`Map.get()` / `Map.set()` / `Map.delete()` 不会被其他异步任务打断
102+- **两个 await 点之间不会发生交错**:代码块在单个 tick 内执行完毕
103+ 
104+示例:`NavigationCoordinator` 的 13 个静态 Map(`lastModuleByPlatform`、`lastNavigateByDeepLinkByPlatform` 等)全部使用同步操作,无需 mutex 保护。
105+ 
106+### 设备级隔离
107+ 
108+Map 键设计遵循 `${platform}:${deviceId}` 格式:
109+ 
110+```typescript
111+// NavigationCoordinator 使用设备组合键
112+static getModule(platform: string, deviceId: string): string | undefined {
113+ const key = `${platform}:${deviceId}`;
114+ return this.lastModuleByPlatform.get(key);
115+}
116+```
117+ 
118+每个 `TestRunner` 实例绑定单个物理设备 → 不同设备之间绝不共享同一个 key → 并发安全自然保证。
119+ 
120+### 并发模型与容错设计
121+ 
122+| 层级 | 并发机制 | 共享状态 | 安全保证 |
123+|------|---------|---------|---------|
124+| `ParallelTestRunner` 多设备 | `Promise.all` 同进程 | 不同 deviceId | Map 键隔离 |
125+| `TestRunner.run()` 多 case | `maxConcurrency`(默认 1) | 同 deviceId | 串行保护 |
126+| `NavigationCoordinator` 静态 Map | 同步操作 | 全局 | 原子性 |
127+ 
128+**默认配置 `maxConcurrency = 1`** 保证同设备串行执行。即使启用并行(罕见场景),代码已有防御逻辑:
129+- TestRunner.runCaseQueue 在 `maxConcurrency > 1` 时主动禁用 `setNextTest`
130+- `shouldSkipDeepLink` 的启发式阈值(`failures >= 2`)容忍 off-by-one 偏差
131+- `deepLinkProbeInterval = 15` 提供足够缓冲空间
132+ 
133+### 何时需要加锁
134+ 
135+| 场景 | 是否需要锁 | 原因 |
136+|------|-----------|------|
137+| 同步 Map 操作 | ❌ 不需要 | Node.js 单线程保证原子性 |
138+| 设备级隔离的状态 | ❌ 不需要 | 键空间不重叠 |
139+| 初始化后只读的配置 | ❌ 不需要 | 无写入竞争 |
140+| **多步骤操作含 await** | ✅ 需要 | 检查-执行间存在窗口期 |
141+| **多进程资源共享** | ✅ 需要 | 跨进程边界失去原子性 |
142+| **外部资源(文件、Redis)** | ✅ 需要 | 不在 Node.js 事件循环内 |
143+ 
144+示例:`ParallelTestRunner.buildQualityReportWithHook()` 执行 `spawn` 子进程 + 写入同一文件 → 需要文件锁。
145+ 
146+### 设计原则
147+ 
148+1. **信任同步原子性**:单线程模型天然防数据竞态
149+2. **键设计包含设备标识**:自动隔离不同设备的状态
150+3. **默认串行执行**:保守的并发策略
151+4. **启发式容错**:阈值设计容忍微小偏差
152+5. **显式清理**:`teardown()` 阶段重置所有状态
153+ 
154+详细分析见 [`docs/architecture/CONCURRENCY-SAFETY.md`](../../../packages/framework/docs/architecture/CONCURRENCY-SAFETY.md)(含完整验证方法和案例分析)。
155+ 
86## Verification156## Verification
87 157 
88```bash158```bash
@@ -100,14 +170,18 @@ find packages/framework/src -name "*.ts" -not -path "*/__tests__/*" -exec head -
100- `cptf-test-api` — User-facing test API170- `cptf-test-api` — User-facing test API
101- `cptf-refactor-rules` — v0.2.1+ 重构规则(Mixin 拆分 / as any 治理 / shell 标准化 / race 风暴 / 模板分离)171- `cptf-refactor-rules` — v0.2.1+ 重构规则(Mixin 拆分 / as any 治理 / shell 标准化 / race 风暴 / 模板分离)
102 172 
103-## v0.2.1 重构成果速查173+## God class 拆分现状(协作类速查)
104 174 
105-| 类 | 拆前 | 拆后 | Mixin 数 |175+经 0.3.1 拆分,大型类的职责分布如下(具体行数以实际代码为准):
106-|----|------|------|----------|176+ 
107-| HarmonySystem | 1813 | 481 | 5 |177+| 类 | 拆分方式 | 协作类/模块 |
108-| AndroidSystem | 1621 | 336 | 5 |178+|----|---------|------------|
109-| IosSystem | 1087 | 457 | 5 |179+| TestRunner | 方法化 + 协作类拆出 | `core/case-queue-scheduler.ts`(SkipResultFactory + CircuitBreakerTracker)、`core/run-metrics-aggregator.ts`、`infrastructure/executor-loader.ts`;create 的 resolveRunDirectories/setupDriverMode/setupMetricServer 三 helper |
110-| TestRunner | 1141 | 1103 | 2(HealthKeeper + ReportOrchestrator) |180+| ComparisonRunner | 流式对比重构 | StreamComparisonQueue(纯聚合器)+ saveReport 单出口 |
111-| ComparisonRunner | 1116 | 1116 | 2(UIComparisonService + ComparisonReportBuilder) |181+| comparison-report-generator | facade + 三专职类 | comparison-html-sections / comparison-html-streaming / comparison-markdown-generator |
182+| expect-executor | 纯函数拆出 | expect-element-utils / expect-color-analyzer |
183+| test-scene-navigator | facade + 组合式 | scene-nav-shared / scene-nav-home / scene-nav-targets |
184+| html-generator | 图表引擎抽出 | chart-renderer.ts |
185+| HarmonySystem / AndroidSystem / IosSystem | Strategy mixin(Native/External 双子类) | mixins/ 目录 + host-contract.ts 宿主契约 |
112 186 
113**未来重构前必读 `cptf-refactor-rules`**,避免重蹈 race 风暴 / shell 重复 / as any 污染等 0.2.1 已修复的反模式。187**未来重构前必读 `cptf-refactor-rules`**,避免重蹈 race 风暴 / shell 重复 / as any 污染等 0.2.1 已修复的反模式。
@@ -1,6 +1,6 @@
1---1---
2name: cptf-cli2name: cptf-cli
3-description: Use when running cptf CLI commands, configuring parallel/multi-device execution, or interpreting CLI output. Triggers: "cptf run", "cptf compare", "cptf scan", "cptf devices", "cptf init", "cptf check-env", "cptf template", "--platform", "--module", "--strategy", "--device", "cptf.config.json", "parallel execution", "multi-device". Do NOT use for writing test cases (cptf-test-api) or for iOS specifics (cptf-ios).3+description: Use when running cptf CLI commands, configuring parallel/multi-device execution, or interpreting CLI output. Triggers: "cptf run", "cptf compare", "cptf scan", "cptf devices", "cptf init", "cptf check-env", "cptf template", "--platform", "--module", "--strategy", "--device", "--driver", "cptf.config.json", "parallel execution", "multi-device". Do NOT use for writing test cases (cptf-test-api) or for iOS specifics (cptf-ios).
4---4---
5 5 
6# CPTF CLI Skill6# CPTF CLI Skill
@@ -19,6 +19,9 @@ cptf compare # cross-platform comparison
19cptf init # create config file19cptf init # create config file
20cptf check-env # check environment dependencies20cptf check-env # check environment dependencies
21cptf template performance -n my-test # generate test template21cptf template performance -n my-test # generate test template
22+cptf run --driver native # 灰盒模式(设备端原生执行,需测试 HAP)
23+cptf run --driver external # 黑盒模式(外部 Agent,默认)
24+cptf run --driver auto # 自动模式(优先 native,降级 external)
22cptf run -p harmony --strategy round-robin --device <id> # 0.2.0+25cptf run -p harmony --strategy round-robin --device <id> # 0.2.0+
23```26```
24 27 
@@ -1,6 +1,6 @@
1---1---
2name: cptf-framework-dev2name: cptf-framework-dev
3-description: "Use when modifying CPTF framework internals (packages/framework) for v0.2.1+. Triggers: 'add platform', 'refactor runner', 'add metric collector', 'BaseSystem', 'TestRunner', 'ComparisonRunner', 'ParallelTestRunner', 'ISystem', 'PLATFORM_REGISTRY', 'IosSystem', 'HarmonySystem', 'AndroidSystem', 'SmartMonkey', 'WhiteScreenDetector', 'MemoryLeakDetector', 'SystemFactory', 'five-layer architecture', 'sub-interface', '@internal', 'TestExecutorInstance', 'StabilityTestConfig', 'drainHistories'. Do NOT use for writing user test cases (use cptf-test-api), CLI command usage (use cptf-cli), iOS WDA / xctrace (use cptf-ios), report customization (use cptf-report), or UI comparison config (use cptf-ui-comparison)."3+description: "Use when modifying CPTF framework internals (packages/framework) for v0.3.0+. Triggers: 'add platform', 'refactor runner', 'add metric collector', 'BaseSystem', 'TestRunner', 'ComparisonRunner', 'ParallelTestRunner', 'ISystem', 'PLATFORM_REGISTRY', 'IosSystem', 'HarmonySystem', 'AndroidSystem', 'SmartMonkey', 'WhiteScreenDetector', 'MemoryLeakDetector', 'SystemFactory', 'five-layer architecture', 'sub-interface', '@internal', 'TestExecutorInstance', 'StabilityTestConfig', 'drainHistories', 'HarmonyNativeDriver', 'Synchronizer', 'AppProbe', 'driverMode', 'native driver', 'selector-based ops', 'clickByTestId', 'inputTextByTestId', 'dual-mode optimization', '双模式优化', 'external vs native', 'performance optimization', '性能优化'. Do NOT use for writing user test cases (use cptf-test-api), CLI command usage (use cptf-cli), iOS WDA / xctrace (use cptf-ios), report customization (use cptf-report), or UI comparison config (use cptf-ui-comparison)."
4---4---
5 5 
6# CPTF Framework Development Skill (Master Entry Point)6# CPTF Framework Development Skill (Master Entry Point)
@@ -10,20 +10,22 @@ description: "Use when modifying CPTF framework internals (packages/framework) f
10> belong to any single sub-skill. For task-specific lookups, use the10> belong to any single sub-skill. For task-specific lookups, use the
11> focused sub-skills instead.11> focused sub-skills instead.
12>12>
13-> | Sub-skill | Covers | When to use |13+ > | Sub-skill | Covers | When to use |
14-> |-----------|--------|-------------|14+ > |-----------|--------|-------------|
15-> | `cptf-arch` | Five-layer architecture, dependency rules, type locations, coding standards, PLATFORM_REGISTRY | "where should I put", "layer dependency", "type definition" |15+ > | `cptf-arch` | Five-layer architecture, dependency rules, type locations, coding standards, PLATFORM_REGISTRY | "where should I put", "layer dependency", "type definition" |
16-> | `cptf-refactor-rules` | v0.2.1 refactor constraints, mixin split rules, anti-patterns | Before any refactoring of packages/framework |16+ > | `cptf-refactor-rules` | v0.2.1 refactor constraints, mixin split rules, anti-patterns | Before any refactoring of packages/framework |
17-> | `cptf-system-extension` | New platform, ISystem sub-interfaces, mixin pattern, SystemFactory | "add platform", "new ISystem" |17+ > | `cptf-system-extension` | New platform, ISystem sub-interfaces, mixin pattern, SystemFactory | "add platform", "new ISystem" |
18-> | `cptf-test-api` | @Test/@Step decorators, TestConfig, StepConfig, ExpectConfig | Writing test cases |18+ > | `cptf-test-api` | @Test/@Step decorators, TestConfig, StepConfig, ExpectConfig | Writing test cases |
19-> | `cptf-ui-locator` | UIAutomation API, non-invasive locators, layout cache rules | "findByTestId", "clickByTestId", "locator strategy" |19+ > | `cptf-ui-locator` | UIAutomation API, non-invasive locators, layout cache rules | "findByTestId", "clickByTestId", "locator strategy" |
20-> | `cptf-ui-comparison` | UIComparisonConfig, UIComparator, cross-platform diff | "uiComparison", "visual comparison" |20+ > | `cptf-ui-comparison` | UIComparisonConfig, UIComparator, cross-platform diff | "uiComparison", "visual comparison" |
21-> | `cptf-stability` | StabilityTest, 9 detector params, StabilityTestConfig, crash recovery | "SmartMonkey", "WhiteScreenDetector", "MemoryLeakDetector" |21+ > | `cptf-stability` | StabilityTest, 9 detector params, StabilityTestConfig, crash recovery | "SmartMonkey", "WhiteScreenDetector", "MemoryLeakDetector" |
22-> | `cptf-cli` | CLI commands, parallel execution, config file, device allocation | "cptf run", "cptf compare", "--strategy" |22+ > | `cptf-cli` | CLI commands, parallel execution, config file, device allocation | "cptf run", "cptf compare", "--strategy" |
23-> | `cptf-ios` | iOS WDA, xctrace, simulator vs real device, Phase 0/1/2 | "WDA", "xctrace", "iOS real device" |23+ > | `cptf-ios` | iOS WDA, xctrace, simulator vs real device, Phase 0/1/2 | "WDA", "xctrace", "iOS real device" |
24-> | `cptf-report` | HTML/Markdown/JSON generators, ReportJobQueue, trend charts | "report format", "custom generator" |24+ > | `cptf-report` | HTML/Markdown/JSON generators, ReportJobQueue, trend charts | "report format", "custom generator" |
25-> | `cptf-troubleshoot` | Common issues, debug recipes, common mistakes table | "timeout", "element not found", "test hangs" |25+ > | `cptf-troubleshoot` | Common issues, debug recipes, common mistakes table | "timeout", "element not found", "test hangs" |
26-> | `cptf-rnoh-app` ⬆️ | App-side integration, scene components, Deep Link config (root `.opencode/skills/`) | "App.tsx", "scene component", "bundle:release" |26+ > | `cptf-optimization-verification` | Static analysis false positive patterns, verification-first workflow, cost-benefit decision framework | Receiving optimization proposals from static analysis tools or code reviews |
27+ > | `cptf-rnoh-app` ⬆️ | App-side integration, scene components, Deep Link config (root `.opencode/skills/`) | "App.tsx", "scene component", "bundle:release" |
28+ > | **Dual-Mode Optimization Rule** *(本 skill 末段)* | **External vs Native 双模式优化约束、决策矩阵、反模式** | **任何性能/架构优化前必读** |
27 29 
28---30---
29 31 
@@ -37,7 +39,7 @@ need `KNOWN_MULTI_EXPORT_FILES` and `SCENE_NAME_MAP` configured in the script.
37 39 
38- Location: `app/scripts/generate-scenes-index.js`40- Location: `app/scripts/generate-scenes-index.js`
39- Trigger: `npm run bundle:release` → `generateScenesIndex` → overwrites manual edits41- Trigger: `npm run bundle:release` → `generateScenesIndex` → overwrites manual edits
40-- Verification: `node generate-scenes-index.js` should output "12个场景"42+- Verification: `node generate-scenes-index.js` output should match the current scene count in `app/tests/scenes/index.ts` (grows with coverage; ~116 as of 2026-08)
41 43 
42### SafeAreaView Nesting44### SafeAreaView Nesting
43 45 
@@ -59,12 +61,12 @@ Any specification change MUST update documentation:
59 61 
60| Change Type | Required Updates |62| Change Type | Required Updates |
61|-------------|------------------|63|-------------|------------------|
62-| API 新增/修改 | `docs/CAPABILITY-SPEC.md` + `README.md` |64+| API 新增/修改 | `docs/architecture/CAPABILITY-SPEC.md` + `README.md` |
63-| 接口变化 | `docs/USER-GUIDE.md` |65+| 接口变化 | `docs/getting-started/USER-GUIDE.md` |
64| 架构调整 | `docs/ARCHITECTURE.md` + `AGENTS.md` |66| 架构调整 | `docs/ARCHITECTURE.md` + `AGENTS.md` |
65-| 类型定义 | `docs/CAPABILITY-SPEC.md` |67+| 类型定义 | `docs/architecture/CAPABILITY-SPEC.md` |
66-| 性能指标 | `docs/CAPABILITY-SPEC.md` |68+| 性能指标 | `docs/architecture/CAPABILITY-SPEC.md` |
67-| 命令格式 | `docs/CAPABILITY-SPEC.md` |69+| 命令格式 | `docs/architecture/CAPABILITY-SPEC.md` |
68| 规格变更 | 补充/修改对应测试用例 |70| 规格变更 | 补充/修改对应测试用例 |
69 71 
70**Verification Checklist**:72**Verification Checklist**:
@@ -98,7 +100,7 @@ Any specification change MUST update documentation:
98 100 
99- 一致性 = expectResults 对比计数(matched/total)101- 一致性 = expectResults 对比计数(matched/total)
100- 不计算比例,只看验证项是否全部一致102- 不计算比例,只看验证项是否全部一致
101-- consistencyScore = 1.0(全部一致)或 0.0(有不一致)103+- consistencyScore = matched/total 比率(>= 0.8 判一致,< 0.5 为 critical)
102 104 
103**不参与一致性判断的指标**(仅作为告警显示):105**不参与一致性判断的指标**(仅作为告警显示):
104- 执行成功与否、执行时长差异、FPS差异、errors差异106- 执行成功与否、执行时长差异、FPS差异、errors差异
@@ -142,7 +144,7 @@ input text 'text'
142```bash144```bash
143cd packages/framework145cd packages/framework
144pnpm build # Compile TypeScript146pnpm build # Compile TypeScript
145-pnpm test # Run Jest tests (696 tests)147+pnpm test # Run Jest tests
146pnpm typecheck # tsc --noEmit148pnpm typecheck # tsc --noEmit
147pnpm verify # Run scripts/verify.ts (architecture integrity checks)149pnpm verify # Run scripts/verify.ts (architecture integrity checks)
148pnpm pack # Generate cptf-framework-*.tgz150pnpm pack # Generate cptf-framework-*.tgz
@@ -201,12 +203,16 @@ Code: `cli.ts` platform selection logic
201 203 
202## Related Documents204## Related Documents
203 205 
204-- `AGENTS.md`: Full project guide (keep ≤100 lines, details in sub-skills)206+- `AGENTS.md`: Full project guide (keep ≤150 lines, details in sub-skills; verify enforces this)
205-- `packages/framework/docs/ARCHITECTURE.md`: Architecture details207+- `packages/framework/docs/architecture/ARCHITECTURE.md`: Architecture details
206-- `packages/framework/docs/CAPABILITY-SPEC.md`: API and metrics specification208+- `packages/framework/docs/architecture/CAPABILITY-SPEC.md`: API and metrics specification
207-- `packages/framework/docs/USER-GUIDE.md`: Quick start and usage guide209+- `packages/framework/docs/getting-started/USER-GUIDE.md`: Quick start and usage guide
208-- `packages/framework/docs/DEVELOPMENT-GUIDE.md`: Extension development guide210+- `packages/framework/docs/development/DEVELOPMENT-GUIDE.md`: Extension development guide
209-- `packages/framework/docs/analysis/OPTIMIZATION-ANALYSIS.md`: Optimization analysis (15/18 items done)211+- `packages/framework/CHANGELOG.md(0.2.1 优化记录已并入)`: Optimization analysis (15/18 items done)
212+- `packages/framework/docs/architecture/CONCURRENCY-SAFETY.md`: **并发安全模型** — 为什么 NavigationCoordinator 等全局状态不需要加锁
213+- `packages/framework/docs/METRICS-SPEC.md`: **测试指标统一规范** — 用例三态模型、通过率公式、崩溃统计、跨格式一致性。所有报告生成器**必须**调用 `computeUnifiedMetrics(result)`,禁止各自计算。
214+- `packages/framework/docs/architecture/STABILITY-SPEC.md`: 稳定性设计与降级策略
215+- `packages/framework/docs/architecture/PERFORMANCE-METRICS-SPEC.md`: 性能指标采集规范
210 216 
211## Related Skills (Quick Routing)217## Related Skills (Quick Routing)
212 218 
@@ -222,3 +228,302 @@ Code: `cli.ts` platform selection logic
222- **Report customization** → `cptf-report`228- **Report customization** → `cptf-report`
223- **Debugging / common issues** → `cptf-troubleshoot`229- **Debugging / common issues** → `cptf-troubleshoot`
224- **App integration (App.tsx, scenes)** → `cptf-rnoh-app`(根目录 `.opencode/skills/`)230- **App integration (App.tsx, scenes)** → `cptf-rnoh-app`(根目录 `.opencode/skills/`)
231+ 
232+ 
233+## Architecture (v0.3.0+ — DeviceBridge Removed)
234+ 
235+### Three-Layer Architecture (Simplified)
236+ 
237+```
238+调度层: UIAutomation → ISystem (direct delegation, no intermediate strategy/bridge layer)
239+系统层: ISystem 7 sub-interfaces → HarmonySystem/AndroidSystem/IosSystem
240+ HarmonySystem has: driverMode (shell|native) + HarmonyNativeDriver (internal WS client)
241+基础层: types/config/logger/cache/port-management/device-discovery
242+```
243+ 
244+### Driver Mode 隔离 + 4 级降级原则(v0.3.0 已完成)
245+ 
246+**核心**:模式构造时定型 + 内部重试 + 禁止操作期静默切实现。
247+ 
248+| 维度 | 状态(v0.3.0+)|
249+|---|---|
250+| 抽象层 | DeviceBridge/UIStrategy 已删除 |
251+| `HarmonyNativeDriver` / `AndroidNativeDriver` | 内部组件 |
252+| `driverMode` | 内部 config(`'shell'` / `'native'`)|
253+| Mixin 路由 | **Strategy 模式 + 构造时定型**(6 mixin × 2 平台 = 12 个策略子类)|
254+| 业务代码 | **零 `isNativeMode` runtime 分支** |
255+| 失败处理 | 4 级降级(启动期/重连/重试/崩溃恢复)|
256+ 
257+**4 级降级表**(详见 `cptf-native-driver/SKILL.md`):
258+ 
259+| Level | 位置 | 切模式 | 范围 | 实现状态 |
260+|---|---|---|---|---|
261+| L1 Pre-flight | `ISystem.enableNativeDriver()`(v0.3.1 起装配下沉 system 层,TestRunner 只调接口) | ✅ 整 run 1 次 | run | ✅ 双平台已实现(Harmony 走 DriverModeArbiter) |
262+| L2 Reconnect | WS 断开 | ❌ 仍 native | 单 case | ✅ 双平台已实现(指数退避 3 次) |
263+| L3 Action Retry | `sendCommand()` 内部 | ❌ 仍 native | 单 action | ✅ 双平台已实现 |
264+| L4 Crash Recovery | APP_ERROR | ❌ 仍 native | 单 case | ✅ 双平台已实现 + TestRunner 经 `ISystem.tryNativeCrashRecovery` 接入 |
265+ 
266+**禁止事项**:
267+- 操作期静默切实现(破坏隔离、报告难归因)
268+- 启动期降级后再次自动切回 native(模式抖动)
269+- 报告 `source` 字段混用 native/external 两种语义
270+ 
271+### 隔离实现模板
272+ 
273+```ts
274+abstract class HarmonyInputController extends BaseSystemMixin<HarmonySystem> {
275+ abstract click(x: number, y: number): Promise<boolean>;
276+ abstract clickByTestId(testID: string): Promise<boolean>;
277+ // ... 15 abstract 方法
278+}
279+class NativeInputController extends HarmonyInputController {
280+ private get driver(): HarmonyNativeDriver { return this.host.nativeDriver as HarmonyNativeDriver; }
281+ async click(x, y) { return this.driver.click(x, y); }
282+ async clickByTestId(t) { return this.driver.clickComponent(t); }
283+}
284+class ExternalInputController extends HarmonyInputController {
285+ // HMD RPC + uitest shell 降级(仅 external 内部,不跨模式)
286+}
287+ 
288+// HarmonySystem 构造时定型
289+this.input = config.platformOptions?.['driver'] === 'native'
290+ ? new NativeInputController(this)
291+ : new ExternalInputController(this);
292+```
293+ 
294+### Key Design Decisions
295+ 
296+1. **DeviceBridge layer REMOVED** — ExternalDeviceBridge was 100% delegation to ISystem (zero value-add), NativeDeviceBridge absorbed into HarmonyNativeDriver
297+2. **UIStrategy layer REMOVED** — UIAutomation delegates directly to ISystem
298+3. **ISystemUI extended** with selector-based ops: clickByTestId, inputTextByTestId, clearTextByTestId, scrollToTop/Bottom, assertComponentExist
299+4. **driverMode** is an internal config on HarmonySystem: `'shell'` | `'native'`
300+5. **HarmonyNativeDriver** is an internal component of HarmonySystem, not a separate abstraction
301+6. **Layout cache** built into UIAutomation (TTL 1s) and HarmonyNativeDriver (TTL 1s)
302+7. **6 mixin × 2 平台 = 12 策略子类**(input/layout/performance/media/navigation + 2 stability)
303+8. **iOS 单实现**(无 native driver,6 mixin 全部单类,符合预期)
304+9. **Android mixin 类名前缀**:所有 Native/External 子类带 `Android` 前缀(`AndroidNativeInputController` 而非 `NativeInputController`)
305+ 
306+### Driver Mode Routing(已通过 Strategy 模式解决)
307+ 
308+Native 模式:策略类 `NativeXxxController` 单路径调用 `nativeDriver.xxx()`
309+External 模式:策略类 `ExternalXxxController` 内部 HMD RPC → uitest shell 二级降级(仅 external 内部,不跨模式)
310+ 
311+### Mixin Strategy Split(v0.3.0 已完成)
312+ 
313+**全部 6 个 mixin(harmony + android 双平台)已按 Native/External 策略类拆分,构造时定型**:
314+ 
315+| Mixin | 基类(abstract) | Harmony Native | Harmony External | Android Native | Android External |
316+|-------|-----------------|----------------|------------------|----------------|------------------|
317+| input | `HarmonyInputController` / `AndroidInputController` | `NativeInputController` | `ExternalInputController` | `AndroidNativeInputController` | `AndroidExternalInputController` |
318+| layout | `HarmonyLayoutProvider` / `AndroidLayoutProvider` | `NativeLayoutProvider` | `ExternalLayoutProvider` | `AndroidNativeLayoutProvider` | `AndroidExternalLayoutProvider` |
319+| performance | `HarmonyPerformanceProvider` / `AndroidPerformanceProvider` | `NativePerformanceProvider` | `ExternalPerformanceProvider` | `AndroidNativePerformanceProvider` | `AndroidExternalPerformanceProvider` |
320+| media | `HarmonyMediaProvider` / `AndroidMediaProvider` | `NativeMediaProvider` | `ExternalMediaProvider` | `AndroidNativeMediaProvider` | `AndroidExternalMediaProvider` |
321+| navigation | `HarmonyNavigationProvider` / `AndroidNavigationProvider` | `NativeNavigationProvider` | `ExternalNavigationProvider` | `AndroidNativeNavigationProvider` | `AndroidExternalNavigationProvider`(空壳)|
322+| stability | `HarmonyStabilityProbe` / `AndroidStabilityProbe` | `NativeStabilityProbe`(空壳)| `ExternalStabilityProbe`(空壳)| `AndroidNativeStabilityProbe`(空壳)| `AndroidExternalStabilityProbe`(空壳)|
323+ 
324+**规则**:
325+1. **禁止在 mixin 内使用 `isNativeMode` 分支**——Native/External 行为差异必须通过策略子类 override 实现
326+2. 策略类构造在 `HarmonySystem`/`AndroidSystem` 构造函数按 `driverMode` 定型(一次性)
327+3. Native 策略内部**必须**提供 External 降级路径(nativeDriver 失败 → fallback 到基类 protected shell 实现)
328+4. 模式无关逻辑(录屏、批量截图、network/storage 采集等)放基类
329+5. Native 策略中 nativeDriver 可能为 null(连接晚于构造)——需 null 防御 + try/catch 降级
330+6. **空壳子类可接受**:当 native/external 行为相同时(如 stability),子类为空壳;扩展 native 路径时填充
331+ 
332+**设备端 metric 推送键名(getLatestMetric)**:
333+- Harmony(NativeTestDriver.ets):`fps / memoryPeak / cpuPeak / cpuApp / fpsJank / fpsBigJank`
334+- Android(DeviceDriver.java):`fps / pss / totalCpu / procCpu`
335+- 两平台键名不同,Native 策略子类各自读取,禁止混用
336+ 
337+**MetricSource 新增值**:`native-device`(Android 进程内 instrumentation 采集)、`native-hidumper`(Harmony 设备端 SP_daemon/hidumper)、`native-sp-daemon`
338+ 
339+**PerformanceCollector 推送链路**(v0.3.0+):
340+- 构造时 `subscribeNativeMetricStream()` 自动调 `nativeDriver.onMetric(this.handleMetricEvent)`
341+- 设备端 `startMonitoring` 定时 hidumper/gfxinfo → WS `metric` 事件 → `handleMetricEvent` → 直接入 `metricsHistory`
342+- `collect()` 优先读 `lastPushedMetric`(≤2*intervalMs 内),不调 shell fallback
343+ 
344+**media-provider 特殊点**:
345+- `screenshot()` 已拆分(Native 走 `driver.screenshot`,External 走 shell `screencap`)
346+- `startScreenRecord/stopScreenRecord` **仍在基类**(两模式共享 shell `screenrecord` 路径,因 native 模式无独立录屏 API)
347+ 
348+---
349+ 
350+## Dual-Mode Optimization Rule (MANDATORY)
351+ 
352+**核心原则**:任何架构重构、性能优化、接口调整都必须**同时分析 external 模式和 native 模式**的影响,禁止"只优化一种模式"的方案。
353+ 
354+### 决策矩阵
355+ 
356+| 优化类型 | External | Native | 实现策略 |
357+|---------|----------|--------|---------|
358+| 两种模式都受益 | ✅ | ✅ | **共享实现**(推荐) |
359+| 仅 native 受益 | ❌ | ✅ | native-only 分支 + external 降级 |
360+| 仅 external 受益 | ✅ | ❌ | external-only 分支 + native 不受影响 |
361+| 两种模式互相冲突 | ❌ | ❌ | **禁止实现**,需重新设计 |
362+ 
363+### 实现检查清单(每次优化前必须完成)
364+ 
365+1. **路径分析**:当前代码路径在两种模式下是否都覆盖?
366+ - 共享路径 → 考虑共享实现
367+ - 分支路径 → 是否需要分别优化?
368+2. **接口兼容性**:优化后的接口/方法是否在两种模式下都可用?
369+ - 是 → 直接实现
370+ - 否 → 提供降级或标记 TODO
371+3. **数据一致性**:优化是否影响数据准确性?
372+ - 共享数据源(如 PerformanceCollector)→ 必须两种模式同时验证
373+ - 独立数据源(如 WS 专用缓存)→ 可独立优化
374+4. **向后兼容**:是否破坏已有 API?
375+ - 两种模式都依赖的 API → 保持兼容 + 新增方法
376+ - 单一模式依赖的 API → 可在该模式内部调整
377+ 
378+### 反模式(禁止)
379+ 
380+| 反模式 | 问题 | 正确做法 |
381+|--------|------|---------|
382+| 只优化 native,external 性能下降 | external 用户体验降级 | 两种模式同时分析,共享优化或分别处理 |
383+| 假设"只有 native 使用"而删除代码 | external 模式报错 | 检查所有调用点,两种模式都可能需要 |
384+| 接口改名只改一种模式 | 另一种模式编译失败 | 接口变更必须两种模式同时支持 |
385+| 性能优化只测一种模式 | 无法发现跨模式问题 | 两种模式都跑基准测试对比 |
386+| 降级路径无条件破坏性清理 | 瞬时错误杀健康资源 | fallback 前先探测资源健康(`isAlive()`),只清理确认死亡的资源 |
387+| 用"请求计数"刷新健康监测的活跃时间 | 持续失败的流量掩盖 staleness,健康监测永不触发 | `recordAttempt()` 只记尝试计数,`recordActivity()` 只在成功时刷新 `lastActivityAt` |
388+ 
389+## External 模式 U2/ATX 关键规则(实测血泪)
390+ 
391+1. **`dumpHierarchy()` 必须 `compressed=false`**:u2 agent 的 compressed dump **剔除 `hint` 属性**(placeholder/accessibilityHint 断言全灭)。未压缩 dump 含 hint(实测 49 个 vs 0 个)。
392+2. **ATX agent 死亡必须可自愈**(三层防护,缺一不可):
393+ - `ConnectionHealthMonitor.recordAttempt()` **不得刷新 `lastActivityAt`**(否则失败流量掩盖 staleness → 永不 ping → 永不重连;曾致 936 次连续降级)
394+ - `AndroidU2Client` 请求失败 streak ≥3 → 主动 `reconnect()`(`launchU2Server` 会先 `probeExistingService` 复用存活 agent,代价低)
395+ - layout fallback kill ATX 前:`!u2.reconnecting` guard(防 kill 刚重启的 agent 竞态)+ U2 瞬时失败先重试 1 次(200ms)再降级,agent 存活则返回缓存
396+3. **设备 `ps -A` 只显示 comm 名(15 字符)**:查 `app_process / EchoServer` 类进程必须 `ps -A -o PID,ARGS`(ARGS=完整 cmdline)。裸 `ps -A | grep X` 对 app_process 派生进程永远 miss。
397+4. **CLI 堆内存 re-exec 必须传播子进程退出码**(`src/cli/index.ts`):`execFileSync` 在子进程 exit≠0 时抛异常,若被外层 `catch {}` 吞掉会 fall-through 到 `program.parse()` → **父进程把整套用例重跑一遍**(有失败用例的 run 全部双倍耗时)。必须 `process.exit(e.status ?? 1)`。
398+5. **native 模式禁止启动 ATX agent**(`AndroidSystem._doInitU2` native 分支跳过):UiAutomation 全系统单例,ATX 注册失败后的残留进程会干扰 DeviceDriver 的 input 注入。
399+ 
400+ 
401+ 
402+### 最佳实践案例
403+ 
404+#### ✅ 案例 1:PerformanceCollector.takeIterationSnapshot (已实现)
405+ 
406+**问题**:每迭代调用 startMonitoring/stopMonitoring 造成 500ms WS 往返开销。
407+ 
408+**优化**:
409+- 新增 `takeIterationSnapshot()` 方法(获取快照但保持定时器)
410+- `ExecutionEngine` 提升 start/stop 到 case 级别
411+- **两种模式都受益**:
412+ - Native:减少 WS 命令发送次数
413+ - External:减少 timer 重建开销
414+ 
415+**实现方式**:共享实现,无需分支。
416+ 
417+#### ✅ 案例 2:collectPerformance 走 native driver (待实现)
418+ 
419+**问题**:collectPerformance() 调用 hdc shell 耗时 1-2s。
420+ 
421+**优化**:
422+- Native 模式:`HarmonyNativeDriver.collectMetrics()` 在设备端执行 shell,通过 WS 返回结果
423+- External 模式:保持原有 hdc shell 路径
424+ 
425+**实现方式**:在 `HarmonySystem.collectPerformance()` 中判断 `isNativeMode`
426+```typescript
427+async collectPerformance() {
428+ if (this.isNativeMode && this.nativeDriver) {
429+ try {
430+ return await this.nativeDriver.collectMetrics(); // native-only
431+ } catch (e) {
432+ this.logger.debug(`Native collect failed, fallback: ${e.message}`);
433+ }
434+ }
435+ return this.collectPerformanceViaShell(); // external / fallback
436+}
437+```
438+ 
439+**关键**:native-only 分支必须提供 external 降级,保证 external 模式不受影响。
440+ 
441+#### ❌ 案例 3:Layout cache TTL 调整(需要两种模式同时验证)
442+ 
443+**问题**:考虑将 layout cache TTL 从 1s 提升到 5s。
444+ 
445+**错误做法**:只测试 native 模式,发现性能提升 40%,直接合入。
446+ 
447+**正确做法**:
448+1. 分析两种模式的 cache 使用场景
449+ - Native:频繁操作时 cache 命中率高,5s TTL 安全
450+ - External:操作间隔长,5s TTL 可能导致 stale data
451+2. 两种模式分别测试
452+3. 结果:External 在 5s TTL 下发现 30% stale data
453+4. **决策**:保持 1s TTL,或两种模式使用不同 TTL
454+ 
455+### 验证清单(每次优化后必须完成)
456+ 
457+- [ ] **两种模式都跑基准测试**:对比优化前后的性能数据
458+- [ ] **检查所有调用点**:grep 查找优化代码的所有引用,确认两种模式都覆盖
459+- [ ] **数据准确性验证**:检查指标数据在两种模式下是否一致
460+- [ ] **向后兼容性验证**:确认已有 API 未破坏,或提供明确的迁移路径
461+ 
462+---
463+ 
464+### Removed Files (do not reference)
465+ 
466+| Removed | Replacement |
467+|--------|-------------|
468+| `system/native-device-bridge.ts` | `system/harmony/harmony-native-driver.ts` / `system/android/android-native-driver.ts` |
469+| `system/external-device-bridge.ts` | Direct ISystem delegation in UIAutomation |
470+| `system/device-bridge-factory.ts` | Not needed |
471+| `capability/ui/ui-strategy.ts` | Not needed |
472+| `schema/src/device-bridge.ts` | `schema/src/types.ts` (SwipeParams, Selector) |
473+ 
474+### Remaining Work
475+ 
476+| 项 | 优先级 | 状态 |
477+|----|-------|------|
478+| `metricWsServer` → `PerformanceCollector` 直连(当前经 `emitMetricEvent` 中转) | P1 | 待做 |
479+| N1 Android 录屏根因(`nohup` 被 adb shell kill,已降级 warn) | P2 | 根因未修 |
480+| N2 `success=true` 与迭代失败不一致 | P2 | 待调查 |
481+ 
482+## Native 模式 fallback 防御原则(v0.3.x)
483+ 
484+设计 Native/External 双子类时,**External 子类的 fallback 路径不应被 Native 子类继承**。常见反模式:
485+ 
486+```ts
487+// ❌ 反模式:Native 子类继承 External fallback
488+class NativeFindStrategy extends FindStrategy {
489+ async findByTestId(testID) {
490+ try {
491+ return await this.facade.findComponent(testID); // native 路径
492+ } catch {
493+ return await this.fallback(); // ← 继承自 External 的 dump,native 模式也会触发!
494+ }
495+ }
496+}
497+```
498+ 
499+```ts
500+// ✅ 正解:native 模式快速返 null + 留 fallback 给调用方
501+class NativeFindStrategy extends FindStrategy {
502+ async findByTestId(testID) {
503+ try {
504+ const el = await this.facade.findComponent(testID);
505+ if (el) return el;
506+ return null; // ← 显式返回,不走 fallback
507+ } catch {
508+ return null;
509+ }
510+ }
511+}
512+```
513+ 
514+### Native 模式必须 early-return 的检查清单
515+ 
516+| 调用点 | 检查 |
517+|------|------|
518+| `System.warmup()` | native 必须跳过 daemon init(UitestRunner.init / AndroidU2Client.connect) |
519+| `System._doInitXxx()` | native 必须跳过 daemon 启动(uitest/u2) |
520+| `LayoutProvider.getUIElements()` | native 必须用 nativeDriver.dumpLayout,不能 fallback 到 system.dump |
521+| `LayoutProvider.scrollContainer()` | native 必须用 driver.scrollToTop/Bottom,不能 fallback 到 shell swipe |
522+| `LayoutProvider.waitForIdle()` | native 必须用 driver.waitForIdle,不能 fallback 到 healthMonitor 轮询 |
523+| `findStrategy.findByText()` | native facade.findByText 失败直接 fallback getLayout(不要再 findComponents('*') 中间层) |
524+ 
525+### Native 模式验证标准
526+ 
527+- typecheck + 776 tests 全过
528+- 设备端进程列表在 native 模式用例后**不包含** uitest daemon / u2 agent / wda runner
529+- 报告字段 `result.driverMode` = 'native',`result.effectiveDriverMode` = 'native',`result.driverFallback` = false
@@ -66,7 +66,7 @@ Get simulator UDID: `xcrun simctl list devices -j | jq '.devices | to_entries |
66 66 
67## xctrace FPS Collection67## xctrace FPS Collection
68 68 
69-v0.2.0 优化 (`ios-system.ts:746+`):69+v0.2.0 优化(`IosSystem` FPS 采集):
70- 串行 `xcrun xctrace` → **并发的 list + export**(`Promise.all`)70- 串行 `xcrun xctrace` → **并发的 list + export**(`Promise.all`)
71- 复用 `BaseSystem.execCommand`(含 retry + ResilientConnection)71- 复用 `BaseSystem.execCommand`(含 retry + ResilientConnection)
72- 2s 短窗缓存 `_xctraceFpsCache` 避免重复采集72- 2s 短窗缓存 `_xctraceFpsCache` 避免重复采集
@@ -0,0 +1,491 @@
1+---
2+name: cptf-native-driver
3+description: Use when working on HarmonyNativeDriver, AndroidNativeDriver, NativeTestDriver.ets, DeviceDriver.java, ohosTest/instrumentation setup, or native driver mode (--driver native). Triggers: "HarmonyNativeDriver", "AndroidNativeDriver", "native driver", "ohosTest", "Driver.create", "NativeTestDriver", "DeviceDriver", "am instrument", "UiObject2", "By.res", "Component-direct", "clickComponent", "inputTextComponent", "waitForComponent", "rport", "adb reverse", "aa test", "wsl-usb". Do NOT use for external/shell mode only.
4+---
5+ 
6+# Native Driver Skill
7+ 
8+## Architecture
9+ 
10+```
11+WSL2 (宿主机)
12+ ├── HarmonyNativeDriver (WS Server :18081)
13+ │ ├── 布局缓存 (TTL 1s)
14+ │ ├── 截图缓存 (同 Step 复用)
15+ │ └── sendCommand → WS JSON-RPC (15s timeout)
16+ │
17+ │ hdc rport tcp:18081 tcp:18081
18+ │
19+ └── 设备端 (ohosTest 进程)
20+ ├── Driver.create() (进程内, 不依赖 SA 5502 daemon)
21+ ├── NativeTestDriver.ets (WS Client + dispatch 25+ actions)
22+ └── Component 直接操作 (comp.click/inputText/clearText/scrollToTop...)
23+ 
24+入口: aa test -b <bundle> -m entry_test -s unittest OpenHarmonyTestRunner
25+```
26+ 
27+## Key Principle
28+ 
29+**Driver.create() 必须在 ohosTest 进程内调用,不在应用进程。**
30+应用进程同时跑 RNOH 渲染 + TestKit Driver 会互相竞争 → 失败。
31+ohosTest 进程通过 `delegator.startAbility(EntryAbility)` 启动应用,然后独立创建 Driver。
32+ 
33+## NativeTestDriver.ets Action List
34+ 
35+| 类别 | Action | 说明 |
36+|------|--------|------|
37+| 坐标 | click/doubleClick/longClick/swipe/drag | 坐标操作 |
38+| Component-direct | clickComponent/doubleClickComponent/longClickComponent | find+comp.click() 1往返 |
39+| Component-direct | inputTextComponent/clearTextComponent | comp.inputText/clearText |
40+| Component-direct | scrollToTop/scrollToBottom | comp.scrollToTop/Bottom |
41+| 原生 | waitForComponent | driver.waitForComponent(on, timeout) |
42+| 原生 | assertComponentExist | driver.assertComponentExist(on) |
43+| 原生 | findComponents | driver.findComponents(on) |
44+| 原生 | getComponentState | find+全属性(13个)1往返 |
45+| 手势 | dragTo/pinchOut/pinchIn/scrollSearch | comp.dragTo/pinchOut/pinchIn/scrollSearch |
46+| 按键 | triggerKey/pressBack/pressHome/wakeUpDisplay | driver.* |
47+| 显示 | getDisplaySize/getDisplayRotation/setDisplayRotation | driver.* |
48+| 布局 | dumpLayout | driver.dumpLayout(path) + cat |
49+| 截图 | screenCap | driver.screenCap(path) |
50+| 工具 | delayMs/waitForIdle/ping | driver.delayMs/waitForIdle |
51+ 
52+## HarmonyNativeDriver Methods
53+ 
54+宿主机侧方法,映射到 WS action:
55+ 
56+| 方法 | WS Action | 缓存 |
57+|------|-----------|------|
58+| click(x,y) | click | invalidateLayoutCache |
59+| clickComponent(testID) | clickComponent | invalidateLayoutCache |
60+| inputTextComponent(testID,text) | inputTextComponent | invalidateLayoutCache |
61+| waitForComponent(testID,timeout) | waitForComponent | — |
62+| getComponentState(testID) | getComponentState | — |
63+| dumpLayout() | dumpLayout | — |
64+| getLayout(forceRefresh) | dumpLayout | 布局缓存 TTL 1s |
65+| screenshot(path) | screenCap | 截图缓存 |
66+| waitForIdle(idleTime,timeout) | waitForIdle | — |
67+| back()/home()/keyEvent() | pressBack/pressHome/triggerKey | invalidateLayoutCache |
68+ 
69+## HAR 包构建
70+ 
71+```bash
72+# 1. 注册 nativeharmony 模块到 app/harmony/build-profile.json5
73+# 2. 构建 HAR
74+hvigorw assembleHar --mode module -p module=nativeharmony@default
75+ 
76+# 3. 引用 HAR(entry/oh-package.json5)
77+"@cptf/native-harmony": "file:../../../cptf/packages/native-harmony/.../nativeharmony.har"
78+ 
79+# 4. List.test.ets
80+import { runNativeTestDriver } from '@cptf/native-harmony';
81+export default function testsuite() {
82+ runNativeTestDriver('com.tester.RNTester', 'EntryAbility');
83+}
84+```
85+ 
86+## WSL2 环境
87+ 
88+- `dev-init.sh` 自动建立 `hdc rport tcp:18081 tcp:18081`
89+- mirrored 模式 127.0.0.1 loopback 共享
90+- `systemd: wsl-adb-server.service` + `wsl-hdc-server.service`
91+ 
92+## 常见问题
93+ 
94+| 问题 | 原因 | 解决 |
95+|------|------|------|
96+| Driver.create() 失败 | 在应用进程内调用 | 改在 ohosTest 进程(aa test 启动) |
97+| WS 连接失败 | rport 未建立 | `hdc -t <dev> rport tcp:18081 tcp:18081` |
98+| deviceCap 失败 | /data/local/tmp 无写入权限 | 应用沙箱路径 |
99+| Beta 固件 daemon 失败 | SA 5502 不暴露 apiCaller | 正式版固件(但 ohosTest 进程内 Driver.create 可绕过) |
100+| HAR file: 引用失败 | hvigor OHM 解析器不支持源码目录 | 必须打包 .har 文件引用 |
101+| **ohosTest connect timeout (30s)** | **external 模式部署清除了设备上的 entry_test 模块**(`deployHarmony:external:release` 只装主 HAP,覆盖安装时 ohosTest HAP 被移除;native 模式依赖它) | `node scripts/build/install-ohos-test.js` 或 `pnpm deployHarmony:native:release` 重装;框架超时报错已带此诊断(bm dump 查 entry_test → 给安装指引);确认命令:`hdc -t <dev> shell bm dump -n <bundle> | grep -c entry_test`(0=缺失) |
102+| rport 残留导致连接假死 | 上次运行的 Reverse 转发还挂着,host 侧已无监听 | `hdc -t <dev> fport rm tcp:18081 tcp:18081`;框架超时诊断已提示此命令 |
103+ 
104+**部署模式纪律**:external/native 模式切换后必须用对应 deploy 命令重装——external 装的包 native 跑不了(无 entry_test),反之 native 装的包 external 也能跑(多装不冲突)。CI 流水线切换驱动模式时此为首要检查项。
105+ 
106+## 4级降级策略(v0.3.x 隔离原则)
107+ 
108+### 核心原则
109+ 
110+**模式启动期定型 + 内部重试 + 禁止操作期静默切实现**
111+ 
112+- ✅ 启动期可 1 次决策整 run 切模式(Level 1)
113+- ✅ run 中 WS 断开、action 超时、app 崩溃 → 内部重试/恢复(Level 2/3/4)
114+- ❌ 禁止 action 级别静默从 native 切到 external(破坏隔离、报告难归因)
115+ 
116+### 4 级降级表
117+ 
118+| Level | 位置 | 触发条件 | 动作 | 切模式 | 范围 |
119+|---|---|---|---|---|---|
120+| **L1 Pre-flight** | `HarmonyNativeDriver.connect()` | WS 起不来 / ohosTest 启动失败 / Driver.create 失败 / AAMS 不可见 / 30s 内 WS 未连上 | `DriverModeArbiter` 决策:native 失败 → 整 run 切 external(需 `driverFallback=true`)| ✅ 整 run 1 次 | run |
121+| **L2 Reconnect** | run 中 WS 断开 | `close` 事件(非主动 disconnect)| `tryReconnect()`:指数退避 1s/2s/4s × 3 次,30s 预算 | ❌ 仍 native | 单 case |
122+| **L3 Action Retry** | `sendCommand()` 内部 | `isRetryableError()` 命中(NETWORK/TIMEOUT/transient DEVICE_ERROR)| 重试 1 次(200-500ms 间隔),失败抛 `ActionError` | ❌ 仍 native | 单 action |
123+| **L4 Crash Recovery** | case 内 action 失败 | `APP_ERROR`(app crash / AAMS 丢窗口)| stopApp → launchApp → relaunchOhosTest → 等 WS 重连 → 重试当前 action | ❌ 仍 native | 单 case(1 次预算)|
124+ 
125+### 实现模板
126+ 
127+**L1 启动期决策**(`DriverModeArbiter`,`system/harmony/driver-mode-arbiter.ts`):
128+```ts
129+class DriverModeArbiter {
130+ async arbitrate(): Promise<ModeArbitrationResult> {
131+ if (!this.nativeDriver) {
132+ return { selectedMode: 'external', effectiveMode: 'external', fallback: false };
133+ }
134+ try {
135+ await this.nativeDriver.connect(); // 30s timeout
136+ return { selectedMode: 'native', effectiveMode: 'native', fallback: false };
137+ } catch (e) {
138+ if (!this.config.driverFallback) throw e;
139+ this.logger.warn(`Native 启动失败,降级 external: ${e.message}`);
140+ this._remountMixins('external'); // 重构造 input + layout 策略
141+ return { selectedMode: 'native', effectiveMode: 'external', fallback: true, reason: e.message };
142+ }
143+ }
144+ 
145+ // 注意:当前实现只 remount input + layout 两个 mixin
146+ // perf/media/stability/navigation 不在 remount 范围内(其自身有降级)
147+}
148+```
149+ 
150+**L2 重连预算**(`HarmonyNativeDriver.tryReconnect()`):
151+```ts
152+async tryReconnect(): Promise<boolean> {
153+ for (let i = 0; i < 3; i++) {
154+ await sleep(1000 * Math.pow(2, i)); // 1s, 2s, 4s
155+ if (this._destroyed) return false;
156+ try {
157+ this.launchOhosTest();
158+ if (await this.waitWSConnection(5000)) return true;
159+ } catch (e) { /* log */ }
160+ }
161+ return false; // 30s 预算耗尽
162+}
163+```
164+ 
165+**L3 操作重试**(`HarmonyNativeDriver.sendCommand()`):
166+```ts
167+try {
168+ return await attempt();
169+} catch (err) {
170+ if (isRetryableError(err) && !this._destroyed) {
171+ await sleep(isTimeoutError(err) ? 500 : 200);
172+ return await attempt(); // 1 次
173+ }
174+ throw err;
175+}
176+```
177+ 
178+**L4 崩溃恢复**(`HarmonyNativeDriver.recoverFromCrash(bundleId)`):
179+```
180+case 进入 → checkpoint
181+ action 失败 (APP_ERROR)
182+ ↓
183+ 1. host.stopApp(bundleId) + sleep 1s
184+ 2. host.launchApp(bundleId) + sleep 3s
185+ 3. this.tryReconnect()
186+ 4. 失败 → case 标 failed,继续下一 case(每 case 1 次预算)
187+```
188+ 
189+### 隔离架构(v0.3.0 已完成)
190+ 
191+**实现状态**:6 mixin × 2 平台 Strategy 模式全部完成,0 处 `isNativeMode` runtime 分支。
192+ 
193+**实现模板**(当前实际代码):
194+```ts
195+// system/harmony/mixins/input-controller.ts
196+abstract class HarmonyInputController extends BaseSystemMixin<HarmonySystem> {
197+ abstract click(x: number, y: number): Promise<boolean>;
198+ abstract clickByTestId(testID: string): Promise<boolean>;
199+ // ... 15 abstract 方法
200+}
201+ 
202+class NativeInputController extends HarmonyInputController {
203+ private get driver(): HarmonyNativeDriver {
204+ return this.host.nativeDriver as HarmonyNativeDriver; // 关键:从 host 读取,不通过构造器注入
205+ }
206+ async click(x, y) {
207+ try { return await this.driver.click(x, y); }
208+ catch (e) { /* log + return false */ }
209+ }
210+ async clickByTestId(t) {
211+ try { return await this.driver.clickComponent(t); }
212+ catch (e) { /* log + return false */ }
213+ }
214+}
215+ 
216+class ExternalInputController extends HarmonyInputController {
217+ async click(x, y) {
218+ if (this.useDriver) { /* HMD RPC */ }
219+ return await this.touchTap(x, y); /* shell fallback 仅 external 内部 */
220+ }
221+ async clickByTestId(t) {
222+ const el = await this.host.layout.getElementByTestId(t, 3000);
223+ return el ? await this.click(el.bounds.center.x, el.bounds.center.y) : false;
224+ }
225+}
226+ 
227+// system/harmony/harmony-system.ts(构造时定型)
228+this.input = config.platformOptions?.['driver'] === 'native'
229+ ? new NativeInputController(this)
230+ : new ExternalInputController(this);
231+```
232+ 
233+**Android 类名前缀**:`AndroidNativeInputController` / `AndroidExternalInputController`(注意 `Android` 前缀)
234+ 
235+业务代码 `await system.input.click(x, y)` 永远单一实现,**无 if 分支**。
236+ 
237+### 报告标注
238+ 
239+```
240+[run]
241+ selected_mode = native
242+ effective_mode = native (or 'external (L1 fallback)')
243+ native_reconnects = 2 # L2
244+ native_action_retries = 5 # L3
245+ native_crash_recoveries = 0 # L4
246+```
247+ 
248+每 case 标注 `mode_source: native-WS | native-shell | external-RPC | external-shell`,便于归因。
249+**注意**:此字段当前未实现到 TestResult 类型,需在 v0.3.x 后续工作中添加。
250+ 
251+### 禁止事项
252+ 
253+- ❌ 操作期静默从 native 切到 external(破坏隔离)
254+- ❌ 启动期降级后再次自动切回 native(避免模式抖动)
255+- ❌ 报告里 `source` 字段混用 native/external 两种语义
256+- ❌ L2/L3/L4 重试预算超限后还强行继续(必须 fail case)
257+ 
258+### 性能约束(禁止修改项)
259+ 
260+**forceRefresh 穿透保证(最高优先)**:`getUIElements(forceRefresh=true)` 必须绕过缓存发起新 fetch——R6/R7 的 waitForTestId/waitForElementDisappear 轮询依赖它。曾因 Strategy 拆分把缓存守卫复制进 Native/External 子类时多带一行 `return cached`,两分支等价导致 forceRefresh 变死参数,轮询全读陈旧布局。修改任何子类缓存逻辑后,必须验证 forceRefresh 穿透(连续两次 `getUIElements(true)` 应触发两次底层 fetch)。
261+ 
262+以下项经过架构分析确认,**不应随意调整**,修改会破坏 External 模式或引入回归:
263+ 
264+| 项 | 当前值 | 文件 | 禁止修改原因 |
265+|----|--------|------|------------|
266+| `LAYOUT_FETCH_TIMEOUT` | Harmony **35s** / Android 5s | `HarmonyLayoutProvider.LAYOUT_FETCH_TIMEOUT` | 外层 `Promise.race` 包裹了 `_getUIElementsInner` 的全部 3 条路径(Native + UITest RPC + Shell 回退)。UITest RPC 路径单次 15s × 2 retries + fallback = 33s 上限。**若缩短到 5s,External Harmony 模式下所有 captureLayout 都会被外层超时截断,导致元素查找永久失败。** 如要优化,必须从内层 `tryCaptureLayout` 的重试策略下手(如 2 retries → 1 retry,15s → 8s),外层 cap 保持不动 |
267+| **清理时机:category 分组**(原"每用例 shell 清理"约束已被更优方案取代) | category 切换 + run 结束 | `orchestration/test-runner.ts` `cleanupRemoteTempFiles()` | 布局文件堆积问题已从根因解决:`dumpLayoutViaShell()` 合并命令**前置 rm** 上一次的 layout 文件,`/data/local/tmp` 任意时刻至多 1 个 layout_*.json,`ls` 性能不会劣化。截图/录屏在**创建时**即加入追踪列表(`addRemoteTempFile` 在 capture 之后、pull 之前),失败 pull 也被精确追踪。category 内不清理(0 shell),切换时 1 条精确 rm + 1 条模式感知残留 rm(Native 省略 layout_*.json) |
268+| `withHdcMutex` 全局串行 | 所有 hdc shell | `HarmonySystem.withHdcMutex()` | **hdc 工具在 HarmonyOS 上不支持并发 spawn**,并行会导致设备端命令冲突、输出串流、连接不稳定。input/layout/perf/stability 四个 mixin 均经此串行。Native 模式主要操作已绕过,仅性能/稳定性采集仍受影响。采集由 `setInterval` 串行定时(默认 500-3000ms),去掉 mutex 也不会并行。**正确优化方向是通过 Native WS 推送指标,绕过 hdc shell** |
269+ 
270+**性能已修复项**(记录在此避免误改回):
271+ 
272+- ✅ `NativeUIStrategy` 3 次往返瓶颈:已通过重构绕过 `NativeUIStrategy`/`ExternalUIStrategy`,`UIAutomation` 直接委托 `ISystem.clickByTestId`,Native 模式 1 WS round-trip(~30-50ms)
273+- ✅ `NativeDeviceBridge` 单队列串行:已删除该类,新 `HarmonyNativeDriver.sendCommand()` 无 `actionQueue`,每个请求独立 `Promise + PendingRequest` map
274+- ⛔ 已删除 `ui-strategy.ts`(410 行死代码)
275+ 
276+**运行时修复项**:
277+ 
278+- ✅ **Native 模式 bundleId 未设置**:Native 分支跳过 `stopApp`/`launchApp` 时,也必须调用 `system.setCurrentApp(bundleId)`,否则 `_currentBundleId` 为空 → `ensurePid()` 失败 → 性能采集 0 条数据 + `pidof`/`ps` 全部空参数执行 → 每次 collect 阻塞 3s 触发 drain 超时
279+- ✅ **WS 意外关闭未 reject pending**(`HarmonyNativeDriver` 的 WS close handler):ohosTest 进程在设备上崩溃/退出 → WS close 事件必须调用 `rejectAllPending()` 立即 reject 所有 pending `sendCommand()`。原代码只设 `_connected=false`,导致 pending 调用傻等 120s 超时,外层测试循环卡死;非主动 disconnect 时必须走此路径。主动 `disconnect()` 时 `_destroyed=true`,`rejectAllPending` 已在 `disconnect()` 内调用,close handler 跳过第二次调用避免重复
280+- ✅ **ResilientConnection connectFn 无超时**(`base/utils/resilient-connection.ts`):`connectFn()`/`isConnectedFn()` 全部经 `withConnTimeout()` 包裹(默认 30s,`connectTimeoutMs` 可配)。TCP 半开连接挂起时不再永久阻塞 reconnect 循环
281+- ✅ **MetricWebSocketServer 帧重组合**(`base/utils/metric-ws-server.ts`):`MetricSocket.recvBuffer` per-socket 缓冲,跨 TCP `data` 事件的不完整帧保留至下次拼接。>1.4KB 消息分段不再丢失。溢出保护 `MAX_RECV_BUFFER_BYTES = 1MB`(畸形流销毁连接)。热路径零拷贝(recvBuffer 空时直接用 buf)
282+- ✅ **unhandledRejection 处理器只生效一次**(`base/utils/logger.ts`):`process.once` → `process.on`(仅 uncaughtException/unhandledRejection;SIGINT/SIGTERM 保持 once——信号只处理一次后 exit)
283+- ✅ **dumpLayout ls/cat 四连 shell → 单条合并命令**(`layout-provider.ts` `dumpLayoutViaShell()`):`rm 旧文件; uitest dumpLayout; 设备内轮询 ls+cat` 合并为 1 次 hdc 调用(原 4 次:dumpLayout + smartWaiter 轮询 ls + ls -t + cat)。前置 rm 保证 `ls` 找到的必然是本次产物(**精确**,不会捡到陈旧文件);文件生成等待在设备 shell 内 sleep 0.3 × 10 重试(不再每次 spawn hdc 轮询);uitest stdout/stderr 重定向 /dev/null 防止污染 cat 输出。布局文件自清理:任意时刻至多 1 个
284+- ✅ **清理 shell 按 category 分组**(`test-runner.ts` `cleanupRemoteTempFiles()`):每用例 0 条清理 shell → category 切换 + run 结束各 1 次(~10-15 次替代 67 次,省 ~28s/67 用例)。两道清理:①追踪列表精确 rm(截图/录屏**创建时**追踪——`addRemoteTempFile` 移到 capture 之后 pull 之前,pull 失败也覆盖)②模式感知残留 rm(Native 省略 layout_*.json)。`cleanupRemoteScreenshots` 内部 `rm -f f1 && rm -f f2` 链改为单条 `rm -f f1 f2 f3`(多文件一条 shell)
285+- ✅ **健康检查时间门控**(`test-runner.ts`):`HEALTH_CHECK_INTERVAL_MS = 10s` + `lastHealthCheckOk` 跟踪。上次健康→10s 间隔才查(functional 用例 ~5s 一个,每 2 个查 1 次);上次不健康→跳过门控立即复查(恢复后不会连环失败)。67 用例从 67 次降到 ~7-10 次(省 ~29s)
286+- ✅ **PID 缓存 TTL 30s → 60s**(`harmony-system.ts`):性能用例单次 25-30s,60s TTL 保证一个用例内只查 1 次;崩溃恢复路径 `clearCurrentApp()` 清缓存兜底
287+- ✅ **tryFindByTestId 冗余 dump 移除**(`ui-automation.ts`):native 模式下 `findComponent` 返回 null 即设备端确认不存在(`layoutOk: true`),不再全量 getLayout 确认。`isNativeFindStrategy` 标志区分策略。External 模式保留原 layout 检查(`layoutOk` 语义需要)
288+- ✅ **waitForTestId 设备端等待**(`ui-automation.ts` + `native-driver-adapter.ts`):native 模式走 `adapter.waitForAndFind()`——设备端 `waitForComponent`(等待在设备侧完成)+ 1 次 `findComponent` 取元素,共 2 WS。替代主机侧 100ms 轮询(8s 超时 ≈ 80 WS)。External 模式保留轮询
289+- ✅ **checkDeviceHealth native 分支走 driver ping**(`harmony-system.ts` + `harmony-native-driver.ts` 新增 `ping()`):native 模式 connected 时 1 次 WS ping(~30ms)替代 `echo alive` shell(~500ms)。native driver 未连接直接返回 false(不落回 shell——策略隔离)
290+- ✅ **性能采集 on-demand 中间层**(`performance-provider.ts` NativePerformanceProvider):缓存未命中时先走 `driver.collectMetrics(pid)`(设备端 on-demand,1 WS ~300ms),仍无数据才 fallback shell hidumper/SP_daemon。降级链变为:推送缓存 → 设备端 on-demand → shell
291+- ✅ **死代码 synchronizer 移除**(`test-runner.ts`):创建后未使用的 `new Synchronizer()` + `setDriverWaitForIdle`(指向 base hash 实现的死 wiring)已删除
292+- ✅ **崩溃恢复统一走 resetApp**(`harmony-system.ts` 新增 `resetApp(level)`):native 模式 driver connected → `driver.resetApp`(设备端 force-stop + aa start + 设备内等待 3s,1 WS);external / fallback → shell 组合(bm clean + stopApp + sleep + launchApp)。三个恢复点接线:①layout-provider 0元素重启 ②stability-test recoverFromCrash ③runner-strategy onUnhealthy。**app 崩溃时 ohosTest 进程通常仍存活(不同进程),WS 可用**——设备端恢复等待不占主机
293+- ✅ **通用 shell 透传 action**(`NativeTestDriver.ets` 新增 `shell` action):`shellCmd(delegator, command)` → `{ stdout }`,复用已有 shellCmd helper。`harmony-native-driver.ts` 暴露 `shell(command, timeout)`。NativeStabilityProbe override `runShell`/`runAppStateCommand` 执行原语——**命令不变,执行位置从 host hdc spawn(~400ms)移到设备端 delegator(1 WS ~30ms)**。checkAppAlive(每迭代)、getCrashLogs、forceStopPackage、detectANR、triggerLowMemory、simulateMemoryWarning 全部受益
294+- ✅ **navigate 完整 URL 透传**(`NativeTestDriver.ets` navigate action):`cmd.params.url` 优先 → `cmd.value` 含 `://` 直接用 → 否则 `rnoh-test://switch/${caseId}`(向后兼容)。`harmony-native-driver.ts` 新增 `navigateUrl(url)`。NativeNavigationProvider override `deepLink(url)`——回首页导航(test-scene-navigator 5 处 `deepLink('rnoh-test://home')`)从 ~800ms shell → ~30ms WS,fallback 到基类 shell
295+- ✅ **External 模式 verifyConnection(3000) 不可移除**(`navigation-provider.ts` `ExternalNavigationProvider`):External 导航前必须 `verifyConnection(3000)` 验证 UITest TCP daemon 连接存活。移除后 TCP 连接已断开但代码不知情,后续所有 UITest RPC 调用超时失败,导致 External 模式大面积用例失败。**禁止**为"优化 External 模式性能"而移除此调用
296+- ✅ **ExternalLayoutProvider _lastFetchFailure 三处必须重置**(`layout-provider.ts` `ExternalLayoutProvider._getUIElementsInner`):布局获取失败时累计 `_lastFetchFailure`(用于 cooldown/backoff),**成功获取后必须清零**(共 3 处赋值点)。不重置会导致后续所有 `getUIElements` 被 cooldown 跳过,External 模式布局永远返回空。Harmony 和 Android 的 `ExternalLayoutProvider` 均需检查
297+- ✅ **截图统一用 snapshot_display**(`media-provider.ts`):native/external 模式截图统一走 `hdc shell snapshot_display`(host 端),**不用 `Driver.screenCap`**(ohosTest 进程沙箱路径 `/data/app/el2/` → `run-as` 拉取路径复杂且权限不稳定)。与 Detox 方案一致(Detox `takeScreenshot` 也用 `snapshot_display`)。**禁止**为 native 模式改用 `screenCap` 再走 `hdc file recv`
298+- ✅ **watchdog 超时后 executor 并发污染("FPS 9.5Hz 假象"根因)**(`test-runner.ts` runTestCase):超时 race 返回 timeoutResult 后**必须等挂起的 executorPromise 收尾**(grace 10s 未结束则强制等待 ≤60s)再放行下一用例——否则残留 executor 与下一用例并发操作同一设备:WS 单线程命令排队(swipe 42s 卡死)+ 0 值迭代灌入下一用例的 json(perf-article iterations=[58,37,0×8] → 假的 avgFPS 9.5,汇总日志证实真实 60Hz)。配套:`aggregateMetrics` 排除全 0 空转迭代(取消后"无数据"≠"0 帧")。**排查此类"指标异常低"先看 executor 并发,再看采集口径**
299+- ⏭️ **截图 WS base64 传输**:暂缓(决策),继续用 WS screenCap + `hdc file recv`
300+ 
301+**Stability Probe 策略模式**(`stability-probe.ts`):base 抽象类提供 `runShell(command, timeout)` / `runAppStateCommand(command)` 两个 protected 执行原语,**全部业务逻辑在 base**(pid 缓存交互、crash baseline、重试)。NativeStabilityProbe 仅 override 执行原语(driver.shell 透传 + hdc fallback),ExternalStabilityProbe 空子类。**新增平台 native 能力时优先复用此模式:抽执行原语而非重写业务逻辑**
302+ 
303+**已知设计决策**(非 bug,勿"修复"):
304+ 
305+- ⚠️ **非 TIMEOUT 异常的 double-click 风险**(`input-controller.ts` `shouldFallback`):WS 断连(非 TIMEOUT)时 native click 可能已送达设备但响应丢失,降级到 external 路径可能 double-click。`shouldFallback` 对 TIMEOUT 返回 false(不降级,已防护);非 TIMEOUT 返回 true(宁可 double-click 也不放弃操作)。这是设计权衡:通信层异常无法确定命令是否已执行,Detox 同样接受此风险。**勿改为"全部不降级"**——会导致 WS 闪断时所有操作静默失败
306+ 
307+- ⚠️ **PortManager 参数顺序**(`port-pool.ts`):`allocate(deviceId, preferredPort, protocol, platform, remotePort)` 共 5 个参数,`setupRport`/`removeRport`/`setupAdbReverse`/`removeAdbReverse` 的 localPort 与 remotePort **位置不同**(setupRport: `local, remote`;setupAdbReverse: `remote, local`)。调错顺序导致端口转发无效,ohosTest 或 instrumentation 永远连不上。修改时对照源码签名,不要靠直觉猜顺序
308+| ArkTS 编译错误 | any/unknown/未类型化字面量 | 参见 cptf-troubleshoot ArkTS 章节 |
309+ 
310+## HarmonyOS 设备端已知限制
311+ 
312+| 限制 | 根因 | 影响 |
313+|------|------|------|
314+| **`executeShellCommand` 阻塞 JS 线程** | ohosTest 进程内 `delegator.executeShellCommand()` 同步阻塞 JS 线程(~3-4s/次) | WS 命令(click/swipe)处理停滞;**不适合持续采样**(如 FPS 轮询),只适合低频 on-demand 调用 |
315+| **FPS 60Hz 限制(AAMS 连接)** | ohosTest 进程持有 AAMS 连接,影响系统刷新率策略,FPS 被限制在 60Hz | **设备端采集 FPS 会引入 60Hz 上限**;host 端 `SP_daemon` 采集不受此限制(最高 120Hz)。回退到 host 端采集后 FPS 数据恢复正常 |
316+| **`hidumper -s RenderService -a "fpsCount"` 不完整** | RenderService 异步服务,`executeShellCommand` 可能不等响应即返回 | 只返回标题(~157 字符),无 `Refresh Rate` 数据。改用 `hidumper hitchs`(完整)或继续 host 端采集 |
317+| **`SP_daemon` exitCode=-1** | ohosTest 进程沙箱权限或 `$PATH` 不含 SP_daemon | 设备端无法直接运行 SP_daemon。性能采集降级链:推送缓存 → 设备端 on-demand `collectMetrics`(hidumper pid/mem/cpu)→ host shell SP_daemon |
318+| **ArkUI 无障碍属性不进组件树**(2026-08-26 穿刺实锤) | `accessibilityText`/`accessibilityDescription`/`accessibilityGroup` 设计上只进无障碍树(AccessibilityElement,读屏消费),uitest 三通道(dumpLayout 默认/`-i`/`-a`、Component API)均不导出——**平台分层设计,非 RNOH 缺陷**(RNOH 已正确调用 `instance.accessibilityText()`,RNViewBase.ets:124 源码确认) | harmony 无法自动验证 accessibilityLabel/Hint/accessible/role 声明值;`getDescription()` 只读 ArkUI description 属性(RNOH 不往这写,值恒空)。可用替代:`isChecked/isCheckable`(Switch 类已接,实测通过)。expect 层用 `platform: ['android']` 门控(Android contentDesc 可读,端到端实证)。官方唯一全量通道:AccessibilityExtensionAbility(需独立 extension 进程 + 用户开启,框架不可透明复用) |
319+ 
320+## HarmonyOS UiTest API 签名陷阱(2026-08-24 实测,SDK 权威验证)
321+ 
322+以 `@ohos.UiTest.d.ts`(`/path/to/sdk/default/openharmony/ets/api/`)为准,**不要凭记忆写 API 调用**:
323+ 
324+### 陷阱 1:`dumpLayout(savePath, displayId?): Promise<boolean>`(不是无参返回 JSON)
325+ 
326+- **错误写法**(曾导致 dumpLayout 恒返回空):`await driver.dumpLayout()` 无参调用 → 参数错误 → catch 静默返回空
327+- **正确用法**:`const ok = await driver.dumpLayout(sandboxPath)` → `fileIo.readTextSync(sandboxPath)` → `fileIo.unlinkSync(sandboxPath)`
328+- 产物格式 = external 模式 CLI `uitest dumpLayout` 的 JSON(attributes 树),host 端 `traverseLayoutTree` 现成解析
329+ 
330+### 陷阱 2:`screenCap(savePath)` / `dumpLayout(savePath)` 要求 **app 沙箱路径**
331+ 
332+- 传 `/data/local/tmp/...` → `Invalid file path` 异常(SDK 文档明确 "must be in the application sandbox directory")
333+- **沙箱路径获取**:`delegator.getAppContext().tempDir`(如 `/data/storage/el2/base/temp`)
334+- **host 拉取障碍**:`hdc file recv` **无法访问沙箱逻辑路径**(`/data/storage/el2/...`)——必须转换为**物理路径** `/data/app/el2/100/base/<bundle>/temp/`(实测 hdc 可读)
335+- **转换规则**(`UiDriverWrapper.publicPathFor()`):`sandboxPath.split('/').pop()` → 拼 `/data/app/el2/100/base/${bundleName}/temp/`
336+ 
337+### 陷阱 3:`ON.id()` 不支持 pattern 参数(静默忽略第二参数)
338+ 
339+- SDK 签名:`id(id: string): On`(**仅单参数**);`text(txt: string, pattern?: MatchPattern): On`(支持 pattern)
340+- **错误写法**(曾导致 `'.*'` 正则枚举恒返回 0):`ON.id('.*', MatchPattern.REG_EXP)` 第二参数被忽略 → 精确匹配 `'.*'` → 0 结果,**无任何报错**
341+- **正则枚举替代方案**:`ON.text` 正则(仅 Text 组件)或 `ON.type` 精确匹配循环(探针实测 Text/Scroll/Image 可用)
342+- `MatchPattern` 枚举:`EQUALS=0, CONTAINS=1, STARTS_WITH=2, ENDS_WITH=3, REG_EXP=4`
343+ 
344+### 陷阱 4:WS selector 的 `pattern` 字段在 dispatch 层被丢弃
345+ 
346+- 设备端 `dispatch()` 只拷贝 `kind`/`value`,`spec['pattern']` 恒 undefined → 正则能力从未生效
347+- 修复:dispatch 补 `spec['pattern'] = sel['pattern']` 转发(findComponent/findComponents 分支)
348+ 
349+### 沙箱路径辅助(UiDriverWrapper 已实现)
350+ 
351+```typescript
352+resolveSandboxDir(delegator, bundleName): void // 初始化 sandboxDir + sandboxPublicDir
353+sandboxPathFor(requested): string // /data/local/tmp/x.jpeg → tempDir/x.jpeg(写入用)
354+publicPathFor(sandboxPath): string // tempDir/x.jpeg → /data/app/el2/.../temp/x.jpeg(返回 host 用)
355+```
356+ 
357+### screenCap 性能实测(Mate 70 Pro,2026-08-24)
358+ 
359+| 方式 | 平均耗时(含 pull + 清理) |
360+|------|--------------------------|
361+| Driver.screenCap via WS(沙箱写入 + hdc file recv 物理路径) | ~3156ms |
362+| snapshot_display shell 合并命令(当前 screenshotBatch 方案) | ~2328ms |
363+| 单次 Driver.screenCap via WS(不含 pull) | ~247ms |
364+ 
365+**结论**:单次调用 Driver.screenCap 快(247ms),但含 pull 的完整链路反而慢于 shell 批量(每次 hdc file recv spawn 开销 ~2s)。screenshotBatch 保持 shell 合并命令方案更优。
366+ 
367+---
368+ 
369+# Android Native Driver(v0.3.x 已实现,实测通过)
370+ 
371+## Architecture(与 Harmony 镜像)
372+ 
373+```
374+Host (CPTF Node.js)
375+ └── AndroidNativeDriver (WS Server, PortManager 分配端口, maxPayload 256MB)
376+ │ adb reverse tcp:<port> tcp:<port> ← PortManager.setupAdbReverse
377+ ▼
378+ 设备端 instrumentation 进程(am instrument 启动)
379+ ├── DeviceDriver.java(WS Client 手写 RFC6455 + dispatch)
380+ ├── UiDevice + UiObject2(Component-direct: By.res(testID))
381+ └── Timer 周期采集(gfxinfo 帧差分 fps + meminfo PSS + top CPU → WS push)
382+ 
383+入口: adb shell am instrument -w -r -e class com.cptf.test.DeviceDriver
384+ -e host 127.0.0.1 -e port <port> <pkg>.test/androidx.test.runner.AndroidJUnitRunner
385+```
386+ 
387+## 与 Harmony 的关键差异(易踩坑,全部实测验证)
388+ 
389+| 项 | Harmony | Android | 根因 |
390+|---|---|---|---|
391+| 元素定位 | `ON.id(testID)` | `UiDevice.findObject(By.res(testID))` + `By.desc` fallback | RN testID → Android resource-id |
392+| dumpLayout 格式 | **JSON 树** | **自实现 AccessibilityNodeInfo XML(全窗口+hint)** → host 用 `parseUILayout` 解析,**不要 JSON.parse** | UiDevice.dumpWindowHierarchy 是 compressed 且丢 hint/丢窗口 |
393+| Component-direct API | `comp.click()/inputText()/scrollToTop()` | `shellInput("input tap/swipe ...")`(⚠️ 勿用 UiObject2 手势,见坑6) | ets 有 Component 对象,Java 是 UiObject2 |
394+| waitForComponent | driver.waitForComponent(on) | `uiDevice.wait(Until.hasObject(By.res(t)), timeout)` | — |
395+| 端口穿透 | hdc rport | **adb reverse**(设备以 127.0.0.1 访问 host) | — |
396+| 进程模型 | ohosTest(aa test,startAbility 启动 app) | instrumentation(am instrument,**须 DeviceDriver.launchTargetApp() 启动 app**) | UiAutomation 服务全系统单例 |
397+ 
398+## Android 专属坑(实测全部踩过)
399+ 
400+1. **UiAutomation 冲突**:ATX(com.wetest.uia2)占用 UiAutomation 服务时,DeviceDriver 的 `UiDevice.getInstance()` 抛 `IllegalStateException: UiAutomationService already registered`。**修复**:launchInstrumentation 前 kill com.wetest.uia2;native 模式成功后 `u2.destroy()` 防 ATX 复活抢占。**v2 修复**:`AndroidSystem._doInitU2()` 在 `driverMode==='native'` 时直接跳过 ATX 启动(启动也必然失败,且失败进程会干扰 input 注入)。
401+2. **手写 WS client 必须 mask**:RFC 6455 client→server 帧必须置 mask 位 + mask payload,否则 Node ws 报 `Invalid WebSocket frame: MASK must be set`。帧编码三分支(≤125 / ≤65535 用 126+16bit / else 127+64bit)都要带 0x80 mask 位。
402+3. **截图不要走 /sdcard**:instrumentation 进程写 /sdcard 受 scoped storage 限制且旧版 takeScreenshot 静默失败。**用 `UiAutomation.takeScreenshot()` → Bitmap → 写 `getExternalFilesDir(null)`(app 外部私有目录)→ host adb pull**。走 WS base64 回传大帧会触发 ws `Max payload size exceeded`(勿用)。
403+4. **dumpWindowHierarchy 无 boolean 重载**:那是 ATX 的 API。UiDevice 只有 `(String)/(File)/(OutputStream)` 重载,用 `dumpWindowHierarchy(ByteArrayOutputStream)` 取字符串。
404+5. **nohup screenrecord 在 Android sh 不可靠**:adb shell 会话退出连带杀进程。录屏改 **host 托管**:`spawn(adb, ['shell', 'screenrecord ...'], {detached:true})` 由 host 持有会话;启动前 `which screenrecord` 检查,缺失直接跳过。
405+6. **⛔ UiObject2/UiAutomation 手势注入在部分 ROM 上恒定 5s/事件(华为 ALN-AL00 Android 12 实测)**:`GestureController: Gestures took longer than expected (10833ms)` / `injectInputEvent` 每次 5s(async 模式同样阻塞)。根因链:ROM 的 `Choreographer.doFrame → AnimationUtils.unlockAnimationClock` 与 UiAutomation 注入路径的动画时钟锁竞争 → app 主线程消费 input 事件延迟 5s → InputDispatcher ANR 计时(`Waited 5001ms for MotionEvent(UP)`)→ clickComponent WS round-trip 恒定 ~10.1s。**修复**:DeviceDriver 全部手势(tap/longClick/swipe/scroll/scrollSearch)改走 `Runtime.exec("input tap|swipe ...")`(shell input 命令,实测 74ms)。**禁止**在 DeviceDriver 内使用 `uiDevice.click/swipe`、`UiObject2.click/scroll`、`uiAutomation.injectInputEvent`(含 runOnMainSync 包装——主线程注入直接死锁:主线程等注入返回,注入等主线程消费事件)。
406+7. **设备端 app 启动**:`am instrument` 只起进程不起 Activity(Harmony 是 ohosTest startAbility 启动)。DeviceDriver.runDeviceDriver 在 WS connect 后必须 `getLaunchIntentForPackage(pkg) + FLAG_ACTIVITY_NEW_TASK|CLEAR_TOP` 主动启动被测 app(`launchTargetApp()`),否则 dump 全是桌面。
407+8. **dumpLayout 必须 AccessibilityNodeInfo 树自实现(getWindows() 全窗口)**:`UiDevice.dumpWindowHierarchy` 是 compressed XML——**剔除 hint 属性**(placeholder 断言失败)且只含部分窗口。自实现要点:`getUiAutomation().getWindows()` 遍历每窗口 root 递归序列化(`getViewIdResourceName()`=testID、API26+ `getHintText()`=placeholder、叶子节点自闭合 `<node .../>` 容器节点 `</node>`——host 的 parseUILayout 栈式解析依赖此结构)。**RN Modal 是独立窗口**——只 dump active window 会丢主窗口内容(Modal 打开时主窗口元素"不存在")。
408+9. **findComponent 的 placeholder 补齐**:UiObject2 无 hint API。`serializeState` 后用 `findHintByResId(resId)`(accessibility 树按 viewIdResourceName 精确匹配,返回 hintText)补充 `placeholder` 字段。`'*'` 查询跳过(防 O(N²))。host 侧 `NativeFindStrategy.supplementPlaceholderFromLayout` 兜底(TextInput/EditText 类 + placeholder 未定义时从布局缓存合并——Harmony 复用此路径)。
409+10. **instrumentation 冷启动 dexopt 首次 >30s**:连接超时必须 ≥60s(首次 am instrument 后 AOT/dex 校验),否则首轮全超时、二轮正常(假象不稳定)。
410+ 
411+## Android L2/L3/L4(镜像 Harmony 实现)
412+ 
413+- **L3 sendCommand 重试**:`isRetryableError` 重试 1 次(timeout 类等 500ms,其他 200ms)
414+- **L2 tryReconnect**:3 次 × 指数退避,每次重新 am instrument + waitWSConnection(5s);`reconnectCount` 计数
415+- **L4 recoverFromCrash(bundleId)**:`adb shell am force-stop` → `monkey -p <pkg> LAUNCHER 1` → sleep 3s → tryReconnect
416+- **L1 降级**(test-runner android 分支):connect 成功 → remount Native mixins + destroy u2;失败且 auto → 切 external mixin + `u2.reconnect()`。**DriverModeArbiter 仅 harmony 用**(android 分支内联同语义逻辑)
417+ 
418+## connect() 失败必须清理(两平台同规则)
419+ 
420+connect 超时/失败路径必须:关 WS server + `PortManager.release(port)` + 清 `_actualPort`,否则**进程内单例 Map 记录泄漏 → 重试时端口漂移**(18081→18082→18083,漂移端口的 adb reverse 未建 → instrumentation 永远连不上,表象为"connect timeout on 18082")。
421+ 
422+## Android native 实测结论(2026-08 全量对比)
423+ 
424+- 功能判例与 external **100% 一致**(109 采样用例 0 分歧),平均用例时长 **2.6s vs external 4.4s(快 41%)**
425+- 渲染基准 renderTime/unmountTime 偏差 <8%;FPS 采集比 external 更真实(无 dump 干扰)
426+- ⚠️ **高交互场景(持续动画 perf-animations/perf-balls)有卡死风险**(超时无返回)——嫌疑 DeviceDriver 单线程 receive 循环被采集 Timer 抢占。**动画类用例暂用 external**,待命令通道并发修复
427+- 全程 0 fallback / 0 reconnects(连接稳定)
428+ 
429+## Native 模式完全跳过 Daemon(v0.3.x 完整隔离链路)
430+ 
431+native 模式下,**所有外部 daemon 必须跳过**,否则与设备端 ohosTest / instrumentation 进程冲突(UiAutomation 全系统单例)。
432+ 
433+### 跳过项清单
434+ 
435+| 平台 | Daemon/Agent | 跳过位置 | 跳过原因 |
436+|------|--------------|---------|---------|
437+| 鸿蒙 | uitest daemon (`app_process /com.ohos.testkit.teskit.teskit_service`) | `HarmonySystem.warmup()` / `initUitest()` / `resetUitest()` native 分支早返 | ohosTest 进程独占 UiAutomation 服务,uitest daemon 启动会抛 `UiAutomationService already registered` |
438+| Android | ATX/u2 agent (`com.wetest.uia2.Main`) | `AndroidSystem._doInitU2()` native 分支早返;`AndroidSystem.warmup()` 走 `initNativeDriver()` | 同上 UiAutomation 单例冲突 |
439+| 鸿蒙 | `HarmonyLayoutProvider` (Uitest dump + shell) | 构造时定 native → `HarmonyNativeLayoutProvider` 走 `driver.dumpLayout()` | 完全避免 Uitest dump |
440+| Android | `AndroidLayoutProvider` (U2 dump + uiautomator dump) | 构造时定 native → `AndroidNativeLayoutProvider` 走 `driver.dumpLayout()` | 完全避免 U2/uiautomator dump |
441+| iOS | WDA(XCTest)| iOS 无 native 模式 | 仅 external 模式 |
442+ 
443+### 完整 native 链路(按调用顺序)
444+ 
445+```
446+TestRunner.runTestCase()
447+ ├─ TestRunner.executeTestCase Native 模式跳过 stopApp/launchApp/warmup(ohosTest/am instrument 已启 app)
448+ ├─ TestRunner.runCaseQueue Native 模式 failed → nativeDriver.recoverFromCrash(bundleId)(L4)
449+ │ ├─ HarmonyNativeDriver: stopApp → launchApp → tryReconnect
450+ │ └─ AndroidNativeDriver: stopApp → launchApp → tryReconnect
451+ └─ TestRunner.runCaseQueue result.effectiveDriverMode = driverInfo.effective(写入报告)
452+ 
453+getUIElements() 调用链:
454+ ├─ AndroidSystem.layout → AndroidNativeLayoutProvider(构造定)
455+ │ └─ driver.dumpLayout()(facade 优先,鸿蒙走 ON.dump)
456+ └─ HarmonySystem.layout → HarmonyNativeLayoutProvider(构造定)
457+ └─ driver.getLayout() / facade.getLayout()
458+ 
459+getElementByTestId() 调用链:
460+ ├─ NativeFindStrategy.findByTestId() → facade.findComponent(1 WS,native 路径)
461+ └─ catch fallback → getLayout() 过滤(仅在 facade 失败时)
462+```
463+ 
464+### 测试覆盖(验证 native 隔离生效)
465+ 
466+```typescript
467+// 类型 / 单测检查:native 模式不应触发 UitestTransport.connect
468+expect(HarmonySystem.warmup).toBeNativeSkipUitestInit();
469+expect(AndroidSystem._doInitU2).toBeNativeSkipU2Connect();
470+expect(AndroidNativeLayoutProvider.getUIElements).toUseNativeDriverOnly();
471+ 
472+// 设备端实际验证:native 模式跑用例后 dump 设备进程列表
473+expect(adb.shell.ps).not.toContain('com.ohos.testkit.teskit.teskit_service'); // 鸿蒙
474+expect(adb.shell.ps).not.toContain('com.wetest.uia2.Main'); // Android
475+```
476+ 
477+### 何时启动 Daemon(不是 native 模式时)
478+ 
479+| 平台 | external 模式 | 自动 daemon 启动 |
480+|------|---------------|------------------|
481+| 鸿蒙 | UitestRunner.init → ensureAgentRunning | uitest daemon (com.ohos.testkit.teskit.teskit_service) 启动 |
482+| Android | AndroidU2Client.connect | u2 agent (com.wetest.uia2.Main) 启动 |
483+| iOS | IosWdaClient.connect | WDA XCTest 启动(首次或定期检测) |
484+ 
485+### 扩展新平台(future)
486+ 
487+新平台 native driver 接入时,必须:
488+1. 在 `BaseSystem.warmup()` override native 分支早返(不调 daemon)
489+2. 在 `_doInitXxx()` 类似方法 native 分支早返(或新建 native-only 路径)
490+3. 创建 `XxxNativeLayoutProvider` 走 `nativeDriver.dumpLayout()` 而非外部 dump
491+4. 在 executeTestCase 的 Native 分支类似位置检查 `nativeDriver` 是否 connected 才 fallback
@@ -0,0 +1,431 @@
1+---
2+name: cptf-optimization-verification
3+description: Guide for evaluating code optimization proposals from static analysis tools, code reviews, or performance audits. Use when receiving optimization suggestions to avoid false positives. Covers verification-first workflow, Node.js concurrency misconceptions, common false positive patterns, and cross-validation techniques. Triggers: "optimization proposal", "static analysis report", "code review suggestions", "performance audit", "concurrency safety", "memory leak", "race condition", "code smell".
4+---
5+ 
6+# CPTF 优化建议验证指南
7+ 
8+本 skill 总结了从静态分析报告、代码审查、性能审计中评估优化建议的实战经验。**核心原则:先验证,后修复**。
9+ 
10+## 关键教训:静态分析工具的典型误报模式
11+ 
12+### 1. 行号错位(Line Number Drift)
13+ 
14+**现象**:报告指向 `performance-collector.ts:45-52` 的 EventBus 泄漏,实际行号指向常量定义。
15+ 
16+**根因**:代码频繁重构,行号持续漂移。静态工具缓存的行号与实际代码不匹配。
17+ 
18+**验证方法**:
19+```bash
20+# 直接查看报告行号的内容
21+sed -n '45,52p' src/capability/measurement/performance-collector.ts
22+ 
23+# 搜索实际位置
24+grep -n "EventEmitter\|EventBus\|\.on(" src/capability/measurement/performance-collector.ts
25+```
26+ 
27+**决策规则**:行号内容与报告描述不符 → **忽略该行号**,用全文搜索定位真实位置。
28+ 
29+### 2. 文件存在性错误(File Existence Error)
30+ 
31+**现象**:报告指向 `test-execution-loop.ts:189-205` 的竞态条件,但文件不存在。
32+ 
33+**根因**:静态工具扫描的是旧版本代码或分支。
34+ 
35+**验证方法**:
36+```bash
37+# 检查文件是否存在
38+ls -la src/orchestration/test-execution-loop.ts
39+ 
40+# 查找相关文件
41+find src -name "*execution*" -type f
42+```
43+ 
44+**决策规则**:文件不存在 → **报告完全无效**,可能指向已删除的代码。
45+ 
46+### 3. Node.js 单线程并发误判(Concurrency Misconceptions)
47+ 
48+**现象**:报告说 `NavigationCoordinator` 的 12 个静态 Map 需要加锁保护。
49+ 
50+**根因**:
51+- 混淆了"并发竞态"(多线程同时访问)和"语义竞态"(异步任务读到旧值)
52+- Node.js 的单线程事件循环模型下,同步 Map 操作原子性由 JavaScript 引擎保证
53+- 工具无法理解 `Promise.race` + `maxConcurrency` 的串行化设计
54+ 
55+**验证方法**:
56+1. 确认代码运行环境(Node.js / Browser / Worker)
57+2. 检查是否真的有多线程(Worker Threads / Child Process)
58+3. 分析 key 设计是否包含设备隔离(如 `${platform}:${deviceId}`)
59+4. 验证并发控制机制(队列、串行化、降级策略)
60+ 
61+**决策规则**:
62+- Node.js 主线程 + 同步操作 → **无需加锁**
63+- Worker Threads 共享内存 → 需要 Atomics / MessageChannel
64+- 跨进程协调 → 需要文件锁 / Redis 分布式锁
65+ 
66+### 4. "设计模式"误判为"缺陷"(Design vs Defect)
67+ 
68+**现象**:
69+- 113 处 `try { ... } catch {}` 被报告为"异常吞没"
70+- 85% 的"魔法数字"实际是单位转换或配置默认值
71+ 
72+**根因**:静态工具不理解业务上下文:
73+- `catch {}` 在资源清理场景是合理的 fire-and-forget 模式
74+- `1000` 作为毫秒→秒转换系数不是魔法数字
75+- 配置默认值(如 `DEFAULT_NAVIGATION_CONFIG`)应该显式列出
76+ 
77+**验证方法**:
78+```bash
79+# 统计并分类所有 catch {}
80+grep -rn "catch {}" src --include="*.ts" | wc -l # 113 处
81+ 
82+# 检查是否已有命名常量
83+grep -rn "const.*=.*1000" src | head -20
84+ 
85+# 检查配置项定义
86+grep -A 5 "DEFAULT_.*CONFIG" src/orchestration/core/decorators.ts
87+```
88+ 
89+**决策规则**:
90+- 资源清理 / 可选 API 调用 / 探测式操作 → `catch {}` **合理**
91+- 单位转换 / 命名常量 / 配置默认值 → **不是魔法数字**
92+- 业务逻辑中的裸数字 → 可能需要提取为常量
93+ 
94+### 5. 工具配置缺失导致的误报(Tool Configuration Gaps)
95+ 
96+**现象**:
97+- ESLint 报告 12 处"未使用导入",实跑结果 0 个
98+- TypeScript 报告类型错误,实际编译通过
99+ 
100+**根因**:工具配置不完整或未启用关键规则。
101+ 
102+**验证方法**:
103+```bash
104+# 实际运行 ESLint
105+pnpm lint 2>&1 | tail -20
106+ 
107+# 检查 ESLint 配置
108+cat .eslintrc*.js 2>/dev/null || cat eslint.config.* 2>/dev/null
109+ 
110+# 实跑 TypeScript
111+pnpm typecheck 2>&1 | tail -20
112+```
113+ 
114+**决策规则**:实跑工具结果与报告不符 → **以实跑为准**。
115+ 
116+### 6. 过度优化的诱惑(Over-Optimization Trap)
117+ 
118+**现象**:建议为每个 `catch {}` 添加 `logger.debug(...)`,增加 113 行噪音代码。
119+ 
120+**根因**:追求"代码整洁度指标"而非实际收益。
121+ 
122+**决策规则**:
123+- 修改后代码行数增加 > 收益 → **拒绝**
124+- 违反项目既有风格(如 CPTF 的"简洁输出"原则)→ **拒绝**
125+- 无运行时行为改进 → **拒绝**
126+ 
127+## 验证优先工作流(Verification-First Workflow)
128+ 
129+收到优化建议后,按以下顺序验证:
130+ 
131+### Step 1: 行号验证(30 秒)
132+ 
133+```bash
134+# 查看报告行号的实际内容
135+sed -n '<start>,<end>p' <file>
136+ 
137+# 搜索报告提到的关键符号
138+grep -n '<symbol_name>' <file>
139+```
140+ 
141+**通过标准**:行号内容与报告描述匹配。
142+ 
143+### Step 2: 文件存在性验证(10 秒)
144+ 
145+```bash
146+ls -la <file_path>
147+find src -name "<file_name>" -type f
148+```
149+ 
150+**通过标准**:文件存在且路径正确。
151+ 
152+### Step 3: 业务上下文验证(2 分钟)
153+ 
154+**并发安全类**:
155+1. 确认运行环境(Node.js 主线程 vs Worker vs 子进程)
156+2. 检查 key 设计是否包含隔离维度(设备 ID / 平台)
157+3. 分析并发控制机制(队列长度、串行化、降级策略)
158+4. 阅读 `CONCURRENCY-SAFETY.md` 了解项目并发模型
159+ 
160+**资源泄漏类**:
161+1. 全文搜索资源类型(`EventEmitter` / `fs.readFile` / `setTimeout`)
162+2. 检查是否已有清理逻辑(`destroy()` / `cleanup()` / `removeAllListeners()`)
163+3. 验证调用链是否调用清理方法
164+ 
165+**类型安全类**:
166+1. 统计 `any` 类型总数
167+2. 分类:动态导入 vs 配置深拷贝 vs API 兼容层
168+3. 评估改进成本 vs 收益
169+ 
170+### Step 4: 实跑验证(5 分钟)
171+ 
172+```bash
173+# TypeScript 类型检查
174+pnpm typecheck 2>&1 | tail -20
175+ 
176+# ESLint 检查
177+pnpm lint 2>&1 | tail -20
178+ 
179+# 单元测试
180+pnpm test 2>&1 | tail -20
181+```
182+ 
183+**通过标准**:
184+- `tsc --noEmit` 无错误
185+- ESLint 0 warnings(或仅与报告相关的 warnings)
186+- 所有测试通过
187+ 
188+### Step 5: 成本收益评估(1 分钟)
189+ 
190+| 维度 | 问题 | 决策 |
191+|------|------|------|
192+| 代码行数 | 修改后增加多少行? | 增加 > 50 行需慎重 |
193+| 风格一致性 | 是否违反项目风格? | 违反则拒绝 |
194+| 运行时收益 | 能否改进性能/正确性? | 无收益则拒绝 |
195+| 维护成本 | 后续修改是否更复杂? | 显著增加则拒绝 |
196+ 
197+## 常见误报案例库
198+ 
199+### Case 1: NavigationCoordinator 并发安全
200+ 
201+**报告**:`orchestration/core/base-test.ts:25-35` 的 12 个静态 Map 需要加锁。
202+ 
203+**验证**:
204+1. ✅ 行号正确(指向 `NavigationCoordinator` 声明)
205+2. ✅ 文件存在
206+3. ❌ 业务上下文:Node.js 单线程 + Map 键包含 `${platform}:${deviceId}` 隔离
207+4. ✅ 已有防御:`maxConcurrency` 默认 1 + `test-runner.ts:655-659` 并发模式禁用优化
208+ 
209+**结论**:❌ 误报。详见 `docs/architecture/CONCURRENCY-SAFETY.md`。
210+ 
211+### Case 2: EventBus 内存泄漏
212+ 
213+**报告**:`capability/measurement/performance-collector.ts:45-52` 的全局 EventBus 未清理。
214+ 
215+**验证**:
216+1. ❌ 行号错误(实际指向 `SOURCE_CODE` 常量映射)
217+2. ❌ 全文搜索 `EventEmitter | EventBus | .on(` 无匹配
218+3. ✅ 文件存在
219+ 
220+**结论**:❌ 误报。行号漂移 + 符号不存在。
221+ 
222+### Case 3: test-execution-loop 竞态条件
223+ 
224+**报告**:`orchestration/test-execution-loop.ts:189-205` 的 Promise.race 竞态。
225+ 
226+**验证**:
227+1. ❌ 文件不存在(`No such file or directory`)
228+2. ✅ 竞态逻辑实际在 `test-runner.ts:1065-1109`
229+3. ✅ 该逻辑已正确实现(watchdog 清理 + null 检查 + try-catch)
230+ 
231+**结论**:❌ 误报。文件路径错误 + 逻辑已正确。
232+ 
233+### Case 4: android-u2-client timeout 僵尸进程
234+ 
235+**报告**:`system/android/android-u2-client.ts:234` 的 `timeout: 0` 导致僵尸进程。
236+ 
237+**验证**:
238+1. ❌ 行号错误(第 234 行是 `await this.sleep(1000);`)
239+2. ❌ 实际 timeout 是 `600000ms`(10 分钟)
240+3. ✅ 文件存在
241+ 
242+**结论**:❌ 误报。行号漂移 + 值已修复。
243+ 
244+### Case 5: 代码风格问题(4 项)
245+ 
246+**报告**:
247+- 空 catch 块 19 处
248+- 魔法数字 23 处
249+- 重复 testID 生成 7 处
250+- 未使用导入 12 处
251+ 
252+**验证**:
253+1. ✅ 空 catch 实际 113 处,全部符合 fire-and-forget 设计
254+2. ✅ 魔法数字 85%+ 是单位转换或配置默认值
255+3. ✅ testID 已通过工厂模式集中化
256+4. ✅ ESLint 实跑验证 0 个未使用导入
257+ 
258+**结论**:❌ 全部误报。工具不理解业务语义。
259+ 
260+## 决策框架:何时修复 vs 何时忽略
261+ 
262+### 立即修复(P0)
263+ 
264+满足以下全部条件:
265+- ✅ 行号与描述匹配
266+- ✅ 文件存在
267+- ✅ 业务上下文验证为真实问题
268+- ✅ 实跑工具能复现(tsc / ESLint / test 失败)
269+- ✅ 修改成本低(< 20 行)
270+ 
271+**示例**:
272+- TypeScript 编译错误
273+- ESLint 报告的语法问题
274+- 单元测试失败的 bug
275+ 
276+### 评估后修复(P1)
277+ 
278+满足以下条件:
279+- ✅ 行号与描述匹配
280+- ✅ 文件存在
281+- ✅ 业务上下文验证为真实问题
282+- ⚠️ 修改成本中等(20-100 行)
283+- ✅ 有明确的运行时收益
284+ 
285+**示例**:
286+- 性能热点优化(有 profiling 数据支持)
287+- 类型安全改进(消除 `any` 且成本可控)
288+- API 文档补全(缺失的关键参数说明)
289+ 
290+### 忽略(P2 / Won't Fix)
291+ 
292+满足以下任一条件:
293+- ❌ 行号与描述不符
294+- ❌ 文件不存在
295+- ❌ 业务上下文验证为设计而非缺陷
296+- ❌ 实跑工具无法复现
297+- ❌ 修改成本高于收益
298+ 
299+**示例**:
300+- "优化"建议导致代码膨胀
301+- 违反项目既有风格
302+- 无运行时行为改进
303+- 工具配置问题导致的误报
304+ 
305+## 交叉验证技术(Cross-Validation Techniques)
306+ 
307+### 1. 多工具一致性问题
308+ 
309+**现象**:Tool A 报告问题,Tool B 未报告。
310+ 
311+**验证**:
312+```bash
313+# 运行多个工具
314+pnpm typecheck # TypeScript
315+pnpm lint # ESLint
316+pnpm test # Jest
317+```
318+ 
319+**决策规则**:
320+- 仅 1 个工具报告 → 可能是误报,需人工验证
321+- 多个工具一致报告 → 高概率真实问题
322+ 
323+### 2. 代码历史验证
324+ 
325+**现象**:报告的问题在旧版本中不存在。
326+ 
327+**验证**:
328+```bash
329+# 查看文件历史
330+git log --oneline -n 10 <file>
331+ 
332+# 查看具体修改
333+git show <commit> -- <file> | grep -A 5 -B 5 "<symbol>"
334+```
335+ 
336+**决策规则**:
337+- 近期重构引入 → 可能是重构副作用,需评估
338+- 长期存在 → 可能是有意设计,需理解上下文
339+ 
340+### 3. 运行时行为验证
341+ 
342+**现象**:静态分析报告的问题在运行时未出现。
343+ 
344+**验证**:
345+```bash
346+# 添加日志观察
347+# 在怀疑位置添加 console.log 或 logger.debug
348+# 运行实际测试用例
349+pnpm test -- --verbose
350+```
351+ 
352+**决策规则**:
353+- 运行时未触发 → 可能是死代码或防御性设计
354+- 运行时触发但无影响 → 可能是无害的边界情况
355+ 
356+### 4. 外部 PR / 补丁评估(PR#7 实战提炼)
357+ 
358+**现象**:收到外部贡献者的 PR(或 codereview 建议),描述看起来都合理,需要判断真伪与是否合入。
359+ 
360+**验证流程**:
361+1. **取码不取话**:`git fetch <remote> +refs/merge-requests/<id>/head:pr_N`,逐 commit `git show` 读语义,PR 描述仅作索引
362+2. **声明核验**:PR 每条声明对照本地代码找"原始 bug 位置"——找到 bug 才算声明成立
363+3. **重叠检测**:`grep` 本地是否已有等价实现(PR#7 的 knownIssue 批次隔离与本地逐行等价 → 跳过)
364+4. **实测注释加权**:带设备验证记录的补丁(如 KEYCODE 映射注明 TAB=2049 实测焦点遍历)可信度显著更高
365+5. **冲突面评估**:`git merge-tree` 预判——PR 基于旧结构 + 本地已大重构(9 文件 changed in both)→ 放弃直接 merge,走手工移植
366+ 
367+**决策规则**:
368+- 声明核验全成立 + 本地无替代 → 吸收(手工移植到重构后结构)
369+- 声明核验时发现本地 bug **比 PR 描述的更严重**(如 forceRefresh 在本地是死参数而非仅 TTL 短路)→ 顺手修本地版,吸收照做
370+- 与本地实现重复 → 跳过该条,其余照常评估
371+ 
372+**常见反转**:PR 声称修复的问题,本地因重构变成了**不同形态**(原单类 → 双子类,bug 也复制成两份且更严重)——核验必须看本地当前代码,不能看 merge-base 处的历史代码。
373+ 
374+## 项目特定上下文
375+ 
376+### CPTF 并发模型
377+ 
378+详见 [`docs/architecture/CONCURRENCY-SAFETY.md`](../../../packages/framework/docs/architecture/CONCURRENCY-SAFETY.md):
379+- Node.js 单线程事件循环
380+- 同步 Map 操作原子性
381+- 设备级 key 隔离(`${platform}:${deviceId}`)
382+- `maxConcurrency` 默认 1(串行执行)
383+- 并发模式下主动降级(禁用 `setNextTest`)
384+ 
385+### CPTF 编码风格
386+ 
387+详见 [`AGENTS.md`](../../../AGENTS.md):
388+- 简洁输出原则
389+- `catch {}` 用于 fire-and-forget 模式合理
390+- 禁止不必要的注释
391+- 配置项应显式列出默认值
392+ 
393+### CPTF 架构分层
394+ 
395+详见 [`docs/architecture/ARCHITECTURE.md`](../../../packages/framework/docs/architecture/ARCHITECTURE.md):
396+- 5 层架构(orchestration → capability → system → infrastructure → base)
397+- 单向依赖规则
398+- 类型定义集中在 `base/types/`
399+ 
400+## 快速检查清单
401+ 
402+收到优化建议后,按此清单快速评估:
403+ 
404+- [ ] **行号验证**:`sed -n '<start>,<end>p' <file>` 内容与描述匹配
405+- [ ] **文件存在**:`ls -la <file>` 确认文件路径正确
406+- [ ] **符号搜索**:`grep -n '<symbol>' <file>` 找到真实位置
407+- [ ] **上下文理解**:阅读相关代码,理解设计意图
408+- [ ] **Concurrency**:确认运行环境 + 隔离机制 + 并发控制
409+- [ ] **实跑验证**:`pnpm typecheck && pnpm lint && pnpm test`
410+- [ ] **成本评估**:修改行数 + 风格一致性 + 运行时收益
411+- [ ] **交叉验证**:多个工具一致报告
412+ 
413+## 总结
414+ 
415+**核心原则**:
416+1. **行号漂移是常态**:静态工具的行号经常错位,必须实跑验证
417+2. **上下文决定一切**:Node.js 单线程、设备隔离、设计模式等上下文决定问题真实性
418+3. **成本收益权衡**:不是所有"问题"都值得修复,过度优化会损害代码质量
419+4. **交叉验证**:多个工具一致报告的可信度远高于单一工具
420+ 
421+**避免的错误**:
422+- ❌ 盲目相信静态分析报告
423+- ❌ 不验证行号就修复
424+- ❌ 追求"零警告"而引入噪音代码
425+- ❌ 忽略项目既有风格和上下文
426+ 
427+**正确的做法**:
428+- ✅ 先验证,后修复
429+- ✅ 理解业务语义和运行环境
430+- ✅ 评估成本收益比
431+- ✅ 尊重项目既有风格
@@ -1,11 +1,11 @@
1---1---
2name: cptf-refactor-rules2name: cptf-refactor-rules
3-description: Use when refactoring CPTF (packages/framework) or applying lessons learned from v0.2.1 refactor. Contains concrete rules for: splitting God Class into Mixin, eliminating `as any` in business code, async-IO migration, shell command normalization, AbortController race cancellation, and template / file-I-O separation. Triggers: "refactor", "split mixin", "god class", "拆分", "重构", "as any", "async io", "shell command", "raceWithCancel", "tryWithTimeout", "index template", "report builder", "@internal", "verify check". Do NOT use for new feature implementation (use cptf-framework-dev) or test writing (use cptf-test-api).3+description: Use when refactoring CPTF (packages/framework) or applying lessons learned from v0.2.1 refactor. Contains concrete rules for: splitting God Class into Mixin, eliminating `as any` in business code, async-IO migration, shell command normalization, AbortController race cancellation, template / file-I-O separation, dead-code保留/删除判定(git 溯源), 复制分叉 bug 检测, 多重继承 protected 可见性. Triggers: "refactor", "split mixin", "god class", "拆分", "重构", "as any", "async io", "shell command", "raceWithCancel", "tryWithTimeout", "index template", "report builder", "@internal", "verify check", "dead code", "死代码", "保留", "删除判定", "copy fork", "复制分叉". Do NOT use for new feature implementation (use cptf-framework-dev) or test writing (use cptf-test-api).
4---4---
5 5 
6# CPTF Refactor Rules (v0.2.1+)6# CPTF Refactor Rules (v0.2.1+)
7 7 
8-> 来源:v0.2.1 重构期间(OPTIMIZATION-ANALYSIS.md 24/26 任务)形成的具体约束、反模式与标准做法。8+> 来源:v0.2.1 重构期间(v0.2.1 重构(24/26 优化任务,详见 CHANGELOG))形成的具体约束、反模式与标准做法。
9> 适用:未来重构 packages/framework 时遵循,避免重复踩坑。9> 适用:未来重构 packages/framework 时遵循,避免重复踩坑。
10 10 
11---11---
@@ -266,28 +266,10 @@ async function writeScenePartitionedIndex(cases, outDir, builder) {
266### 反模式266### 反模式
267ComparisonRunner 1116 行,UI 对比 + 报告生成 + 邮件发送全部混在一起。267ComparisonRunner 1116 行,UI 对比 + 报告生成 + 邮件发送全部混在一起。
268 268 
269-### 修复(已落地)269+### 修复状态
270-拆为 2 个独立组件:270+**未单独抽取**。`UIComparisonService` / `ComparisonReportBuilder` 作为独立文件**未创建**(实际代码在 `comparison-runner.ts` 内联实现)。如下游需要单独测试/复用,可后续抽取。
271 271 
272-```ts272+### TestRunner 拆分(已落地)
273-// orchestration/ui-comparison-service.ts
274-export class UIComparisonService {
275- async startEarlyUIComparisons(): Promise<void> { /* 队列调度 */ }
276- async waitEarlyUIComparisons(): Promise<void> { /* 等待 */ }
277- compareAcrossPlatforms(): ComparisonResult[] { /* 跨平台一致性 */ }
278-}
279- 
280-// orchestration/comparison-report-builder.ts
281-export class ComparisonReportBuilder {
282- async build(results: ComparisonResult[]): Promise<ComparisonReport> {
283- // 包含 QualityReport 计算(多维度评分 + 覆盖统计)
284- return { meta, summary, results, recommendations, qualityReport };
285- }
286- private buildQualityReport(): QualityReport { /* ... */ }
287-}
288-```
289- 
290-### TestRunner 拆分(保守)
291- 抽出 `HealthKeeper`(keepalive + 健康检查)273- 抽出 `HealthKeeper`(keepalive + 健康检查)
292- 抽出 `ReportOrchestrator`(HTML/MD/JSON 报告编排 + 邮件)274- 抽出 `ReportOrchestrator`(HTML/MD/JSON 报告编排 + 邮件)
293- 主类保留:`run()` / `runTestCase()` / `executeTestCase()` 主体(跨方法状态依赖紧密,强行拆会引入更多复杂度)275- 主类保留:`run()` / `runTestCase()` / `executeTestCase()` 主体(跨方法状态依赖紧密,强行拆会引入更多复杂度)
@@ -392,7 +374,114 @@ Coverage: 2 ✓
392 374 
393---375---
394 376 
395-## 12. 重构决策模板377+## 12. Strategy 模式替换 if 分支(v0.3.0 已完成)
378+ 
379+### 历史反模式(v0.3.0 之前,已修复)
380+```ts
381+// system/harmony/mixins/input-controller.ts(旧版,已删除)
382+async click(x: number, y: number): Promise<boolean> {
383+ if (this.native) { try { await this.native.click(x, y); return true; } catch (e) { ... } } // 路径1
384+ if (this.useDriver) { try { await this.host.runner.driver.click(x, y); ... } } // 路径2
385+ const ok = await this.touchTap(x, y); // 路径3
386+ return ok;
387+}
388+```
389+ 
390+**历史问题**:
391+- 业务代码 17 处 `if (this.native)` 分支(input-controller.ts + layout-provider.ts)
392+- 同一函数体 3 路径 + try/catch 降级
393+- 报告 `source` 字段混用 native/hmd/uitest,难归因
394+ 
395+### 当前实现(v0.3.0)
396+**抽象 + 构造时定型**:
397+```ts
398+// system/harmony/mixins/input-controller.ts(当前)
399+export abstract class HarmonyInputController extends BaseSystemMixin<HarmonySystem> {
400+ abstract click(x: number, y: number): Promise<boolean>;
401+ abstract clickByTestId(testID: string): Promise<boolean>;
402+ abstract swipe(...): Promise<boolean>;
403+ // ... 15 abstract methods
404+}
405+ 
406+export class NativeInputController extends HarmonyInputController {
407+ private get driver(): HarmonyNativeDriver {
408+ return this.host.nativeDriver as HarmonyNativeDriver; // 从 host 读,不通过构造器注入
409+ }
410+ async click(x, y) { return await this.driver.click(x, y); }
411+ async clickByTestId(t) { return await this.driver.clickComponent(t); }
412+ async swipe(x1, y1, x2, y2, d) { return await this.driver.swipe(x1, y1, x2, y2, d); }
413+}
414+ 
415+export class ExternalInputController extends HarmonyInputController {
416+ async click(x, y) {
417+ if (this.useDriver) { /* HMD RPC */ }
418+ return await this.touchTap(x, y);
419+ }
420+ async clickByTestId(t) {
421+ const el = await this.host.layout.getElementByTestId(t, 3000);
422+ return el ? await this.click(el.bounds.center.x, el.bounds.center.y) : false;
423+ }
424+}
425+```
426+ 
427+**主类构造时定型**(`HarmonySystem`,当前):
428+```ts
429+class HarmonySystem extends BaseSystem {
430+ input: HarmonyInputController;
431+ layout: HarmonyLayoutProvider;
432+ perf: HarmonyPerformanceProvider;
433+ // ... 6 个策略类
434+ 
435+ constructor(config: SystemConfig) {
436+ super(config);
437+ this.layout = config.platformOptions?.['driver'] === 'native'
438+ ? new NativeLayoutProvider(this)
439+ : new ExternalLayoutProvider(this);
440+ this.input = config.platformOptions?.['driver'] === 'native'
441+ ? new NativeInputController(this)
442+ : new ExternalInputController(this);
443+ this.perf = config.platformOptions?.['driver'] === 'native'
444+ ? new NativePerformanceProvider(this)
445+ : new ExternalPerformanceProvider(this);
446+ // ... 6 个 mixin 全部按 driverMode 选
447+ }
448+}
449+```
450+ 
451+**Android 类名前缀**:`AndroidNativeInputController` / `AndroidExternalInputController`(注意 `Android` 前缀)
452+ 
453+### 验证
454+- ✅ `grep -n "if (this.isNativeMode)\|if (this.native)" packages/framework/src/system/harmony/mixins/` 返回 0
455+- ✅ `grep -n "if (this.isNativeMode)\|if (this.native)" packages/framework/src/system/android/mixins/` 返回 0
456+- ✅ `pnpm typecheck && pnpm test && pnpm verify` 全过
457+ 
458+### 4 级降级(v0.3.0 状态)
459+ 
460+| Level | 位置 | 切模式 | 范围 | 状态 |
461+|---|---|---|---|---|
462+| L1 Pre-flight | `DriverModeArbiter`(`harmony/driver-mode-arbiter.ts`)| ✅ 整 run 1 次 | run | ✅ 类已实现,⚠️ 未接入 TestRunner |
463+| L2 Reconnect | `HarmonyNativeDriver.tryReconnect()` 指数退避 1s/2s/4s × 3 30s 预算 | ❌ 仍 native | 单 case | ✅ Harmony 已实现,❌ Android 待补 |
464+| L3 Action Retry | `HarmonyNativeDriver.sendCommand()` 内部 1 次重试 | ❌ 仍 native | 单 action | ✅ Harmony 已实现,❌ Android 待补 |
465+| L4 Crash Recovery | `ISystem.tryNativeCrashRecovery` / `HarmonyNativeDriver.recoverFromCrash(bundleId)` | ❌ 仍 native | 单 case | ✅ 双平台已实现并接入 TestRunner |
466+ 
467+**禁止操作期静默切模式**(破坏隔离、报告难归因)。
468+ 
469+### 当前保留的兼容方法
470+ 
471+- `HarmonyNativeDriver._permanentlyFailed` 标记(`HarmonyNativeDriver`)— 仍在用,标记 native driver 进入永久失败状态
472+- `AndroidNativeDriver._permanentlyFailed` 标记(`AndroidNativeDriver`)— 同上
473+- `UitestRunner._permanentlyFailed` 标记(`UitestRunner`)— UITest 重连失败标记
474+- `IosWdaClient._permanentlyFailed` 标记(`IosWdaClient`)— WDA 重连失败标记
475+- `AndroidU2Client._permanentlyFailed` 标记(`AndroidU2Client`)— U2 重连失败标记
476+ 
477+**这些 `_permanentlyFailed` 是 connection-level 永久失败标记,与 L2 重连预算(L2 budget)是不同维度**:
478+- `_permanentlyFailed=true` 表示连接已无法恢复(多次重连失败)
479+- L2 budget 是每次断开后的重试次数(3 次指数退避 30s)
480+- 两者并存:先尝试 L2 budget,失败后设 `_permanentlyFailed=true`,后续直接走 shell 降级
481+ 
482+---
483+ 
484+## 13. 重构决策模板
396 485 
397遇到 1000+ 行文件时按此顺序判断:486遇到 1000+ 行文件时按此顺序判断:
398 487 
@@ -407,9 +496,228 @@ Coverage: 2 ✓
407 496 
408---497---
409 498 
499+ 
500+## 14. God class 机械拆分流程(0.3.1 实战沉淀)
501+ 
502+拆分 1000+ 行类时的安全流程(已应用于 comparison-report-generator / expect-executor / test-scene-navigator / TestRunner 的拆分)。
503+ 
504+### 拆分前(决定拆什么)
505+ 
506+1. **死代码审计先行**:`grep -rn "\.method(" src --include="*.ts" | grep -v 定义文件` 零调用的方法不拆、直接删(曾把 `generateReportStreaming` 拆到新文件后才发现 HEAD 起就零调用,连带 300 行 StreamingWriter 全链死)
507+2. **调用面编目**:列出 facade 必须保留的公共 API(外部调用点 grep 确定),其余全部下沉
508+3. **按职责定形态**:3 种输出格式 → 3 个类;纯函数域(颜色/元素属性解析)→ 模块级函数文件;组合体(导航)→ Core 接口 + Part 基类 + 2-3 个 Part 类 + 薄 facade 委托
509+ 
510+### 提取中(防转录错误)
511+ 
512+4. **从 git HEAD 取原始源**:工作区可能处于中间态,`git show HEAD:path > /tmp/orig.ts` 后按行号提取,禁止凭记忆重写
513+5. **边界双断言**:提取段首行必须是方法签名、尾行必须是 ` }`(方法闭合);python 提取脚本对两断言 assert,失败即停
514+6. **签名适配最小化**:`this.xxx()` 调用点用机械字符串替换(this.ui→core.ui),私有方法签名改 public/export;参数化时闭包变量显式进签名(startTime 等)
515+7. **替换必须验数**:python `str.replace` 对含 `\n`/反引号的模板字符串会静默失配——每次替换前 `assert old in src`、替换后核对 count(曾因 `\n` 转义不匹配导致转义修复静默失败,下轮审计才发现)
516+ 
517+### 拆分后(清扫清单)
518+ 
519+8. **未用 import 扫描**:机械拆分必然残留(renderSection/DeviceInfo/Writable 类);简易法:import 符号在文件内出现次数 ≤1 即删
520+9. **过度导出收敛**:仅文件内使用的函数去 export(拆分时倾向全 export,收尾要回收)
521+10. **重复逻辑收编**:同一逻辑在旧文件/新文件各一份时(设备 map 构建曾 3 份),保留一份、其余改调用
522+11. **验证门**:每阶段 typecheck + jest + verify;tsc 偶发单次语法错误(WSL 文件写入竞态)先复跑再调试
523+ 
524+### 测试配套陷阱(u2-race 实战)
525+ 
526+12. **mock 高层方法丢副作用**:真实 `connect()` 成功时自置 `_connected=true`;`mockImplementation` 替换后副作用消失 → 断言必挂。改 mock 底层原语(launchU2Server/waitForServerReady/callRpc)
527+13. **构造期 fire-and-forget 击穿实例 spy**:构造函数里 `this.initU2()`(无 await)先于测试的 `jest.spyOn` 执行 → 实例级 mock 永远晚一步。必须 mock 原型方法(`AndroidU2Client.prototype.xxx`)或底层原语
528+14. **pending promise 时序**:hold 型 mock(外部 resolve)需要 `waitRpcReady()` 轮询辅助函数等待 promise 真正 pending 后再 resolve,否则 resolve 调用在 nobody listen 时为 no-op → 测试超时
529+ 
530+---
531+ 
532+## 15. 死代码判定:保留 vs 删除(git 溯源 + 保留/删除决策树)
533+ 
534+> 来源:v0.3.1 PRINCIPLES-AUDIT 复核。教训:原审计报告把 9 项零调用代码全标"删",实际其中 9 项(Synchronizer / plugin-rn / AccessibilityAnalyzer / RunnerStrategyRegistry / KNOWN_REPORT_TYPES / NavigationStrategyFactory / ReportGeneratorRegistry 方法 / MetricCollectorFactory 方法 / 预留类型 + ConfigManager 预留字段)都是 **ec1f5af 插件化批次引入的预留能力**,被错杀。
535+ 
536+### 核心方法论:删除前必须 git 溯源
537+ 
538+不能因为"零调用"就删除——必须先回答**"为什么没用起来"**,否则会误杀预留能力。
539+ 
540+```bash
541+# 1. 找到引入提交
542+git log --diff-filter=A --oneline -- <file>
543+ 
544+# 2. 找到失去消费方的提交
545+git log --oneline -S "<SymbolName>" --all -- <path>
546+ 
547+# 3. 看引入批次的 commit message(理解引入意图)
548+git show <commit-introducer> --stat | rg "<symbol>"
549+git log -1 --format="%B" <commit-introducer>
550+```
551+ 
552+### 保留/删除决策树
553+ 
554+按顺序判断(命中任一即停):
555+ 
556+| 信号 | 来源 | 处置 |
557+|---|---|---|
558+| 引入批次是 `feat: 重构` / `feat(God class 拆分)` / `feat: 全量修复+报告审计` 等重构提交 | git log | **删除**(重构副产物) |
559+| 类型/接口被另一个活跃类型完全替代(TestResult 取代 TestResultInternal) | rg 全仓 | **删除** |
560+| 注释自述"预留配置" / "尚未在运行时引用" / "Reserved" | 文件内 | **保留**(标注预留) |
561+| 类型/class 注释自述 `user-extensible` / `third-party` / `plugin` | 文件内 | **保留**(extension contract) |
562+| docstring 示例引用了某个 import,但该 import 从未在 `index.ts`/`internal.ts` 导出 | rg 检查 | **保留 + 修复接线遗漏** |
563+| 配套 test 文件覆盖完整(assertion 数 > 50)但生产零消费 | 引入批次是 `feat: <能力>` | **保留**(等待接线,预留能力) |
564+| 被 `internal.ts` 导出但 `index.ts` 不导出 | 查两文件 | **保留**(深度入口,不需要导出到 `index.ts`) |
565+ 
566+### 典型引入批次与判定
567+ 
568+| 引入提交 | 内容 | 判定 | 理由 |
569+|---|---|---|---|
570+| `8248907` Phase-0 DeviceBridge | Synchronizer + schema probe types | **保留** | 双模式架构预留能力 |
571+| `ec1f5af` benchmark 插件化 | plugin-rn / AccessibilityAnalyzer / RunnerStrategyRegistry / KNOWN_REPORT_TYPES / 注册表门面方法 / ConfigManager 预留字段 / 预留类型 | **全部保留** | 同一批插件化扩展点,用户已表态保留 plugin-rn → 同批全保留 |
572+| `616bd2b` / `716c916` 0.3.1 重构 | buildSparklineSvg(提取副产物)/ 被取代的内部形状(TestResultInternal 等)/ 内联后遗留的工具函数 | **删除** | 重构提取只接线了 buildSvgChart 等活跃部分;buildSparklineSvg 等为提取副产物 |
573+ 
574+### 操作清单(删除前必做)
575+ 
576+1. **三方搜索全消费方**(不仅框架 src):
577+ ```bash
578+ rg -rn "<SymbolName>" app/ cptf/packages/ --glob '*.ts' --glob '*.tsx' | rg -v node_modules | rg -v <自身文件>
579+ ```
580+2. **检查公共出口**:`index.ts` 和 `internal.ts` 是否导出(决定删除是否破坏公共 API)
581+3. **检查配套测试**:test 文件是否引用(决定是否连带删除)
582+4. **检查 docstring 示例**:example code 是否引用(决定是否示例编译失败——这是接线遗漏信号)
583+5. **删除时同步清理**:unused import、unused type export、同名的死 alias
584+ 
585+### 反例:什么场景下"看起来死"实际是预留
586+ 
587+| 看起来 | 实际 |
588+|---|---|
589+| `registerRNProbes` 零调用 | 插件化扩展点;plugin-rn 包预留 |
590+| `AccessibilityAnalyzer` 仅测试存活(439 行测试) | 完整 a11y 规则引擎 + 测试,对应 app 无障碍用例 `unsupported` 恢复计划 |
591+| `RunnerStrategyRegistry` 零调用 | 文档示例引用 `@cptf/framework/internal` 导出但**从未导出**——这是**接线遗漏**,需补 internal.ts 导出而非删除 |
592+| `ConfigManager.device/cacheTimeout/report/retryPolicy` | 注释自述"预留配置",对应重连/缓存/报告/重试策略的未来接线 |
593+| `KNOWN_REPORT_TYPES` 零引用 | 报告插件契约常量,文档化的 extension API |
594+ 
595+---
596+ 
597+## 16. 复制分叉是系统性 bug 源(抽基类的"信号识别")
598+ 
599+> 来源:v0.3.1 PRINCIPLES-AUDIT 清理期间连续发现 4 个真 bug,均为 harmony/android 逐行复制分叉的产物:
600+> - BUG-1 `swipe(x, y, endY, endY)` — 应为 `(x, y, endX, endY)`
601+> - `click` 缺 `await`(所有兄弟方法及 android 版均有 await)
602+> - `RandomAction` 模板 40 行复制
603+> - `scrollTo` 几何 16 行复制
604+ 
605+### 抽基类的判断信号
606+ 
607+任何**两条**满足即应抽基类:
608+ 
609+1. `rg -n "<methodName>" src/system/<a> src/system/<b>` 出现完全相同的实现块(≥ 10 行)
610+2. 该方法有 `async` + `try/catch` 或 `setTimeout`/资源管理逻辑
611+3. (加分项)兄弟方法已经存在但本方法缺关键调用(如 `await`/`clearTimeout`)
612+ 
613+### 实操:发现复制分叉后
614+ 
615+```bash
616+# 1. 找重复块
617+diff <(sed -n '/async randomAction/,/^ }/p' src/system/harmony/mixins/input-controller.ts) \
618+ <(sed -n '/async randomAction/,/^ }/p' src/system/android/mixins/input-controller.ts)
619+ 
620+# 2. 查 iOS 是否有第三变体(可能差异更大)
621+rg -n "async randomAction" src/system/ios/mixins/
622+ 
623+# 3. 抽到 base-system-mixin.ts 的 default* 模板 + 子类提供 ops hook
624+```
625+ 
626+### 抽基类后必须验证
627+ 
628+1. **import 链**:删除子类原方法后是否还有 import 引用
629+2. **iOS 第三变体**:如果 iOS 不同,保留 override(避免破坏 iOS 语义)
630+3. **method call site**:子类业务方法调 `this.xxx()` 是否仍合法(尤其 `protected` 可见性,见下节)
631+4. **等价性逐项对比**:每个删除的代码块与重构版做行为对比,记录任何不等价点(错误消息文案、调试日志等)
632+ 
633+### 反例:不要被"零调用 + 无文档"误导
634+ 
635+不是所有重复代码都是 bug——只有当**两个平台同步漂移**时才需要抽基类。如果只有一处实现(即便 100 行重复),先单独看那处是否有问题;不要为抽而抽。
636+ 
637+---
638+ 
639+## 17. TypeScript 多重继承 + protected 可见性陷阱
640+ 
641+> 来源:v0.3.1 WsRpcClient 基类抽取时踩坑。
642+ 
643+### 陷阱描述
644+ 
645+抽 `WsRpcClient extends EventEmitter` 作为基类,子类 `HarmonyNativeDriver` 需要继承基类的方法:
646+ 
647+```ts
648+// ❌ 错误:子类只 extends EventEmitter,基类 protected 方法不可见
649+export class HarmonyNativeDriver extends EventEmitter {
650+ async click(...) {
651+ await this.sendCommand(...); // TS: Property 'sendCommand' does not exist
652+ }
653+}
654+ 
655+// ✅ 正确:子类必须 extends WsRpcClient(即使已间接 extends EventEmitter)
656+export class HarmonyNativeDriver extends WsRpcClient {
657+ // ...
658+}
659+```
660+ 
661+### 核心规则
662+ 
663+**子类必须显式 extends 基类**——TypeScript 不会从 mixin/参数化类型推断出继承关系。即使基类的方法通过 `protected` 标记对**未来子类**可见,**当前类**若没 extends 该基类,protected 方法不存在于自身的 type 中。
664+ 
665+### 抽基类的 class 签名模板
666+ 
667+```ts
668+// 基类:extends 必要父类(EventEmitter / BaseSystem / 等)
669+export abstract class WsRpcClient extends EventEmitter {
670+ private pendingRequests: Map<string, PendingRequest> = new Map();
671+ protected requestIdCounter = 0;
672+ 
673+ // 抽象方法由子类提供
674+ protected abstract getWs(): WebSocket | null;
675+ protected abstract shouldRetry(err: Error): boolean;
676+ protected abstract isDestroyed(): boolean;
677+ 
678+ // 模板方法:protected 允许子类调用
679+ protected async sendCommand(action: string, ...): Promise<unknown> {
680+ // 模板实现
681+ }
682+ 
683+ // Hook 方法:protected,子类按需 override
684+ protected async preSend(_action: string): Promise<void> { /* noop */ }
685+ protected wrapResolve(_action: string, resolve: (v: unknown) => void, _timer: NodeJS.Timeout): (v: unknown) => void {
686+ return resolve;
687+ }
688+}
689+```
690+ 
691+### 子类覆盖规则
692+ 
693+```ts
694+// ✅ protected override 必须与基类签名一致
695+protected override getWs(): WebSocket | null { return this.driverWs; }
696+protected override shouldRetry(err: Error): boolean { return isRetryableError(err); }
697+ 
698+// ❌ 不要把基类方法改成 private——会报 "is private in type X but not in type Y"
699+private rejectAllPending(err: Error): void { ... }
700+ 
701+// ❌ 不要重复定义基类已有的接口(命令/PendingRequest 等)—— 抽到 base
702+interface CommandMessage { ... } // 删,引用基类 WsRpcCommand
703+```
704+ 
705+### TS 编译错误的精确诊断
706+ 
707+抽基类后常见 TS 错误 → 原因映射:
708+ 
709+| 错误信息 | 根因 | 修正 |
710+|---|---|---|
711+| `Property 'X' does not exist on type 'Y'` | 子类没 extends 基类 / 基类方法签名未声明 | 检查 `class Y extends X` + 基类方法可见性 |
712+| `'Y' is private in type 'X' but not in type 'Z'` | 基类是 protected,子类声明了 private 同名 | 删子类的 private 声明,依赖基类继承 |
713+| `Class 'X' incorrectly extends base class` | 多重继承中子类保留父类的 private 字段 | 把 private 字段提到基类 protected 或删 |
714+| `Argument of type 'A[]' is not assignable to parameter of type 'B[]'` | A 与 B 缺索引签名兼容 | 接口加 `[key: string]: T` 索引签名,或调用点 cast `as unknown as B[]` |
715+ 
716+---
717+ 
410## 相关 Skill718## 相关 Skill
411 719 
412-- `cptf-arch` — 3层架构 + 类型位置(v0.2.1 之前定义)720+- `cptf-arch` — 5 层架构 + 类型位置
413- `cptf-framework-dev` — 改 packages/framework 时的具体规则721- `cptf-framework-dev` — 改 packages/framework 时的具体规则
414- `cptf-system-extension` — 新平台/ISystem 子接口(继承自 mixin 模式)722- `cptf-system-extension` — 新平台/ISystem 子接口(继承自 mixin 模式)
415- `cptf-stability` — 稳定性 detector 配置723- `cptf-stability` — 稳定性 detector 配置
@@ -1,112 +1,161 @@
1---1---
2name: cptf-report2name: cptf-report
3-description: Use when customizing HTML / Markdown / JSON reports, integrating trend charts, or understanding ReportService / ReportJobQueue / off-thread report generation. Triggers: "HtmlReportGenerator", "MarkdownTestReportGenerator", "ComparisonReportGenerator", "ReportService", "ReportJobQueue", "TrendChart", "trend points", "report custom", "JUnit XML", "CI integration". Do NOT use for general test writing (cptf-test-api) or CLI invocation (cptf-cli).3+description: Use when customizing HTML / Markdown / JSON reports, integrating trend charts, or understanding ReportService / ReportJobQueue / off-thread report generation. Triggers: "HtmlReportGenerator", "MarkdownTestReportGenerator", "ComparisonReportGenerator", "ReportService", "ReportJobQueue", "TrendChart", "trend points", "report custom", "JUnit XML", "CI integration", "escapeHtml", "unifiedMetrics", "computeUnifiedMetrics", "report escape", "chart-renderer", "ComparisonReport". Do NOT use for general test writing (cptf-test-api) or CLI invocation (cptf-cli).
4---4---
5 5 
6-# Report Customization Skill6+# CPTF 报告模块规则 (v0.3.1+)
7 7 
8-## Built-in Reports (v0.2.0)8+> 报告模块位于 `packages/framework/src/orchestration/report/`。本文件是该模块的"硬约束 + 速查",所有改报告代码必须遵守。
9+> 准则来源:CORE-PRINCIPLES(准确性第一)+ METRICS-SPEC(指标单一真相)+ v0.3.1 报告审计修复。
9 10 
10-| Format | Generator | Output |11+## 1. 模块结构
11-|--------|-----------|--------|
12-| `html` | `HtmlReportGenerator` | `test-report.html` (full HTML with trend charts) |
13-| `json` | (built-in) | `test-report.json` (raw data) |
14-| `markdown` | `MarkdownTestReportGenerator` | `test-report.md` (concise summary) |
15-| comparison | `ComparisonReportGenerator` | `comparison-report.html/md/json` |
16 12 
17-## ReportService (v0.2.0)13+| 文件 | 职责 | 行数上限 |
14+|------|------|---------|
15+| `report-theme.ts` | 调色板 + 布局常量(单一真相) | ≤ 80 |
16+| `report-css.ts` | `SHARED_CSS` / `TEST_REPORT_EXTRA_CSS` / `COMPARISON_REPORT_EXTRA_CSS` | ≤ 350 |
17+| `report-utils.ts` | 转义/格式化纯函数:`escapeHtml` `escapeMdCell` `sanitizeHtmlId` `formatMemory` `formatDuration` | ≤ 80 |
18+| `html-generator.ts` | 单平台 HTML 报告(编排 + 详情卡) | ≤ 1000 |
19+| `chart-renderer.ts` | SVG 图表纯函数(buildSvgChart/buildSparklineSvg/buildTrendTableRows/sampleData/niceScale/computeTrendStats/lightenColor) | ≤ 300 |
20+| `markdown-test-generator.ts` | 单平台 Markdown 报告 | ≤ 700 |
21+| `comparison-report-generator.ts` | 跨平台对比报告(HTML/MD/JSON) | ≤ 1200(God class 待继续拆) |
22+| `metrics-computer.ts` | `computeUnifiedMetrics` + `buildQualityReport` + 违规检测(指标单一真相源) | ≤ 750 |
23+| `report-orchestrator.ts` | TestReport 报告编排(async/sync + 邮件) | ≤ 200 |
24+| `report-service.ts` | ReportJobQueue 门面 | ≤ 200 |
25+| `html-components.ts` | 可复用 HTML 片段 + 过滤器 JS(`renderTableFilterBar`/`renderTableFilterScript`) | ≤ 300 |
26+| `report-generator-registry.ts` | 生成器注册(test-html/json/markdown, comparison-*) | ≤ 300 |
18 27 
19-Batches multiple report formats for **concurrent off-thread generation** via `ReportJobQueue`:28+**入口**:单平台 HTML 经 `report-generator-registry.ts:createTestHtmlGenerator` → `HtmlReportGenerator.buildTestHtmlContent`(**非** `generateTestReportStreaming`,后者是备选流式路径,默认不调用)。
29+ 
30+## 2. 硬约束(违反即 verify 失败或安全漏洞)
31+ 
32+### R1. HTML 转义 — 所有用户/测试派生字符串必须 `escapeHtml`
33+- 测试派生字段(caseName / caseId / e.name / e.details / errors / notices / logs / automationNote / actualValue / expectedValue / knownIssue)插值进 HTML 时**必须** `escapeHtml(...)`。
34+- `formatValue()` 返回**原始字符串**(用于非 HTML 的分类 reason),HTML 站点必须包 `escapeHtml(this.formatValue(x))`。
35+- 控制词汇(module / platform / status 枚举)可豁免。
36+- 任何 `onclick="document.getElementById('${id}')"` 中的 id 用 `sanitizeHtmlId` 清洗(仅 `\w\-`),同时用于 `id="..."` 与 `getElementById`,保证匹配。
37+- 反例:`<td>${r.caseName}</td>`、`title="${e.knownIssue}"`、`onclick="...('${anchorId}')"` 裸插值 → 含 `<`/`"`/`'` 即破结构/XSS。
38+ 
39+### R2. Markdown 表格单元必须 `escapeMdCell`
40+- 表格行 `| ${a} | ${b} |` 中每个用户单元用 `escapeMdCell`(转义 `|`→`\|`、换行→空格、`` ` ``→`` \` ``)。
41+- 标题/列表项(`###`、`-`)至少转义换行。
42+- `report-utils.ts` 提供 `escapeHtml` / `escapeMdCell` / `sanitizeHtmlId`,两生成器均 import 使用。
43+ 
44+### R3. 截图路径 — 相对报告根,禁止剥 basename
45+- 截图存于 `<timestampDir>/<platform>/screenshots/<name>.png`,HTML 在 `<timestampDir>/test-report.html`。
46+- result.screenshots 在 push 前转成**相对 timestampDir**:`path.relative(this.timestampDir, s).replace(/\\/g,'/')`(得 `android/screenshots/foo.png`),非 `path.basename(s)`。
47+- `buildScreenshotsHtml` 对绝对路径计算相对路径、对相对路径直接用;basename 剥离会绕过该逻辑导致 `src="foo.png"` 404。
48+- 报告生成**之后**的 slim-down(ComparisonRunner 内存)才允许 basename(已落盘不影响)。
49+ 
50+### R4. 指标单一真相源 — 全走 `computeUnifiedMetrics`
51+- **TestReport 与 ComparisonReport 都必须挂 `unifiedMetrics`**。ComparisonReport 由 `comparison-runner` 用 `computeUnifiedMetrics([...harmony, ...android], qualityReport, timestampDir)` 计算。
52+- 所有报告出口(HTML/MD/JSON/Email)的 expect 统计(totalExpects/passedExpects/knownIssueExpects/limitedExpects/effectivePassedExpects)**只读** `unifiedMetrics`,fallback 顺序 `unifiedMetrics ?? qr ?? results.reduce`(保证口径一致、同 ui 过滤)。
53+- **禁止** totalExpects 取 unifiedMetrics 而 passedExpects 取 qr 的混源(曾导致 417/364 通过>总数)。
54+- `UnifiedMetrics` 类型在 `base/types`,`computeUnifiedMetrics` 在 `metrics-computer.ts`。
55+ 
56+### R5. 指标匹配 — 禁子串、禁 `includes(维度名)`
57+- 丰富度(AR)匹配用**词边界正则**:`new RegExp('(?:^|[^a-z])(' + types.join('|') + ')(?:[^a-z]|$)','i')`,禁止 `e.details.includes('text')`("textColor" 误命中 "text")。
58+- 维度查找用 `QualityDimension.key` 字段(如 `key:'combinationCoverage'`),`find(d => d.key === 'combinationCoverage')`;`d.name.includes('组合覆盖')` 仅作兜底,不作为主键。
59+- 权重按 METRICS-SPEC §9.2 固定值:双平台 Harmony/Android 各 20%;**三平台各 15% + iOS 10%**(非等比 redistribute 缩放)。`redistributeWeights` 仅作浮点归一安全网,total=1.0 时为 no-op。
60+ 
61+### R6. 过滤计数器 — 数唯一用例,非元素
62+- 过滤栏 `data-filter-target="#cases-table tbody tr, .detail-card"` 同时锁表格行+详情卡,`tf-total` 必须只数 `.detail-card`(= 用例数),不能数全部元素(曾显示 218/218 = 109 行 + 109 卡)。
63+- `applyFilter` 中 `if (ok && row.classList.contains('detail-card')) shown++`。
64+ 
65+### R7. 渲染去重与采样
66+- 趋势图 HTML 在 `buildTestHtmlContent` 顶部**计算一次**存局部变量(`const trendChartsHtml = this.buildTrendChartsHtml(...)`),nav 检查/section 检查/渲染三处复用(曾 3 遍 SVG 计算)。
67+- 趋势表 `buildTrendTableRows` 必须**采样**(`data.length > 200 ? sampleData(data,200) : data`),与 sparkline 一致;禁止数千行全量渲染。
68+- 对比报告 HTML 默认走**流式**(`generateHtmlStreaming`),非全量字符串拼接(峰值内存 -10~20MB)。
69+ 
70+### R8. 配色 — 浅色主题 + 对比度合规
71+- 报告为**浅色主题**:页面 bg `#eef2f7`,外层卡白 `#ffffff`,内层卡极浅蓝 `#f5f8fc`,边框 `#e2e8f0`/`#cbd5e1`,强调蓝 `#2563eb`(blue-600)。
72+- 文字深 slate:primary `#0f172a`、secondary `#334155`、muted `#64748b`。**蓝色仅做强调**(边框/左条/徽章/链接),卡背景用极浅蓝保证深字高对比(≥11:1)。
73+- 状态色用深变体适配浅底:success `#16a34a`、danger `#dc2626`、warning `#d97706`;徽章 bg 用 `*Light`(`#dcfce7`/`#fee2e2`/`#fef3c7`)。
74+- 蓝色按钮(如 `.back-top`)白字保留 `color:#fff`(蓝底白字合规)。
75+- 所有颜色经 `report-theme.ts` / CSS 变量,禁止内联硬编码深色魔法值(`#0f0c29`/`#06070d`/`rgba(255,255,255,0.0X)` 已清除)。
76+ 
77+### R9. God class 拆分
78+- 单文件 ≤ 上表上限。超限按关注点拆:SVG 图表 → `chart-renderer.ts`(纯函数);设备 map 构建 → 单一 `buildDeviceMaps` helper(禁止 3 份复制)。
79+- 模板渲染与文件 I/O 分离(参考 `cptf-refactor-rules §6`)。
80+- 对比生成器(1421 行)下一步:HTML/MD/JSON 三格式拆独立类或 mixin,共享 `buildDeviceMaps`/`getDeviceInfoCompleteness`/`formatFailedStepLog`。
81+- expect-executor.ts(1004) / test-scene-navigator.ts(972) 为非报告层 God class,单独排期。
82+ 
83+## 3. 可复用助手(`report-utils.ts` + `chart-renderer.ts` + `metrics-computer.ts`)
20 84 
21```typescript85```typescript
22-import { ReportService } from 'cptf/internal';86+import { escapeHtml, escapeMdCell, sanitizeHtmlId, formatMemory, formatDuration } from './report-utils';
23- 87+import { buildSvgChart, buildSparklineSvg, buildTrendTableRows, sampleData, niceScale, computeTrendStats, lightenColor } from './chart-renderer';
24-const service = new ReportService({ logger });88+import { computeUnifiedMetrics, buildQualityReport, buildComparisonQualityReport } from './metrics-computer';
25-const paths = await service.generateTestReports(report, outputDir, 'all');
26-await service.shutdown();
27```89```
28 90 
29-Off-thread generation in v0.2.0 prevents report generation from blocking test execution.91+- `escapeHtml(s)` — & < > " ' 全转义。`escapeMdCell(s)` — 表格单元安全。`sanitizeHtmlId(s)` — HTML id 清洗。
92+- 图表纯函数无 `this` 状态,仅依赖 `REPORT_THEME`;编排方法(`buildTrendChartsHtml`/`buildPerformanceTrendHtml`)留在 `html-generator`,调用纯函数 + 自有 `buildScreenshotsHtml`/`formatValue`。
30 93 
31-## ReportJobQueue (Worker Threads)94+## 4. ReportService / ReportJobQueue
32 95 
33-`ReportJobQueue` schedules jobs to `JobQueue<T,R>` which dispatches to a worker thread pool.96+- `ReportService` 批量多格式经 `ReportJobQueue`(worker_threads 池)并发生成;`CPTF_ASYNC_REPORTS=false` 回退 inline。
97+- `defaultRegistry` 注册 `test-html/json/markdown` + `comparison-html/json/markdown`;自定义格式 `defaultRegistry.register(type, gen)`(worker 侧需单独注册,主线程注册不自动可见)。
98+- `ReportJobQueue`:`jobTimeoutMs`(120s) 与 `workerInitTimeoutMs`(10s) 分离;大 payload 不再 10s 超时 inline 重跑。
34 99 
35-```typescript100+## 5. 趋势数据
36-export type ReportExecutionMode = 'inline' | 'worker' | 'auto';
37 101 
38-const queue = new ReportJobQueue({ mode: 'auto', maxConcurrency: 4 });102+- `TestRunner` 产 3 全局序列:`globalMemoryTrend`(KB→MB) / `globalFpsTrend` / `globalCpuTrend`,capped 10k(CompressMetrics)。
39-queue.enqueue({ type: 'test-html', jobId: '...', outputPath, data: report });103+- `buildTrendPoints<T>()` 静态助手去重 sort+map。
40-```104+- HTML 内联 SVG(经 `chart-renderer` 纯函数);Markdown 不含图。
41 105 
42-## Trend Points106+## 6. 输出位置
43 107 
44-`TestRunner` emits 3 global trend series:108+| 文件 | 路径 |
45-- `globalMemoryTrend` — Memory (KB → MB) over time
46-- `globalFpsTrend` — FPS over time
47-- `globalCpuTrend` — CPU % over time
48- 
49-`HtmlReportGenerator` inlines these as inline SVG charts.
50- 
51-`buildTrendPoints<T>()` (static helper, v0.2.0) — eliminates 3× duplicated sort+map.
52- 
53-## Roadmap: Custom Report Registration
54- 
55-v0.3.0 plan (per `PLUGIN-GUIDE.md §5`):
56- 
57-```typescript
58-export interface ReportGenerator<TData> {
59- format: string;
60- generate(data: TData, outputPath: string, options?: unknown): Promise<ReportJobResult>;
61-}
62- 
63-ReportService.register('junit-xml', new JUnitXmlReportGenerator());
64-```
65- 
66-## JUnit XML Example (CI integration)
67- 
68-```typescript
69-import type { TestReportGenerator, ReportJobResult } from 'cptf/internal';
70- 
71-export class JUnitXmlReportGenerator implements TestReportGenerator {
72- format = 'junit-xml';
73- 
74- async generate(report: TestReport, outputPath: string): Promise<ReportJobResult> {
75- const testsuites = report.results.map(r => this.toTestSuite(r));
76- const xml = `<?xml version="1.0" encoding="UTF-8"?>\n${testsuites.join('\n')}\n`;
77- await fs.promises.writeFile(outputPath, xml, 'utf8');
78- return { /* ReportJobResult */ };
79- }
80- 
81- private toTestSuite(r: TestResult): string {
82- const failures = r.success ? 0 : 1;
83- return `<testsuite name="${r.caseId}" tests="1" failures="${failures}" time="${r.duration / 1000}">
84- <testcase name="${r.caseName}" classname="${r.module}"/>
85- ${r.success ? '' : `<failure>${r.errors.join('; ')}</failure>`}
86-</testsuite>`;
87- }
88-}
89-```
90- 
91-## Output Locations
92- 
93-| File | Path |
94|------|------|109|------|------|
95-| HTML | `test-reports/yyyy-MM-ddTHH-mm-ss/test-report.html` |110+| HTML | `test-reports/<ts>/test-report.html` |
96| Markdown | `.../test-report.md` |111| Markdown | `.../test-report.md` |
97| JSON | `.../test-report.json` |112| JSON | `.../test-report.json` |
113+| 截图 | `.../<platform>/screenshots/*.png` |
98| Crash logs | `.../<platform>/crash-logs/crash-<ts>.log` |114| Crash logs | `.../<platform>/crash-logs/crash-<ts>.log` |
99-| Quality report | bundled inside `test-report.html` |115+| 用例 JSON | `.../<platform>/case-results/<caseId>.json` |
116+| 对比报告 | `.../comparison-report.{html,md,json}` |
100 117 
101-## Performance Tips118+## 7. 自定义报告示例(CI 集成,含正确转义)
102 119 
103-- `CPTF_ASYNC_REPORTS=true` (default) — off-thread generation120+```typescript
104-- `ReportFormat = 'all'` — 3 formats in parallel121+import type { ReportGenerator, ReportJobResult } from 'cptf/internal';
105-- `ReportJobQueue` concurrency default = CPU count122+import { escapeHtml } from 'cptf/internal';
106-- `globalMemoryTrend` capped at 10k samples (CompressMetrics)
107 123 
108-## Related Skills124+export class JUnitXmlReportGenerator implements ReportGenerator {
125+ type = 'junit-xml'; format = 'junit-xml'; fileExtension = 'xml'; scope = 'test';
126+ generate(data, outputPath) {
127+ const report = data as any;
128+ const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<testsuites>\n${
129+ report.results.map((r: any) => `<testsuite name="${escapeHtml(r.caseId)}" tests="1" failures="${r.success ? 0 : 1}" time="${r.duration / 1000}"><testcase name="${escapeHtml(r.caseName)}" classname="${escapeHtml(r.module)}"/>${r.success ? '' : `<failure>${escapeHtml(r.errors.join('; '))}</failure>`}</testsuite>`).join('\n')
130+ }\n</testsuites>`;
131+ return { success: true, outputPath, durationMs: 0, sizeBytes: Buffer.byteLength(xml), jobId: '' };
132+ }
133+}
134+```
135+注意:XML 属性值同样必须 `escapeHtml`(`"` → `&quot;`)。
109 136 
110-- `cptf-cli` — CLI invocation137+## 8. 反模式黑名单
111-- `cptf-test-api` — Test config138+ 
112-- `cptf-arch` — Architecture139+| 反模式 | 后果 | 正解 |
140+|--------|------|------|
141+| `${caseName}` 裸插值 HTML | `<`/`"` 破结构、XSS | `escapeHtml(caseName)` |
142+| `path.basename(screenshot)` push 前 | 截图 404 不显示 | `path.relative(timestampDir, s)` |
143+| totalExpects 取 unifiedMetrics、passedExpects 取 qr | 417/364 通过>总数 | 全取 unifiedMetrics |
144+| `e.details.includes('text')` | "textColor" 误算富断言 | 词边界正则 |
145+| `d.name.includes('组合覆盖')` 查维度 | 改名即失效 | `d.key === 'combinationCoverage'` |
146+| 三平台 `redistributeWeights` 等比缩放 | 权重偏离 spec §9.2 | Harmony/Android 0.15、iOS 0.10 |
147+| `buildTrendChartsHtml` 调 3 次 | SVG 算 3 遍 | 顶部缓存局部变量 |
148+| 趋势表全量渲染 | 数千行 DOM | `sampleData(data,200)` |
149+| 对比报告全量字符串拼接 | 峰值内存 +10~20MB | 默认 `generateHtmlStreaming` |
150+| 设备 map 循环复制 3 份 | 维护负担 | 单 `buildDeviceMaps` helper |
151+| 内联 SVG ~465 行塞 html-generator | God class | 抽 `chart-renderer.ts` 纯函数 |
152+| 过滤计数器数元素非用例 | 218/218 误显 | 只数 `.detail-card` |
153+| 内联硬编码深色魔法值 | 主题不可控 | 全走 theme 变量 |
154+ 
155+## 相关 Skill
156+ 
157+- `cptf-cli` — CLI 调用
158+- `cptf-test-api` — 用例/Step 配置
159+- `cptf-arch` — 五层架构
160+- `cptf-refactor-rules` — God class 拆分 / mixin / 模板-IO 分离
161+- `cptf-optimization-verification` — 静态分析误报识别
@@ -162,8 +162,6 @@ export interface TestConfig {
162 scene?: string;162 scene?: string;
163 category?: string;163 category?: string;
164 sections?: string[];164 sections?: string[];
165- scrollTarget?: string;
166- scrollTargetRequired?: boolean;
167 165 
168 captureScreenshots?: boolean;166 captureScreenshots?: boolean;
169 waitAfterAction?: number;167 waitAfterAction?: number;
@@ -141,8 +141,286 @@ cptf scan -m stability # list cases without running
141cptf run -p harmony -t button-onpress --output ./debug-run141cptf run -p harmony -t button-onpress --output ./debug-run
142```142```
143 143 
144+## WSL2 + HarmonyOS 设备调试陷阱(实战总结)
145+ 
146+### USB/IP 透传方案已废弃
147+ 
148+**问题**: usbipd-win 将 USB 设备透传到 WSL2 在 ADB/HDC 场景下极不稳定:
149+- 复合 USB 设备(如 Mate 60 Pro: ADB+MTP+HDC)attach 后 0.008 秒断连
150+- `adb restart-server` / `hdc kill` 触发 USB 重新枚举,破坏虚拟总线绑定
151+- Windows 宿主机 adb.exe/hdc.exe 抢占设备控制权
152+ 
153+**正确方案**: TCP 端口共享(Windows 原生管理 USB,WSL2 通过 TCP 连接)
154+```
155+WSL2 adb/hdc client → TCP 127.0.0.1:5037/8710 → Windows adb.exe/hdc.exe server → USB → 设备
156+```
157+ 
158+### Mirrored 网络模式特性
159+ 
160+| 特性 | NAT 模式 | Mirrored 模式 |
161+|------|----------|---------------|
162+| 127.0.0.1 | 独立子网 | **共享 loopback**(WSL ↔ Windows 互通) |
163+| 未监听端口 connect | 立即 ECONNREFUSED | **永久 hang**(不返回错误) |
164+| adb start-server | 正常 | **卡死**(先 connect 检查 → hang) |
165+| 代理 127.0.0.1:port | 正常 | **连接失败**(走 Windows 网络栈) |
166+ 
167+**修复 adb hang**: 用 `nc -z -w1` 检测端口是否监听,避免直接 connect。未监听时后台启动 `adb.exe -a -P 5037 nodaemon server`。
168+ 
169+**修复代理连接失败**: `no_proxy` 加入内网域名 `.openharmony.cn,.huawei.com,.huaweicloud.com`。
170+ 
171+### HDC 设备 Offline 排查
172+ 
173+| 原因 | 症状 | 修复 |
174+|------|------|------|
175+| 手机未授权 USB 调试 | `Offline`,序列号为 `0123456789ABCDEF` | 手机设置 → 开发者选项 → USB 调试 → 授权弹窗点「允许」 |
176+| usbipd bind 后 Windows adb 看不到 | bind 接管设备给 USB/IP | **unbind** 让 Windows adb 直连 |
177+| 系统固件 Beta 版 | `uitest start-daemon` 报 `Failed to get apiCaller` | Beta2 不暴露 testserver SA 5502 apiCaller,需正式版固件 |
178+ 
179+### Native Driver (ohosTest) 架构
180+ 
181+**核心原则**: `Driver.create()` 必须在 ohosTest 进程内调用,不在应用进程内。
182+ 
183+```
184+错误(应用进程内): EntryAbility → Driver.create() → 与 RNOH 渲染竞争 → 失败
185+正确(ohosTest 进程): aa test → ohosTest 进程 → Driver.create() → delegator.startAbility(EntryAbility) → 正常
186+```
187+ 
188+**aa test 命令格式**:
189+```bash
190+aa test -b <bundleName> -m entry_test -s unittest OpenHarmonyTestRunner -s timeout 600000 -w 600
191+```
192+ 
193+**WebSocket 连接方案**: `hdc rport tcp:18081 tcp:18081`(反向端口转发),设备端 WS client → `ws://127.0.0.1:18081` → USB → 宿主机 WS Server。
194+ 
195+**HAR 包依赖**: ArkTS 源码不能通过 `file:` 目录引用(hvigor OHM 解析器报 `Cannot import files outside of the current module`),必须打包为 `.har` 文件引用。
196+ 
197+### ArkTS 严格模式常见错误
198+ 
199+| 错误 | 原因 | 修复 |
200+|------|------|------|
201+| `arkts-no-any-unknown` | 用了 `any`/`unknown` 类型 | 改为显式类型 `number`/`string`/`Record<string, Object>` |
202+| `arkts-no-untyped-obj-literals` | `{ key: value }` 无类型 | 先声明 `const r: Record<string, Object> = {}` 再 `r['key'] = value` |
203+| `arkts-no-decl-merging` | 同名 interface 定义两次 | 删除重复定义 |
204+| `arkts-limited-throw` | `throw err`(err 是 unknown) | `throw new Error(msg)` |
205+| `Cannot find module '@ohos/hilog'` | ohosTest 用旧 import | 改为 `import { hilog } from '@kit.PerformanceAnalysisKit'` |
206+| `On.id()` 编译报错 | `On` 是 class 不是 namespace | 用 `new On()` 或 `On as Object as Record<string, Object>` 动态调用 |
207+| `hdc.exe` 在 systemd service 中启动失败 | systemd 无法管理 Windows .exe | 用 `dev-init.sh` fallback(nc 检测 + nohup 启动) |
208+ 
209+### 测试报告 screenCap 路径问题
210+ 
211+ohosTest 进程中 `Driver.screenCap()` 在某些固件上拒绝 `/data/local/tmp/` 路径。`dumpLayout` 写 `/data/local/tmp/` 正常但 `screenCap` 不行——这是 Driver API 自身限制,不是权限问题。
212+ 
213+### 构建优化要点
214+ 
215+| 问题 | 修复 |
216+|------|------|
217+| `tsconfig.build.json` 含 `sourceMap: true` | 改为 `false`,发布包不需要 source map |
218+| `tsconfig.build.json` 含 `declarationMap: true` | 改为 `false` |
219+| dist 包含 `__tests__/` | `exclude` 加 `"src/**/__tests__/**"` |
220+| 循环导入 `enhanced-error-handler ↔ error-patterns` | 共享类型定义移到被依赖方 |
221+| `exec(cmd, { timeout: 0 }, () => {})` | 设 `timeout: 600000` + 回调记录退出日志 |
222+| fire-and-forget `.catch()` | 改为 `await` + try/catch |
223+| `as string`/`as boolean` 强转 | 改用 `String()`/`Boolean()` 安全转换 |
224+| `Promise<any>` / `args: any[]` | 改为 `Promise<unknown>` / `args: unknown[]` |
225+ 
144## Related Skills226## Related Skills
145 227 
146- `cptf-test-api` — Test case writing228- `cptf-test-api` — Test case writing
147- `cptf-cli` — Running tests229- `cptf-cli` — Running tests
148- `cptf-troubleshoot` (this skill) — Debugging230- `cptf-troubleshoot` (this skill) — Debugging
231+ 
232+ 
233+## Architecture Changes (v0.3.0+)
234+ 
235+### DeviceBridge Layer Removed
236+ 
237+**What changed:**
238+- `DeviceBridge` / `NativeDeviceBridge` / `ExternalDeviceBridge` / `DeviceBridgeFactory` → DELETED
239+- `UIStrategy` / `ExternalUIStrategy` / `NativeUIStrategy` → DELETED
240+- `UIAutomation` now delegates directly to `ISystem` (no intermediate layer)
241+ 
242+**New architecture:**
243+```
244+用例 → UIAutomation → ISystem (clickByTestId etc.)
245+ ├── native: HarmonyNativeDriver.clickComponent() (1 WS)
246+ └── external: getElementByTestId → click(x,y) (shell)
247+```
248+ 
249+**If you see DeviceBridge references:**
250+DeviceBridge / NativeDeviceBridge / ExternalDeviceBridge / DeviceBridgeFactory 已于 v0.3.0 全部删除。`grep -rn "DeviceBridge\|getDeviceBridge\|deviceBridge" packages/framework/src/` 应返回 0 结果。如果发现新引用,说明引入了已删除的 API,需改为:
251+- 旧的 `bridge.findElement()` → `system.getElementByTestId()` 或 `UIAutomation.findByTestId()`
252+- 旧的 `bridge.click(x, y)` → `system.click(x, y)` 或 `UIAutomation.click(x, y)`
253+- 旧的 `bridge.dumpLayout()` → `system.getUIElements()` 或 `UIAutomation.getLayout()`
254+ 
255+### 已完成的清理(v0.3.0)
256+ 
257+以下清理在 v0.3.0 已完成,无需再处理:
258+- ✅ `scroll-automator.ts`:DeviceBridge import + bridge 字段已移除
259+- ✅ `metric-collectors.ts`:DeviceBridge import + bridge params 已移除
260+- ✅ `test-scene-navigator.ts`:6x `getDeviceBridge()` 替换为 `system.switchCase()`
261+- ✅ `execution-engine.ts`:2x `getDeviceBridge()` 替换为 `system.switchCase()`
262+- ✅ `ui-automation.ts`:`getDeviceBridge(): any` compat 方法已删除
263+ 
264+### Native Driver Action List (NativeTestDriver.ets)
265+ 
266+25+ actions exposed over WebSocket:
267+- Coordinate: click, doubleClick, longClick, swipe, drag
268+- Component-direct: clickComponent, inputTextComponent, clearTextComponent, scrollToTop, scrollToBottom
269+- Native: waitForComponent, assertComponentExist, findComponents, getComponentState
270+- Gesture: dragTo, pinchOut, pinchIn, scrollSearch
271+- Display: getDisplaySize, getDisplayRotation, setDisplayRotation
272+- Keys: triggerKey, pressBack, pressHome, wakeUpDisplay
273+- Layout: dumpLayout, screenCap, findComponent
274+- Utility: delayMs, waitForIdle, ping
275+ 
276+---
277+ 
278+## 环境与长跑踩坑(2026-08 全量验证实测)
279+ 
280+### Gradle 外部 srcDir 不重编(Android native 静默旧代码)
281+ 
282+`app/build.gradle` 的 `sourceSets.androidTest.java.srcDirs += ['../../../cptf/packages/native-android/...']` 指向 workspace 外部目录时,**改 Java 文件后 `assembleAndroidTest` 报 BUILD SUCCESSFUL in 1-2s 但 dex 未更新**(up-to-date 检查对跨工程外部路径失效)。表象:改了 DeviceDriver.java 行为不变 / apk 里 strings 查不到新方法名。
283+**验证方法**:`unzip -p apk classes*.dex | strings | grep <新方法名>`(注意多 dex,classes.dex 可能不含目标类)。
284+**修复**:改完后跑 `./gradlew :app:cleanPackageDebugAndroidTest :app:assembleAndroidTest`,并用 strings 验证新符号真的进了 dex。**凡是"改了设备端 Java 但日志行为没变",先怀疑这个**。
285+ 
286+### adb hang / 设备掉线的恢复序列
287+ 
288+USB 长跑中 adb server 会卡死(`adb devices` 超时 exit=124,5037 无监听)。恢复顺序:
289+1. `timeout 12 adb devices` 确认(**永远带 timeout**,裸跑会挂死整个 shell)
290+2. 卡死时 `pkill -9 -f adb` + 等设备物理重连(USB 掉线无软件解法)
291+3. 反复 `adb kill-server && adb start-server` 无效且可能加剧卡死——server hang 时优先 pkill
292+4. 与 HarmonyOS 设备并行测试时,**adb/hdc/18081-18083 端口会被 harmony 进程挤占**,android native connect timeout 前先 `ss -tlnp | grep 1808` 检查是谁占的
293+ 
294+### 单进程全量 functional 必 OOM(16G 主机)
295+ 
296+815 用例单进程跑 ~40min 后 node(--max-old-space-size=16384)被内核 OOM kill,表象:**exit=0 但 log 戛然而止、无总通过/失败汇总、case-results 缺失**(pnpm 把 SIGKILL 吞成 0)。
297+**修复模式:分批跑**——每批 ~15 用例独立进程(`pnpm test -t id1 id2 ...`),批间 sleep 让内存归零。116 采样用例 8 批全程零中断。**"exit=0 但没有汇总行" = 进程被杀,不要当成功**。
298+ 
299+### bash 工具跑长任务的三档后台方案(实测可靠度递增)
300+ 
301+| 方案 | 结果 | 结论 |
302+|---|---|---|
303+| `命令 &` / `nohup ... &` | bash 会话结束时被杀 | 不可用 |
304+| `screen -dmS` | 偶尔存活,会被会话清理连带杀 | 不稳定 |
305+| **`nohup setsid bash -c '...' < /dev/null > /dev/null 2>&1 & disown`** | 全程存活 | ✅ 唯一可靠 |
306+ 
307+前台直接跑受 shell 工具 timeout 限制(超时即 SIGKILL,"Killed" 进 log)。**长任务一律 setsid 后台 + 轮询 log 文件**。轮询命令本身也别裸跑 adb。
308+ 
309+### native 端口漂移(connect 失败 → 18081→18082→18083)
310+ 
311+PortManager 是**进程内单例**:connect 失败不 release 时,Map 记录泄漏,同进程重试拿到 18082,但 18082 的 adb reverse 未建立 → instrumentation 永远连不上(表象 `connect timeout on port 18082`)。
312+**规则**:任何 `pm.allocate()` 的失败路径必须 `pm.release(port)` + 关 WS server + 清 `_actualPort`(android-native-driver / harmony-native-driver 的 connect catch 已修,新平台 driver 复制该模式)。
313+ 
314+### watchdog 低估导致 5 迭代用例必然超时
315+ 
316+旧公式 `estimatedMs = executeTimes × max(cooldownMs, 1500) + 30s`:cooldown=2000 → 5×2+30=40s 估算,实际单迭代 ~28s → 真实 ~170s > watchdog 120s → **稳定在第 3-4 迭代被杀**。
317+新公式(case-parser.ts):解析用例源码里的 `this.ui?.sleep(N)` 累加为 stepSleepMs,`perIterTotal = max(stepSleepMs + cooldown, 15s)`,`(executeTimes) × perIterTotal + 30s`。**新增性能用例若仍超时,先查估算是否覆盖:步骤 sleep + 导航 + 指标等待 + warmup**。
318+ 
319+### metric 事件总线协议(D3 修复后的接线全景)
320+ 
321+```
322+设备端推送 ─┬─ MetricWebSocketServer(external) ── test-runner 桥接 ──┐
323+ └─ nativeDriver WS(native) ── subscribeNativeMetricStream ─┤
324+ ▼
325+ 全局 bus(base/utils/metric-event-bus.ts,
326+ 复用 benchmark 的 global.__metricEventBus)
327+ ▼
328+ RenderTimeCollector.subscribe(`renderTime:${deviceId}`)
329+```
330+**三路分流规则**(performance-collector.subscribeNativeMetricStream):`renderTime/unmountTime` → emitMetricEvent(bus);`type:'metric'`(设备端批量 fps/pss/cpu 格式)→ **只进 latestMetric 缓存,不进 metricsHistory**(否则全 0 脏数据污染 avg);`fps/memory/cpu/marker`(MetricEvent 格式)→ handleMetricEvent 进 history。deviceId 缺省由 native driver 用 config.deviceId 补齐(bus key 必须匹配)。
331+ 
332+### forceRefresh 死参数(Strategy 拆分类重构的典型回归)
333+ 
334+重构把单类拆成 Native/External 子类时,基类缓存守卫代码被复制进两个子类,`if (!forceRefresh) return cached;` 后面**多带了一行 `return cached;`**(两分支等价)或穿透分支末尾保留 `return cached`——forceRefresh 从此永不生效。
335+**表象**:`waitForTestId`/`waitForElementDisappear` 轮询(R6/R7 要求 forceRefresh=true)在 cache TTL 窗口内全部读到陈旧布局 → "元素已出现但轮询超时"类 flake,且 native/external 双模式都中。
336+**守则**:① 复制缓存守卫到子类后,必须写一个"forceRefresh 必须穿透"的单测(mock fetch 计数,断言连续两次 getUIElements(true) 触发两次 fetch);② code review 缓存相关 diff 时专门盯"两个 return cached 相邻"这一模式。
337+ 
338+### static Map 类单例的测试并发 flake(跨文件污染)
339+ 
340+`MetricCollectorFactory.customCollectors` 这类 **static Map 在同一 jest worker 内跨测试文件共享**。测试里 `register('fps', ...)` 后不清理:若后续断言先抛(unregister 行没执行)或该文件先跑完,同 worker 的其他文件(execution-engine-cleanup、extra-metrics)读到污染 factory → **间歇失败**(取决于 jest 文件调度顺序,单跑必过、全量偶发,5-10 连跑都可能复现不了)。
341+**守则**:① 测试内 register 了全局 static 注册表(MetricCollectorFactory/ReportGeneratorRegistry/PLATFORM_REGISTRY/SystemFactory)→ describe 级 `afterEach(() => Xxx.clearCustom/unregister)`;② 这类 flake 的判定法:失败用例**单跑通过** + 全量偶发 + 涉及共享注册表 = 并发污染,不要怀疑业务代码。
342+ 
343+### 吸收外部 PR 到重构后代码的移植方法(PR#7 实战)
344+ 
345+直接 `git merge`/`cherry-pick` 一个基于**旧类结构**的 PR 到已重构分支(如 Strategy 拆分后),冲突解出的补丁极易放错类(如把 External 路径补丁放进 Native 子类)。正确流程:
346+1. `git fetch <remote> +refs/merge-requests/<id>/head:pr_N` 取 PR head
347+2. `git show <commit> -- <file>` 逐 commit 提取**语义补丁**(而非文本补丁)
348+3. 按"PR 改的是哪条执行路径"定位重构后的落点:external 路径补丁 → External 子类;解析类补丁 → 基类(protected,两子类共用);类型补丁 → base/types
349+4. 跳过与本地已等价实现的重复项(对照实现逐行比对,如 knownIssue 批次隔离)
350+5. 吸收后全量验证(typecheck/tests×连跑/verify/build)+ 重读 PR 注释里的实测数据一起移植(如 KEYCODE 映射的设备验证注释)
351+**评估 PR 本身**:先验证声明是否成立(对照本地代码找原始 bug 位置),不采信 PR 描述——PR#7 的 forceRefresh 声明"低估了严重性"(本地是加强版死参数),也有 PR 声称修复但本地已有等价实现(跳过)。
352+ 
353+## Android 全量验证踩坑(2026-08-22 external/native 双模式实测)
354+ 
355+### ATX agent 死后永不自愈(936 次连续降级)
356+ 
357+**根因链**(三层叠加,缺一不可):
358+1. 一次瞬时 adb 隧道超时 → layout fallback **无条件 kill 健康的 ATX agent**(防 UiAutomation 冲突的过度防御)
359+2. `HealthMonitor.recordAttempt()` 在**失败请求**前也刷新 `lastActivityAt` → `isStale()` 永假 → `ensureFreshBeforeSend` 永不 ping → 永不触发重连
360+3. 死 agent 后每次 dumpHierarchy 都走 shell uiautomator fallback(+3~8s),且 fallback 每次还再 kill 一次(已是尸体)
361+ 
362+**修复**(三层防护):`recordAttempt()` 只记 `lastAttemptAt` 不动 activity;U2 client 连续 3 次请求失败 → 主动 `reconnect()`(先 probeExistingService 复用存活 agent);fallback kill 前查 `!u2.reconnecting` guard + 瞬时错误先重试 1 次(agent 存活则返回缓存布局)。
363+**教训**:健康监测的"活跃时间"只能由**成功**刷新;降级路径破坏资源前必须探测资源是否真的死了。
364+ 
365+### u2 dumpHierarchy compressed 剔除 hint(placeholder 断言全灭)
366+ 
367+`dumpWindowHierarchy(compressed=true)` 的 XML **没有 hint 属性**(实测同一界面 compressed 0 个 / uncompressed 49 个)。placeholder/accessibilityHint 断言依赖 `dumpHierarchy(compressed=false)`。native 侧同理:`UiDevice.dumpWindowHierarchy` 是压缩 XML——自实现 AccessibilityNodeInfo 树序列化(含 `getHintText()`,API26+)。
368+ 
369+### 华为 ROM UiAutomation 手势注入恒定 5s/事件(ALN-AL00 Android 12)
370+ 
371+`GestureController: Gestures took longer than expected (10833ms)` / 每次 `injectInputEvent` 5s(async 模式同样阻塞)。ANR log 铁证:`Waited 5001ms for MotionEvent(UP)`。主线程栈卡在 `Choreographer.doFrame → AnimationUtils.unlockAnimationClock`(ROM 动画时钟锁与注入路径竞争)。
372+**修复**:DeviceDriver 全部手势走 `Runtime.exec("input tap|swipe ...")`(shell input 命令 74ms,绕过 UiAutomation 注入路径)。**禁止** `uiDevice.click/swipe`、`UiObject2.click/scroll`、`injectInputEvent`(含 runOnMainSync 包装——主线程等注入返回、注入等主线程消费事件,直接死锁)。
373+**诊断手法**:分段计时埋点(find/bounds/tap 各多少 ms)→ 锁定 tap → 每事件计时(down/up 各 5s)→ ANR logcat 交叉定位 InputDispatcher 等待。**"恒定整数秒"耗时 = 系统/ROM 层超时,不是业务代码慢**。
374+ 
375+### instrumentation 冷启动 dexopt 首次 >30s
376+ 
377+首轮 am instrument 触发 dexopt/AOT 校验可超 30s——连接超时必须 ≥60s,否则首轮全超时、二轮正常(假象不稳定)。
378+ 
379+### Native 模式 ATX 必须彻底跳过
380+ 
381+`AndroidSystem._doInitU2()` 在 `driverMode==='native'` 时直接 return(不 connect、不 spawn)。UiAutomation 全系统单例:ATX 注册失败后的残留 app_process 会干扰 DeviceDriver 的 input 注入。之前"启动失败也无妨"是错觉。
382+ 
383+### 多窗口 dump 爆 WS maxPayload(linking-opensettings)
384+ 
385+点击"打开系统设置"后系统 Settings 窗口(巨型 a11y 树)进入 `getWindows()` 遍历 → 单帧 dump >256MB 上限 → WS 断连。**修复**:dump 按 `getTargetContext().getPackageName()` 过滤窗口 + 序列化 16MB 硬上限。**教训**:遍历系统全窗口的 API(getWindows/getRootInActiveWindow)必须按包名过滤。
386+ 
387+### a11y 树容器合并(历史案例:49 个用例连坐失败,scrollTarget 已废弃)
388+ 
389+Android a11y 树**合并容器节点**:`View`/`ScrollView` 等无文本容器的 testID 永不出现在 dump——断言与点击只能指向**真实渲染的叶子元素**(详见 app 侧 rn-test-case skill R54)。历史根因:scrollTarget 曾指向 section 容器 → scrollSearch 永远找不到 → `场景导航失败` → 每例烧 100s。scrollTarget 字段已从框架删除,元素就绪等待由 expect 层承担(`findElementForExpect`)。
390+**注意 zsh 陷阱**:`pnpm test -- -t $IDS`(空格分隔字符串)在 zsh 不分词——必须 `${=IDS}` 或命令替换。被吞成单参时框架报"未发现测试用例",极易误判为筛选器坏了。
391+ 
392+### CLI 堆内存 re-exec 吞退出码(全量跑双倍耗时)
393+ 
394+`src/cli/index.ts` 的 heap-boost re-exec:`execFileSync` 在子进程 exit≠0 时**抛异常**,被外层 `catch {}` 吞掉 → fall-through 到 `program.parse()` → **父进程把整套用例重跑一遍**。任何有失败用例的 run 全部双倍耗时(4234s 的 run 实际重复了两遍)。**修复**:catch 里 `process.exit(e.status ?? 1)` 传播退出码。**表象识别**:log 里出现两次"CPTF 测试执行"头 + 两份相同报告目录。
395+ 
396+## Harmony native "元素不存在" 诊断方法论(2026-08-24 实战)
397+ 
398+### 症状特征
399+ 
400+native 模式下 expect 全量报 `元素不存在`(每个恰好 5s = expect 层 waitForTestId 超时),但:
401+- 场景容器检查通过(`Native nav: scene ready`)
402+- `期望 visible=false` 的用例"通过"(元素本该存在却找不到)
403+ 
404+### 分层定位法(从快到慢)
405+ 
406+1. **区分 findComponent / dumpLayout 两条链**:
407+ - `findComponent`(scene 检查走的)正常 + expect 失败 → 查 UIAutomation 构造时序(findStrategy 锁定问题)
408+ - 探针直调 `findComponent` 正常 + `dumpLayout` 返回 `{"layout":""}` → 设备端 dump API 问题
409+2. **UI 导出数据佐证**:报告 `baseline/ui-*.json` 显示 `(0个元素)` → dumpLayout 链路断
410+3. **探针直连设备 WS**(绕过框架,一锤定音):手写 TCP+WS 握手(server→client 帧**必须不 mask**),依次调 `navigate`/`findComponent`/`dumpLayout`/`screenCap` 对比
411+4. **SDK 权威验证**:`/path/sdk/default/openharmony/ets/api/@ohos.UiTest.d.ts` 查真实签名——**不要凭记忆写 API**(dumpLayout 实际是 `(savePath, displayId?): Promise<boolean>`,不是无参返回 JSON)
412+ 
413+### 已实锤的三类根因(勿重复排查)
414+ 
415+| 根因 | 判别特征 | 修复 |
416+|------|---------|------|
417+| UIAutomation 构造时序(findStrategy 锁定 External) | 探针 findComponent 正常但 expect 全挂 | system 层换方案(findByTestId 直通 system.getElementByTestId → NativeLayoutProvider facade) |
418+| `driver.dumpLayout()` 无参调用参数错误 | 探针 dumpLayout 返回空 + hilog 无报错(catch 静默) | 传沙箱 savePath + fileIo.readTextSync 读回 |
419+| `driver.screenCap(path)` 沙箱路径限制 | `Native screenCap失败: Invalid file path:/data/local/tmp/...` | sandboxPathFor 转 tempDir + publicPathFor 转物理路径返回 host |
420+ 
421+### 探针脚本要点(自建 WS server 连 ohosTest)
422+ 
423+- `hdc rport tcp:PORT tcp:PORT` + `aa test ... -s wsPort PORT -s wsHost 127.0.0.1`
424+- WS 握手:`Sec-WebSocket-Accept = base64(sha1(key + GUID))`
425+- **server→client 帧禁止 mask**(client→server 必须 mask)——mask 位写反则命令全部无响应(timeout)
426+- 帧解码注意握手字节残留(首次 data 含 HTTP 头,必须先剥离 `\r\n\r\n` 再解帧)
MAGENTS.md+25-7
@@ -6,25 +6,30 @@
6>6>
7> **核心准则**:[CORE-PRINCIPLES.md](CORE-PRINCIPLES.md) — 准确性(第一原则)、稳定性、执行性能。所有开发必须遵守。7> **核心准则**:[CORE-PRINCIPLES.md](CORE-PRINCIPLES.md) — 准确性(第一原则)、稳定性、执行性能。所有开发必须遵守。
8>8>
9-> **未来架构**:[DEVICE-NATIVE-ARCHITECTURE.md](packages/framework/docs/architecture/DEVICE-NATIVE-ARCHITECTURE.md) — 参照 Detox 模式的设备端原生执行架构方案。9+> **当前架构**:[DEVICE-NATIVE-ARCHITECTURE.md](packages/framework/docs/architecture/DEVICE-NATIVE-ARCHITECTURE.md) — 双模式(native/external)架构现状 + 4 级降级策略。
10 10 
11## 关键约束11## 关键约束
12 12 
13- **License**:MIT / gaoyang(gyange@163.com)。所有新增文件必须添加 License 头。13- **License**:MIT / gaoyang(gyange@163.com)。所有新增文件必须添加 License 头。
14-- **架构**:3层分离(基础设施 → 系统 → 调度),禁止反向依赖。详见 [ARCHITECTURE-VIEWS.md](packages/framework/docs/architecture/ARCHITECTURE-VIEWS.md) 和 [ARCHITECTURE.md](packages/framework/docs/architecture/ARCHITECTURE.md)。14+- **架构**:五层分离(编排 orchestration → 能力 capability → 系统 system → 基础设施 infrastructure → 基础 base,src/ 目录即五层;对外概览常将 base+infrastructure 合称基础设施、capability+orchestration 合称调度,共 3 组),禁止反向依赖。详见 [ARCHITECTURE-VIEWS.md](packages/framework/docs/architecture/ARCHITECTURE-VIEWS.md) 和 [ARCHITECTURE.md](packages/framework/docs/architecture/ARCHITECTURE.md)。
15- **接口分离**:`ISystem` 拆 7 子接口(Core/UI/App/Perf/Stability/Media/Session),新增平台实现 1 个 class 即可。15- **接口分离**:`ISystem` 拆 7 子接口(Core/UI/App/Perf/Stability/Media/Session),新增平台实现 1 个 class 即可。
16- **包依赖**:`app` 通过 workspace 协议依赖 `@cptf/framework`(pnpm monorepo),改 `packages/framework` 后自动同步。16- **包依赖**:`app` 通过 workspace 协议依赖 `@cptf/framework`(pnpm monorepo),改 `packages/framework` 后自动同步。
17- **包组织(@cptf npm 组织)**:所有包归 `@cptf` 域下,pnpm monorepo(双层 workspace)。`app` 经 `workspace:*` 依赖 `@cptf/framework` 等,改框架包后自动同步。详见下方「工程结构与构建打包」。17- **包组织(@cptf npm 组织)**:所有包归 `@cptf` 域下,pnpm monorepo(双层 workspace)。`app` 经 `workspace:*` 依赖 `@cptf/framework` 等,改框架包后自动同步。详见下方「工程结构与构建打包」。
18+- **指标口径(单一真相)**:**所有测试报告(HTML / Markdown / Comparison JSON)的指标计算必须遵守 [METRICS-SPEC.md](packages/framework/docs/METRICS-SPEC.md)**。三态互斥用例模型(Unsupported / Passed / Failed)、通过率 = passedCases / executedCases、崩溃数独立统计、性能指标未启用时隐藏。任何新增指标展示必须走 `computeUnifiedMetrics()`。
19+- **用例字段语义(单一真相)**:**用例声明与运行时字段的语义必须遵守 [TEST-CASE-FIELDS-SPEC.md](packages/framework/docs/TEST-CASE-FIELDS-SPEC.md)**。`automationStatus`/`automationNote`/`skipped`/`success`/`knownIssue`/`verificationScope` 等字段的定义、传播规则、状态矩阵。
20+- **并发安全**:全局状态(`NavigationCoordinator` 等静态 Map)**不需要加锁**,依赖 Node.js 单线程原子性 + 设备级 Map 键隔离 + 默认串行执行。详见 [`CONCURRENCY-SAFETY.md`](packages/framework/docs/architecture/CONCURRENCY-SAFETY.md) 和 `cptf-arch` skill。
18- **编码规范**:禁止注释 / 禁止 emoji / 简洁输出 / 遵循现有文件风格。21- **编码规范**:禁止注释 / 禁止 emoji / 简洁输出 / 遵循现有文件风格。
19- **禁止动作**:未经用户明确允许,不修改 git config / 不 force push / 不 commit / 不做 proactive actions。22- **禁止动作**:未经用户明确允许,不修改 git config / 不 force push / 不 commit / 不做 proactive actions。
20 23 
21-## 3层职责速查24+## 五层职责速查(src/ 目录即五层)
22 25 
23| 层 | 职责 |26| 层 | 职责 |
24|---|---|27|---|---|
25-| 调度层 | ExecutionEngine / TestRunner / ComparisonRunner / UI自动化 / 指标采集 / 报告生成 / 导航管理 / @Test@Step |28+| 编排层 orchestration | TestRunner / ComparisonRunner / ExecutionEngine / ParallelTestRunner / 报告生成 / @Test@Step |
26-| 系统层 | ISystem 7子接口 / HarmonySystem(UITest) / AndroidSystem(ATX) / IosSystem(WDA) / 统一降级策略 |29+| 能力层 capability | UIAutomation / ExpectExecutor / PerformanceCollector / VisualComparator / UIComparator |
27-| 基础设施层 | 设备管理 / 端口管理(MetricPortPool) / MetricWS Server / 配置加载 / 用例扫描 / Logger / CacheManager / SmartWaiter / 类型定义 |30+| 系统层 system | ISystem 7子接口 / HarmonySystem(UITest+NativeDriver) / AndroidSystem(ATX+NativeDriver) / IosSystem(WDA) / SystemFactory |
31+| 基础设施层 infrastructure | 配置加载 / 用例扫描(lazy-scanner) / 设备发现 / 索引生成 |
32+| 基础层 base | 类型定义 / Logger / CacheManager / SmartWaiter / PortManager / JobQueue / MetricWS Server / Helpers |
28 33 
29## CLI 速查34## CLI 速查
30 35 
@@ -45,6 +50,9 @@ cptf compare -t button-onpress # 按用例
45# 筛选 / 调试50# 筛选 / 调试
46cptf run -m performance # 性能模块51cptf run -m performance # 性能模块
47cptf run -t button-onpress # 指定用例52cptf run -t button-onpress # 指定用例
53+cptf run --driver native # 灰盒模式(设备端原生执行,需测试 HAP)
54+cptf run --driver external # 黑盒模式(外部 Agent,默认)
55+cptf run --driver auto # 自动模式(优先 native,降级 external)
48CPTF_LOG_LEVEL=debug cptf run # 调试日志56CPTF_LOG_LEVEL=debug cptf run # 调试日志
49cptf scan # 扫描用例57cptf scan # 扫描用例
50cptf index # 生成测试索引58cptf index # 生成测试索引
@@ -76,9 +84,13 @@ pnpm test -- -m <mod> # 单模块
76 84 
77| 包(npm name) | 目录 | 形态 | 依赖 |85| 包(npm name) | 目录 | 形态 | 依赖 |
78|---|---|---|---|86|---|---|---|---|
79-| `@cptf/framework` | `packages/framework` | built dist(`main: dist/index.js`),bin `cptf` | 核心,无 workspace 依赖 |87+| `@cptf/framework` | `packages/framework` | built dist(`main: dist/index.js`),bin `cptf` | 核心,`@cptf/schema` |
88+| `@cptf/schema` | `packages/schema` | built dist(跨宿主/设备共享类型) | 零依赖 |
80| `@cptf/benchmark` | `packages/benchmark` | built dist(host 侧 collector) | `@cptf/framework` |89| `@cptf/benchmark` | `packages/benchmark` | built dist(host 侧 collector) | `@cptf/framework` |
81| `@cptf/benchmark-rn` | `packages/benchmark-rn` | src(设备侧 JS + android kotlin + harmony cpp,metro 直接消费,无 dist) | peer `react-native` |90| `@cptf/benchmark-rn` | `packages/benchmark-rn` | src(设备侧 JS + android kotlin + harmony cpp,metro 直接消费,无 dist) | peer `react-native` |
91+| `@cptf/native-harmony` | `packages/native-harmony` | harmony(设备侧 HAR,纯 ArkTS 实现 ohosTest Driver + NativeTestDriver.ets,无 C++) | `@cptf/framework`, `@cptf/schema` |
92+| `@cptf/plugin-rn` | `packages/plugin-rn` | src(RN AppProbe 实现,预留能力,metro 直接消费,无 dist) | `@cptf/native-harmony`, peer `react-native` |
93+| (非 npm 包)`native-android` | `packages/native-android` | android(DeviceDriver.java + WebSocketClient.java,Android native 驱动设备端,经 `app/android/app/build.gradle` java.srcDirs 直接消费,无 package.json) | — |
82 94 
83```bash95```bash
84# 构建(在 cptf/ 根)96# 构建(在 cptf/ 根)
@@ -108,6 +120,10 @@ pnpm build:release # 全量:codegen + bundle + 三平台并行 native
108## 路由指引(用哪个 skill)120## 路由指引(用哪个 skill)
109 121 
110- **改 packages/framework 代码** → `cptf-framework-dev` / `cptf-arch` / `cptf-system-extension`122- **改 packages/framework 代码** → `cptf-framework-dev` / `cptf-arch` / `cptf-system-extension`
123+- **Harmony/Android native driver(HarmonyNativeDriver / AndroidNativeDriver / NativeTestDriver.ets / DeviceDriver.java / ohosTest / am instrument)** → `cptf-native-driver`
124+- **资源管理规范(Timer / Worker / 子进程 / Stream / Socket / Map 清理)** → `cptf-resource-management`
125+- **CPTF 测试规则(testID / @Step 格式 / 场景导航 / 设备稳定性)** → `cptf-rules`
126+- **UITest 连接稳定性设计(TCP 重连 / 健康检查 / 布局提供者)** → `cptf-stability-design`
111- **写测试用例 @Test/@Step** → `cptf-test-api` / `cptf-ui-locator`127- **写测试用例 @Test/@Step** → `cptf-test-api` / `cptf-ui-locator`
112- **跑 CLI** → `cptf-cli`128- **跑 CLI** → `cptf-cli`
113- **iOS 接入** → `cptf-ios`129- **iOS 接入** → `cptf-ios`
@@ -118,6 +134,8 @@ pnpm build:release # 全量:codegen + bundle + 三平台并行 native
118- **RN 应用** → `cptf-rnoh-app`(根目录 `.opencode/skills/`)134- **RN 应用** → `cptf-rnoh-app`(根目录 `.opencode/skills/`)
119- **调试 / 排错** → `cptf-troubleshoot`135- **调试 / 排错** → `cptf-troubleshoot`
120- **自定义指标** → 从 `cptf-framework-dev` 进入(暂无独立 skill)136- **自定义指标** → 从 `cptf-framework-dev` 进入(暂无独立 skill)
137+- **并发安全 / 状态管理** → `cptf-arch` skill 和 [CONCURRENCY-SAFETY.md](packages/framework/docs/architecture/CONCURRENCY-SAFETY.md)
138+- **优化建议评估** → `cptf-optimization-verification`(静态分析误报识别、验证优先工作流、成本收益决策框架)
121 139 
122## 变更日志140## 变更日志
123 141 
@@ -27,7 +27,7 @@
27多设备场景下,每个设备的端口转发必须指向正确的主机端口。27多设备场景下,每个设备的端口转发必须指向正确的主机端口。
28 28 
29- **rport 不支持覆盖**:`hdc rport` 在端口已占用时直接失败。设置新 rport 前必须先 `fport rm` 清理旧的。29- **rport 不支持覆盖**:`hdc rport` 在端口已占用时直接失败。设置新 rport 前必须先 `fport rm` 清理旧的。
30-- **使用 actualPort**:当首选端口 18081 被占用,`MetricPortPool` 分配 18091/18092 等。rport/reverse 的 host 侧必须用 `actualPort`,不能用硬编码的 18081。30+- **使用 actualPort**:当首选端口 18081 被占用,`PortManager`(`base/utils/port-pool.ts`)分配 18091/18092 等。rport/reverse 的 host 侧必须用 `actualPort`,不能用硬编码的 18081。
31- **测试后清理**:`stopMetricWsServer` 必须移除 rport,避免遗留影响下次测试。31- **测试后清理**:`stopMetricWsServer` 必须移除 rport,避免遗留影响下次测试。
32 32 
33### 1.4 时钟33### 1.4 时钟
@@ -47,7 +47,7 @@
47 47 
48### 2.2 崩溃恢复48### 2.2 崩溃恢复
49 49 
50-- **恢复上限**:`MAX_CRASH_RECOVERIES = 2`。达到上限后跳过用例,不无限重试。50+- **恢复上限**:`MAX_CRASH_RECOVERIES = 5`(`test-scene-navigator.ts:14`)。达到上限后跳过用例,不无限重试。
51- **状态重置**:恢复后必须重置导航状态(当前页面、deep link),防止在错误页面上继续执行。51- **状态重置**:恢复后必须重置导航状态(当前页面、deep link),防止在错误页面上继续执行。
52- **降级而非阻塞**:UITest 连接断开时自动重连,重连失败则降级到 hdc shell,不阻塞测试流程。52- **降级而非阻塞**:UITest 连接断开时自动重连,重连失败则降级到 hdc shell,不阻塞测试流程。
53 53 
@@ -94,7 +94,7 @@ WebSocket 事件(主通道)
94 94 
95### 3.4 端口池管理95### 3.4 端口池管理
96 96 
97-- **按设备分配**:`MetricPortPool` 为每个设备分配独立端口,首选 18081,冲突时递增到 18091、18092。97+- **按设备分配**:`PortManager` 为每个设备分配独立端口,首选 18081,冲突时递增到 18091、18092。
98- **用后释放**:`releaseMetricServer` 释放端口,引用计数归零时停止服务器。98- **用后释放**:`releaseMetricServer` 释放端口,引用计数归零时停止服务器。
99- **不累积**:同设备的多个用例复用同一服务器(`refCount` 递增),不为每个用例创建新服务器。99- **不累积**:同设备的多个用例复用同一服务器(`refCount` 递增),不为每个用例创建新服务器。
100 100 
@@ -9,7 +9,7 @@
9| 特性 | CPTF | 其他框架 |9| 特性 | CPTF | 其他框架 |
10|------|------|---------|10|------|------|---------|
11| **HarmonyOS 支持** | ✅ 原生支持 | ❌ 不支持 |11| **HarmonyOS 支持** | ✅ 原生支持 | ❌ 不支持 |
12-| **性能指标采集** | ✅ 13种性能指标+3种稳定性指标(真实FPS) | ⚠️ 2-3种(模拟FPS) |12+| **性能指标采集** | ✅ 19种性能指标+3种稳定性指标(真实FPS) | ⚠️ 2-3种(模拟FPS) |
13| **稳定性测试** | ✅ SmartMonkey + ANR + 白屏检测 + 内存泄漏 + 崩溃恢复 | ❌ 需自行搭建 |13| **稳定性测试** | ✅ SmartMonkey + ANR + 白屏检测 + 内存泄漏 + 崩溃恢复 | ❌ 需自行搭建 |
14| **跨平台对比** | ✅ HarmonyOS vs Android | ⚠️ 需自行搭建 |14| **跨平台对比** | ✅ HarmonyOS vs Android | ⚠️ 需自行搭建 |
15| **用例编写** | ✅ 声明式(减少70-90%代码) | ⚠️ 命令式 |15| **用例编写** | ✅ 声明式(减少70-90%代码) | ⚠️ 命令式 |
@@ -17,16 +17,16 @@
17**核心亮点**:17**核心亮点**:
18- **唯一性**:业界唯一支持 HarmonyOS 自动化测试18- **唯一性**:业界唯一支持 HarmonyOS 自动化测试
19- **精准性**:真实 FPS 采集(RenderService fpsCount + hitchs),非模拟数据19- **精准性**:真实 FPS 采集(RenderService fpsCount + hitchs),非模拟数据
20-- **完整性**:13种性能指标 + 3种稳定性指标 + 完整稳定性测试套件(SmartMonkey/白屏/内存泄漏/ANR/崩溃恢复)20+- **完整性**:19种性能指标 + 3种稳定性指标 + 完整稳定性测试套件(SmartMonkey/白屏/内存泄漏/ANR/崩溃恢复)
21- **简洁性**:声明式框架 `@Test/@Step`,代码量减少 70-90%21- **简洁性**:声明式框架 `@Test/@Step`,代码量减少 70-90%
22-- **专业性**:3层架构设计,职责分离清晰,易于扩展22+- **专业性**:五层架构设计(src/ 目录即五层),职责分离清晰,易于扩展
23 23 
24## 业界最佳实践24## 业界最佳实践
25 25 
26**React Native for OpenHarmony (RNOH)** 基于 CPTF 框架完成全量功能、性能、稳定性测试,是目前业界最大的 HarmonyOS 跨端测试实践:26**React Native for OpenHarmony (RNOH)** 基于 CPTF 框架完成全量功能、性能、稳定性测试,是目前业界最大的 HarmonyOS 跨端测试实践:
27 27 
28- **功能测试**:覆盖 Switch、Button、RefreshControl 等核心组件的全量属性跨平台一致性验证28- **功能测试**:覆盖 Switch、Button、RefreshControl 等核心组件的全量属性跨平台一致性验证
29-- **性能测试**:13 种指标采集 + Warmup迭代 + 阈值告警 + HTML趋势图29+- **性能测试**:19 种指标采集 + Warmup迭代 + 阈值告警 + HTML趋势图
30- **稳定性测试**:SmartMonkey + ANR检测 + 白屏检测 + 内存泄漏 + 崩溃自动恢复30- **稳定性测试**:SmartMonkey + ANR检测 + 白屏检测 + 内存泄漏 + 崩溃自动恢复
31- **跨平台对比**:HarmonyOS vs Android UI结构4维对比(属性/样式/位置/层级)31- **跨平台对比**:HarmonyOS vs Android UI结构4维对比(属性/样式/位置/层级)
32 32 
@@ -45,7 +45,7 @@
45```45```
46cptf/46cptf/
47├── packages/framework/ # 框架层(npm 包 @cptf/framework)47├── packages/framework/ # 框架层(npm 包 @cptf/framework)
48-│ └── src/ # 3层架构源码48+│ └── src/ # 五层架构源码(base/infrastructure/system/capability/orchestration)
49│ ├── base/ # 基础设施层(类型/配置/工具/设备管理/端口管理)49│ ├── base/ # 基础设施层(类型/配置/工具/设备管理/端口管理)
50│ ├── infrastructure/ # 基础设施层(扫描/索引/设备发现)50│ ├── infrastructure/ # 基础设施层(扫描/索引/设备发现)
51│ ├── system/ # 系统层(ISystem接口+平台实现+Agent客户端)51│ ├── system/ # 系统层(ISystem接口+平台实现+Agent客户端)
@@ -130,7 +130,7 @@ cptf run -p harmony
130- CPU 使用率130- CPU 使用率
131- 内存占用(含泄漏检测:线性回归+R²置信度)131- 内存占用(含泄漏检测:线性回归+R²置信度)
132- 白屏检测(PNG+zlib解析,亮度>240+白像素比>85%)132- 白屏检测(PNG+zlib解析,亮度>240+白像素比>85%)
133-- 13种性能指标 + 3种稳定性指标 + HTML趋势图133+- 19种性能指标 + 3种稳定性指标 + HTML趋势图
134 134 
135### 稳定性测试135### 稳定性测试
136 136 
@@ -174,7 +174,7 @@ cptf template <module> -n <name> # 生成测试用例模板
174| 文档 | 说明 |174| 文档 | 说明 |
175|------|------|175|------|------|
176| [架构总览](packages/framework/docs/architecture/ARCHITECTURE.md) | 框架架构、源码目录、依赖规则 |176| [架构总览](packages/framework/docs/architecture/ARCHITECTURE.md) | 框架架构、源码目录、依赖规则 |
177-| [架构优势](packages/framework/docs/architecture/ARCHITECTURE-OVERVIEW.md) | 3层架构、三平台加速、性能优势 |177+| [架构优势](packages/framework/docs/architecture/ARCHITECTURE-OVERVIEW.md) | 五层架构、三平台加速、性能优势 |
178| [能力规格](packages/framework/docs/architecture/CAPABILITY-SPEC.md) | 性能指标、API 规格、数据结构 |178| [能力规格](packages/framework/docs/architecture/CAPABILITY-SPEC.md) | 性能指标、API 规格、数据结构 |
179| [用户指南](packages/framework/docs/getting-started/USER-GUIDE.md) | 安装、配置、CLI、API 使用 |179| [用户指南](packages/framework/docs/getting-started/USER-GUIDE.md) | 安装、配置、CLI、API 使用 |
180| [声明式测试指南](packages/framework/docs/getting-started/DECLARATIVE-TEST-GUIDE.md) | @Test/@Step 装饰器、TestConfig 详解 |180| [声明式测试指南](packages/framework/docs/getting-started/DECLARATIVE-TEST-GUIDE.md) | @Test/@Step 装饰器、TestConfig 详解 |
Rpackages/framework/src/system/harmony/port-manager.ts→packages/framework/src/base/utils/port-pool.ts+134-14
Rpackages/framework/src/capability/expect/index.ts→packages/native-harmony/harmony/nativeharmony/Index.ets+2-1
Rpackages/framework/src/capability/ui/index.ts→packages/plugin-rn/src/index.ts+4-2