Open
fix(init): include required deepLinkScheme in the generated config #1
liu_wentaoAdd on 27 days ago
fix(init): include required deepLinkScheme in the generated config #1
Open
liu_wentaoAdd on 27 days ago
Total: 36 files changed+3232-2158
@@ -92,6 +92,7 @@ app/harmony/entry/src/main/resources/rawfile/bundle.harmony.js
92 92 
93# Package tarballs93# Package tarballs
94packages/cptf/*.tgz94packages/cptf/*.tgz
95+packages/*/cptf-*.tgz
95 96 
96# Device-specific97# Device-specific
97device-info.json98device-info.json
@@ -11,24 +11,24 @@ Audit and fix the entire `docs/` directory — **not limited to a single file or
11 11 
12**Hard requirements**:12**Hard requirements**:
13 13 
14-- **Bilingual alignment**: When editing `zh-cn` or `en`, if a counterpart exists, both sides must be edited together. See [references/bilingual-sync.md](.quality-toolkit/skills/docs-quality-audit/references/bilingual-sync.md).14+- **Bilingual alignment**: When editing `zh-cn` or `en`, if a counterpart exists, both sides must be edited together. See [references/bilingual-sync.md](../../.quality-toolkit/skills/docs-quality-audit/references/bilingual-sync.md).
15- **Wording**: In `docs/` prose the product name must be written **React Native** (two words); **ReactNative** is forbidden.15- **Wording**: In `docs/` prose the product name must be written **React Native** (two words); **ReactNative** is forbidden.
16 16 
17## Audit Scope17## Audit Scope
18 18 
19-| Include | Exclude (unless user names them) |19+| Include | Exclude (unless user names them) |
20-|---------|----------------------------------|20+| ----------------------------------------------- | ----------------------------------- |
21-| `docs/**/*.md` | `node_modules`, `oh_modules` |21+| `docs/**/*.md` | `node_modules`, `oh_modules` |
22| `docs/**/figures/**` and image references in md | project deps (`package.json`, etc.) |22| `docs/**/figures/**` and image references in md | project deps (`package.json`, etc.) |
23-| `docs/Samples/**/README*.md` | Samples source code and config |23+| `docs/Samples/**/README*.md` | Samples source code and config |
24 24 
25-For the locale path map, see [locale-map.md](.quality-toolkit/skills/upgrade-version/references/locale-map.md).25+For the locale path map, see [locale-map.md](../../.quality-toolkit/skills/upgrade-version/references/locale-map.md).
26 26 
27## 1. Images Not Displaying27## 1. Images Not Displaying
28 28 
29Covers: wrong reference paths, missing files, double extensions (e.g. `.png.png`), miscalculated relative paths, zh/en figures out of sync, **filenames containing `()` not percent-encoded** (e.g. `RN-alter-getNode().png`).29Covers: wrong reference paths, missing files, double extensions (e.g. `.png.png`), miscalculated relative paths, zh/en figures out of sync, **filenames containing `()` not percent-encoded** (e.g. `RN-alter-getNode().png`).
30 30 
31-**Detailed source of truth**: [references/image-audit.md](.quality-toolkit/skills/docs-quality-audit/references/image-audit.md) (encoding table, fix rules, check script).31+**Detailed source of truth**: [references/image-audit.md](../../.quality-toolkit/skills/docs-quality-audit/references/image-audit.md) (encoding table, fix rules, check script).
32 32 
331. Run the check script (recommended):331. Run the check script (recommended):
34 ```bash34 ```bash
@@ -55,7 +55,7 @@ Covers: reader-facing Chinese paragraphs in `docs/en/**`; inappropriate large En
55 55 
56Covers: external 404s, invalid anchors, outdated repo/branch references, wrong relative paths between docs, and historical version links not labeled with applicable version.56Covers: external 404s, invalid anchors, outdated repo/branch references, wrong relative paths between docs, and historical version links not labeled with applicable version.
57 57 
58-**Detailed source of truth**: [references/link-audit.md](.quality-toolkit/skills/docs-quality-audit/references/link-audit.md) (upstream RN external-link map, common relative-path mistakes, false-positive filters).58+**Detailed source of truth**: [references/link-audit.md](../../.quality-toolkit/skills/docs-quality-audit/references/link-audit.md) (upstream RN external-link map, common relative-path mistakes, false-positive filters).
59 59 
601. Collect links (markdown links and bare URLs) in `docs/**/*.md`.601. Collect links (markdown links and bare URLs) in `docs/**/*.md`.
612. Internal links: whether the relative path points to an existing file (count `../` from **the file containing the link**; after edits, must resolve-verify). Key pitfalls:612. Internal links: whether the relative path points to an existing file (count `../` from **the file containing the link**; after edits, must resolve-verify). Key pitfalls:
@@ -94,13 +94,13 @@ Covers: external 404s, invalid anchors, outdated repo/branch references, wrong r
94 94 
95**Problem**: In `environment-setup.md` / `getting-started.md` package.json examples or `npm install` / install commands, `react-native` / `@react-native/*` has been upgraded, but **a related harmony package (and cli) is still on an old major** (e.g. RN=`0.86.3`, harmony=`0.84.1`). Common after a major upgrade with missed fields, or a merge conflict where only one side was applied.95**Problem**: In `environment-setup.md` / `getting-started.md` package.json examples or `npm install` / install commands, `react-native` / `@react-native/*` has been upgraded, but **a related harmony package (and cli) is still on an old major** (e.g. RN=`0.86.3`, harmony=`0.84.1`). Common after a major upgrade with missed fields, or a merge conflict where only one side was applied.
96 96 
97-**Rule** (consistent with [upgrade-version](.quality-toolkit/skills/upgrade-version/), see [version-number-style.md](.quality-toolkit/skills/upgrade-version/references/version-number-style.md)):97+**Rule** (consistent with [upgrade-version](../../.quality-toolkit/skills/upgrade-version/), see [version-number-style.md](../../.quality-toolkit/skills/upgrade-version/references/version-number-style.md)):
98 98 
99- **Main docs** dependency/install examples use **A exact number** (matching the source of truth, e.g. `0.86.3`); do not change to `.xx`, and do not mix two major lines.99- **Main docs** dependency/install examples use **A exact number** (matching the source of truth, e.g. `0.86.3`); do not change to `.xx`, and do not mix two major lines.
100- **Samples README** dependency-install lines use **B `{MAJOR}.{MINOR}.xx`**; do not write an exact patch.100- **Samples README** dependency-install lines use **B `{MAJOR}.{MINOR}.xx`**; do not write an exact patch.
101- **release note / version index** uses **A exact number**; do not change to `.xx`.101- **release note / version index** uses **A exact number**; do not change to `.xx`.
102- Edit zh/en in pairs: `environment-setup.md` ↔ its localized counterpart; the `getting-started.md` script numbers must match.102- Edit zh/en in pairs: `environment-setup.md` ↔ its localized counterpart; the `getting-started.md` script numbers must match.
103-- **`getting-started.md` unreleased WARNING**: while the current version is **not yet officially released**, keep the TODO + WARNING in the file (see [bilingual-sync.md](.quality-toolkit/skills/upgrade-version/references/bilingual-sync.md) environment-setup section). During a quality audit, **do not** delete it as outdated content; it may only be removed after official release.103+- **`getting-started.md` unreleased WARNING**: while the current version is **not yet officially released**, keep the TODO + WARNING in the file (see [bilingual-sync.md](../../.quality-toolkit/skills/upgrade-version/references/bilingual-sync.md) environment-setup section). During a quality audit, **do not** delete it as outdated content; it may only be removed after official release.
104 104 
105**Audit steps**:105**Audit steps**:
106 106 
@@ -125,7 +125,7 @@ Covers: external 404s, invalid anchors, outdated repo/branch references, wrong r
125 125 
1261. Confirm scope: all categories, or user-specified categories/subtrees.1261. Confirm scope: all categories, or user-specified categories/subtrees.
1272. Audit by category → fix → bilingual pairing.1272. Audit by category → fix → bilingual pairing.
128-3. Verify: images per [image-audit.md](.quality-toolkit/skills/docs-quality-audit/references/image-audit.md) via script, links per [link-audit.md](.quality-toolkit/skills/docs-quality-audit/references/link-audit.md) via spot-check + resolve, locale language correct, no inappropriate `ReactNative`, environment-setup version fields consistent, paired docs `zh ✓ en ✓` or gap recorded.128+3. Verify: images per [image-audit.md](../../.quality-toolkit/skills/docs-quality-audit/references/image-audit.md) via script, links per [link-audit.md](../../.quality-toolkit/skills/docs-quality-audit/references/link-audit.md) via spot-check + resolve, locale language correct, no inappropriate `ReactNative`, environment-setup version fields consistent, paired docs `zh ✓ en ✓` or gap recorded.
1294. Output summary.1294. Output summary.
130 130 
131## Output Summary131## Output Summary
@@ -158,7 +158,7 @@ Covers: external 404s, invalid anchors, outdated repo/branch references, wrong r
158- Do not change `package.json` or other project deps unless the user explicitly asks.158- Do not change `package.json` or other project deps unless the user explicitly asks.
159- **Never edit only zh or only en** (when a paired path exists).159- **Never edit only zh or only en** (when a paired path exists).
160- **Never** use `ReactNative` in reader-facing `docs/` prose; must write `React Native`.160- **Never** use `ReactNative` in reader-facing `docs/` prose; must write `React Native`.
161-- **Do not** do release version-number matrix replacement → [upgrade-version](.quality-toolkit/skills/upgrade-version/)161+- **Do not** do release version-number matrix replacement → [upgrade-version](../../.quality-toolkit/skills/upgrade-version/)
162-- **Do not** do general English spellchecking / full cspell → [docs-spellcheck](.quality-toolkit/skills/docs-spellcheck/) (prose `ReactNative` is still fixed per this skill's rules)162+- **Do not** do general English spellchecking / full cspell → [docs-spellcheck](../../.quality-toolkit/skills/docs-spellcheck/) (prose `ReactNative` is still fixed per this skill's rules)
163 163 
164-Top-level index: [upgrade-version/SKILL.md](.quality-toolkit/skills/upgrade-version/SKILL.md)164+Top-level index: [upgrade-version/SKILL.md](../../.quality-toolkit/skills/upgrade-version/SKILL.md)
@@ -10,30 +10,31 @@ mode: subagent
10Audit and fix **spelling correctness** in `docs/` Markdown, using the repo's existing **cspell** (root `.cspell.json`) as the authority.10Audit and fix **spelling correctness** in `docs/` Markdown, using the repo's existing **cspell** (root `.cspell.json`) as the authority.
11 11 
12> **Scope boundary**: This skill only handles spelling (typos / misspellings).12> **Scope boundary**: This skill only handles spelling (typos / misspellings).
13-> - Product name `ReactNative` → `React Native` → [docs-quality-audit](.quality-toolkit/skills/docs-quality-audit/)13+>
14-> - Version-number upgrades → [upgrade-version](.quality-toolkit/skills/upgrade-version/) (docs-version-upgrade)14+> - Product name `ReactNative` → `React Native` → [docs-quality-audit](../../.quality-toolkit/skills/docs-quality-audit/)
15-> - Samples `package.json` → [upgrade-version](.quality-toolkit/skills/upgrade-version/) (samples-version-bump)15+> - Version-number upgrades → [upgrade-version](../../.quality-toolkit/skills/upgrade-version/) (docs-version-upgrade)
16-> - Third-party submodule alignment → [upgrade-version](.quality-toolkit/skills/upgrade-version/) (upgrade-third-party)16+> - Samples `package.json` → [upgrade-version](../../.quality-toolkit/skills/upgrade-version/) (samples-version-bump)
17+> - Third-party submodule alignment → [upgrade-version](../../.quality-toolkit/skills/upgrade-version/) (upgrade-third-party)
17 18 
18## Audit Scope19## Audit Scope
19 20 
20-| Include | Exclude (unless user names them) |21+| Include | Exclude (unless user names them) |
21-|---------|----------------------------------|22+| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
22-| `docs/**/*.md` (incl. `zh-cn`, `en`, `Samples/**/README*.md`) | `node_modules`, `oh_modules` |23+| `docs/**/*.md` (incl. `zh-cn`, `en`, `Samples/**/README*.md`) | `node_modules`, `oh_modules` |
23| English words in reader-facing prose, headings, lists, tables | identifiers inside code blocks, command args, URLs, file paths (unless an obvious typo safe to fix) |24| English words in reader-facing prose, headings, lists, tables | identifiers inside code blocks, command args, URLs, file paths (unless an obvious typo safe to fix) |
24-| | project deps / non-md files |25+| | project deps / non-md files |
25 26 
26-Locale path map: [locale-map.md](.quality-toolkit/skills/upgrade-version/references/locale-map.md).27+Locale path map: [locale-map.md](../../.quality-toolkit/skills/upgrade-version/references/locale-map.md).
27 28 
28**Note**: the current `.cspell.json` `ignorePaths` includes `docs/zh-cn` and `docs/Samples`. When doing a full docs audit, you must **explicitly scan these paths** (see commands below); do not treat running the default cspell as "docs passed".29**Note**: the current `.cspell.json` `ignorePaths` includes `docs/zh-cn` and `docs/Samples`. When doing a full docs audit, you must **explicitly scan these paths** (see commands below); do not treat running the default cspell as "docs passed".
29 30 
30## Tools and Sources of Truth31## Tools and Sources of Truth
31 32 
32-| Item | Path / Command |33+| Item | Path / Command |
33-|------|----------------|34+| ---------------------- | ------------------------------------------------------------------------------------------ |
34-| Dictionary and ignores | root `.cspell.json` (see `tools/spellcheck/`) |35+| Dictionary and ignores | root `.cspell.json` (see `tools/spellcheck/`) |
35-| Local run | `pnpm exec cspell --no-must-find-files <paths>` (depends on root `devDependencies.cspell`) |36+| Local run | `pnpm exec cspell --no-must-find-files <paths>` (depends on root `devDependencies.cspell`) |
36-| lint-staged | `package.json` `*.md` → `cspell --no-must-find-files` |37+| lint-staged | `package.json` `*.md` → `cspell --no-must-find-files` |
37 38 
38**When cspell is unavailable**: if `pnpm exec cspell` reports `Command "cspell" not found`, first run `pnpm i`; if it still fails, **do not just end with an empty run** — fall back to the "known typos regex" below + manual spot-check, and note in the summary that "cspell did not run".39**When cspell is unavailable**: if `pnpm exec cspell` reports `Command "cspell" not found`, first run `pnpm i`; if it still fails, **do not just end with an empty run** — fall back to the "known typos regex" below + manual spot-check, and note in the summary that "cspell did not run".
39 40 
@@ -62,15 +63,15 @@ You can also first run cspell on the **non-ignored** docs paths, then spot-check
62 63 
63For each warning / regex hit, pick one (do **not** blindly dump everything into `words`):64For each warning / regex hit, pick one (do **not** blindly dump everything into `words`):
64 65 
65-| Case | Action |66+| Case | Action |
66-|------|--------|67+| ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
67-| Obvious English typo (e.g. `enviroment`, `jsBundlePrivider`, `CMakeList.txt`, `*.est` mis-write) | **Fix the prose**; if paired zh/en docs exist, sync both sides (where semantically corresponding) |68+| Obvious English typo (e.g. `enviroment`, `jsBundlePrivider`, `CMakeList.txt`, `*.est` mis-write) | **Fix the prose**; if paired zh/en docs exist, sync both sides (where semantically corresponding) |
68-| Legitimate proper noun / package name / API / HarmonyOS term (e.g. `hvigor`, `ohpm`, `ArkTS`, `FrameNode`) | If it recurs and is correct → add to `.cspell.json` `words` (lowercase entry is enough) |69+| Legitimate proper noun / package name / API / HarmonyOS term (e.g. `hvigor`, `ohpm`, `ArkTS`, `FrameNode`) | If it recurs and is correct → add to `.cspell.json` `words` (lowercase entry is enough) |
69-| **Real directory name / project path** (e.g. `MutilBundleSample/`) | **Do not change the path** (must match the repo dir); if prose writes Multi* but the link still points to that dir, keep the link and dir name consistent |70+| **Real directory name / project path** (e.g. `MutilBundleSample/`) | **Do not change the path** (must match the repo dir); if prose writes Multi\* but the link still points to that dir, keep the link and dir name consistent |
70-| External URL path fragment (e.g. a Huawei doc `decrease_pakage_size`) | **Do not change the URL** |71+| External URL path fragment (e.g. a Huawei doc `decrease_pakage_size`) | **Do not change the URL** |
71-| Correct API / class / package name in a code block | **Do not change by default** |72+| Correct API / class / package name in a code block | **Do not change by default** |
72-| A **typo** in prose or inline backticks (e.g. `` `Index.est` ``, `` `jsBundlePrivider` ``, comment `// index.est`) | **Fix it** (this is an error readers will copy; not a legitimate identifier) |73+| A **typo** in prose or inline backticks (e.g. `` `Index.est` ``, `` `jsBundlePrivider` ``, comment `// index.est`) | **Fix it** (this is an error readers will copy; not a legitimate identifier) |
73-| Cannot confirm | Mark `pending` in summary; **do not guess** |74+| Cannot confirm | Mark `pending` in summary; **do not guess** |
74 75 
75**Dictionary hygiene**: if a suspected typo is whitelisted in `.cspell.json` `words`, change it to **fix the doc** and delete the erroneous entry.76**Dictionary hygiene**: if a suspected typo is whitelisted in `.cspell.json` `words`, change it to **fix the doc** and delete the erroneous entry.
76 77 
@@ -95,11 +96,11 @@ rg -n "jsBundlePrivider|NodeControllerd|CMakeList\.txt|MyApplicaton|\.est\`|\.es
95```96```
96 97 
97Handle hits per step 3.98Handle hits per step 3.
98-**Do not** "fix" `MutilBundleSample` (a real directory name) into Multi*.99+**Do not** "fix" `MutilBundleSample` (a real directory name) into Multi\*.
99 100 
100### 6. Relationship with React Native wording101### 6. Relationship with React Native wording
101 102 
102-- Concatenated **`ReactNative`** (reader-facing prose, not a code identifier) → change to **`React Native`**; rule source: [docs-quality-audit](.quality-toolkit/skills/docs-quality-audit/)103+- Concatenated **`ReactNative`** (reader-facing prose, not a code identifier) → change to **`React Native`**; rule source: [docs-quality-audit](../../.quality-toolkit/skills/docs-quality-audit/)
103- If a spelling scan finds `ReactNative` in prose, **fix it directly per quality-audit and sync zh/en**; do not skip it because "it belongs to another skill"104- If a spelling scan finds `ReactNative` in prose, **fix it directly per quality-audit and sync zh/en**; do not skip it because "it belongs to another skill"
104- Legitimate symbols in code / paths (e.g. `ReactNativePage`, `Libraries/ReactNative/`) **must not** be force-split105- Legitimate symbols in code / paths (e.g. `ReactNativePage`, `Libraries/ReactNative/`) **must not** be force-split
105 106 
@@ -116,22 +117,26 @@ Handle hits per step 3.
116```markdown117```markdown
117## docs/ Spelling Audit Summary118## docs/ Spelling Audit Summary
118 119 
119-| Scope | Tool | Result |120+| Scope | Tool | Result |
120-|-------|------|--------|121+| -------------- | --------------- | -------------------- |
121-| docs/en … | cspell | N warnings / M fixed |122+| docs/en … | cspell | N warnings / M fixed |
122-| docs/zh-cn … | cspell / manual | … |123+| docs/zh-cn … | cspell / manual | … |
123-| Samples README | … | … |124+| Samples README | … | … |
124 125 
125### Fixed (prose edits)126### Fixed (prose edits)
127+ 
126| file:line | typo | correction |128| file:line | typo | correction |
127 129 
128### Added to dictionary (.cspell.json words)130### Added to dictionary (.cspell.json words)
131+ 
129| word | reason |132| word | reason |
130 133 
131### Pending134### Pending
135+ 
132| file:line | word | note |136| file:line | word | note |
133 137 
134### Bilingual gaps / not synced138### Bilingual gaps / not synced
139+ 
135| Chinese | English | note |140| Chinese | English | note |
136```141```
137 142 
@@ -143,4 +148,4 @@ Handle hits per step 3.
143- When unsure, mark pending; do not "guess" a correct spelling into the dictionary to fake a pass.148- When unsure, mark pending; do not "guess" a correct spelling into the dictionary to fake a pass.
144- **Do not** do release version-number matrix / Samples `package.json` upgrades.149- **Do not** do release version-number matrix / Samples `package.json` upgrades.
145 150 
146-Top-level index: [upgrade-version/SKILL.md](.quality-toolkit/skills/upgrade-version/SKILL.md)151+Top-level index: [upgrade-version/SKILL.md](../../.quality-toolkit/skills/upgrade-version/SKILL.md)
@@ -0,0 +1,301 @@
1+---
2+description: Use when editing C++ code that manages multiple coexisting instances (e.g., multiple engine instances, multiple rendering contexts, multiple view hierarchies). Checks that per-instance state is properly isolated: global singleton registry cross-instance pollution, cache clearing scope, listener deduplication, instance-scoped key naming, move-semantics registry consistency.
3+mode: subagent
4+---
5+ 
6+> **Toolkit location**: this repo vendors the quality-toolkit at `.quality-toolkit/`. Toolkit-relative paths mentioned below (e.g. `tools/…`, `standards/…`) resolve under that directory.
7+ 
8+# Instance Isolation Checks
9+ 
10+Sourced from isolation-check patterns in the target project's C++ layer actual bug-fix commits (example source path: `src/main/cpp/`).
11+ 
12+## Applicable Scenarios
13+ 
14+| Scenario | Description |
15+| ---------------------------------------- | ---------------------------------------------------------------------------------------------------- |
16+| Editing a global singleton registry | E.g. `HarmonyReactMarker`, `TextMeasureRegistry` — data structures shared across threads/instances |
17+| Implementing move-semantics classes | E.g. `ArkUINode` — classes that maintain handle→owner mappings and need move construction/assignment |
18+| Handling cache invalidation and recovery | E.g. cache loss recovery after `registerFont()` in `TextComponentInstance` |
19+| Multi-instance scenarios | Any isolation and clearing logic involving multiple coexisting instances or rendering contexts |
20+ 
21+## 1. Thread-Safe Listener Registry
22+ 
23+**Problem:** A bare `static std::vector<Listener*> listeners` field has no mutex, no cross-translation-unit initialization-order guarantee, and no deduplication. Repeated `onInit` calls silently register the same listener N times, amplifying each marker log.
24+ 
25+**Pattern:** Replace the static member with a function-local static accessed via an accessor method, guarded by a dedicated mutex.
26+ 
27+```cpp
28+// .h
29+class HarmonyReactMarker {
30+ static std::mutex& listenersMutex();
31+ static std::vector<HarmonyReactMarkerListener*>& listenersStorage();
32+ // Remove the old `static std::vector<...> listeners;` field
33+};
34+ 
35+// .cpp
36+std::mutex& HarmonyReactMarker::listenersMutex() {
37+ static std::mutex m;
38+ return m;
39+}
40+std::vector<HarmonyReactMarkerListener*>& HarmonyReactMarker::listenersStorage() {
41+ static std::vector<HarmonyReactMarkerListener*> v;
42+ return v;
43+}
44+```
45+ 
46+**Dispatch rule:** Take a snapshot of listeners inside the lock, then dispatch outside the critical section. This prevents a listener that re-enters `addListener` / `removeListener` from causing a deadlock.
47+ 
48+```cpp
49+void logMarker(...) {
50+ std::vector<HarmonyReactMarkerListener*> snapshot;
51+ {
52+ std::lock_guard<std::mutex> lock(listenersMutex());
53+ snapshot = listenersStorage();
54+ }
55+ for (auto* listener : snapshot) {
56+ listener->logMarker(markerId, tag, timestamp);
57+ }
58+}
59+```
60+ 
61+**`addListener` deduplication:**
62+ 
63+```cpp
64+void addListener(HarmonyReactMarkerListener& listener) {
65+ std::lock_guard<std::mutex> lock(listenersMutex());
66+ auto& storage = listenersStorage();
67+ if (std::find(storage.begin(), storage.end(), &listener) != storage.end()) {
68+ return;
69+ }
70+ storage.push_back(&listener);
71+}
72+```
73+ 
74+**`removeListener` must also lock:**
75+ 
76+```cpp
77+void removeListener(HarmonyReactMarkerListener& listener) {
78+ std::lock_guard<std::mutex> lock(listenersMutex());
79+ auto& storage = listenersStorage();
80+ storage.erase(
81+ std::remove(storage.begin(), storage.end(), &listener), storage.end());
82+}
83+```
84+ 
85+## 2. Per-Instance Cache Clearing (TextMeasureRegistry)
86+ 
87+**Problem:** `TextMeasurer::registerFont()` calls `TextMeasureRegistry::clear()`, erasing cached measurement data for **all** engine instances/rendering contexts. Other instances' `TextComponentInstance` may lose their styled strings on re-render.
88+ 
89+**Pattern:** Add `clearForRnInstance(int instanceId)` that only clears entries with the target instance's prefix key, and erases the shared `m_textMeasureInfoCache` as a whole (because `TextMeasureCacheKey` does not contain an instance ID — polluted entries cannot be cheaply identified).
90+ 
91+```cpp
92+void TextMeasureRegistry::clearForRnInstance(int instanceId) {
93+ std::lock_guard<std::mutex> lock(m_mutex);
94+ 
95+ const std::string prefix = std::to_string(instanceId) + "_";
96+ for (auto it = m_keyToMeasureInfo.begin();
97+ it != m_keyToMeasureInfo.end();) {
98+ if (it->first.compare(0, prefix.size(), prefix) == 0) {
99+ m_keyToCacheKey.erase(it->first);
100+ it = m_keyToMeasureInfo.erase(it);
101+ } else {
102+ ++it;
103+ }
104+ }
105+ 
106+ m_textMeasureInfoCache.clear();
107+}
108+```
109+ 
110+**In `TextMeasurer::registerFont`:**
111+ 
112+```cpp
113+m_fontCollection.reset();
114+TextMeasureRegistry::getTextMeasureRegistry().clearForRnInstance(m_instanceId);
115+```
116+ 
117+**Also fix `clear()` itself:** The old code had a duplicate `m_keyToMeasureInfo.clear()` call, missing the clearing of `m_keyToCacheKey` and `m_textMeasureInfoCache`. A correct `clear()` must clear all three maps.
118+ 
119+## 3. ArkUINode Move Semantics
120+ 
121+**Problem:** `ArkUINode`'s move constructor steals `m_nodeHandle`, but the `NODE_BY_HANDLE` registry still points to the moved-from object. The moved-from object's destructor later unregisters that handle, so the new owner no longer receives events. Double erasure may also occur.
122+ 
123+**Pattern:**
124+ 
125+### Destructor — nullptr guard
126+ 
127+```cpp
128+ArkUINode::~ArkUINode() noexcept {
129+ if (m_nodeHandle == nullptr) {
130+ return; // moved-from; new owner is responsible for cleanup
131+ }
132+ // ... normal cleanup ...
133+}
134+```
135+ 
136+### Destructor — conditional registry erasure
137+ 
138+```cpp
139+auto it = NODE_BY_HANDLE.find(m_nodeHandle);
140+if (it != NODE_BY_HANDLE.end() && it->second == this) {
141+ NODE_BY_HANDLE.erase(it);
142+}
143+```
144+ 
145+### Move constructor — redirect the registry
146+ 
147+```cpp
148+ArkUINode::ArkUINode(ArkUINode&& other) noexcept
149+ : m_nodeHandle(other.m_nodeHandle) {
150+ other.m_nodeHandle = nullptr;
151+ if (m_nodeHandle != nullptr) {
152+ std::lock_guard<std::mutex> lock(NODE_BY_HANDLE_MUTEX);
153+ NODE_BY_HANDLE[m_nodeHandle] = this;
154+ }
155+}
156+```
157+ 
158+### Move assignment — swap + update both entries
159+ 
160+```cpp
161+ArkUINode& ArkUINode::operator=(ArkUINode&& other) noexcept {
162+ std::swap(m_nodeHandle, other.m_nodeHandle);
163+ std::lock_guard<std::mutex> lock(NODE_BY_HANDLE_MUTEX);
164+ if (m_nodeHandle != nullptr) {
165+ NODE_BY_HANDLE[m_nodeHandle] = this;
166+ }
167+ if (other.m_nodeHandle != nullptr) {
168+ NODE_BY_HANDLE[other.m_nodeHandle] = &other;
169+ }
170+ return *this;
171+}
172+```
173+ 
174+## 4. TextComponentInstance Cache-Miss Recovery
175+ 
176+**Problem:** After `registerFont()` clears the current instance's cache bucket, subsequent `onStateChanged` can't find the `TextMeasureInfo` for the corresponding key in the registry and silently skips — text nodes don't display styled content.
177+ 
178+**Pattern:** Add `buildTextMeasureInfoFromState()` that re-runs the standard measurement path when the registry lookup misses:
179+ 
180+```cpp
181+std::optional<std::shared_ptr<TextMeasureInfo>>
182+TextComponentInstance::buildTextMeasureInfoFromState(
183+ facebook::react::ParagraphState const& stateData) {
184+ auto textLayoutManager = stateData.paragraphLayoutManager.getTextLayoutManager();
185+ if (textLayoutManager == nullptr) return std::nullopt;
186+ auto nativeTextLayoutManager = textLayoutManager->getNativeTextLayoutManager();
187+ auto textMeasurer = static_cast<TextMeasurer*>(nativeTextLayoutManager);
188+ if (textMeasurer == nullptr) return std::nullopt;
189+ 
190+ auto contentSize = m_layoutMetrics.getContentFrame().size;
191+ if (std::isnan(contentSize.width) || contentSize.width <= 0) return std::nullopt;
192+ 
193+ auto attributedString = stateData.attributedString;
194+ auto paragraphAttributes = stateData.paragraphAttributes;
195+ auto layoutConstraints = facebook::react::LayoutConstraints{
196+ {.width = 0, .height = 0},
197+ contentSize,
198+ facebook::react::LayoutDirection::Undefined};
199+ 
200+ textMeasurer->measure(attributedString, paragraphAttributes, layoutConstraints);
201+ return TextMeasureRegistry::getTextMeasureRegistry().getTextMeasureInfoByKey(m_key);
202+}
203+```
204+ 
205+**In `onStateChanged`:**
206+ 
207+```cpp
208+auto info = TextMeasureRegistry::getTextMeasureRegistry().getTextMeasureInfoByKey(m_key);
209+if (!info.has_value()) {
210+ info = buildTextMeasureInfoFromState(stateData);
211+}
212+```
213+ 
214+**Also simplify `onStateChanged`:** Remove the `m_childNodes` vector and the child-node removal loop in the destructor and `onStateChanged`. Remove the old duplicate `m_textNode.setTextContent()` calls. Instead, always call `setTextContent` **outside** the `info.has_value()` branch, ensuring plain text is always set even when styled content is unavailable.
215+ 
216+## 5. Multi-engine instance and Multi-context Isolation
217+ 
218+The target project runs multiple `engine instance`s in the same process (each instance has multiple `rendering context`s). Any global/singleton registry or cache must respect this topology: an operation triggered by one instance **must never** corrupt state owned by another instance or rendering context.
219+ 
220+### 5.1 Singleton registries must be instance-aware
221+ 
222+**Anti-pattern:** The singleton `TextMeasureRegistry` stores all instances' entries in one flat map. Calling `clear()` on it (e.g. triggered from `registerFont`) erases all instances' data.
223+ 
224+**Correct pattern:** Use a `{instanceId}_` prefix to name keyed entries, so per-instance clearing can be selective:
225+ 
226+```
227+key = "<instanceId>_<tag>_<rendering contextId>"
228+```
229+ 
230+When one instance triggers invalidation, use `clearForRnInstance(id)` to erase only entries matching that prefix. Other instances' entries are preserved.
231+ 
232+### 5.2 Shared caches without an instance ID must be handled carefully
233+ 
234+Some caches (e.g. `m_textMeasureInfoCache` keyed by `TextMeasureCacheKey = {attributedString, paragraphAttributes, layoutConstraints}`) do not contain `instanceId`. Two instances may produce the same cacheKey but have different `fontCollection` / `TextMeasureInfo` state. After instance A registers a new font, instance B may still hold a stale `TextMeasureInfo` cache measured with the old font.
235+ 
236+**Strategy:** When erasing keyed entries per instance, also erase the shared cache as a whole. Polluted entries cannot be cheaply identified; the conservative approach is to clear all and let each instance lazily rebuild on the next `measure()` call. This is safe:
237+ 
238+- Other instances' keyed entries (`m_keyToMeasureInfo`) are preserved; their `TextComponentInstance::onStateChanged` lookups still succeed.
239+- `m_textMeasureInfoCache` entries will be correctly rebuilt by each instance's next standard `measure()` path, using its own `fontCollection`.
240+ 
241+> ⚠️ **Never skip the shared cache erasure** — skipping it "to preserve other instances' measurement data" is wrong. Stale cache entries silently replace fallback glyphs with the newly registered font in whichever instance hits them first.
242+ 
243+### 5.3 Multi-instance Listener registration
244+ 
245+`HarmonyReactMarker` is a process-level singleton. Multiple `engine instance`s each add their own `HarmonyReactMarkerListener`. On reload, `onInit` runs again for the same instance, possibly re-adding the same listener.
246+ 
247+**Rules:**
248+ 
249+- Deduplicate in `addListener` — `std::find` check prevents N-fold registration from repeated `onInit`.
250+- `removeListener` must erase all occurrences (erase-remove idiom), not just the first — defending against pre-existing duplicates from before the fix.
251+- Never assume "only one instance exists" when designing listener lifetimes. A second instance can `addListener` during the first's dispatch; the snapshot pattern (§1) handles this correctly.
252+ 
253+### 5.4 ComponentInstance key naming (multi-context)
254+ 
255+`TextComponentInstance` constructs the registry key as:
256+ 
257+```
258+m_key = "<instanceId>_<tag>_<rendering contextId>"
259+```
260+ 
261+`rendering contextId` is **required**. Without it, two rendering contexts in the same instance that render the same shadow-view tag `<Text>` would collide on the same key, and clearing one rendering context's entry would break the other.
262+ 
263+**Rule:** In a global registry, the registry key identifying a component must include **all** scope dimensions: `instanceId + tag + rendering contextId`. Never use just `tag` or just `tag + rendering contextId` in a global registry.
264+ 
265+### 5.5 Cross-instance move semantics and NODE_BY_HANDLE
266+ 
267+`NODE_BY_HANDLE` is a global mapping: `ArkUI_NodeHandle → ArkUINode*`. Multiple instances concurrently create nodes, and nodes may be moved (e.g. during reconciliation). A move must:
268+ 
269+1. Null out the moved-from object's `m_nodeHandle` (making its destructor a no-op).
270+2. Update `NODE_BY_HANDLE` to point to the new owner.
271+3. The moved-from object's destructor must erase the registry entry only when `it->second == this` — otherwise it may erase the entry the new owner just installed.
272+ 
273+**Rule:** Never assume the global handle→owner mapping has only one writer. Lock before every change; check identity before erasing.
274+ 
275+### 5.6 Test example: TextMutationWithoutMeasureExample
276+ 
277+When adding or modifying test examples for multi-instance scenarios, avoid triggering text re-measurement across unrelated instances. `TextMutationWithoutMeasureExample` demonstrates:
278+ 
279+- Toggling `selectable` / `accessibilityLabel` (non-layout properties) rather than changing text content or style — avoiding layout recalculation that could spill into the shared `TextMeasureRegistry`.
280+- Mutating a sibling `View` (backgroundColor/opacity) rather than `<Text>` itself — sibling mutations don't invalidate Text's measurement cache.
281+- This pattern is especially important in multi-context scenarios, where one rendering context's animation should not force another rendering context to re-measure its text.
282+ 
283+## Prohibitions
284+ 
285+| ❌ Don't | ✅ Do |
286+| -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
287+| Call global `clear()` in per-instance code | Use `clearForRnInstance(id)` or a similar scope-limited invalidation |
288+| Use only `tag` or only `tag+rendering contextId` as the key in a global registry | The key must include `instanceId + tag + rendering contextId` |
289+| Skip shared-cache erasure "to preserve other instances' data" | Erase the shared cache as a whole; stale entries silently produce wrong results |
290+| Register listeners without deduplication | `addListener` must `std::find` to deduplicate |
291+| Mutate the global handle→owner mapping without locking | Lock before every change; check `it->second == this` before erasing |
292+| Silently skip on cache miss in `onStateChanged` | Re-run the standard measurement path to recover the cache |
293+| Use a bare `static std::vector<>` as a cross-thread registry | Function-local static + mutex + accessor |
294+ 
295+## General Rules
296+ 
297+- Any static registry shared across threads **must** have a mutex. Use a function-local static to guarantee safe initialization order.
298+- A global registry's `clear()` is almost always wrong when the trigger source is per-instance. Prefer prefix-scoped clearing + whole shared-cache erasure.
299+- Classes with move semantics that maintain a handle→owner mapping **must** update the mapping in move construction/assignment and guard with a nullptr check in the destructor.
300+- A cache miss in `onStateChanged` should be recovered by re-running the standard measurement path, not silently skipped.
301+- Always include `<algorithm>` when using `std::find` or `std::remove`.
@@ -7,6 +7,18 @@ mode: subagent
7 7 
8# Pre-commit Best Practices8# Pre-commit Best Practices
9 9 
10+## ⛔ Hook Integrity Rules (MANDATORY)
11+ 
12+When setting up or reviewing hooks, NEVER:
13+ 
14+1. Use `--no-verify` on any git command without explicit user confirmation
15+2. Clear `core.hooksPath` to disable hooks
16+3. Delete or rename hook files to bypass checks
17+4. Modify hook content to weaken validations
18+ 
19+If a hook fails, the correct response is to **fix the code, not weaken the hook**.
20+If a hook produces a false positive, report to the user and wait for confirmation before any bypass.
21+ 
10## Architecture: Husky + lint-staged (Node/pnpm ecosystem)22## Architecture: Husky + lint-staged (Node/pnpm ecosystem)
11 23 
12Use Husky to manage Git hooks, and lint-staged to run targeted checks on staged files. Do not use the Python `pre-commit` framework.24Use Husky to manage Git hooks, and lint-staged to run targeted checks on staged files. Do not use the Python `pre-commit` framework.
@@ -15,86 +27,87 @@ Use Husky to manage Git hooks, and lint-staged to run targeted checks on staged
15 27 
16## Hook Layout28## Hook Layout
17 29 
18-| Hook | File | Purpose | Tool |30+| Hook | File | Purpose | Tool |
19-| ------------ | ------------------- | ------------------------------------ | ---------------------- |31+| ------------ | ------------------- | -------------------------------------- | ---------------------- |
20-| `pre-commit` | `.husky/pre-commit` | Fast checks on staged files | gitleaks + lint-staged |32+| `pre-commit` | `.husky/pre-commit` | Fast checks on staged files | gitleaks + lint-staged |
21-| `commit-msg` | `.husky/commit-msg` | Commit message spec | commitlint |33+| `commit-msg` | `.husky/commit-msg` | Commit message spec | commitlint |
22| `pre-push` | `.husky/pre-push` | Heavy checks (typecheck + test + docs) | verify command |34| `pre-push` | `.husky/pre-push` | Heavy checks (typecheck + test + docs) | verify command |
23 35 
24## lint-staged Rules (package.json)36## lint-staged Rules (package.json)
25 37 
26-| glob match | Command | Description |38+The authoritative source is `tools/lint-staged/lint-staged.config.js` (location-independent, `__dirname`-based tool paths). The table below mirrors it exactly:
27-| --------------------------------------- | --------------------------------------------- | ------------------------------------ |39+ 
28-| `*.{js,jsx,ts,tsx,ets}` | prettier --write + eslint --fix | JS/TS/ArkTS formatting and lint |40+| glob match | Command | Description |
29-| `*.{json,yaml,yml}` | check-config-files.sh + prettier --write | JSON/YAML syntax validation + format |41+| ----------------------- | ------------------------------------------------ | ---------------------------------------------------------- |
30-| `*.md` | cspell --no-must-find-files + prettier --write | Spell check + format |42+| `*.{js,jsx,ts,tsx,ets}` | prettier --write | JS/TS/ArkTS formatting |
31-| `*.{cpp,h}` | ts-node format-cpp.ts | C++ clang-format auto-fix |43+| `*.{json,yaml,yml}` | ts-node check-config-files.ts + prettier --write | JSON/YAML syntax validation + format |
32-| `*` | pre-commit-checks.sh | Whitespace/line-endings/large files/merge conflict markers |44+| `*.md` | cspell --no-must-find-files + prettier --write | Spell check + format |
33-| `*.{ts,tsx,js,jsx,ets,cpp,h,c,java,kt}` | check-license-header.sh | License/Copyright header check |45+| `*.{cpp,h}` | ts-node format-cpp.ts | C++ clang-format auto-fix |
46+| `*` | ts-node pre-commit-checks.ts | Whitespace/line-endings/large files/merge conflict markers |
47+ 
48+> **Note**: eslint and license-header checks are NOT part of the toolkit's baseline lint-staged config. Projects that need them should add their own entries (e.g. `"*.{ts,tsx}": ["eslint --fix"]`) in a project-level `.lintstagedrc.json` layered over this config.
34 49 
35## commitlint Custom Rules50## commitlint Custom Rules
36 51 
37The tool layer `tools/commitlint/commitlint.config.js` is based on `@commitlint/config-conventional`; projects can layer custom business rules on this baseline. The table below lists **example custom rules** sourced from the RNOH project, for target projects to adopt or trim as needed:52The tool layer `tools/commitlint/commitlint.config.js` is based on `@commitlint/config-conventional`; projects can layer custom business rules on this baseline. The table below lists **example custom rules** sourced from the RNOH project, for target projects to adopt or trim as needed:
38 53 
39-| Example rule name (from RNOH) | Level | Description | Applicable scenario |54+| Example rule name (from RNOH) | Level | Description | Applicable scenario |
40-| -------------------------------- | ---------- | ------------------------------------------------------------------------ | ---------------------- |55+| ------------------------------------ | ---------- | --------------------------------------------------------------------------------- | ------------------------------------ |
41-| `dco-email-match` (example) | error | Signed-off-by must contain an email address | DCO compliance |56+| `dco-email-match` (example) | error | Signed-off-by must contain an email address | DCO compliance |
42-| `body-required` (example) | error | feat/fix/refactor/perf/deprecated/removed/security types must include body | Significant changes need explanation |57+| `body-required` (example) | error | feat/fix/refactor/perf/deprecated/removed/security types must include body | Significant changes need explanation |
43-| `symbols-change-warning` (example) | info | Symbol changes should explain symbol-handling status | Native-layer release |58+| `symbols-change-warning` (example) | info | Symbol changes should explain symbol-handling status | Native-layer release |
44-| `native-change-warning` (example) | info | Native-layer changes should explain `.so` impact | Native-layer release |59+| `native-change-warning` (example) | info | Native-layer changes should explain `.so` impact | Native-layer release |
45-| `build-change-warning` (example) | info | Build changes should explain Debug/Release impact | Build config changes |60+| `build-change-warning` (example) | info | Build changes should explain Debug/Release impact | Build config changes |
46-| `component-change-warning` (example) | info | Component changes should explain compatibility and API changes | Component library release |61+| `component-change-warning` (example) | info | Component changes should explain compatibility and API changes | Component library release |
47-| `breaking-change-format` (example) | error/warn | BREAKING CHANGE must have a colon; suggest adding impact scope and migration plan | Breaking changes |62+| `breaking-change-format` (example) | error/warn | BREAKING CHANGE must have a colon; suggest adding impact scope and migration plan | Breaking changes |
48-| `stability-ref-warning` (example) | warn | Stability-related changes should reference historical fix docs | Stability governance |63+| `stability-ref-warning` (example) | warn | Stability-related changes should reference historical fix docs | Stability governance |
49 64 
50All custom rule error messages can carry documentation links so developers can quickly find the spec (corresponding specs are under this repository's `standards/`).65All custom rule error messages can carry documentation links so developers can quickly find the spec (corresponding specs are under this repository's `standards/`).
51 66 
52## Key Files67## Key Files
53 68 
54-| File | Purpose |69+| File | Purpose |
55-| --------------------------------- | ------------------------------------------------------------ |70+| ----------------------------------------------- | --------------------------------------------------------------------------------------------------- |
56-| `package.json` | lint-staged config, devDependencies |71+| `package.json` | lint-staged config, devDependencies |
57-| `.husky/pre-commit` | gitleaks + lint-staged |72+| `.husky/pre-commit` | gitleaks + lint-staged |
58-| `.husky/commit-msg` | commitlint |73+| `.husky/commit-msg` | commitlint |
59-| `.husky/pre-push` | verify |74+| `.husky/pre-push` | verify |
60-| `.gitleaks.toml` | gitleaks allowlist |75+| `.gitleaks.toml` | gitleaks allowlist |
61-| `.cspell.json` | cspell dictionary + ignore paths |76+| `.cspell.json` | cspell dictionary + ignore paths |
62-| `.gitmessage` | commit template (auto-configured by setup script) |77+| `.gitmessage` | commit template (auto-configured by setup script) |
63-| `commitlint.config.js` | commit spec + custom business rules (see tools/commitlint/) |78+| `commitlint.config.js` | commit spec + custom business rules (see tools/commitlint/) |
64-| `tools/gitleaks/gitleaks-check.sh` | gitleaks pre-commit wrapper script |79+| `tools/gitleaks/gitleaks-check.ts` | gitleaks pre-commit wrapper (ts-node) |
65-| `tools/pre-commit-checks/pre-commit-checks.sh` | Whitespace/line-endings/large files/merge conflict markers (accepts lint-staged file args) |80+| `tools/pre-commit-checks/pre-commit-checks.ts` | Whitespace/line-endings/large files/merge conflict markers (ts-node, accepts lint-staged file args) |
66-| `tools/pre-commit-checks/check-config-files.sh` | JSON/YAML syntax validation (accepts lint-staged file args) |81+| `tools/pre-commit-checks/check-config-files.ts` | JSON/YAML syntax validation (ts-node, accepts lint-staged file args) |
67-| `tools/pre-commit-checks/check-license-header.sh` | License/Copyright header check (accepts lint-staged file args)|82+| `tools/husky/setup-husky.ts` | Auto-create hooks + configure commit.template |
68-| `tools/husky/setup-husky.ts` | Auto-create hooks + configure commit.template |83+| `tools/format/format-cpp.ts` | C++ clang-format auto-fix |
69-| `tools/format/format-cpp.ts` | C++ clang-format auto-fix |
70 84 
71## Community Spec Coverage85## Community Spec Coverage
72 86 
73-| Community spec requirement | pre-commit coverage |87+| Community spec requirement | pre-commit coverage |
74-| --------------------------------------- | -------------------------------------------------- |88+| -------------------------------------------- | --------------------------------------------------------------------------------- |
75-| Commit format (type/scope/subject) | ✅ fully covered by commitlint |89+| Commit format (type/scope/subject) | ✅ fully covered by commitlint |
76-| Signed-off-by (DCO) | ✅ commitlint + dco-email-match example rule |90+| Signed-off-by (DCO) | ✅ commitlint + dco-email-match example rule |
77-| BREAKING CHANGE annotation | ✅ commitlint breaking-change-format example rule |91+| BREAKING CHANGE annotation | ✅ commitlint breaking-change-format example rule |
78-| Symbols/Native/Build/Component special notes | ✅ commitlint 4 warning example rules |92+| Symbols/Native/Build/Component special notes | ✅ commitlint 4 warning example rules |
79-| New file License/Copyright header | ✅ lint-staged check-license-header.sh |93+| Secret/sensitive info detection | ✅ gitleaks (pre-commit) |
80-| Secret/sensitive info detection | ✅ gitleaks (pre-commit) |94+| Commit template guidance | ✅ .gitmessage + setup script auto-config |
81-| Commit template guidance | ✅ .gitmessage + setup script auto-config |95+| Code formatting (JS/TS/C++) | ✅ prettier + clang-format (eslint is project-level, not in the toolkit baseline) |
82-| Code formatting (JS/TS/C++) | ✅ prettier + eslint + clang-format |96+| Spell check | ✅ cspell (lint-staged) |
83-| Spell check | ✅ cspell (lint-staged) |97+| Large file blocking | ✅ pre-commit-checks.ts |
84-| Large file blocking | ✅ pre-commit-checks.sh |98+| Whitespace/line-ending normalization | ✅ pre-commit-checks.ts |
85-| Whitespace/line-ending normalization | ✅ pre-commit-checks.sh |99+| JSON/YAML syntax | ✅ check-config-files.ts |
86-| JSON/YAML syntax | ✅ check-config-files.sh |100+| merge conflict markers | ✅ pre-commit-checks.ts |
87-| merge conflict markers | ✅ pre-commit-checks.sh |101+| Changelog automation | ✅ standard-version (pnpm run release) |
88-| Changelog automation | ✅ standard-version (pnpm run release) |102+| License/Copyright header | ❌ not in toolkit baseline; add per-project if needed |
89-| Branch naming spec | ⏳ Not suitable for pre-commit; recommend CI-level checks |103+| Branch naming spec | ⏳ Not suitable for pre-commit; recommend CI-level checks |
90-| Test coverage | ⏳ Not suitable for pre-commit; covered by pre-push verify + CI |104+| Test coverage | ⏳ Not suitable for pre-commit; covered by pre-push verify + CI |
91 105 
92## Verification106## Verification
93 107 
94```bash108```bash
95pnpm exec lint-staged --diff="HEAD" --verbose109pnpm exec lint-staged --diff="HEAD" --verbose
96pnpm exec cspell --no-must-find-files README.md110pnpm exec cspell --no-must-find-files README.md
97-sh ./tools/pre-commit-checks/check-license-header.sh src/App.tsx111+pnpm exec ts-node ./tools/gitleaks/gitleaks-check.ts
98-sh ./tools/gitleaks/gitleaks-check.sh
99node -e "require('./commitlint.config.js'); console.log('commitlint OK')"112node -e "require('./commitlint.config.js'); console.log('commitlint OK')"
100```113```
@@ -0,0 +1,205 @@
1+---
2+description: Perform a local pre-commit code review on staged changes before they are committed. Reads the staged diff, applies evidence-based review methodology (logic correctness, architecture, performance, thread safety), and outputs structured findings with severity levels. Triggers when the user mentions pre-commit review, local code review, review before commit, or when the pre-commit-review tool is invoked.
3+mode: subagent
4+---
5+ 
6+> **Toolkit location**: this repo vendors the quality-toolkit at `.quality-toolkit/`. Toolkit-relative paths mentioned below (e.g. `tools/…`, `standards/…`) resolve under that directory.
7+ 
8+# Pre-Commit Code Review (Closed Loop)
9+ 
10+Review staged changes locally before committing, using the evidence-based methodology from [`../../standards/review/review-guidelines.md`](../../.quality-toolkit/standards/review/review-guidelines.md). **Critical findings block the commit** until resolved.
11+ 
12+## ⛔ Bypass Protection (MANDATORY)
13+ 
14+You MUST NOT bypass the quality gate. Specifically:
15+ 
16+1. **NEVER use `git commit --no-verify`** unless the user has explicitly confirmed after you reported the issue.
17+2. **NEVER use `git push --no-verify`** — the push-guard will catch bypassed commits.
18+3. **NEVER clear `core.hooksPath`** to skip hooks.
19+4. **NEVER delete or modify hook files** (`.husky/*`, `.githooks/*`).
20+ 
21+If the pre-commit hook blocks your commit:
22+ 
23+1. Read the error output — it lists the exact Critical/Major findings with file:line and trigger paths
24+2. **Fix the underlying issue** — do not weaken the check or bypass the gate
25+3. Re-stage: `git add <fixed-files>`
26+4. Re-commit (the gate will re-run the AI review automatically)
27+5. Only if you believe a finding is a false positive:
28+ - Report to the user with the finding details and your reasoning
29+ - **Wait for explicit user confirmation** ("yes, skip this" or similar)
30+ - Only then use `--no-verify`, and document why in the commit message body
31+ 
32+The push-guard (`push-guard.js` in `.husky/pre-push`) independently validates ALL commits in the push range. Even if a commit was made with `--no-verify`, the push will be blocked if it violates format/DCO/test-reference rules. **Bypassing at commit time does NOT bypass at push time.**
33+ 
34+## Closed-loop flow
35+ 
36+```
37+┌─────────────────────────────────────────────────────────────────┐
38+│ 1. AI Agent reviews staged diff │
39+│ (this skill + review-guidelines.md) │
40+│ Output: structured findings (Critical/Major/Minor) │
41+├─────────────────────────────────────────────────────────────────┤
42+│ 2. AI Agent records findings │
43+│ node .quality-toolkit/tools/pre-commit-review/ │
44+│ pre-commit-review.js --record '{"findings":[…]}' │
45+├─────────────────────────────────────────────────────────────────┤
46+│ 3. git commit → .husky/pre-commit → --gate │
47+│ Critical unresolved → ❌ BLOCKED (fix → re-stage → re-review) │
48+│ Critical resolved → ⚠️ WARN (Major/Minor shown) → proceed │
49+│ Clean → ✓ commit allowed │
50+├─────────────────────────────────────────────────────────────────┤
51+│ 4. git push → .husky/pre-push → pnpm harness │
52+│ Full 185-rule validation (all files, all categories) │
53+└─────────────────────────────────────────────────────────────────┘
54+```
55+ 
56+## How to trigger
57+ 
58+When you (the AI agent) are about to commit code:
59+ 
60+```bash
61+# Step 1: Generate review context
62+node .quality-toolkit/tools/pre-commit-review/pre-commit-review.js
63+ 
64+# Step 2: Review the staged diff using the guidelines (see below)
65+ 
66+# Step 3: Record your findings
67+node .quality-toolkit/tools/pre-commit-review/pre-commit-review.js \
68+ --record '{"findings":[
69+ {"severity":"critical","file":"src/a.ts","line":42,"description":"...","triggerPath":"...","status":"open"},
70+ {"severity":"major","file":"src/b.ts","line":10,"description":"...","status":"open"}
71+ ]}'
72+ 
73+# Step 4: If Critical findings → fix them, re-stage, go to Step 1
74+# Step 5: When clean → git commit (the --gate in pre-commit hook verifies)
75+```
76+ 
77+## Review execution
78+ 
79+1. **Read the guidelines** (mandatory): [`../../standards/review/review-guidelines.md`](../../.quality-toolkit/standards/review/review-guidelines.md) — 366-line evidence-based methodology covering logic correctness, architecture, performance, thread safety, polymorphic call-path detection, and evidence requirements.
80+ 
81+2. **Read the review context**: The `pre-commit-review.js` script outputs a JSON context with:
82+ 
83+ - Changed files (sorted by priority: code > config > other)
84+ - Diff hunks with context lines
85+ - File metadata (added/removed line counts)
86+ - Surrounding file context for small files
87+ 
88+3. **Perform the review** following the guidelines:
89+ 
90+ - Focus on logic correctness, architecture, performance, thread safety
91+ - Avoid generic coding style feedback (naming, formatting, missing comments)
92+ - Every finding MUST have: specific file + line, trigger path, code evidence
93+ - Use deterministic language — no "possibly", "maybe", "seems like"
94+ 
95+4. **Output structured findings**:
96+ 
97+```
98+## Pre-Commit Review Findings
99+ 
100+### 🔴 Critical (blocks commit)
101+- **[代码逻辑错误]** `src/example.ts:42` — The callback captures `this` but the
102+ owning object is destroyed before the callback fires.
103+ Trigger path: `onMount() → setTimeout(5000) → component unmounted at 2000ms →
104+ callback fires → this.refs.list is null → crash`
105+ Evidence: Read `src/example.ts:38-50`, confirmed no null guard.
106+ 
107+### 🟡 Major (should fix before commit)
108+- **[性能问题]** `src/render.ts:120` — `JSON.parse(JSON.stringify(data))` in a
109+ render loop for a 10k-item list. O(n) per frame at 60fps = 600k ops/sec.
110+ Suggestion: Use structured clone or memoize.
111+ 
112+### 🟢 Minor (can fix later)
113+- **[软件设计]** `src/utils.ts:15` — Circular dependency between utils.ts and
114+ helpers.ts. Consider extracting shared types to a separate file.
115+ 
116+### Summary
117+| Severity | Count | Verdict |
118+|----------|-------|---------|
119+| Critical | 1 | ❌ BLOCK — fix before commit |
120+| Major | 1 | ⚠️ WARN — strongly recommended |
121+| Minor | 1 | ✅ OK — can proceed |
122+```
123+ 
124+## Evidence requirements (from review-guidelines.md)
125+ 
126+Every finding must satisfy ALL of:
127+ 
128+1. **Specific code reference**: file + line number, no vague "somewhere in this file"
129+2. **Trigger path**: complete execution path (function → condition → timing → consequence)
130+3. **Based on read code**: judgments from actual file content, not speculation
131+4. **Excluded safe paths**: if the code path is unreachable (protected by caller), don't report
132+5. **Deterministic wording**: no "possibly/maybe/probably" — either it's a definite issue or a risk with clear conditions
133+6. **Distinguish certainty from risk**: "this WILL cause X" vs "under condition Y, this COULD cause X"
134+ 
135+## Severity levels
136+ 
137+| Level | Color | Meaning | Action |
138+| -------- | ----- | ---------------------------------------------------------------- | ---------------------- |
139+| Critical | 🔴 | Will cause crash, data loss, security vulnerability | Block commit |
140+| Major | 🟡 | Performance degradation, race condition, architectural violation | Strongly recommend fix |
141+| Minor | 🟢 | Code smell, maintainability concern | Can proceed |
142+ 
143+## Integration
144+ 
145+The review runs as part of the pre-commit hook chain:
146+ 
147+```
148+.husky/pre-commit:
149+ 1. gitleaks (secret scan)
150+ 2. lint-staged (formatting + syntax)
151+ 3. pre-commit-review (AI review) ← this skill
152+```
153+ 
154+## Project-specific review guides
155+ 
156+Projects can layer domain-specific review guides on top of the generic guidelines. Place them in `.quality/review-guides/` and they will be read alongside the generic guidelines.
157+ 
158+Example structure:
159+ 
160+```
161+.quality/review-guides/
162+ stability.md ← project-specific stability patterns
163+ security.md ← project-specific security requirements
164+ performance.md ← project-specific performance budgets
165+```
166+ 
167+## Gate enforcement (in .husky/pre-commit)
168+ 
169+The pre-commit hook chain enforces the closed loop:
170+ 
171+```bash
172+# .husky/pre-commit (chained after gitleaks and lint-staged):
173+node ./.quality-toolkit/tools/pre-commit-review/pre-commit-review.js --gate
174+```
175+ 
176+Gate behavior:
177+ 
178+- **Critical unresolved** → `exit 1` (commit BLOCKED, findings listed with trigger paths)
179+- **Major/Minor only** → `exit 0` (warnings shown, commit proceeds)
180+- **No findings file** → `exit 0` by default (set `QUALITY_REQUIRE_REVIEW=1` to enforce mandatory review)
181+ 
182+Bypass: `git commit --no-verify` (skips ALL hooks — use only in emergencies, CI will catch it).
183+ 
184+## Findings file format
185+ 
186+`.quality/review-findings.json`:
187+ 
188+```json
189+{
190+ "recordedAt": "2026-09-27T...",
191+ "stagedSha": "abc123def456",
192+ "findings": [
193+ {
194+ "severity": "critical|major|minor",
195+ "file": "src/example.ts",
196+ "line": 42,
197+ "description": "Clear description of the problem",
198+ "triggerPath": "function A → condition B → timing C → consequence D",
199+ "status": "open|resolved"
200+ }
201+ ]
202+}
203+```
204+ 
205+The file is auto-cleaned after successful commit (add to .gitignore or clean in post-commit).
@@ -1,13 +1,35 @@
1---1---
2-description: Use to run, interpret, or extend the quality-toolkit compliance harness: executing all or selected standards checks, reading the dependency-gated report (pass/warn/skip/fail, skipped-due-to-failed-dependency), validating or rendering the standards registry graph, and adding new specs or checks. Triggers when the user mentions harness, compliance run, registry, dependency gating, standards checks, quality gate, or asks why a check was skipped.2+description: Agent generated from the quality-harness skill.
3mode: subagent3mode: subagent
4---4---
5 5 
6> **Toolkit location**: this repo vendors the quality-toolkit at `.quality-toolkit/`. Toolkit-relative paths mentioned below (e.g. `tools/…`, `standards/…`) resolve under that directory.6> **Toolkit location**: this repo vendors the quality-toolkit at `.quality-toolkit/`. Toolkit-relative paths mentioned below (e.g. `tools/…`, `standards/…`) resolve under that directory.
7 7 
8+## <!-- Sourced from RNOH quality practices, generalized -->
9+ 
10+name: quality-harness
11+description: >-
12+Use to run, interpret, or extend the quality-toolkit compliance harness:
13+executing all or selected standards checks, reading the dependency-gated
14+report (pass/warn/skip/fail, skipped-due-to-failed-dependency), validating
15+or rendering the standards registry graph, and adding new specs or checks.
16+Triggers when the user mentions harness, compliance run, registry,
17+dependency gating, standards checks, quality gate, or asks why a check was
18+skipped.
19+allowed-tools:
20+ 
21+- Read
22+- Grep
23+- Glob
24+- Bash
25+- Edit
26+- Write
27+ 
28+---
29+ 
8# quality-harness — Standards Compliance Harness30# quality-harness — Standards Compliance Harness
9 31 
10-Run and extend the toolkit's harness: the executable bridge between the standards layer (`standards/registry.json`) and the tools layer (checks). See [`../../docs/harness.md`](.quality-toolkit/docs/harness.md) for the full reference, [`../../docs/usage-faq.md`](.quality-toolkit/docs/usage-faq.md) for troubleshooting, and [`../../docs/enforcement.md`](.quality-toolkit/docs/enforcement.md) for how execution is guaranteed in CI (local hooks are convenience; the required pipeline is the contract — use `--commit-range <base>..HEAD` in CI so every commit in the MR is linted, not just HEAD).32+Run and extend the toolkit's harness: the executable bridge between the standards layer (`standards/registry.json`) and the tools layer (checks). See [`../../docs/harness.md`](../../.quality-toolkit/docs/harness.md) for the full reference, [`../../docs/usage-faq.md`](../../.quality-toolkit/docs/usage-faq.md) for troubleshooting, and [`../../docs/enforcement.md`](../../.quality-toolkit/docs/enforcement.md) for how execution is guaranteed in CI (local hooks are convenience; the required pipeline is the contract — use `--commit-range <base>..HEAD` in CI so every commit in the MR is linted, not just HEAD).
11 33 
12## Commands34## Commands
13 35 
@@ -27,13 +49,13 @@ Exit codes: `0` pass · `1` failure (or warning with `--strict`) · `2` registry
27 49 
28## Reading the report50## Reading the report
29 51 
30-| Status | Meaning |52+| Status | Meaning |
31-|--------|---------|53+| -------- | ---------------------------------------------------------------------------- |
32-| `PASS` | check verified the spec |54+| `PASS` | check verified the spec |
33-| `WARN` | advisory violation — never gates dependents |55+| `WARN` | advisory violation — never gates dependents |
34-| `FAIL` | violation — dependent specs' checks are skipped (`dependency failed: <ids>`) |56+| `FAIL` | violation — dependent specs' checks are skipped (`dependency failed: <ids>`) |
35-| `SKIP` | check not applicable (missing tool/config/filesystem input) |57+| `SKIP` | check not applicable (missing tool/config/filesystem input) |
36-| `MANUAL` | spec has no automated check — apply via review |58+| `MANUAL` | spec has no automated check — apply via review |
37 59 
38The report has two levels: **specs** (compliance per registered spec, dependency-gated) and **rules** (the itemized catalog, `standards/rules.json` — every finding carries a stable rule ID like `COM-TAG-01` / `DOC-LNK-02` / `I18N-CMT-01`). Cite rule IDs in fix tasks and PRs; use `harness rules` to browse the catalog.60The report has two levels: **specs** (compliance per registered spec, dependency-gated) and **rules** (the itemized catalog, `standards/rules.json` — every finding carries a stable rule ID like `COM-TAG-01` / `DOC-LNK-02` / `I18N-CMT-01`). Cite rule IDs in fix tasks and PRs; use `harness rules` to browse the catalog.
39 61 
@@ -46,6 +68,21 @@ When a dependent category is skipped, **fix the root (dependency) failure first*
463. Fix the target repo (never weaken the spec to make a check pass; propose a spec change via a docs PR instead).683. Fix the target repo (never weaken the spec to make a check pass; propose a spec change via a docs PR instead).
474. Re-run the single check, then the full harness to confirm no regressions or newly-gated dependents.694. Re-run the single check, then the full harness to confirm no regressions or newly-gated dependents.
48 70 
71+## ⛔ Bypass Protection (MANDATORY)
72+ 
73+You MUST NOT:
74+ 
75+1. Use `--no-verify` on any git command without explicit user confirmation after reporting the issue.
76+2. Clear `core.hooksPath` or delete hook files to skip checks.
77+3. Modify `.quality/harness.config.json` to silence a failure (e.g., adding checks to `advisoryChecks` without a cleanup plan).
78+4. Skip running the harness when it's part of the workflow.
79+ 
80+If you believe a check is a false positive:
81+ 
82+- Report the exact check, error, and your reasoning to the user
83+- **Wait for explicit user confirmation** before any bypass
84+- Document the bypass reason in the commit message body
85+ 
49## Extending86## Extending
50 87 
51- **New rule (the itemized path)**: add an entry to `standards/rules.json` (`{id, spec, title, severity, automation, check}`) — done for a manual rule; for an automated one also declare it in the owning check's `rules` array (or attribute findings per rule via `ruleResults`). `pnpm harness:validate` keeps IDs unique and wiring resolvable.88- **New rule (the itemized path)**: add an entry to `standards/rules.json` (`{id, spec, title, severity, automation, check}`) — done for a manual rule; for an automated one also declare it in the owning check's `rules` array (or attribute findings per rule via `ruleResults`). `pnpm harness:validate` keeps IDs unique and wiring resolvable.
@@ -58,3 +95,29 @@ When a dependent category is skipped, **fix the root (dependency) failure first*
58- The harness core stays zero-dependency Node — checks that need external tools must resolve binaries defensively and `skip` when absent.95- The harness core stays zero-dependency Node — checks that need external tools must resolve binaries defensively and `skip` when absent.
59- Standards layer never references the harness; wiring lives only in `registry.json` and `rules.json`.96- Standards layer never references the harness; wiring lives only in `registry.json` and `rules.json`.
60- Do not modify the catalogs to silence a failure — fix the repo, the check, or record an explicit override in project config with a cleanup note.97- Do not modify the catalogs to silence a failure — fix the repo, the check, or record an explicit override in project config with a cleanup note.
98+ 
99+## End-to-end workflow
100+ 
101+The complete development loop (Issue → Branch → Code → Test → Commit → PR → CI) is documented in [`../../docs/workflow.md`](../../.quality-toolkit/docs/workflow.md) with one command per step. Key entry points:
102+ 
103+```bash
104+# Create a spec-compliant issue
105+node .quality-toolkit/tools/create-issue/create-issue.js --type bug --title "…" --fields '{…}' --yes
106+ 
107+# Create branch + PR (auto-carries test evidence from the diff, links issue)
108+GBR_SCOPE=X GBR_DESC="…" GBR_ISSUE=42 GBR_YES=1 \
109+node .quality-toolkit/tools/generate-branch-pr/generate-branch-pr.js
110+```
111+ 
112+## Submit (one-command workflow)
113+ 
114+After committing (with test reference + DCO), the complete issue + PR + linkage flow is one command:
115+ 
116+```bash
117+pnpm submit --create-issue --yes
118+`pnpm submit --create-issue --issue-type bug --issue-title "crash on scroll" --yes` # custom issue type/title # issue + PR + link
119+pnpm submit --issue 42 --yes # PR linked to existing issue
120+pnpm submit --yes # PR only
121+```
122+ 
123+See [`../../docs/workflow.md`](../../.quality-toolkit/docs/workflow.md) and [`../../tools/submit/`](../../.quality-toolkit/tools/submit/) for details.
@@ -7,11 +7,21 @@ mode: subagent
7 7 
8# Submit PRs8# Submit PRs
9 9 
10+## ⛔ Bypass Protection (MANDATORY)
11+ 
12+Before any commit or push in this workflow:
13+ 
14+1. **NEVER use `--no-verify`** without explicit user confirmation after reporting the issue.
15+2. **NEVER skip the pre-commit AI review** — the gate runs automatically on `git commit`.
16+3. If the gate blocks: fix the issue, re-stage, re-commit. Do not bypass.
17+4. If you believe a finding is a false positive: report to user → wait for confirmation → only then bypass.
18+5. The push-guard in `.husky/pre-push` validates ALL commits in the push range — bypassing at commit does NOT bypass at push.
19+ 
10## Overview20## Overview
11 21 
12Automate the contribution flow for a multi-branch maintained project: apply selected diffs to target local repositories, create signed commits, push branches, create Issues/PRs from the community templates, link them, and comment `start build`.22Automate the contribution flow for a multi-branch maintained project: apply selected diffs to target local repositories, create signed commits, push branches, create Issues/PRs from the community templates, link them, and comment `start build`.
13 23 
14-> This skill depends on the tools layer `tools/generate-branch-pr/` (commit/PR generation scripts) and the standards layer `standards/community/` (commit and PR spec, Issue spec).24+> This skill depends on the tools layer `tools/submit-prs/` (multi-branch Python automation), `tools/static-check/` (static analysis), `tools/submit/` (one-command Node.js) and the standards layer `standards/community/` (commit and PR spec, Issue spec).
15 25 
16## Required Inputs26## Required Inputs
17 27 
@@ -34,7 +44,7 @@ Default primary language is `zh-CN`. If no language config is found, generate co
34Before drafting commit, PR, or Issue text, run:44Before drafting commit, PR, or Issue text, run:
35 45 
36```bash46```bash
37-python3 <skill>/scripts/submit.py language47+python3 .quality-toolkit/tools/submit-prs/scripts/submit_prs.py language
38```48```
39 49 
40Use the returned `primary_language` for all generated text. Language priority:50Use the returned `primary_language` for all generated text. Language priority:
@@ -91,10 +101,10 @@ Issue rules from this workflow:
91 101 
92The skill supports two modes. Infer the mode from the user's instruction; when ambiguous, ask.102The skill supports two modes. Infer the mode from the user's instruction; when ambiguous, ask.
93 103 
94-| Mode | When to use | Steps executed |104+| Mode | When to use | Steps executed |
95-|---|---|---|105+| --------------- | -------------------------------------------------------------------------------------------------- | -------------- |
96-| **commit-push** | User says "just commit/push", "don't create a PR", "commit first", "help me commit the code" | Steps 1–4 only |106+| **commit-push** | User says "just commit/push", "don't create a PR", "commit first", "help me commit the code" | Steps 1–4 only |
97-| **full** | User says "commit and create a PR", "run the full flow", or gives no special instruction (default) | Steps 1–9 |107+| **full** | User says "commit and create a PR", "run the full flow", or gives no special instruction (default) | Steps 1–9 |
98 108 
99In **commit-push** mode, stop after all branches are pushed. Do not create Issue, PR, or comment `start build`.109In **commit-push** mode, stop after all branches are pushed. Do not create Issue, PR, or comment `start build`.
100 110 
@@ -106,13 +116,13 @@ In **commit-push** mode, stop after all branches are pushed. Do not create Issue
106 - Before creating or switching to the feature branch, fetch the latest target baseline branch from the upstream remote, for example `git fetch upstream <branch> --prune`.116 - Before creating or switching to the feature branch, fetch the latest target baseline branch from the upstream remote, for example `git fetch upstream <branch> --prune`.
107 - Verify the fetched remote ref exists, then create/reset the feature branch from the freshly fetched upstream ref, for example `git checkout -B <feature-branch> upstream/<branch>`. Never create a feature branch from a stale local branch or previously fetched baseline.117 - Verify the fetched remote ref exists, then create/reset the feature branch from the freshly fetched upstream ref, for example `git checkout -B <feature-branch> upstream/<branch>`. Never create a feature branch from a stale local branch or previously fetched baseline.
108 - Read community docs from the freshly checked-out target baseline when available, and use those docs as the current source of truth before drafting commit/PR/Issue text:118 - Read community docs from the freshly checked-out target baseline when available, and use those docs as the current source of truth before drafting commit/PR/Issue text:
109- - `standards/community/commit-and-pr-spec.md`119+ - `standards/community/commit-and-pr-spec.md`
110- - `standards/community/issue-spec.md`120+ - `standards/community/issue-spec.md`
111- - Query the effective primary language with `python3 <skill>/scripts/submit.py language` and follow it when drafting commit/PR/Issue text.121+ - Query the effective primary language with `python3 .quality-toolkit/tools/submit-prs/scripts/submit_prs.py language` and follow it when drafting commit/PR/Issue text.
112 - Apply patch with `git apply --check` then `git apply`.122 - Apply patch with `git apply --check` then `git apply`.
113 - Run `git diff --check`.123 - Run `git diff --check`.
114 - Stage the patch (`git add <files>`).124 - Stage the patch (`git add <files>`).
115- - Run `python3 <skill>/scripts/static_check.py --staged --fix`. If the private key is not configured this is a silent no-op. Then run `clang-format --style=file:.clang-format -i` for touched `.cpp`/`.h` files, re-stage touched files, run `python3 <skill>/scripts/static_check.py --staged`, and finish with `git diff --check --cached`.125+ - Run `python3 .quality-toolkit/tools/static-check/scripts/static_check.py --staged --fix`. If the private key is not configured this is a silent no-op. Then run `clang-format --style=file:.clang-format -i` for touched `.cpp`/`.h` files, re-stage touched files, run `python3 .quality-toolkit/tools/static-check/scripts/static_check.py --staged`, and finish with `git diff --check --cached`.
116 - Commit with `git commit -s`.126 - Commit with `git commit -s`.
117 - Push to `origin`.127 - Push to `origin`.
1183. For each pushed branch, create a PR to the matching target branch.1283. For each pushed branch, create a PR to the matching target branch.
@@ -121,7 +131,7 @@ In **commit-push** mode, stop after all branches are pushed. Do not create Issue
1216. Comment `start build` on each PR.1316. Comment `start build` on each PR.
1227. Verify by querying PR URL, Issue URL, linked Issue list, and comment list.1327. Verify by querying PR URL, Issue URL, linked Issue list, and comment list.
123 133 
124-Use `scripts/submit.py` (located in this skill package, or in the tools layer `tools/generate-branch-pr/`) for repeatable operations. Run `--help` before first use.134+Use the appropriate tool: `python3 .quality-toolkit/tools/submit-prs/scripts/submit_prs.py` for multi-branch operations, or `pnpm submit` (Node.js, `tools/submit/submit.js`) for single-branch work. Run `--help` before first use.
125 135 
126## Branch Matrix Guidance136## Branch Matrix Guidance
127 137 
@@ -172,10 +182,10 @@ Commit flow requirements:
172Manual command for maintainers:182Manual command for maintainers:
173 183 
174```bash184```bash
175-python3 <skill>/scripts/static_check.py --staged --fix185+python3 .quality-toolkit/tools/static-check/scripts/static_check.py --staged --fix
176clang-format --style=file:.clang-format -i <touched-cpp-or-h-files>186clang-format --style=file:.clang-format -i <touched-cpp-or-h-files>
177git add <files>187git add <files>
178-python3 <skill>/scripts/static_check.py --staged188+python3 .quality-toolkit/tools/static-check/scripts/static_check.py --staged
179git diff --check --cached189git diff --check --cached
180```190```
181 191 
@@ -188,7 +198,7 @@ git diff --check --cached
188 - `~/.config/codex-secrets/gitcode.env`: `<HOST>_TOKEN=...`, `<HOST>_FORK_PATH=...`, or `SUBMIT_PRIMARY_LANGUAGE=...` for zsh/bash/Linux/macOS/Windows Git Bash.198 - `~/.config/codex-secrets/gitcode.env`: `<HOST>_TOKEN=...`, `<HOST>_FORK_PATH=...`, or `SUBMIT_PRIMARY_LANGUAGE=...` for zsh/bash/Linux/macOS/Windows Git Bash.
189 - `~/.config/codex-secrets/gitcode.fish`: `set -gx <HOST>_TOKEN ...`, `set -gx <HOST>_FORK_PATH ...`, or `set -gx SUBMIT_PRIMARY_LANGUAGE ...` for fish.199 - `~/.config/codex-secrets/gitcode.fish`: `set -gx <HOST>_TOKEN ...`, `set -gx <HOST>_FORK_PATH ...`, or `set -gx SUBMIT_PRIMARY_LANGUAGE ...` for fish.
190 - Validate without printing the token: `test -n "$<HOST>_TOKEN" && echo TOKEN_SET`.200 - Validate without printing the token: `test -n "$<HOST>_TOKEN" && echo TOKEN_SET`.
191- - Validate the effective primary language: `python3 <skill>/scripts/submit.py language`.201+ - Validate the effective primary language: `python3 .quality-toolkit/tools/submit-prs/scripts/submit_prs.py language`.
192 - Never print, echo, log, or write the token value.202 - Never print, echo, log, or write the token value.
193- PR create endpoint: `POST /api/v5/repos/<org>/<repo>/pulls` (adjust to target project).203- PR create endpoint: `POST /api/v5/repos/<org>/<repo>/pulls` (adjust to target project).
194- Cross-fork `head`: `<fork-owner>:<branch>`, for example from `<HOST>_FORK_PATH=yourname/repo`.204- Cross-fork `head`: `<fork-owner>:<branch>`, for example from `<HOST>_FORK_PATH=yourname/repo`.
@@ -217,3 +227,18 @@ rg -n 'PRIVATE-TOKEN: [^$]|TOKEN=.*[A-Za-z0-9_-]{20}|@[A-Za-z0-9_.-]+\\.[A-Za-z]
217```227```
218 228 
219Expected matches should only be documentation examples or generic token variable names, not token values.229Expected matches should only be documentation examples or generic token variable names, not token values.
230+ 
231+## Static-check integration
232+ 
233+PRs created via `submit_prs.py` automatically run `tools/static-check/scripts/static_check.py` on the changed files before submission. Cyclomatic complexity > 20, function length > 50 NBNC lines, or nesting depth > 4 blocks the PR until fixed. Rule sets are external (see `tools/static-check/rules/`).
234+ 
235+## Multi-version submission
236+ 
237+For projects maintaining multiple release lines (e.g., 0.84-main + 0.82-main), `submit_prs.py` creates PRs for each version simultaneously:
238+ 
239+```bash
240+python3 .quality-toolkit/tools/submit-prs/scripts/submit_prs.py create-pr \
241+ --versions 0.84 0.82 \
242+ --patch-file fix.patch \
243+ --title "fix(Component): description"
244+```
@@ -1,6 +1,6 @@
1---1---
2name: cptf-test-api2name: cptf-test-api
3-description: Use when writing user-facing test cases that consume CPTF public APIs (Test / Step / PerformanceTest / FunctionalTest / StabilityTest / UIAutomation / ExpectExecutor) for v0.2.1+. Triggers: "@Test", "@Step", "TestConfig", "StepConfig", "uiComparison", "SceneContract", "UIAutomation", "expect", "MetricCollector", "VisualComparator", "stability", "whiteScreen", "memoryLeak", "SmartMonkey", "from '@cptf/framework'", "scene contract", "Section". Do NOT use for framework internals (use cptf-framework-dev), CLI commands (use cptf-cli), or troubleshooting (use cptf-troubleshoot).3+description: Use when writing user-facing test cases that consume CPTF public APIs (Test / Step / PerformanceTest / FunctionalTest / StabilityTest / UIAutomation / ExpectExecutor) for v0.3.x. Triggers: "@Test", "@Step", "TestConfig", "StepConfig", "uiComparison", "SceneContract", "UIAutomation", "expect", "MetricCollector", "VisualComparator", "stability", "whiteScreen", "memoryLeak", "SmartMonkey", "from '@cptf/framework'", "scene contract", "Section". Do NOT use for framework internals (use cptf-framework-dev), CLI commands (use cptf-cli), or troubleshooting (use cptf-troubleshoot).
4---4---
5 5 
6# CPTF Test API Reference6# CPTF Test API Reference
@@ -35,6 +35,7 @@ export interface SceneDefinition {
35```35```
36 36 
37**Rules**:37**Rules**:
38+ 
38- Scene root testID MUST use `SCENE_TESTID_PREFIX` prefix (R4, R5)39- Scene root testID MUST use `SCENE_TESTID_PREFIX` prefix (R4, R5)
39- Section container testID MUST use `SECTION_TESTID_PREFIX` prefix (R44)40- Section container testID MUST use `SECTION_TESTID_PREFIX` prefix (R44)
40- Application layer MUST use `import type` for cptf type imports; runtime constants defined locally (R48, R49)41- Application layer MUST use `import type` for cptf type imports; runtime constants defined locally (R48, R49)
@@ -47,103 +48,139 @@ export interface SceneDefinition {
47## Enum Types (Complete Reference)48## Enum Types (Complete Reference)
48 49 
49### Platform50### Platform
51+ 
50```typescript52```typescript
51type Platform = 'harmony' | 'android' | 'ios';53type Platform = 'harmony' | 'android' | 'ios';
52```54```
55+ 
53Default: `'harmony'` (when device platform unspecified)56Default: `'harmony'` (when device platform unspecified)
54 57 
55### TestModule58### TestModule
59+ 
56```typescript60```typescript
57type TestModule = 'functional' | 'performance' | 'stability';61type TestModule = 'functional' | 'performance' | 'stability';
58```62```
59-| Value | BaseTest Default | StabilityTest Default | Usage |63+ 
60-|-------|-----------------|----------------------|-------|64+| Value | BaseTest Default | StabilityTest Default | Usage |
61-| `'performance'` | **YES** | | PerformanceTest |65+| --------------- | ---------------- | --------------------- | ------------------------------ |
62-| `'functional'` | | | FunctionalTest(含跨平台对比) |66+| `'performance'` | **YES** | | PerformanceTest |
63-| `'stability'` | | **YES** | StabilityTest |67+| `'functional'` | | | FunctionalTest(含跨平台对比) |
68+| `'stability'` | | **YES** | StabilityTest |
64 69 
65### TestPlatform70### TestPlatform
71+ 
66```typescript72```typescript
67type TestPlatform = Platform | 'all' | Platform[];73type TestPlatform = Platform | 'all' | Platform[];
68```74```
75+ 
69Default: `'all'`76Default: `'all'`
70 77 
71-| Value | Meaning |78+| Value | Meaning |
72-|-------|---------|79+| ------------------------ | ------------------------------------- |
73-| `'harmony'` | HarmonyOS only |80+| `'harmony'` | HarmonyOS only |
74-| `'android'` | Android only |81+| `'android'` | Android only |
75-| `'ios'` | iOS only (reserved) |82+| `'ios'` | iOS only (reserved) |
76-| `'all'` | All available platforms (**default**) |83+| `'all'` | All available platforms (**default**) |
77-| `['harmony', 'android']` | Array of specific platforms |84+| `['harmony', 'android']` | Array of specific platforms |
78 85 
79### PerfMetricType (13 values)86### PerfMetricType (13 values)
87+ 
80```typescript88```typescript
81type PerfMetricType =89type PerfMetricType =
82- | 'startLatency' | 'finishLatency' | 'exitLatency'90+ | 'startLatency'
83- | 'fps' | 'fpsMin' | 'fpsMax' | 'fpsJank' | 'fpsStdDev'91+ | 'finishLatency'
92+ | 'exitLatency'
93+ | 'fps'
94+ | 'fpsMin'
95+ | 'fpsMax'
96+ | 'fpsJank'
97+ | 'fpsStdDev'
84 | 'blankScreenRatio'98 | 'blankScreenRatio'
85- | 'memoryPeak' | 'memoryAvg'99+ | 'memoryPeak'
86- | 'cpuPeak' | 'cpuAvg';100+ | 'memoryAvg'
101+ | 'cpuPeak'
102+ | 'cpuAvg';
87```103```
88-| Value | Unit | Description |104+ 
89-|-------|------|-------------|105+| Value | Unit | Description |
90-| `startLatency` | ms | App start latency |106+| ------------------ | ------- | ---------------------- |
91-| `finishLatency` | ms | App finish latency |107+| `startLatency` | ms | App start latency |
92-| `exitLatency` | ms | App exit latency |108+| `finishLatency` | ms | App finish latency |
93-| `fps` | fps | Average FPS |109+| `exitLatency` | ms | App exit latency |
94-| `fpsMin` | fps | Minimum FPS |110+| `fps` | fps | Average FPS |
95-| `fpsMax` | fps | Maximum FPS |111+| `fpsMin` | fps | Minimum FPS |
96-| `fpsJank` | count | Jank frame count |112+| `fpsMax` | fps | Maximum FPS |
97-| `fpsStdDev` | fps | FPS standard deviation |113+| `fpsJank` | count | Jank frame count |
98-| `blankScreenRatio` | 0.0-1.0 | Blank screen ratio |114+| `fpsStdDev` | fps | FPS standard deviation |
99-| `memoryPeak` | KB | Peak memory usage |115+| `blankScreenRatio` | 0.0-1.0 | Blank screen ratio |
100-| `memoryAvg` | KB | Average memory usage |116+| `memoryPeak` | KB | Peak memory usage |
101-| `cpuPeak` | % | Peak CPU usage |117+| `memoryAvg` | KB | Average memory usage |
102-| `cpuAvg` | % | Average CPU usage |118+| `cpuPeak` | % | Peak CPU usage |
119+| `cpuAvg` | % | Average CPU usage |
103 120 
104### MetricType (16 values — broader than PerfMetricType)121### MetricType (16 values — broader than PerfMetricType)
122+ 
105```typescript123```typescript
106type MetricType =124type MetricType =
107- | 'fps' | 'fpsMin' | 'fpsMax' | 'fpsStdDev'125+ | 'fps'
108- | 'memoryPeak' | 'memoryAvg' | 'cpuPeak' | 'cpuAvg'126+ | 'fpsMin'
109- | 'whiteScreenRate' | 'whiteScreenCount'127+ | 'fpsMax'
110- | 'completionLatency' | 'completionLatencyAvg' | 'completionLatencyMax'128+ | 'fpsStdDev'
111- | 'crashCount' | 'jankCount' | 'droppedFrames';129+ | 'memoryPeak'
130+ | 'memoryAvg'
131+ | 'cpuPeak'
132+ | 'cpuAvg'
133+ | 'whiteScreenRate'
134+ | 'whiteScreenCount'
135+ | 'completionLatency'
136+ | 'completionLatencyAvg'
137+ | 'completionLatencyMax'
138+ | 'crashCount'
139+ | 'jankCount'
140+ | 'droppedFrames';
112```141```
113 142 
114### AggregateStrategy143### AggregateStrategy
144+ 
115```typescript145```typescript
116type AggregateStrategy = 'average' | 'median' | 'max' | 'min';146type AggregateStrategy = 'average' | 'median' | 'max' | 'min';
117```147```
118-| Value | Description | Use Case |148+ 
119-|-------|-------------|----------|149+| Value | Description | Use Case |
150+| ----------- | --------------- | ------------------------------------ |
120| `'average'` | Arithmetic mean | General (**default recommendation**) |151| `'average'` | Arithmetic mean | General (**default recommendation**) |
121-| `'median'` | Median value | Outlier-resistant |152+| `'median'` | Median value | Outlier-resistant |
122-| `'max'` | Maximum value | Worst-case analysis |153+| `'max'` | Maximum value | Worst-case analysis |
123-| `'min'` | Minimum value | Best-case analysis |154+| `'min'` | Minimum value | Best-case analysis |
124 155 
125### AlertLevel156### AlertLevel
157+ 
126```typescript158```typescript
127type AlertLevel = 'warning' | 'error' | 'critical';159type AlertLevel = 'warning' | 'error' | 'critical';
128```160```
129 161 
130### ReportFormat162### ReportFormat
163+ 
131```typescript164```typescript
132type ReportFormat = 'html' | 'markdown' | 'json' | 'all';165type ReportFormat = 'html' | 'markdown' | 'json' | 'all';
133```166```
167+ 
134Default: `'all'` (test-runner.ts); `'html'` (cptf.config.json init)168Default: `'all'` (test-runner.ts); `'html'` (cptf.config.json init)
135 169 
136### LoggerLevel170### LoggerLevel
171+ 
137```typescript172```typescript
138type LoggerLevel = 'debug' | 'info' | 'warn' | 'error';173type LoggerLevel = 'debug' | 'info' | 'warn' | 'error';
139```174```
140 175 
141### AppState.state176### AppState.state
177+ 
142```typescript178```typescript
143type AppStateValue = 'foreground' | 'background' | 'crashed' | 'not_running';179type AppStateValue = 'foreground' | 'background' | 'crashed' | 'not_running';
144```180```
145 181 
146### DeviceInfo.status182### DeviceInfo.status
183+ 
147```typescript184```typescript
148type DeviceStatus = 'connected' | 'disconnected' | 'error';185type DeviceStatus = 'connected' | 'disconnected' | 'error';
149```186```
@@ -199,60 +236,63 @@ export interface TestConfig {
199 236 
200### TestConfig Complete Property Reference237### TestConfig Complete Property Reference
201 238 
202-| Property | Type | Required | BaseTest Default | StabilityTest Default | Boundary / Notes |239+| Property | Type | Required | BaseTest Default | StabilityTest Default | Boundary / Notes |
203-|----------|------|----------|-----------------|----------------------|------------------|240+| --------------------- | --------------------- | -------- | ---------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
204-| `id` | string | YES | `'test'` | `'stability-test'` | Must match `^[a-zA-Z0-9_-]+$`; maps to `test-case-{id}` |241+| `id` | string | YES | `'test'` | `'stability-test'` | Must match `^[a-zA-Z0-9_-]+$`; maps to `test-case-{id}` |
205-| `name` | string | YES | `'测试'` | `'稳定性测试'` | Display name in reports |242+| `name` | string | YES | `'测试'` | `'稳定性测试'` | Display name in reports |
206-| `module` | TestModule | YES | `'performance'` | `'stability'` | 3 values: functional/performance/stability |243+| `module` | TestModule | YES | `'performance'` | `'stability'` | 3 values: functional/performance/stability |
207-| `platform` | TestPlatform | NO | `'all'` | `'all'` | 5 single values + Platform[] |244+| `platform` | TestPlatform | NO | `'all'` | `'all'` | 5 single values + Platform[] |
208-| `description` | string | NO | undefined | undefined | Free text |245+| `description` | string | NO | undefined | undefined | Free text |
209-| `scene` | string | NO | undefined | undefined | Must match `^[a-zA-Z0-9_-]+$`; WITHOUT "scene-" prefix; auto-prefixed by framework |246+| `scene` | string | NO | undefined | undefined | Must match `^[a-zA-Z0-9_-]+$`; WITHOUT "scene-" prefix; auto-prefixed by framework |
210-| `category` | string | NO | undefined | undefined | Must match `^[a-zA-Z0-9_-]+$`; auto-prefixed "category-" in navigation |247+| `category` | string | NO | undefined | undefined | Must match `^[a-zA-Z0-9_-]+$`; auto-prefixed "category-" in navigation |
211-| `sections` | string[] | NO | undefined | undefined | Section IDs declared in this test |248+| `sections` | string[] | NO | undefined | undefined | Section IDs declared in this test |
212-| `captureScreenshots` | boolean | NO | `true` | `false` | Stability tests default off for performance |249+| `captureScreenshots` | boolean | NO | `true` | `false` | Stability tests default off for performance |
213-| `waitAfterAction` | number | NO | `300` | undefined | ms; min=0, max=60000 practical |250+| `waitAfterAction` | number | NO | `300` | undefined | ms; min=0, max=60000 practical |
214-| `autoRecoverScene` | boolean | NO | undefined | undefined | Auto-recover when not in correct scene |251+| `autoRecoverScene` | boolean | NO | undefined | undefined | Auto-recover when not in correct scene |
215-| `autoLogProgress` | boolean | NO | undefined | undefined | Log progress every `reportInterval` iterations |252+| `autoLogProgress` | boolean | NO | undefined | undefined | Log progress every `reportInterval` iterations |
216-| `baselines` | Record\<string, any\> | NO | undefined | undefined | Key-value baseline data |253+| `baselines` | Record\<string, any\> | NO | undefined | undefined | Key-value baseline data |
217-| `expects` | ExpectCheck[] | NO | undefined | undefined | See ExpectCheck section |254+| `expects` | ExpectCheck[] | NO | undefined | undefined | See ExpectCheck section |
218-| `compareWithBaseline` | boolean | NO | undefined | undefined | Enable baseline comparison |255+| `compareWithBaseline` | boolean | NO | undefined | undefined | Enable baseline comparison |
219-| `baselinePlatform` | string | NO | undefined | undefined | Platform for baseline |256+| `baselinePlatform` | string | NO | undefined | undefined | Platform for baseline |
220-| `allowedDeviation` | number | NO | undefined | undefined | Deviation tolerance; 0=exact; typical 0-100% |257+| `allowedDeviation` | number | NO | undefined | undefined | Deviation tolerance; 0=exact; typical 0-100% |
221-| `collect` | PerfMetricType[] | NO | undefined | undefined | 1-16 metric types from PerfMetricType |258+| `collect` | PerfMetricType[] | NO | undefined | undefined | Metric types from PerfMetricType (19-member union: 17 built-in + 2 plugin). Raw channels `memory`/`cpu` are NOT members — use aggregated `memoryPeak`/`cpuAvg` etc. |
222-| `executeTimes` | number | NO | undefined | undefined | Min=1; performance test iterations |259+| `executeTimes` | number | NO | undefined | undefined | Min=1; performance test iterations |
223-| `aggregate` | AggregateStrategy | NO | undefined | undefined | 4 values: average/median/max/min |260+| `aggregate` | AggregateStrategy | NO | undefined | undefined | 4 values: average/median/max/min |
224-| `warmupIterations` | number | NO | undefined | undefined | Min=0; iterations to discard |261+| `warmupIterations` | number | NO | undefined | undefined | Min=0; iterations to discard |
225-| `cooldownMs` | number | NO | undefined | undefined | Min=0; ms between iterations |262+| `cooldownMs` | number | NO | undefined | undefined | Min=0; ms between iterations |
226-| `thresholds` | TestThreshold[] | NO | undefined | undefined | See TestThreshold section |263+| `thresholds` | TestThreshold[] | NO | undefined | undefined | See TestThreshold section |
227-| `executeTimes` | number | NO | module-dependent | Min=1; stability/performance iterations (R52: NOT repeatTimes) |264+| `executeTimes` | number | NO | module-dependent | Min=1; stability/performance iterations (R52: NOT repeatTimes) |
228-| `durationMs` | number | NO | undefined | undefined | Min=0; stability duration mode (overrides executeTimes) |265+| `durationMs` | number | NO | undefined | undefined | Min=0; stability duration mode (overrides executeTimes) |
229-| `checkAppAlive` | boolean | NO | undefined | `true` | Check app alive each iteration |266+| `checkAppAlive` | boolean | NO | undefined | `true` | Check app alive each iteration |
230-| `collectCrashLogs` | boolean | NO | undefined | `true` | Collect crash logs on crash |267+| `collectCrashLogs` | boolean | NO | undefined | `true` | Collect crash logs on crash |
231-| `intervalMs` | number | NO | `100` (fallback) | `100` | Min=0; ms between iterations; 0=no delay |268+| `intervalMs` | number | NO | `100` (fallback) | `100` | Min=0; ms between iterations; 0=no delay |
232-| `reportInterval` | number | NO | `10` (fallback) | `10` | Min=1; iterations between progress reports |269+| `reportInterval` | number | NO | `10` (fallback) | `10` | Min=1; iterations between progress reports |
233-| `checkANR` | boolean | NO | undefined | undefined | Check for ANR each iteration |270+| `checkANR` | boolean | NO | undefined | undefined | Check for ANR each iteration |
234-| `checkWhiteScreen` | boolean | NO | undefined | undefined | Check for white screen periodically (every sceneCheckInterval) |271+| `checkWhiteScreen` | boolean | NO | undefined | undefined | Check for white screen periodically (every sceneCheckInterval) |
235-| `checkMemoryLeak` | boolean | NO | undefined | undefined | Run memory leak detection at test end |272+| `checkMemoryLeak` | boolean | NO | undefined | undefined | Run memory leak detection at test end |
236-| `recoverFromCrash` | boolean | NO | undefined | undefined | Auto-recover: forceStop → launchApp → ensureInScene |273+| `recoverFromCrash` | boolean | NO | undefined | undefined | Auto-recover: forceStop → launchApp → ensureInScene |
237-| `sceneCheckInterval` | number | NO | `10` (fallback) | undefined | Iterations between scene recovery AND white screen checks; dual-use |274+| `sceneCheckInterval` | number | NO | `10` (fallback) | undefined | Iterations between scene recovery AND white screen checks; dual-use |
238-| `useSmartMonkey` | boolean | NO | undefined | undefined | Use SmartMonkey for random actions |275+| `useSmartMonkey` | boolean | NO | undefined | undefined | Use SmartMonkey for random actions |
239 276 
240**Required fields**: `id`, `name`, `module`, `scene` (R20)277**Required fields**: `id`, `name`, `module`, `scene` (R20)
241 278 
242**Navigation ID Naming Convention** (enforced at compile-time by scanner + runtime by autoNavigate):279**Navigation ID Naming Convention** (enforced at compile-time by scanner + runtime by autoNavigate):
280+ 
243- All navigation identifiers (`id`, `category`, `module`, `scene`) MUST match `^[a-zA-Z0-9_-]+$`281- All navigation identifiers (`id`, `category`, `module`, `scene`) MUST match `^[a-zA-Z0-9_-]+$`
244-- Allowed: letters (a-z, A-Z), digits (0-9), hyphens (-), underscores (_)282+- Allowed: letters (a-z, A-Z), digits (0-9), hyphens (-), underscores (\_)
245- Prohibited: Chinese characters, spaces, dots, slashes, special symbols283- Prohibited: Chinese characters, spaces, dots, slashes, special symbols
246- Reason: search navigation uses `adb input text` which only supports ASCII; search boxes match by these IDs284- Reason: search navigation uses `adb input text` which only supports ASCII; search boxes match by these IDs
247- Validation: `cptf index` (compile-time warning) + `autoNavigate` (runtime error)285- Validation: `cptf index` (compile-time warning) + `autoNavigate` (runtime error)
248 286 
249**Search Navigation Contract** (framework default navigation):287**Search Navigation Contract** (framework default navigation):
288+ 
250- App pages MUST provide search inputs: `module-search`, `category-search`, `test-case-search`289- App pages MUST provide search inputs: `module-search`, `category-search`, `test-case-search`
251- List items MUST follow testID pattern: `module-{id}`, `category-{id}`, `test-case-{id}`, `scene-{id}`290- List items MUST follow testID pattern: `module-{id}`, `category-{id}`, `test-case-{id}`, `scene-{id}`
252- Navigation strategy: search box input → close keyboard → click result; fallback: scroll-to-find291- Navigation strategy: search box input → close keyboard → click result; fallback: scroll-to-find
253- Page transitions clear search state automatically292- Page transitions clear search state automatically
254 293 
255**Key rules**:294**Key rules**:
295+ 
256- TestCase.scene: use name WITHOUT "scene-" prefix (R4)296- TestCase.scene: use name WITHOUT "scene-" prefix (R4)
257- Use declarative pattern for 95% cases (R15)297- Use declarative pattern for 95% cases (R15)
258- Stability tests: MUST set `executeTimes` or `durationMs`, plus `stability.enabled[]` detectors (R21/R52)298- Stability tests: MUST set `executeTimes` or `durationMs`, plus `stability.enabled[]` detectors (R21/R52)
@@ -263,25 +303,25 @@ export interface TestConfig {
263 303 
264**Step Naming & Manual Verification Rule (R52)**:304**Step Naming & Manual Verification Rule (R52)**:
265 305 
266-Step设计必须满足手工验证者能理解预期行为:306+Step 设计必须满足手工验证者能理解预期行为:
267 307 
268-1. **Step名称必须描述属性核心差异** — 禁止对不区分属性值的通用行为命名(如"验证Switch可点击"对hitSlop无区分度)308+1. **Step 名称必须描述属性核心差异** — 禁止对不区分属性值的通用行为命名(如"验证 Switch 可点击"对 hitSlop 无区分度)
269-2. **每个Step必须让验证者知道**:309+2. **每个 Step 必须让验证者知道**:
270 - 观察什么(哪个元素/状态文本)310 - 观察什么(哪个元素/状态文本)
271 - 理想预期是什么(属性语义应有的行为)311 - 理想预期是什么(属性语义应有的行为)
272 - 实际可能是什么(平台限制导致的行为差异)312 - 实际可能是什么(平台限制导致的行为差异)
273-3. **差异属性必须有对照基准** — hitSlop/pointerEvents/disabled等必须提供"有/无"属性的两个场景对比313+3. **差异属性必须有对照基准** — hitSlop/pointerEvents/disabled 等必须提供"有/无"属性的两个场景对比
274-4. **禁止冗余Step** — 多个Step断言完全相同时只保留一个(如hitSlop=0等价无hitSlop)314+4. **禁止冗余 Step** — 多个 Step 断言完全相同时只保留一个(如 hitSlop=0 等价无 hitSlop)
275-5. **禁止验证与属性无关的行为** — hitSlop不验证clickable/checked(不区分有/无hitSlop),应验证边界外点击响应315+5. **禁止验证与属性无关的行为** — hitSlop 不验证 clickable/checked(不区分有/无 hitSlop),应验证边界外点击响应
276 316 
277**示例**:317**示例**:
278 318 
279-| 属性 | ❌ 禁止的Step | ✅ 正确的Step |319+| 属性 | ❌ 禁止的 Step | ✅ 正确的 Step |
280-|------|--------------|--------------|320+| ------------- | ------------------------------------------------------- | ------------------------------------------------- |
281-| hitSlop | "验证无hitSlop开关clickable=true" (与hitSlop无关) | "点击有hitSlop开关右边界外10px验证外扩区域" |321+| hitSlop | "验证无 hitSlop 开关 clickable=true" (与 hitSlop 无关) | "点击有 hitSlop 开关右边界外 10px 验证外扩区域" |
282-| hitSlop | "点击hitSlop=20开关验证可交互" (所有Switch都可交互) | "点击无hitSlop开关右边界外10px验证无外扩响应" |322+| hitSlop | "点击 hitSlop=20 开关验证可交互" (所有 Switch 都可交互) | "点击无 hitSlop 开关右边界外 10px 验证无外扩响应" |
283-| disabled | "验证enabled开关clickable=true" (通用行为) | "点击disabled开关验证无响应" (反向验证) |323+| disabled | "验证 enabled 开关 clickable=true" (通用行为) | "点击 disabled 开关验证无响应" (反向验证) |
284-| pointerEvents | "验证pointerEvents=auto的Switch可交互" (通用) | "验证pointerEvents=none的Switch不可交互" |324+| pointerEvents | "验证 pointerEvents=auto 的 Switch 可交互" (通用) | "验证 pointerEvents=none 的 Switch 不可交互" |
285 325 
286---326---
287 327 
@@ -307,21 +347,21 @@ export interface StepConfig {
307 347 
308### StepConfig Complete Property Reference348### StepConfig Complete Property Reference
309 349 
310-| Property | Type | Required | Default | Boundary / Notes |350+| Property | Type | Required | Default | Boundary / Notes |
311-|----------|------|----------|---------|------------------|351+| ------------------- | ------------------ | -------- | -------------------- | ----------------------------------------------------------------------------- |
312-| `name` | string | YES | (decorator arg) | Display name; MUST describe the property-specific behavior being tested (R52) |352+| `name` | string | YES | (decorator arg) | Display name; MUST describe the property-specific behavior being tested (R52) |
313-| `method` | string | YES | (auto: propertyKey) | Method name on test class |353+| `method` | string | YES | (auto: propertyKey) | Method name on test class |
314-| `order` | number | YES | (auto: steps.length) | 0-indexed; auto-assigned |354+| `order` | number | YES | (auto: steps.length) | 0-indexed; auto-assigned |
315-| `waitForStable` | number | NO | `300` | ms; min=0; wait after auto-action |355+| `waitForStable` | number | NO | `300` | ms; min=0; wait after auto-action |
316-| `repeatCount` | number | NO | undefined | Min=1; falls back to TestConfig.executeTimes or 100 |356+| `repeatCount` | number | NO | undefined | Min=1; falls back to TestConfig.executeTimes or 100 |
317-| `checkAppAlive` | boolean | NO | undefined | Step-level app alive check |357+| `checkAppAlive` | boolean | NO | undefined | Step-level app alive check |
318-| `captureScreenshot` | boolean | NO | `true` | Defaults to true via `?? true` |358+| `captureScreenshot` | boolean | NO | `true` | Defaults to true via `?? true` |
319-| `autoLog` | boolean | NO | `true` | Defaults to true via `?? true` |359+| `autoLog` | boolean | NO | `true` | Defaults to true via `?? true` |
320-| `expects` | ExpectConfig[] | NO | undefined | See ExpectConfig section |360+| `expects` | ExpectConfig[] | NO | undefined | See ExpectConfig section |
321-| `uiComparison` | UIComparisonConfig | NO | undefined | See UI Comparison section |361+| `uiComparison` | UIComparisonConfig | NO | undefined | See UI Comparison section |
322-| `action` | enum | NO | undefined | 6 values: click/input/scroll/swipe/longpress/back |362+| `action` | enum | NO | undefined | 6 values: click/input/scroll/swipe/longpress/back |
323-| `target` | string | NO | undefined | TestID for auto-action target |363+| `target` | string | NO | undefined | TestID for auto-action target |
324-| `value` | string | NO | undefined | Input value for 'input' action |364+| `value` | string | NO | undefined | Input value for 'input' action |
325 365 
326---366---
327 367 
@@ -339,23 +379,23 @@ interface TestThreshold {
339}379}
340```380```
341 381 
342-| Property | Type | Required | Boundary / Notes |382+| Property | Type | Required | Boundary / Notes |
343-|----------|------|----------|------------------|383+| --------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------ |
344-| `metric` | string | YES | Any metric name string |384+| `metric` | string | YES | Any metric name string |
345-| `operator` | enum (6 values) | NO | `'>'`/`'<'`/`'>='`/`'<='`/`'equal'`/`'contains'` |385+| `operator` | enum (6 values) | NO | `'>'`/`'<'`/`'>='`/`'<='`/`'equal'`/`'contains'` |
346-| `value` | number \| string | YES | Number for comparison operators; string for 'contains'/'equal' |386+| `value` | number \| string | YES | Number for comparison operators; string for 'contains'/'equal' |
347-| `maxDeviationPercent` | number | NO | 0-100; 0=exact match |387+| `maxDeviationPercent` | number | NO | 0-100; 0=exact match |
348-| `maxCrashCount` | number | NO | Min=0; max allowed crashes |388+| `maxCrashCount` | number | NO | Min=0; max allowed crashes |
349-| `minSuccessRate` | number | NO | 0.0-1.0; stability test 默认 >= 0.95(可通过 `stability.successRateThreshold` 配置)|389+| `minSuccessRate` | number | NO | 0.0-1.0; stability test 默认 >= 0.95(可通过 `stability.successRateThreshold` 配置) |
350-| `message` | string | NO | Custom violation message |390+| `message` | string | NO | Custom violation message |
351 391 
352### Threshold Operators Comparison392### Threshold Operators Comparison
353 393 
354-| Context | Type | Operators |394+| Context | Type | Operators |
355-|---------|------|-----------|395+| --------------- | -------- | ----------------------------------------------------- |
356-| TestThreshold | 6 values | `'>'`, `'<'`, `'>='`, `'<='`, `'equal'`, `'contains'` |396+| TestThreshold | 6 values | `'>'`, `'<'`, `'>='`, `'<='`, `'equal'`, `'contains'` |
357-| PerfThreshold | 4 values | `'>'`, `'<'`, `'>='`, `'<='` |397+| PerfThreshold | 4 values | `'>'`, `'<'`, `'>='`, `'<='` |
358-| MetricThreshold | 6 values | `'>'`, `'<'`, `'='`, `'>='`, `'<='`, `'!='` |398+| MetricThreshold | 6 values | `'>'`, `'<'`, `'='`, `'>='`, `'<='`, `'!='` |
359 399 
360---400---
361 401 
@@ -375,10 +415,12 @@ interface ExpectCheck {
375```415```
376 416 
377**NOTE**: ExpectCheck.type has 5 values. ExpectConfig.type (step-level) has 8 values. They differ:417**NOTE**: ExpectCheck.type has 5 values. ExpectConfig.type (step-level) has 8 values. They differ:
418+ 
378- ExpectCheck has `'keyboard'` (not in ExpectConfig)419- ExpectCheck has `'keyboard'` (not in ExpectConfig)
379- ExpectConfig has `'behavior'`, `'layout'`, `'interaction'`, `'ui'` (not in ExpectCheck)420- ExpectConfig has `'behavior'`, `'layout'`, `'interaction'`, `'ui'` (not in ExpectCheck)
380 421 
381**NOTE**: ExpectCheck.attribute has 8 values. ExpectAttributeConfig.attribute has 11 values:422**NOTE**: ExpectCheck.attribute has 8 values. ExpectAttributeConfig.attribute has 11 values:
423+ 
382- Shared: `text`, `placeholder`, `type`, `clickable`, `enabled`, `visible`, `focused`, `checked`424- Shared: `text`, `placeholder`, `type`, `clickable`, `enabled`, `visible`, `focused`, `checked`
383- ExpectAttributeConfig only: `value`, `selected`, `scrollable`425- ExpectAttributeConfig only: `value`, `selected`, `scrollable`
384 426 
@@ -391,6 +433,7 @@ type ExpectType = 'attribute' | 'visual' | 'behavior' | 'layout' | 'state' | 'in
391```433```
392 434 
393### ExpectAttributeConfig (11 attributes)435### ExpectAttributeConfig (11 attributes)
436+ 
394```typescript437```typescript
395attribute: 'text' | 'placeholder' | 'type' | 'value' | 'enabled' | 'visible' | 'focused' | 'checked' | 'selected' | 'clickable' | 'scrollable';438attribute: 'text' | 'placeholder' | 'type' | 'value' | 'enabled' | 'visible' | 'focused' | 'checked' | 'selected' | 'clickable' | 'scrollable';
396expected?: unknown;439expected?: unknown;
@@ -407,7 +450,7 @@ ignoreCase?: boolean;
407 450 
408```typescript451```typescript
409// L0 dump 签名检测(零开销,推荐)452// L0 dump 签名检测(零开销,推荐)
410-const overlays = await this.getOverlays(); // BaseTest 门面453+const overlays = await this.getOverlays(); // BaseTest 门面
411const kbUp = overlays.some(o => o.kind === 'keyboard');454const kbUp = overlays.some(o => o.kind === 'keyboard');
412 455 
413// L1 hidumper 精确窗口枚举(~1.4s/次,需精确 bounds 时)456// L1 hidumper 精确窗口枚举(~1.4s/次,需精确 bounds 时)
@@ -417,17 +460,18 @@ const overlays = await this.getOverlays({ precise: true });
417await this.ui.dismissOverlayAndWait('keyboard', 3000);460await this.ui.dismissOverlayAndWait('keyboard', 3000);
418 461 
419// 键盘守卫(断言前收键盘——鸿蒙 resize 陷阱,见 rn-test-case skill)462// 键盘守卫(断言前收键盘——鸿蒙 resize 陷阱,见 rn-test-case skill)
420-await this.dismissKeyboardIfOpen(); // BaseTest 方法463+await this.dismissKeyboardIfOpen(); // BaseTest 方法
421// 或声明式:@Step({ dismissKeyboardBeforeAssert: true, ... })464// 或声明式:@Step({ dismissKeyboardBeforeAssert: true, ... })
422```465```
423 466 
424### OverlayInfo(Overlay 统一方案——2026-09-16)467### OverlayInfo(Overlay 统一方案——2026-09-16)
468+ 
425```typescript469```typescript
426type OverlayKind = 'keyboard' | 'dialog' | 'systemBar';470type OverlayKind = 'keyboard' | 'dialog' | 'systemBar';
427type OverlayOcclusion = 'resize' | 'cover' | 'replace' | 'none';471type OverlayOcclusion = 'resize' | 'cover' | 'replace' | 'none';
428interface OverlayInfo {472interface OverlayInfo {
429 kind: OverlayKind;473 kind: OverlayKind;
430- name?: string; // L0=dump节点id / L1=hidumper WindowName474+ name?: string; // L0=dump节点id / L1=hidumper WindowName
431 bounds?: { left; top; right; bottom; width; height };475 bounds?: { left; top; right; bottom; width; height };
432 occlusion: OverlayOcclusion; // resize=窗口收缩 / cover=点击拦截 / replace=app退后台476 occlusion: OverlayOcclusion; // resize=窗口收缩 / cover=点击拦截 / replace=app退后台
433 source: 'layout-dump' | 'hidumper';477 source: 'layout-dump' | 'hidumper';
@@ -439,6 +483,7 @@ interface OverlayInfo {
439~~bounds/relative/alignment~~ **已删除**(零使用)。483~~bounds/relative/alignment~~ **已删除**(零使用)。
440 484 
441### ExpectStateConfig485### ExpectStateConfig
486+ 
442```typescript487```typescript
443focused?: boolean;488focused?: boolean;
444enabled?: boolean;489enabled?: boolean;
@@ -451,6 +496,7 @@ customState?: string;
451```496```
452 497 
453### ExpectInteractionConfig498### ExpectInteractionConfig
499+ 
454```typescript500```typescript
455interactionType: 'click' | 'input' | 'scroll' | 'swipe' | 'longpress' | 'drag' | 'pinch';501interactionType: 'click' | 'input' | 'scroll' | 'swipe' | 'longpress' | 'drag' | 'pinch';
456clickSuccess?: boolean;502clickSuccess?: boolean;
@@ -469,6 +515,7 @@ eventPayload?: unknown;
469```515```
470 516 
471### ExpectResponseConfig517### ExpectResponseConfig
518+ 
472```typescript519```typescript
473maxResponseTime?: number; // min=0, ms520maxResponseTime?: number; // min=0, ms
474avgResponseTime?: number; // min=0, ms521avgResponseTime?: number; // min=0, ms
@@ -478,4 +525,3 @@ measureIterations?: number; // min=1
478```525```
479 526 
480---527---
481- 
M.quality-toolkit+1-1File content may contain Illegal information
MREADME.md+61-39
@@ -6,18 +6,19 @@
6 6 
7## 为什么选择 CPTF?7## 为什么选择 CPTF?
8 8 
9-| 特性 | CPTF | 其他框架 |9+| 特性 | CPTF | 其他框架 |
10-|------|------|---------|10+| ------------------ | ----------------------------------------------------- | --------------------- |
11-| **HarmonyOS 支持** | ✅ 原生支持 | ❌ 不支持 |11+| **HarmonyOS 支持** | ✅ 原生支持 | ❌ 不支持 |
12-| **性能指标采集** | ✅ 19种性能指标+3种稳定性指标(真实FPS) | ⚠️ 2-3种(模拟FPS) |12+| **性能指标采集** | ✅ 19 个性能指标(17 内置 + 2 插件,真实 FPS) | ⚠️ 2-3 种(模拟 FPS) |
13-| **稳定性测试** | ✅ SmartMonkey + ANR + 白屏检测 + 内存泄漏 + 崩溃恢复 | ❌ 需自行搭建 |13+| **稳定性测试** | ✅ SmartMonkey + ANR + 白屏检测 + 内存泄漏 + 崩溃恢复 | ❌ 需自行搭建 |
14-| **跨平台对比** | ✅ HarmonyOS vs Android | ⚠️ 需自行搭建 |14+| **跨平台对比** | ✅ HarmonyOS vs Android | ⚠️ 需自行搭建 |
15-| **用例编写** | ✅ 声明式(减少70-90%代码) | ⚠️ 命令式 |15+| **用例编写** | ✅ 声明式(减少 70-90%代码) | ⚠️ 命令式 |
16 16 
17**核心亮点**:17**核心亮点**:
18+ 
18- **唯一性**:业界唯一支持 HarmonyOS 自动化测试19- **唯一性**:业界唯一支持 HarmonyOS 自动化测试
19- **精准性**:真实 FPS 采集(RenderService fpsCount + hitchs),非模拟数据20- **精准性**:真实 FPS 采集(RenderService fpsCount + hitchs),非模拟数据
20-- **完整性**:19种性能指标 + 3种稳定性指标 + 完整稳定性测试套件(SmartMonkey/白屏/内存泄漏/ANR/崩溃恢复)21+- **完整性**:19 个性能指标(17 内置 + 2 插件)+ 3 个稳定性检测器 + 完整稳定性测试套件(SmartMonkey/白屏/内存泄漏/ANR/崩溃恢复)
21- **简洁性**:声明式框架 `@Test/@Step`,代码量减少 70-90%22- **简洁性**:声明式框架 `@Test/@Step`,代码量减少 70-90%
22- **专业性**:五层架构设计(src/ 目录即五层),职责分离清晰,易于扩展23- **专业性**:五层架构设计(src/ 目录即五层),职责分离清晰,易于扩展
23 24 
@@ -26,31 +27,33 @@
26**React Native for OpenHarmony (RNOH)** 基于 CPTF 框架完成全量功能、性能、稳定性测试,是目前业界最大的 HarmonyOS 跨端测试实践:27**React Native for OpenHarmony (RNOH)** 基于 CPTF 框架完成全量功能、性能、稳定性测试,是目前业界最大的 HarmonyOS 跨端测试实践:
27 28 
28- **功能测试**:覆盖 Switch、Button、RefreshControl 等核心组件的全量属性跨平台一致性验证29- **功能测试**:覆盖 Switch、Button、RefreshControl 等核心组件的全量属性跨平台一致性验证
29-- **性能测试**:19 种指标采集 + Warmup迭代 + 阈值告警 + HTML趋势图30+- **性能测试**:19 个指标采集(17 内置 + 2 插件)+ Warmup 迭代 + 阈值告警 + HTML 趋势图
30-- **稳定性测试**:SmartMonkey + ANR检测 + 白屏检测 + 内存泄漏 + 崩溃自动恢复31+- **稳定性测试**:SmartMonkey + ANR 检测 + 白屏检测 + 内存泄漏 + 崩溃自动恢复
31-- **跨平台对比**:HarmonyOS vs Android UI结构4维对比(属性/样式/位置/层级)32+- **跨平台对比**:HarmonyOS vs Android UI 结构 4 维对比(属性/样式/位置/层级)
32 33 
33> 测试用例工程:[RNOH Test App](https://atomgit.com/CPF-RN/rnoh_test)34> 测试用例工程:[RNOH Test App](https://atomgit.com/CPF-RN/rnoh_test)
34 35 
36+> 框架源码双托管:[GitCode](https://gitcode.com/CPF-RN/CPTF)(主)· [AtomGit](https://atomgit.com/react-native/CPTF)(镜像,`packages/framework/package.json` `repository`)
37+ 
35## 平台支持38## 平台支持
36 39 
37-| 平台 | 状态 |40+| 平台 | 状态 |
38-|------|------|41+| --------- | --------- |
39| HarmonyOS | ✅ 已支持 |42| HarmonyOS | ✅ 已支持 |
40-| Android | ✅ 已支持 |43+| Android | ✅ 已支持 |
41-| iOS | 🚧 开发中 |44+| iOS | 🚧 开发中 |
42 45 
43## 项目结构46## 项目结构
44 47 
45```48```
46cptf/49cptf/
47├── packages/framework/ # 框架层(npm 包 @cptf/framework)50├── packages/framework/ # 框架层(npm 包 @cptf/framework)
48-│ └── src/ # 五层架构源码(base/infrastructure/system/capability/orchestration)51+│ └── src/ # 五层架构源码(对外概览三组合并:base+infrastructure=基础设施、capability+orchestration=调度、system=系统)
49-│ ├── base/ # 基础设施层(类型/配置/工具/设备管理/端口管理)52+│ ├── base/ # 基础层(类型/配置/工具/设备管理/端口管理)
50│ ├── infrastructure/ # 基础设施层(扫描/索引/设备发现)53│ ├── infrastructure/ # 基础设施层(扫描/索引/设备发现)
51│ ├── system/ # 系统层(ISystem接口+平台实现+Agent客户端)54│ ├── system/ # 系统层(ISystem接口+平台实现+Agent客户端)
52-│ ├── capability/ # 调度层(UI/断言/对比/采集)55+│ ├── capability/ # 能力层(UI/断言/对比/采集)
53-│ ├── orchestration/ # 调度层(运行器/测试类/报告)56+│ ├── orchestration/ # 编排层(运行器/测试类/报告)
54│ ├── cli/ # CLI命令57│ ├── cli/ # CLI命令
55│ └── __tests__/ # 框架单元测试58│ └── __tests__/ # 框架单元测试
56```59```
@@ -65,17 +68,27 @@ pnpm install
65pnpm build68pnpm build
66```69```
67 70 
68-### CLI命令71+### CLI 命令
69 72 
70```bash73```bash
71cptf devices # 列出设备74cptf devices # 列出设备
72cptf devices -p harmony # 列出HarmonyOS设备75cptf devices -p harmony # 列出HarmonyOS设备
73cptf scan # 扫描测试用例76cptf scan # 扫描测试用例
77+cptf index # 生成测试用例索引文件
74cptf run # 执行所有测试(自动平台选择)78cptf run # 执行所有测试(自动平台选择)
75cptf run -p harmony # HarmonyOS测试79cptf run -p harmony # HarmonyOS测试
76cptf run -m performance # 指定模块测试80cptf run -m performance # 指定模块测试
81+cptf run -C Button # 按分类测试
77cptf run -t all-metrics-performance # 指定用例测试82cptf run -t all-metrics-performance # 指定用例测试
83+cptf run --driver native # 驱动模式(native灰盒 | external黑盒 | auto,默认auto:优先native失败降级external)
84+cptf run --strategy by-module # 多设备分配策略(round-robin | by-module | by-category | by-case | single)
85+cptf run --timeout 600 # 全局超时(秒),超时后生成部分报告
86+cptf run --include-unsupported # 包含无法自动化的用例(默认跳过并从通过率分母排除)
87+cptf run --force-baseline # 强制覆盖已有视觉基线(覆盖前自动备份到 .history/)
78cptf compare # 跨平台对比测试88cptf compare # 跨平台对比测试
89+cptf compare --driver native # 对比测试同样支持驱动模式
90+cptf index # 生成测试用例索引(test-cases-index.ts)
91+cptf install # 安装应用到连接设备(--atx 同时装 Android ATX Agent)
79cptf init # 初始化配置92cptf init # 初始化配置
80cptf check-env # 检查环境依赖93cptf check-env # 检查环境依赖
81cptf template performance -n my-test # 生成测试模板94cptf template performance -n my-test # 生成测试模板
@@ -126,11 +139,9 @@ cptf run -p harmony
126 139 
127### 性能采集140### 性能采集
128 141 
129-- FPS 监控(真实FPS:RenderService fpsCount + hitchs)142+- FPS 监控(真实 FPS:RenderService fpsCount + hitchs)
130- CPU 使用率143- CPU 使用率
131-- 内存占用(含泄漏检测:线性回归+R²置信度)144+- 19 个性能指标(17 内置 + 2 插件)+ 3 个稳定性检测器 + HTML 趋势图
132-- 白屏检测(PNG+zlib解析,亮度>240+白像素比>85%)
133-- 19种性能指标 + 3种稳定性指标 + HTML趋势图
134 145 
135### 稳定性测试146### 稳定性测试
136 147 
@@ -149,7 +160,7 @@ cptf run -p harmony
149 160 
150### 报告生成161### 报告生成
151 162 
152-- HTML 格式(默认)163+- HTML 格式
153- Markdown 格式164- Markdown 格式
154- JSON 格式165- JSON 格式
155- 性能告警166- 性能告警
@@ -159,12 +170,23 @@ cptf run -p harmony
159```bash170```bash
160cptf devices # 列出设备171cptf devices # 列出设备
161cptf devices -p harmony # 列出平台设备172cptf devices -p harmony # 列出平台设备
173+cptf scan # 扫描用例
174+cptf index # 生成测试索引文件
162cptf run # 执行所有测试(自动平台选择)175cptf run # 执行所有测试(自动平台选择)
163cptf run -p harmony # HarmonyOS测试176cptf run -p harmony # HarmonyOS测试
164cptf run -m performance # 指定模块177cptf run -m performance # 指定模块
178+cptf run -C Button # 指定分类
165cptf run -t calendar-perf-declarative # 指定用例179cptf run -t calendar-perf-declarative # 指定用例
180+cptf run --driver native # 驱动模式 native|external|auto(默认auto)
181+cptf run --strategy by-category # 多设备分配策略
182+cptf run --timeout 600 # 全局超时(秒),超时生成部分报告
183+cptf run --include-unsupported # 包含不可自动化用例
166cptf run -p harmony --rn-version rn84 --update-baseline # 按 RN 版本分段建立/验证视觉基线184cptf run -p harmony --rn-version rn84 --update-baseline # 按 RN 版本分段建立/验证视觉基线
185+cptf run -p harmony --rn-version rn84 --force-baseline # 强制覆盖视觉基线(自动备份旧基线)
167cptf compare # 跨平台对比测试186cptf compare # 跨平台对比测试
187+cptf compare --driver native # 对比测试同样支持驱动模式
188+cptf index # 生成测试用例索引
189+cptf install # 安装应用到设备
168cptf init # 初始化配置文件190cptf init # 初始化配置文件
169cptf check-env # 检查环境依赖191cptf check-env # 检查环境依赖
170cptf template <module> -n <name> # 生成测试用例模板192cptf template <module> -n <name> # 生成测试用例模板
@@ -172,21 +194,21 @@ cptf template <module> -n <name> # 生成测试用例模板
172 194 
173## 文档195## 文档
174 196 
175-| 文档 | 说明 |197+| 文档 | 说明 |
176-|------|------|198+| ----------------------------------------------------------------------------------- | ----------------------------------- |
177-| [架构总览](packages/framework/docs/architecture/ARCHITECTURE.md) | 框架架构、源码目录、依赖规则 |199+| [架构总览](packages/framework/docs/architecture/ARCHITECTURE.md) | 框架架构、源码目录、依赖规则 |
178-| [架构优势](packages/framework/docs/architecture/ARCHITECTURE-OVERVIEW.md) | 五层架构、三平台加速、性能优势 |200+| [架构优势](packages/framework/docs/architecture/ARCHITECTURE-OVERVIEW.md) | 五层架构、三平台加速、性能优势 |
179-| [能力规格](packages/framework/docs/architecture/CAPABILITY-SPEC.md) | 性能指标、API 规格、数据结构 |201+| [能力规格](packages/framework/docs/architecture/CAPABILITY-SPEC.md) | 性能指标、API 规格、数据结构 |
180-| [用户指南](packages/framework/docs/getting-started/USER-GUIDE.md) | 安装、配置、CLI、API 使用 |202+| [用户指南](packages/framework/docs/getting-started/USER-GUIDE.md) | 安装、配置、CLI、API 使用 |
181| [声明式测试指南](packages/framework/docs/getting-started/DECLARATIVE-TEST-GUIDE.md) | @Test/@Step 装饰器、TestConfig 详解 |203| [声明式测试指南](packages/framework/docs/getting-started/DECLARATIVE-TEST-GUIDE.md) | @Test/@Step 装饰器、TestConfig 详解 |
182-| [命令式测试指南](packages/framework/docs/advanced/IMPERATIVE-TEST-GUIDE.md) | 命令式测试编写 |204+| [命令式测试指南](packages/framework/docs/advanced/IMPERATIVE-TEST-GUIDE.md) | 命令式测试编写 |
183-| [Expect 断言指南](packages/framework/docs/advanced/EXPECT-GUIDE.md) | 声明式断言使用 |205+| [Expect 断言指南](packages/framework/docs/advanced/EXPECT-GUIDE.md) | 声明式断言使用 |
184-| [UI 对比指南](packages/framework/docs/advanced/UI-COMPARISON.md) | 跨平台 UI 结构对比 |206+| [UI 对比指南](packages/framework/docs/advanced/UI-COMPARISON.md) | 跨平台 UI 结构对比 |
185-| [扩展开发](packages/framework/docs/development/DEVELOPMENT-GUIDE.md) | 新增平台、能力、报告生成 |207+| [扩展开发](packages/framework/docs/development/DEVELOPMENT-GUIDE.md) | 新增平台、能力、报告生成 |
186-| [框架对比分析](packages/framework/docs/reference/FRAMEWORK-COMPARISON.md) | 与 Jest/Appium/Cypress 等对比 |208+| [框架对比分析](packages/framework/docs/reference/FRAMEWORK-COMPARISON.md) | 与 Jest/Appium/Cypress 等对比 |
187-| [稳定性对比分析](packages/framework/docs/reference/STABILITY-COMPARISON.md) | 7 维度对标业界稳定性方案 |209+| [稳定性对比分析](packages/framework/docs/reference/STABILITY-COMPARISON.md) | 7 维度对标业界稳定性方案 |
188-| [npm 包说明](packages/framework/README.md) | CLI 命令、快速开始、架构设计 |210+| [npm 包说明](packages/framework/README.md) | CLI 命令、快速开始、架构设计 |
189 211 
190## License212## License
191 213 
192-MIT214+MIT
@@ -0,0 +1,21 @@
1+MIT License
2+ 
3+Copyright 2026 gaoyang (gyange@163.com)
4+ 
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+ 
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+ 
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
@@ -4,7 +4,7 @@
4 "description": "CPTF benchmark plugin - React Native device-side measurement (RTNTimeToRender + MeasureContainer + scenes)",4 "description": "CPTF benchmark plugin - React Native device-side measurement (RTNTimeToRender + MeasureContainer + scenes)",
5 "main": "src/index",5 "main": "src/index",
6 "scripts": {6 "scripts": {
7- "pack": "npm pack"7+ "pack": "pnpm pack"
8 },8 },
9 "codegenConfig": {9 "codegenConfig": {
10 "name": "RenderTimerSpec",10 "name": "RenderTimerSpec",
@@ -0,0 +1,21 @@
1+MIT License
2+ 
3+Copyright 2026 gaoyang (gyange@163.com)
4+ 
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+ 
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+ 
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
@@ -19,7 +19,7 @@
19 "build": "tsc -p tsconfig.build.json",19 "build": "tsc -p tsconfig.build.json",
20 "typecheck": "tsc --noEmit -p tsconfig.build.json",20 "typecheck": "tsc --noEmit -p tsconfig.build.json",
21 "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",21 "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
22- "pack": "pnpm run clean && pnpm run build && npm pack",22+ "pack": "pnpm run clean && pnpm run build && pnpm pack",
23 "prepublishOnly": "pnpm run clean && pnpm run build"23 "prepublishOnly": "pnpm run clean && pnpm run build"
24 },24 },
25 "keywords": ["cptf", "benchmark", "render-time", "metrics"],25 "keywords": ["cptf", "benchmark", "render-time", "metrics"],
@@ -2,6 +2,34 @@
2 2 
3All notable changes to CPTF will be documented in this file.3All notable changes to CPTF will be documented in this file.
4 4 
5+## [未发布] - 2026-10-14(init 默认配置补必填字段)
6+ 
7+### Fixed
8+ 
9+- **`cptf init` 默认配置补 `deepLinkScheme`**:0.3.0 起 loader 对缺失该字段的配置直接拒绝(框架不再提供默认值),但 init 生成的模板没有它——新用户 init 完直接 `cptf run` 必撞 `deepLinkScheme 未配置`。模板补 `"rnoh-test"` 占位并在下一步提示中说明需改为被测应用 scheme(配套测试:init 产物必须原样通过 `resolveConfigAsync` 校验)
10+ 
11+## [未发布] - 2026-10-14(安装命令加固)
12+ 
13+### Fixed
14+ 
15+- **`AppInstaller` 安装命令加固**:`install -r` 显式替换语义(hdc/adb 裸 install 对已装包的行为随版本而异);`exec` 拼 shell 字符串改为 `execFile` 参数数组(设备 ID/路径含空格或 shell 元字符时不再断裂或注入);无法识别的安装输出由视为成功改为视为失败(`app-installer.ts`)
16+ 
17+## [未发布] - 2026-10-13(指标口径统一 + exitLatency 移除)
18+ 
19+### Removed
20+ 
21+- **`PerfMetricType` 移除 `exitLatency` 幽灵成员**:该成员自声明以来从未有 collector 实现(`MetricCollectorFactory.create('exitLatency')` 一直抛 `Unknown metric type`),用户在 `collect`/`thresholds` 中引用只会得到运行时错误。从类型联合删除,让误用在编译期即暴露。同步移除断言其抛错的测试(该测试固化的是缺陷而非契约)
22+ 
23+### Changed
24+ 
25+- **指标计数口径全仓统一为「19 = 17 内置 + 2 插件」**:以 `src/base/types/index.ts` 联合与 `MetricCollectorFactory` 注册表为单一真相,修复三口径漂移(根 README 19 / docs 索引与 PERFORMANCE-METRICS-SPEC 16 / STABILITY-SPEC 与 framework README 13)。CAPABILITY-SPEC §1 重建清单:补 `fpsP5`/`stability`/`dropRate`/`renderTime`/`unmountTime`,删已不存在的 `fpsMin`/`fpsMax` 行,并在 §1.0 显式声明计数规则(直接采集 1 + 聚合 12 + lifecycle 4 + 插件 2;`memory`/`cpu` 是原始采集通道而非联合成员,不计入)。首行「19 种性能指标 + 3 种稳定性指标」表述更正——`stability`/`dropRate`/`fpsP5` 是 `PerfMetricType` 成员而非独立的「稳定性指标」类目
26+ 
27+## [未发布] - 2026-10-13(分片日志设备编号对齐 1-based)
28+ 
29+### Fixed
30+ 
31+- **`ParallelTestRunner.shardToDevices` 分片日志设备编号 0-based → 1-based**:此前打印 `设备${i}`(首个非空分片为「设备 0」),与 `DeviceAllocator` 的「按 Category/模块/轮询分配」日志 `设备${i + 1}` 不一致,同一设备在两段日志中编号不同;统一为 `设备${i + 1}`(`parallel-test-runner.ts`)
32+ 
5## [未发布] - 2026-10-13(ts-node 注册固定 node 解析)33## [未发布] - 2026-10-13(ts-node 注册固定 node 解析)
6 34 
7### Fixed35### Fixed
@@ -12,6 +12,7 @@
12CPTF(Cross-Platform Test Framework)是一个跨平台自动化测试框架,支持 HarmonyOS、Android、iOS 平台的自动化测试和跨平台对比测试。12CPTF(Cross-Platform Test Framework)是一个跨平台自动化测试框架,支持 HarmonyOS、Android、iOS 平台的自动化测试和跨平台对比测试。
13 13 
14适用于多种跨平台技术栈:14适用于多种跨平台技术栈:
15+ 
15- React Native / RN16- React Native / RN
16- Flutter17- Flutter
17- Kotlin Multiplatform (KMP)18- Kotlin Multiplatform (KMP)
@@ -21,68 +22,74 @@ CPTF(Cross-Platform Test Framework)是一个跨平台自动化测试框架
21## 核心特性22## 核心特性
22 23 
23### 1. 声明式测试框架24### 1. 声明式测试框架
25+ 
24- ✅ `@Test` 装饰器 - 配置化测试定义26- ✅ `@Test` 装饰器 - 配置化测试定义
25- ✅ `@Step` 装饰器 - 自动步骤日志27- ✅ `@Step` 装饰器 - 自动步骤日志
26-- ✅ 自动导航 - setup自动完成28+- ✅ 自动导航 - setup 自动完成
27- ✅ 场景自动恢复 - autoRecoverScene29- ✅ 场景自动恢复 - autoRecoverScene
28-- ✅ 配置化基线 - baselines配置30+- ✅ 配置化基线 - baselines 配置
29 31 
30### 2. 三类测试基类32### 2. 三类测试基类
33+ 
31- ✅ **PerformanceTest** - 性能测试(FPS、时延、阈值)34- ✅ **PerformanceTest** - 性能测试(FPS、时延、阈值)
32- ✅ **FunctionalTest** - 功能测试(属性验证、视觉检查、跨平台一致性)35- ✅ **FunctionalTest** - 功能测试(属性验证、视觉检查、跨平台一致性)
33- ✅ **StabilityTest** - 稳定性测试(反复执行、崩溃检测)36- ✅ **StabilityTest** - 稳定性测试(反复执行、崩溃检测)
34 37 
35### 3. 跨平台测试支持38### 3. 跨平台测试支持
39+ 
36- ✅ HarmonyOS 自动化测试(hdc + UITest 双引擎)40- ✅ HarmonyOS 自动化测试(hdc + UITest 双引擎)
37- ✅ Android 自动化测试(adb + U2 双引擎)41- ✅ Android 自动化测试(adb + U2 双引擎)
38- ✅ iOS 自动化测试(simctl + WDA + xctrace)42- ✅ iOS 自动化测试(simctl + WDA + xctrace)
39 43 
40### 4. 性能监控44### 4. 性能监控
45+ 
41- ✅ FPS 实时采集46- ✅ FPS 实时采集
42- ✅ 内存占用监控47- ✅ 内存占用监控
43- ✅ CPU 使用率统计48- ✅ CPU 使用率统计
44- ✅ 白屏率分析49- ✅ 白屏率分析
45-- ✅ 用例级采集配置(MonitoringConfig:指标选择+频率)50+- ✅ 用例级采集配置(TestConfig.collect:指标选择)
46- ✅ FPS/内存/CPU 时序趋势(TrendPoint)51- ✅ FPS/内存/CPU 时序趋势(TrendPoint)
47-- ✅ 19 个 PerfMetricType 全部落地52+- ✅ 19 个 PerfMetricType 全部落地(17 内置 collector + 2 个 @cptf/benchmark 插件指标)
48 53 
49### 5. 稳定性测试54### 5. 稳定性测试
55+ 
50- ✅ SmartMonkey(带应用崩溃自动恢复)56- ✅ SmartMonkey(带应用崩溃自动恢复)
51- ✅ 白屏检测(像素级)57- ✅ 白屏检测(像素级)
52- ✅ 内存泄漏检测(线性回归)58- ✅ 内存泄漏检测(线性回归)
53- ✅ ANR / Crash 检测59- ✅ ANR / Crash 检测
54 60 
55### 6. 跨平台 UI 对比61### 6. 跨平台 UI 对比
62+ 
56- ✅ 自动扫描 baseline63- ✅ 自动扫描 baseline
57- ✅ 像素级 diff.png 生成64- ✅ 像素级 diff.png 生成
58- ✅ ignoreTestID / ignoreTestIDPatterns65- ✅ ignoreTestID / ignoreTestIDPatterns
59- ✅ 跨平台一致性评分66- ✅ 跨平台一致性评分
60-- 📋 ignoreRegions(0.3.0 路线图)67+- ✅ ignoreRegions / compareRegions(物理像素矩形,交集语义)
61-- 📋 pixelmatch / pHash / SSIM 真实像素对比(0.3.0 路线图)68+- ✅ 逐像素 diff(perPixelTolerance / diffThreshold 分离)
62 69 
63## Evidence(数据支撑)70## Evidence(数据支撑)
64 71 
65### 双引擎加速效果72### 双引擎加速效果
66 73 
67-| 操作 | CPTF (HTX/ATX) | SoloPi (adb) | 提升 |74+| 操作 | CPTF (HTX/ATX) | SoloPi (adb) | 提升 |
68-|------|------------------|---------------|------|75+| --------------- | -------------- | ------------ | -------- |
69-| 单步 click 响应 | 80-120ms | 250-400ms | **3-4x** |76+| 单步 click 响应 | 80-120ms | 250-400ms | **3-4x** |
70-| 截图采集 | 60-80ms | 150-250ms | **2-3x** |77+| 截图采集 | 60-80ms | 150-250ms | **2-3x** |
71-| FPS 实时采集 | 100-200ms | 500-1000ms | **3-5x** |78+| FPS 实时采集 | 100-200ms | 500-1000ms | **3-5x** |
72-| 启动用例加载 | 200-300ms | 800-1500ms | **3-5x** |79+| 启动用例加载 | 200-300ms | 800-1500ms | **3-5x** |
73 80 
74> 测试环境:HarmonyOS 4.0 Mate 60 / Android 13 Pixel 7。基线对比工具:SoloPi v1.7.6。81> 测试环境:HarmonyOS 4.0 Mate 60 / Android 13 Pixel 7。基线对比工具:SoloPi v1.7.6。
75 82 
76### 自动化就绪度83### 自动化就绪度
77 84 
78-| 指标 | 数值 | 来源 |85+| 指标 | 数值 | 来源 |
79-|------|------|------|86+| ------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------- |
80-| 单元测试用例 | 以 `pnpm test` 实际输出为准 | `pnpm test` |87+| 单元测试用例 | 以 `pnpm test` 实际输出为准 | `pnpm test` |
81-| 框架验证项 | 以 `pnpm verify` 实际输出为准 | `pnpm verify`(架构 / 边界 / 版权 / 类型 / 平台 / Logger / API / Config / Docs / Skill / Perf / Coverage) |88+| 框架验证项 | 以 `pnpm verify` 实际输出为准 | `pnpm verify`(架构 / 边界 / 版权 / 类型 / 平台 / Logger / API / Config / Docs / Skill / Perf / Coverage) |
82-| TS strict 模式 | ✅ | `tsconfig.json` |89+| TS strict 模式 | ✅ | `tsconfig.json` |
83-| 业务代码 `as any` | **0 处** | `verify.ts` 强制 |90+| 业务代码 `as any` | **0 处** | `verify.ts` 强制 |
84-| 同步 I/O 阻塞主线程 | **0 处**(除 worker 线程内) | 0.2.1 整改完成 |91+| 同步 I/O 阻塞主线程 | **0 处**(除 worker 线程内) | 0.2.1 整改完成 |
85-| 巨型类(>1000 行) | 4 个(已在拆分 Roadmap) | 见 [OPTIMIZATION-ANALYSIS.md](CHANGELOG.md) |92+| 巨型类(>1000 行) | 4 个(已在拆分 Roadmap) | 见 [OPTIMIZATION-ANALYSIS.md](CHANGELOG.md) |
86 93 
87### CI 集成示例94### CI 集成示例
88 95 
@@ -109,8 +116,9 @@ CPTF(Cross-Platform Test Framework)是一个跨平台自动化测试框架
109### 用户案例116### 用户案例
110 117 
111> CPTF 0.1.x 起在 `rnoh_test` 仓库使用,覆盖:118> CPTF 0.1.x 起在 `rnoh_test` 仓库使用,覆盖:
119+>
112> - 35 个核心组件(Button / Switch / Text / List / Form 等)120> - 35 个核心组件(Button / Switch / Text / List / Form 等)
113-> - 13 个性能指标采集121+> - 19 个性能指标采集(17 内置 + 2 插件)
114> - 跨 HarmonyOS 4.0/5.0 + Android 13/14 双平台对比122> - 跨 HarmonyOS 4.0/5.0 + Android 13/14 双平台对比
115> - Deep Link 导航节省 75% 用例切换耗时123> - Deep Link 导航节省 75% 用例切换耗时
116 124 
@@ -128,19 +136,20 @@ CPTF(Cross-Platform Test Framework)是一个跨平台自动化测试框架
128- [迁移指南](docs/MIGRATION.md)136- [迁移指南](docs/MIGRATION.md)
129- [变更日志](CHANGELOG.md)137- [变更日志](CHANGELOG.md)
130 138 
139+### 5. UI 自动化
131 140 
132-### 5. UI自动化
133- ✅ 元素查找(testID、文本)141- ✅ 元素查找(testID、文本)
134- ✅ 点击/滑动/输入操作142- ✅ 点击/滑动/输入操作
135- ✅ 滚动查找元素143- ✅ 滚动查找元素
136- ✅ 截图对比144- ✅ 截图对比
137 145 
138### 6. 报告生成146### 6. 报告生成
139-- ✅ HTML可视化报告(Tab式趋势面板+可滚动数据表格)147+ 
140-- ✅ Markdown文档报告148+- ✅ HTML 可视化报告(Tab 式趋势面板+可滚动数据表格)
141-- ✅ JSON数据报告149+- ✅ Markdown 文档报告
150+- ✅ JSON 数据报告
142- ✅ 智能建议生成151- ✅ 智能建议生成
143-- ✅ 邮件通知(Outlook/Modern双格式)152+- ✅ 邮件通知(Outlook/Modern 双格式)
144 153 
145## 快速开始154## 快速开始
146 155 
@@ -166,9 +175,9 @@ pnpm test
166pnpm typecheck175pnpm typecheck
167```176```
168 177 
169-### 4. 使用CLI命令178+### 4. 使用 CLI 命令
170 179 
171-> **完整CLI命令**: 参见 [根目录README.md](../../README.md) 命令速查章节180+> **完整 CLI 命令**: 参见 [根目录 README.md](../../README.md) 命令速查章节
172 181 
173```bash182```bash
174# 列出设备183# 列出设备
@@ -206,12 +215,13 @@ cptf template stability -n monkey-test -i monkey-stab -c Monkey
206| 选项 | 说明 | 默认值 |215| 选项 | 说明 | 默认值 |
207|------|------|--------|216|------|------|--------|
208| `-n, --name` | 测试用例名称 | `test-case` |217| `-n, --name` | 测试用例名称 | `test-case` |
209-| `-i, --id` | 测试用例ID | `test-case-id` |218+| `-i, --id` | 测试用例 ID | `test-case-id` |
210| `-o, --output` | 输出目录 | `tests/cases` |219| `-o, --output` | 输出目录 | `tests/cases` |
211| `-c, --category` | 测试分类 | 模块默认值 |220| `-c, --category` | 测试分类 | 模块默认值 |
212| `-s, --scene` | 测试场景 | 模块默认值 |221| `-s, --scene` | 测试场景 | 模块默认值 |
213 222 
214**生成模板示例**(性能测试):223**生成模板示例**(性能测试):
224+ 
215```typescript225```typescript
216@Test({226@Test({
217 id: 'list-scroll-perf',227 id: 'list-scroll-perf',
@@ -219,9 +229,7 @@ cptf template stability -n monkey-test -i monkey-stab -c Monkey
219 module: 'performance',229 module: 'performance',
220 collect: ['startLatency', 'fps', 'fpsMin', 'blankScreenRatio'],230 collect: ['startLatency', 'fps', 'fpsMin', 'blankScreenRatio'],
221 executeTimes: 5,231 executeTimes: 5,
222- thresholds: [232+ thresholds: [{ metric: 'fps', operator: '<', value: 55 }],
223- { metric: 'fps', operator: '<', value: 55 },
224- ],
225})233})
226class ListScroll extends PerformanceTest {234class ListScroll extends PerformanceTest {
227 @Step('执行测试操作', { waitForStable: 500 })235 @Step('执行测试操作', { waitForStable: 500 })
@@ -231,11 +239,11 @@ class ListScroll extends PerformanceTest {
231}239}
232```240```
233 241 
234-**说明**:实际测试执行在 `../../../app` 目录中进行,参见根目录 README.md。242+**说明**:实际测试执行在被测应用的 `app/` 目录(配套的 rnoh_test 仓)中进行,参见根目录 README.md。
235 243 
236## 架构设计244## 架构设计
237 245 
238-CPTF采用**五层架构**设计(src/ 目录即五层;对外概览常合并叙述为 3 组:基础设施/系统/调度):246+CPTF 采用**五层架构**设计(src/ 目录即五层;对外概览常合并叙述为 3 组:基础设施/系统/调度):
239 247 
240```248```
241基础层(base) → types、config、utils(Logger、Helpers、CacheManager)249基础层(base) → types、config、utils(Logger、Helpers、CacheManager)
@@ -255,6 +263,7 @@ CPTF采用**五层架构**设计(src/ 目录即五层;对外概览常合并
255```263```
256 264 
257**声明式框架**:265**声明式框架**:
266+ 
258```267```
259orchestration/core/268orchestration/core/
260├── decorators.ts → @Test、@Step装饰器269├── decorators.ts → @Test、@Step装饰器
@@ -269,41 +278,46 @@ orchestration/core/
269## 文档导航278## 文档导航
270 279 
271### 核心文档280### 核心文档
281+ 
272- [架构总览](docs/architecture/ARCHITECTURE.md) - 五层架构、性能优化、层级依赖282- [架构总览](docs/architecture/ARCHITECTURE.md) - 五层架构、性能优化、层级依赖
273-- [能力规格](docs/architecture/CAPABILITY-SPEC.md) - 性能指标、平台采集、API规格、数据结构283+- [能力规格](docs/architecture/CAPABILITY-SPEC.md) - 性能指标、平台采集、API 规格、数据结构
274-- [用户指南](docs/getting-started/USER-GUIDE.md) - 快速开始、配置说明、API使用、最佳实践284+- [用户指南](docs/getting-started/USER-GUIDE.md) - 快速开始、配置说明、API 使用、最佳实践
275- [扩展开发](docs/development/DEVELOPMENT-GUIDE.md) - 新增平台、能力、报告生成285- [扩展开发](docs/development/DEVELOPMENT-GUIDE.md) - 新增平台、能力、报告生成
276- [框架对比](docs/reference/FRAMEWORK-COMPARISON.md) - CPTF vs Jest/Appium/Cypress/Detox/Playwright286- [框架对比](docs/reference/FRAMEWORK-COMPARISON.md) - CPTF vs Jest/Appium/Cypress/Detox/Playwright
277-- [UI对比指南](docs/advanced/UI-COMPARISON.md) - 视觉对比能力使用指南287+- [UI 对比指南](docs/advanced/UI-COMPARISON.md) - 视觉对比能力使用指南
278-- [Expect断言指南](docs/advanced/EXPECT-GUIDE.md) - 声明式断言使用指南288+- [Expect 断言指南](docs/advanced/EXPECT-GUIDE.md) - 声明式断言使用指南
279- [声明式测试指南](docs/getting-started/DECLARATIVE-TEST-GUIDE.md) - 声明式测试编写模式289- [声明式测试指南](docs/getting-started/DECLARATIVE-TEST-GUIDE.md) - 声明式测试编写模式
280- [命令式测试指南](docs/advanced/IMPERATIVE-TEST-GUIDE.md) - 命令式测试编写模式290- [命令式测试指南](docs/advanced/IMPERATIVE-TEST-GUIDE.md) - 命令式测试编写模式
281 291 
282### 应用文档292### 应用文档
283-- [测试用例编写](../../../app/tests/README.md) - Executor模式、声明式模式293+- [UI 对比指南](docs/advanced/UI-COMPARISON.md) - 跨平台 UI 结构对比(属性/样式/位置/结构四种模式)
284-- [对比测试指南](../../../app/tests/COMPARISON-GUIDE.md) - TextInput跨平台对比294+- [用户指南](docs/getting-started/USER-GUIDE.md) - 配置、CLI、API 使用与指标清单
285 295 
286### 辅助文档296### 辅助文档
297+ 
287- [构建文档](BUILD.md) - 构建流程298- [构建文档](BUILD.md) - 构建流程
288- [变更日志](CHANGELOG.md) - 版本历史299- [变更日志](CHANGELOG.md) - 版本历史
289 300 
290## 示例项目301## 示例项目
291 302 
292-完整的示例项目位于:`../../../app/`303+示例与实战配置位于配套的 RNOH 测试仓(`rnoh_test`,与 `@cptf/framework` 通过 `workspace:*` 协作)。
293 304 
294### 测试用例示例305### 测试用例示例
306+ 
295```307```
296app/tests/308app/tests/
297├── cases/ # 测试用例目录309├── cases/ # 测试用例目录
298│ ├── performance/ # 性能测试用例310│ ├── performance/ # 性能测试用例
299-│ └── functional/ # 功能测试用例(含跨平台对比)311+│ ├── functional/ # 功能测试用例(含跨平台对比)
300-├── TextInput-Comparison-Manifest.json # TextInput对比配置312+│ └── stability/ # 稳定性测试用例
301-└── scenes/ # 测试场景组件313+├── scenes/ # 测试场景组件
314+└── test-cases-index.ts # cptf index 生成的用例索引
302```315```
303 316 
304### 执行示例317### 执行示例
305 318 
306-框架层CLI命令(需先 `pnpm build`):319+框架层 CLI 命令(需先 `pnpm build`):
320+ 
307```bash321```bash
308# 在项目根目录执行322# 在项目根目录执行
309node packages/framework/dist/cli.js run -p harmony -m performance323node packages/framework/dist/cli.js run -p harmony -m performance
@@ -314,7 +328,7 @@ node packages/framework/dist/cli.js compare -t textinput-placeholder-compatibili
314cptf run -m performance328cptf run -m performance
315cptf compare -t textinput-placeholder-compatibility329cptf compare -t textinput-placeholder-compatibility
316 330 
317-# 应用层执行(cd app)331+# 应用层执行(cd app,位于 rnoh_test 仓)
318pnpm functional:run # 功能模块批量运行(app 侧脚本)332pnpm functional:run # 功能模块批量运行(app 侧脚本)
319pnpm test:compare # 委托给 cptf compare333pnpm test:compare # 委托给 cptf compare
320cptf compare -t <case-id> # 单用例对比334cptf compare -t <case-id> # 单用例对比
@@ -322,12 +336,13 @@ cptf compare -t <case-id> # 单用例对比
322 336 
323## 测试覆盖337## 测试覆盖
324 338 
325-### Executor测试用例339+### Executor 测试用例
340+ 
326- 性能测试(全指标、日历、列表滚动、内存泄漏等)341- 性能测试(全指标、日历、列表滚动、内存泄漏等)
327- 稳定性测试(后台恢复、点击、崩溃恢复、内存压力、Monkey、时长)342- 稳定性测试(后台恢复、点击、崩溃恢复、内存压力、Monkey、时长)
328- 功能测试(基础组件、跨平台一致性)343- 功能测试(基础组件、跨平台一致性)
329 344 
330-详见:[对比测试指南](../../../app/tests/COMPARISON-GUIDE.md)345+详见:[UI 对比指南](docs/advanced/UI-COMPARISON.md)
331 346 
332## 性能优化成果347## 性能优化成果
333 348 
@@ -363,7 +378,7 @@ cptf compare -t <case-id> # 单用例对比
363// 1. 实现IosSystem类(约300行)378// 1. 实现IosSystem类(约300行)
364class IosSystem extends BaseSystem implements ISystem {379class IosSystem extends BaseSystem implements ISystem {
365 getDeviceFlag(): string { return '-u'; }380 getDeviceFlag(): string { return '-u'; }
366- 381+ 
367 async launchApp(bundleId: string): Promise<boolean> {382 async launchApp(bundleId: string): Promise<boolean> {
368 await this.execCommand(`ios-deploy --bundle_id=${bundleId}`);383 await this.execCommand(`ios-deploy --bundle_id=${bundleId}`);
369 return true;384 return true;
@@ -389,8 +404,8 @@ const system = platform === 'ios' ? new IosSystem(config, logger) : ...
389 404 
390```typescript405```typescript
391// TestRunner.run()自动清理406// TestRunner.run()自动清理
392-await this.performanceCollector.destroy(); // 清理性能监控定时器407+await this.performanceCollector.destroy(); // 清理性能监控定时器
393-await this.system.destroy(); // 清理系统缓存定时器408+await this.system.destroy(); // 清理系统缓存定时器
394```409```
395 410 
396## License411## License
@@ -408,9 +423,10 @@ v0.3.1
408---423---
409 424 
410**相关链接**:425**相关链接**:
411-- [React Native官方文档](https://www.react-native.cn/docs)426+ 
412-- [Flutter官方文档](https://flutter.dev/docs)427+- [React Native 官方文档](https://www.react-native.cn/docs)
428+- [Flutter 官方文档](https://flutter.dev/docs)
413- [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html)429- [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html)
414- [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/)430- [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/)
415-- [HarmonyOS开发文档](https://developer.harmonyos.com)431+- [HarmonyOS 开发文档](https://developer.harmonyos.com)
416-- [Android开发文档](https://developer.android.com)432+- [Android 开发文档](https://developer.android.com)
@@ -1,6 +1,8 @@
1# 迁移指南1# 迁移指南
2 2 
3-> v0.1.x → v0.2.x 的破坏性变更与迁移步骤。配合 [CHANGELOG.md](../CHANGELOG.md) 使用。3+# 迁移指南
4+ 
5+> v0.1.x → v0.3.x 的破坏性变更与迁移步骤。配合 [CHANGELOG.md](../CHANGELOG.md) 使用。
4 6 
5---7---
6 8 
@@ -9,27 +11,37 @@
9### 1. `ISystem` 拆分为 7 个子接口11### 1. `ISystem` 拆分为 7 个子接口
10 12 
11**变更前(0.1.x)**:13**变更前(0.1.x)**:
14+ 
12```ts15```ts
13class HarmonySystem implements ISystem {16class HarmonySystem implements ISystem {
14 // 2000+ 行,所有方法集中17 // 2000+ 行,所有方法集中
15- async click() { /* ... */ }18+ async click() {
16- async screenshot() { /* ... */ }19+ /* ... */
17- async collectFPS() { /* ... */ }20+ }
18- async detectANR() { /* ... */ }21+ async screenshot() {
22+ /* ... */
23+ }
24+ async collectFPS() {
25+ /* ... */
26+ }
27+ async detectANR() {
28+ /* ... */
29+ }
19 // ... 100+ 方法30 // ... 100+ 方法
20}31}
21```32```
22 33 
23**变更后(0.2.x)**:34**变更后(0.2.x)**:
35+ 
24```ts36```ts
25class HarmonySystem37class HarmonySystem
26- implements ISystemCore, ISystemUI, ISystemApp, ISystemPerf,38+ implements ISystemCore, ISystemUI, ISystemApp, ISystemPerf, ISystemStability, ISystemMedia, ISystemSession {
27- ISystemStability, ISystemMedia, ISystemSession {
28 // 每个子接口 5-15 个方法39 // 每个子接口 5-15 个方法
29}40}
30```41```
31 42 
32**迁移步骤**:43**迁移步骤**:
44+ 
33- ✅ **无需改动**:如果你只调用 `ISystem` 公共方法(`click`/`screenshot`/`installApp` 等)45- ✅ **无需改动**:如果你只调用 `ISystem` 公共方法(`click`/`screenshot`/`installApp` 等)
34- ⚠️ **需要改动**:如果你依赖 `ISystem` 的内部实现细节(如 `_aborted` 字段、`resetHmd` 私有方法)46- ⚠️ **需要改动**:如果你依赖 `ISystem` 的内部实现细节(如 `_aborted` 字段、`resetHmd` 私有方法)
35 - 这些已提升为公共方法:`abort(reason)` / `isAborted()` / `hmdReset?(): Promise<boolean>` / `hmdResetCancelled?(): void`47 - 这些已提升为公共方法:`abort(reason)` / `isAborted()` / `hmdReset?(): Promise<boolean>` / `hmdResetCancelled?(): void`
@@ -37,33 +49,36 @@ class HarmonySystem
37 49 
38### 2. CLI 命令参数50### 2. CLI 命令参数
39 51 
40-| 0.1.x | 0.2.x | 说明 |52+| 0.1.x | 0.2.x | 说明 |
41-|-------|-------|------|53+| ------------------------------- | ---------------------------- | ------------------------------------------------ |
42-| `cptf run --platform harmony` | `cptf run -p harmony` | 短选项 |54+| `cptf run --platform harmony` | `cptf run -p harmony` | 短选项 |
43-| `cptf run --module performance` | `cptf run -m performance` | 短选项 |55+| `cptf run --module performance` | `cptf run -m performance` | 短选项 |
44-| `cptf run --case foo` | `cptf run -c foo` / `-t foo` | 短选项 |56+| `cptf run --case foo` | `cptf run -c foo` / `-t foo` | 短选项 |
45-| `cptf run --install` | `cptf run --install` | 不变 |57+| `cptf run --install` | `cptf run --install` | 不变 |
46-| `cptf run --concurrency 4` | `cptf run --concurrency 4` | 不变 |58+| `cptf run --concurrency 4` | `cptf run --concurrency 4` | 不变 |
47-| 无 | `cptf run --strategy <s>` | 新增:round-robin / by-module / by-case / single |59+| 无 | `cptf run --strategy <s>` | 新增:round-robin / by-module / by-case / single |
48-| 无 | `cptf run --device <udid>` | 新增:指定设备(可重复) |60+| 无 | `cptf run --device <udid>` | 新增:指定设备(可重复) |
49-| 无 | `cptf run --reconnect` | 新增:强制重连 |61+| 无 | `cptf run --reconnect` | 新增:强制重连 |
50-| 无 | `cptf run --dry-run` | 新增:仅校验用例不执行 |62+| 无 | `cptf run --dry-run` | 新增:仅校验用例不执行 |
51 63 
52### 3. `scanTestCases` 同步版删除64### 3. `scanTestCases` 同步版删除
53 65 
54**变更前(0.1.x)**:66**变更前(0.1.x)**:
67+ 
55```ts68```ts
56import { scanTestCases } from '@cptf/framework';69import { scanTestCases } from '@cptf/framework';
57const cases = scanTestCases('./cases');70const cases = scanTestCases('./cases');
58```71```
59 72 
60**变更后(0.2.x)**:73**变更后(0.2.x)**:
74+ 
61```ts75```ts
62import { scanTestCasesLazy } from '@cptf/framework';76import { scanTestCasesLazy } from '@cptf/framework';
63const { cases } = await scanTestCasesLazy('./cases', { parallel: true, cache: true });77const { cases } = await scanTestCasesLazy('./cases', { parallel: true, cache: true });
64```78```
65 79 
66**自动迁移**:80**自动迁移**:
81+ 
67```bash82```bash
68# 1. 全局搜索83# 1. 全局搜索
69grep -rn "scanTestCases\b" app/84grep -rn "scanTestCases\b" app/
@@ -87,6 +102,7 @@ sed -i 's|scanTestCases(|scanTestCasesLazy(|g' app/**/*.ts
87`PerformanceCollector` 内部 `enabledMetrics` 由 `Set<'fps' | 'memory' | 'cpu' | 'whiteScreen'>` 缩减为 `Set<'fps' | 'memory' | 'cpu'>`(`whiteScreen` 改为 `BlankScreenRatioCollector` 独立注册)。102`PerformanceCollector` 内部 `enabledMetrics` 由 `Set<'fps' | 'memory' | 'cpu' | 'whiteScreen'>` 缩减为 `Set<'fps' | 'memory' | 'cpu'>`(`whiteScreen` 改为 `BlankScreenRatioCollector` 独立注册)。
88 103 
89**用户侧影响**:104**用户侧影响**:
105+ 
90- `monitoring.metrics: ['fps', 'memory', 'cpu', 'whiteScreen']` → 改为 `monitoring.metrics: ['fps', 'memory', 'cpu', 'blankScreenRatio']`106- `monitoring.metrics: ['fps', 'memory', 'cpu', 'whiteScreen']` → 改为 `monitoring.metrics: ['fps', 'memory', 'cpu', 'blankScreenRatio']`
91 107 
92### 6. 稳定性测试参数配置化108### 6. 稳定性测试参数配置化
@@ -116,6 +132,7 @@ ls app/test-reports/harmony-abcd1234/2026-06-08T10-30-00/screenshots/
116```132```
117 133 
118**自动迁移**:134**自动迁移**:
135+ 
119```bash136```bash
120# 一次性迁移旧报告137# 一次性迁移旧报告
121mv app/test-reports/2026-* app/test-reports/_archive/138mv app/test-reports/2026-* app/test-reports/_archive/
@@ -160,6 +177,105 @@ pnpm test
160 177 
161---178---
162 179 
180+## 0.2.x → 0.3.x 主要变更
181+ 
182+### 1. `deepLinkScheme` 转必填(破坏性)
183+ 
184+**变更前(0.2.x)**:`deepLinkScheme` 有框架默认值,配置可省略。
185+ 
186+**变更后(0.3.x)**:框架不再提供默认值,`cptf.config.json` 未配置时启动即抛错。
187+ 
188+**迁移步骤**:
189+ 
190+- ✅ 顶层补 `"deepLinkScheme": "rnoh-test"`(与 `scenes/index.ts` 的 Deep Link scheme 保持一致)。
191+ 
192+### 2. `automationStatus: 'unsupported'` → `'manual'`(破坏性)
193+ 
194+**变更后(0.3.x)**:类型收窄为 `automationStatus?: 'manual'`,术语统一为 manual(三态 Unsupported/Passed/Failed 语义不变,见 [TEST-CASE-FIELDS-SPEC.md](./TEST-CASE-FIELDS-SPEC.md))。
195+ 
196+**迁移步骤**:
197+ 
198+- ⚠️ 用例代码中 `automationStatus: 'unsupported'` 全部改为 `'manual'`(仓库 2026-10 已全量迁移)。
199+- 框架读取侧保留 `'unsupported'` 双值兼容(仅历史报告 JSON 数据),新代码统一写 `'manual'`。
200+ 
201+### 3. `fpsMin` / `fpsMax` 指标删除(破坏性)
202+ 
203+**变更后(0.3.x)**:`PerfMetricType` / `MetricType` 枚举删除 `fpsMin` / `fpsMax`(`FPSMinCollector` / `FPSMaxCollector` 一并删除)。替代指标:`stability`(卡顿率)/ `fpsP5`(分位帧率),见 [SMOOTHNESS-METRICS-SPEC.md](./reference/SMOOTHNESS-METRICS-SPEC.md)。
204+ 
205+**迁移步骤**:
206+ 
207+- ⚠️ 性能用例 `collect` 引用 `fpsMin` / `fpsMax` 改为 `stability` 或 `fpsP5`。
208+ 
209+### 4. `applicability.rnVersion` → `applicability.host`(破坏性)
210+ 
211+**变更后(0.3.x)**:`ApplicabilitySpec` 框架无关化,RN 专属 `rnVersion` 改为通用 host map(可多框架声明)。
212+ 
213+**迁移步骤**:
214+ 
215+- ⚠️ `@Test({ applicability: { rnVersion: '>=0.84' } })` → `@Test({ applicability: { host: { 'react-native': '>=0.84' } } })`。
216+- 用例级版本门控走 `host`;顶层 `cptf.config.json` 的 `rnVersion` 仍用于视觉基线分段。
217+ 
218+### 5. `VisualComparator.captureElement` 删除(破坏性)
219+ 
220+**迁移步骤**:
221+ 
222+- ⚠️ `captureElement(testId, name)` → `captureComponent`(全仓库零生产调用,仅历史遗留)。
223+ 
224+### 6. 新增:native 驱动模式(可选迁移)
225+ 
226+- `cptf run --driver native`:灰盒模式(设备端原生执行,需构建测试 HAP:Harmony `ohosTest` / Android `androidTest`)。
227+- `cptf run --driver external`:黑盒模式(外部 Agent,默认)。
228+- `cptf run --driver auto`:自动模式(优先 native,降级 external)。
229+ 
230+无需改动现有配置;如需 native 模式采集(如白屏率),先构建并安装测试 HAP,见 [DEVICE-NATIVE-ARCHITECTURE.md](./architecture/DEVICE-NATIVE-ARCHITECTURE.md)。
231+ 
232+### 7. 超时控制体系
233+ 
234+- `TestConfig.caseTimeout?: number`(ms)— 单用例超时,覆盖默认 `max(估算时长 × 3, 120s)`
235+- `cptf run --timeout <seconds>` / `cptf compare --timeout <seconds>` — 全局超时,超时后剩余用例标记 `skipped`(不计入失败)并生成部分报告
236+- SIGINT/SIGTERM 优雅中断:Ctrl-C 不再直接退出,等待最多 30s 生成部分报告
237+ 
238+### 8. 指标口径统一(0.3.1)
239+ 
240+- `PerfMetricType` 联合 19 成员 = 17 内置 + 2 插件(`renderTime`/`unmountTime` 由 `@cptf/benchmark` 注册);`exitLatency` 幽灵成员(仅声明未实现,引用必抛 `Unknown metric type`)已删除——若配置里引用了它,删除即可
241+- 报告指标一律走 `computeUnifiedMetrics()`(`TestReport.unifiedMetrics` / `ComparisonReport.unifiedMetrics`);通过率 = passedCases / executedCases,崩溃数独立统计,性能未启用时隐藏性能行。自定义报告模板请从 `unifiedMetrics` 取数,勿再从散字段拼算
242+- `monitoring` 时代的原始采集通道(`fps`/`memory`/`cpu`)不是 `PerfMetricType` 成员——`memory`/`cpu` 不能出现在 `collect` 里,聚合指标用 `memoryPeak`/`memoryAvg`/`cpuPeak`/`cpuAvg` 等;完整 19 成员联合见 `src/base/types/index.ts`(单一真相)
243+ 
244+### 9. 其余行为变化速览
245+ 
246+- **报告转义**:HTML/Markdown 全量转义,自定义模板若依赖「未转义注入」的行为需要调整
247+- **`--include-unsupported`**:无法自动化的用例默认跳过且从通过率分母排除;传此参数可纳入执行
248+- **Android XML 布局解析**:`parseUILayout` 改栈式解析并携带 children 层级,容器文本断言(子 Text 聚合)行为修正——0.2.x 误判「元素不存在」的断言现在可能真实通过/失败
249+- **截图缓存**:expect 截图不再删除文件(样式对比依赖),磁盘占用会略增
250+ 
251+### 0.2.x → 0.3.x 升级步骤
252+ 
253+```bash
254+# 1. 升级依赖
255+cd app && pnpm update @cptf/framework
256+ 
257+# 2. 补必填配置(若缺)
258+# cptf.config.json 顶层加 "deepLinkScheme": "<你的应用scheme>"
259+ 
260+# 3. 跑类型检查(collect/PerfMetricType 拼写错误在这里暴露)
261+pnpm typecheck
262+ 
263+# 4. 跑现有用例(默认 driver=auto,行为与 0.2.x external 一致)
264+pnpm test
265+ 
266+# 5. 如有失败,按上述分项指南调整
267+# - deepLinkScheme 缺失 → 补配置
268+# - automationStatus: 'unsupported' → 'manual'
269+# - collect 里的 fpsMin/fpsMax → stability / fpsP5
270+# - applicability.rnVersion → applicability.host
271+# - captureElement → captureComponent
272+# - collect 里的 memory/cpu → 聚合指标(memoryPeak 等)
273+# - exitLatency → 删除
274+# - 自定义报告 → 改读 unifiedMetrics
275+```
276+ 
277+---
278+ 
163## 0.2.x 内部小版本迁移279## 0.2.x 内部小版本迁移
164 280 
165### 0.2.0 → 0.2.1281### 0.2.0 → 0.2.1
@@ -1,6 +1,6 @@
1# 性能调优指南1# 性能调优指南
2 2 
3-> 调优 `PerformanceCollector` + 16 个 `BaseMetricCollector` 的间隔、阈值、采样策略。3+> 调优 `PerformanceCollector` + 17 个 `BaseMetricCollector` 的间隔、阈值、采样策略。
4> 配合 [PERFORMANCE-METRICS-SPEC.md](architecture/PERFORMANCE-METRICS-SPEC.md) 和 [STABILITY-SPEC.md](architecture/STABILITY-SPEC.md) 使用。4> 配合 [PERFORMANCE-METRICS-SPEC.md](architecture/PERFORMANCE-METRICS-SPEC.md) 和 [STABILITY-SPEC.md](architecture/STABILITY-SPEC.md) 使用。
5 5 
6---6---
@@ -18,18 +18,18 @@
18})18})
19```19```
20 20 
21-| 场景 | 推荐 interval | 理由 |21+| 场景 | 推荐 interval | 理由 |
22-|------|---------------|------|22+| ------------------ | ------------- | -------------- |
23-| 启动耗时测试 | 200ms | 抓瞬时峰值 |23+| 启动耗时测试 | 200ms | 抓瞬时峰值 |
24-| 滚动 / 动画 | 100ms | 抓丢帧 |24+| 滚动 / 动画 | 100ms | 抓丢帧 |
25-| 长时压测(>10min) | 2000ms | 控制样本量 |25+| 长时压测(>10min) | 2000ms | 控制样本量 |
26-| 默认 | 1000ms | 平衡精度与开销 |26+| 默认 | 1000ms | 平衡精度与开销 |
27 27 
28`historySize`(默认 720 = 12 分钟):28`historySize`(默认 720 = 12 分钟):
29 29 
30```ts30```ts
31import { globalConfig } from '@cptf/framework';31import { globalConfig } from '@cptf/framework';
32-globalConfig.getPerformance().historySize = 1800; // 30 分钟32+globalConfig.getPerformance().historySize = 1800; // 30 分钟
33```33```
34 34 
35## 2. 阈值配置35## 2. 阈值配置
@@ -52,12 +52,12 @@ globalConfig.getPerformance().historySize = 1800; // 30 分钟
52})52})
53```53```
54 54 
55-| 指标 | 字段 | 单位 | 用途 |55+| 指标 | 字段 | 单位 | 用途 |
56-|------|------|------|------|56+| ------------------------ | -------------------------------- | ------ | -------- |
57-| `fps` | `min` / `avg` / `max` / `stdDev` | Hz | 帧率波动 |57+| `fps` | `min` / `avg` / `max` / `stdDev` | Hz | 帧率波动 |
58-| `memory` | `peak` / `avg` / `growthRate` | KB | 内存压力 |58+| `memory` | `peak` / `avg` / `growthRate` | KB | 内存压力 |
59-| `cpu` | `avg` / `peak` | % | CPU 压力 |59+| `cpu` | `avg` / `peak` | % | CPU 压力 |
60-| `fpsJank` / `fpsBigJank` | `max` / `count` | frames | 卡顿 |60+| `fpsJank` / `fpsBigJank` | `max` / `count` | frames | 卡顿 |
61 61 
62## 3. 稳定性 detector 调优62## 3. 稳定性 detector 调优
63 63 
@@ -90,20 +90,20 @@ globalConfig.getPerformance().historySize = 1800; // 30 分钟
90 90 
91### 3.1 OLED vs LCD91### 3.1 OLED vs LCD
92 92 
93-| 屏幕类型 | brightnessThreshold | whiteRatioThreshold |93+| 屏幕类型 | brightnessThreshold | whiteRatioThreshold |
94-|----------|---------------------|---------------------|94+| ------------ | ------------------- | ------------------- |
95-| LCD(高亮) | 245 | 0.90 |95+| LCD(高亮) | 245 | 0.90 |
96-| OLED(深黑) | 235 | 0.80 |96+| OLED(深黑) | 235 | 0.80 |
97-| 折叠屏 | 240 | 0.85 |97+| 折叠屏 | 240 | 0.85 |
98 98 
99### 3.2 短压测 vs 长压测99### 3.2 短压测 vs 长压测
100 100 
101| 测试时长 | growthThresholdKBPerMin | minSamples | minDurationMs |101| 测试时长 | growthThresholdKBPerMin | minSamples | minDurationMs |
102-|----------|--------------------------|------------|----------------|102+| -------- | ----------------------- | ---------- | ------------- |
103-| < 1min | 不检查 | 0 | 0 |103+| < 1min | 不检查 | 0 | 0 |
104-| 1-5min | 50 | 5 | 30_000 |104+| 1-5min | 50 | 5 | 30_000 |
105-| 5-30min | 100 | 10 | 60_000 |105+| 5-30min | 100 | 10 | 60_000 |
106-| > 30min | 200 | 30 | 300_000 |106+| > 30min | 200 | 30 | 300_000 |
107 107 
108## 4. 自定义 collector108## 4. 自定义 collector
109 109 
@@ -125,7 +125,7 @@ class FirstPaintCollector extends BaseMetricCollector {
125 async collect(ctx: MetricCollectorContext): Promise<Record<string, number>> {125 async collect(ctx: MetricCollectorContext): Promise<Record<string, number>> {
126 // 通过自定义 hook 检测首屏渲染完成126 // 通过自定义 hook 检测首屏渲染完成
127 if (this._renderedAt === 0) {127 if (this._renderedAt === 0) {
128- this._renderedAt = await ctx.device.executeShell?.('getprop cptf.firstRenderAt') ?? 0;128+ this._renderedAt = (await ctx.device.executeShell?.('getprop cptf.firstRenderAt')) ?? 0;
129 }129 }
130 return { firstPaint: this._renderedAt - this.startTime };130 return { firstPaint: this._renderedAt - this.startTime };
131 }131 }
@@ -139,11 +139,7 @@ MetricCollectorFactory.register('firstPaint', FirstPaintCollector);
139`PerformanceCollector` 默认并行采集 `fps | memory | cpu`:139`PerformanceCollector` 默认并行采集 `fps | memory | cpu`:
140 140 
141```ts141```ts
142-const [fps, mem, cpu] = await Promise.all([142+const [fps, mem, cpu] = await Promise.all([this.getFPS(), this.getMemory(), this.getCPU()]);
143- this.getFPS(),
144- this.getMemory(),
145- this.getCPU(),
146-]);
147```143```
148 144 
149如需禁用其中某项(节省开销):145如需禁用其中某项(节省开销):
@@ -163,7 +159,7 @@ const [fps, mem, cpu] = await Promise.all([
163```ts159```ts
164import { PerformanceCollector } from '@cptf/framework';160import { PerformanceCollector } from '@cptf/framework';
165const collector = new PerformanceCollector(system, historySize, logger, {161const collector = new PerformanceCollector(system, historySize, logger, {
166- driftThresholdPct: 0.2, // 默认 20%162+ driftThresholdPct: 0.2, // 默认 20%
167});163});
168```164```
169 165 
@@ -181,7 +177,7 @@ CPTF_ASYNC_REPORTS=false cptf run # 关闭异步(同步生成)
181长 monkey 测试(> 5000 次)启用并发桶(0.2.x 已实现):177长 monkey 测试(> 5000 次)启用并发桶(0.2.x 已实现):
182 178 
183```ts179```ts
184-await system.executeMonkey(10_000, 50); // 10000 次,50ms 间隔180+await system.executeMonkey(10_000, 50); // 10000 次,50ms 间隔
185```181```
186 182 
187- `MONKEY_CONCURRENCY` 默认 4(`HarmonySystem.MONKEY_CONCURRENCY`)183- `MONKEY_CONCURRENCY` 默认 4(`HarmonySystem.MONKEY_CONCURRENCY`)
@@ -192,37 +188,37 @@ await system.executeMonkey(10_000, 50); // 10000 次,50ms 间隔
192 188 
193### Harmony189### Harmony
194 190 
195-| 命令 | 超时(默认) | 调优建议 |191+| 命令 | 超时(默认) | 调优建议 |
196-|------|--------------|----------|192+| ----------------------------------------- | ------------ | -------------------- |
197-| `hidumper --mem` | 8s | 大量进程时调高到 15s |193+| `hidumper --mem` | 8s | 大量进程时调高到 15s |
198-| `hidumper --cpuusage` | 5s | 多核设备调高到 10s |194+| `hidumper --cpuusage` | 5s | 多核设备调高到 10s |
199-| `hidumper -s RenderService -a 'fpsCount'` | 3s | 锁定窗口名后稳定 |195+| `hidumper -s RenderService -a 'fpsCount'` | 3s | 锁定窗口名后稳定 |
200 196 
201### Android197### Android
202 198 
203-| 命令 | 超时 | 调优 |199+| 命令 | 超时 | 调优 |
204-|------|------|------|200+| ----------------- | ---- | ------------------------------ |
205-| `dumpsys gfxinfo` | 5s | 压测前 `dumpsys gfxinfo reset` |201+| `dumpsys gfxinfo` | 5s | 压测前 `dumpsys gfxinfo reset` |
206-| `dumpsys meminfo` | 8s | 大量进程时调高 |202+| `dumpsys meminfo` | 8s | 大量进程时调高 |
207-| `top -n 1` | 3s | 性能压测建议 |203+| `top -n 1` | 3s | 性能压测建议 |
208 204 
209### iOS205### iOS
210 206 
211-| 命令 | 超时 | 调优 |207+| 命令 | 超时 | 调优 |
212-|------|------|------|208+| ------------------------------- | ------------- | ----------------- |
213-| `xctrace record` | 15s + 2s 录屏 | 失败时 retry 2 次 |209+| `xctrace record` | 15s + 2s 录屏 | 失败时 retry 2 次 |
214-| `xcrun simctl spawn ... sysctl` | 5s | 模拟器专属 |210+| `xcrun simctl spawn ... sysctl` | 5s | 模拟器专属 |
215 211 
216## 10. 性能基准212## 10. 性能基准
217 213 
218以下数据基于内部测试(仅参考):214以下数据基于内部测试(仅参考):
219 215 
220-| 场景 | CPTF (HTX/ATX) | SoloPi (adb) | 差异 |216+| 场景 | CPTF (HTX/ATX) | SoloPi (adb) | 差异 |
221-|------|------------------|---------------|------|217+| ----------------- | -------------- | ------------ | -------- |
222-| 单用例 click 响应 | 80-120ms | 250-400ms | **3-4x** |218+| 单用例 click 响应 | 80-120ms | 250-400ms | **3-4x** |
223-| 截图采集 | 60-80ms | 150-250ms | **2-3x** |219+| 截图采集 | 60-80ms | 150-250ms | **2-3x** |
224-| FPS 实时采集 | 100-200ms | 500-1000ms | **3-5x** |220+| FPS 实时采集 | 100-200ms | 500-1000ms | **3-5x** |
225-| 跨平台对比报告 | 1-2s | N/A | 独有 |221+| 跨平台对比报告 | 1-2s | N/A | 独有 |
226 222 
227详细 benchmark 数据待补充(见 README §Evidence 章节)。223详细 benchmark 数据待补充(见 README §Evidence 章节)。
228 224 
@@ -244,11 +240,11 @@ cptf run -m performance --monitoring.interval=2000
244 240 
245`cptf` CLI 启动时(`packages/framework/src/cli/index.ts`)检测当前 V8 堆上限,若低于阈值则 re-exec 加大堆(`--max-old-space-size`),避免大 comparison 任务(34+ cases × full UI tree)OOM。阈值、re-exec 目标与 ts-node 内存缓存**可经环境变量配置,默认与上游一致**(8GB 阈值 / 16GB re-exec / 不设 memoryCache),不影响默认行为。241`cptf` CLI 启动时(`packages/framework/src/cli/index.ts`)检测当前 V8 堆上限,若低于阈值则 re-exec 加大堆(`--max-old-space-size`),避免大 comparison 任务(34+ cases × full UI tree)OOM。阈值、re-exec 目标与 ts-node 内存缓存**可经环境变量配置,默认与上游一致**(8GB 阈值 / 16GB re-exec / 不设 memoryCache),不影响默认行为。
246 242 
247-| 环境变量 | 含义 | 默认 | 16GB Mac 示例 |243+| 环境变量 | 含义 | 默认 | 16GB Mac 示例 |
248-|---|---|---|---|244+| --------------------------- | --------------------------------------- | ---------- | ------------- |
249-| `CPTF_HEAP_THRESHOLD` | 触发 re-exec 的堆阈值(GB) | `8` | `4` |245+| `CPTF_HEAP_THRESHOLD` | 触发 re-exec 的堆阈值(GB) | `8` | `4` |
250-| `CPTF_MAX_OLD_SPACE` | re-exec 目标 `--max-old-space-size`(MB) | `16384` | `8192` |246+| `CPTF_MAX_OLD_SPACE` | re-exec 目标 `--max-old-space-size`(MB) | `16384` | `8192` |
251-| `CPTF_TS_NODE_MEMORY_CACHE` | ts-node `memoryCache`,设 `false` 关闭 | 不设(官方) | `false` |247+| `CPTF_TS_NODE_MEMORY_CACHE` | ts-node `memoryCache`,设 `false` 关闭 | 不设(官方) | `false` |
252 248 
253**默认行为**(不设环境变量):阈值 8GB,re-exec 到 16GB——适合 32GB+ 内存机器跑大 comparison。249**默认行为**(不设环境变量):阈值 8GB,re-exec 到 16GB——适合 32GB+ 内存机器跑大 comparison。
254 250 
@@ -6,75 +6,75 @@
6 6 
7## 快速开始7## 快速开始
8 8 
9-| 文档 | 说明 | 适用读者 |9+| 文档 | 说明 | 适用读者 |
10-|------|------|----------|10+| ----------------------------------------------------------- | ------------------------------------------------------------ | ---------- |
11-| [用户指南](getting-started/USER-GUIDE.md) | 安装、配置、CLI命令、API使用、最佳实践 | 测试工程师 |11+| [用户指南](getting-started/USER-GUIDE.md) | 安装、配置、CLI 命令、API 使用、最佳实践 | 测试工程师 |
12-| [声明式测试指南](getting-started/DECLARATIVE-TEST-GUIDE.md) | @Test/@Step装饰器、TestConfig/StepConfig配置、性能测试编写 | 测试工程师 |12+| [声明式测试指南](getting-started/DECLARATIVE-TEST-GUIDE.md) | @Test/@Step 装饰器、TestConfig/StepConfig 配置、性能测试编写 | 测试工程师 |
13 13 
14## 进阶使用14## 进阶使用
15 15 
16-| 文档 | 说明 | 适用读者 |16+| 文档 | 说明 | 适用读者 |
17-|------|------|----------|17+| --------------------------------------------------- | ------------------------------------------------ | -------------- |
18-| [命令式测试指南](advanced/IMPERATIVE-TEST-GUIDE.md) | TestExecutor模式、自定义导航/迭代/聚合 | 高级测试工程师 |18+| [命令式测试指南](advanced/IMPERATIVE-TEST-GUIDE.md) | TestExecutor 模式、自定义导航/迭代/聚合 | 高级测试工程师 |
19-| [Expect断言指南](advanced/EXPECT-GUIDE.md) | 声明式断言、ExpectConfig、验证策略 | 测试工程师 |19+| [Expect 断言指南](advanced/EXPECT-GUIDE.md) | 声明式断言、ExpectConfig、验证策略 | 测试工程师 |
20-| [UI对比指南](advanced/UI-COMPARISON.md) | 跨平台UI结构对比、属性/样式/位置/层级4维对比 | 测试工程师 |20+| [UI 对比指南](advanced/UI-COMPARISON.md) | 跨平台 UI 结构对比、属性/样式/位置/层级 4 维对比 | 测试工程师 |
21 21 
22## 架构与规格22## 架构与规格
23 23 
24-| 文档 | 说明 | 适用读者 |24+| 文档 | 说明 | 适用读者 |
25-|------|------|----------|25+| -------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------- |
26-| [架构详解](architecture/ARCHITECTURE.md) | 五层架构、源码目录、依赖规则、接口分离 | 框架开发者 |26+| [架构详解](architecture/ARCHITECTURE.md) | 五层架构、源码目录、依赖规则、接口分离 | 框架开发者 |
27-| [架构优势](architecture/ARCHITECTURE-OVERVIEW.md) | 双引擎加速、Deep Link导航、可扩展性 | 选型决策者 |27+| [架构优势](architecture/ARCHITECTURE-OVERVIEW.md) | 双引擎加速、Deep Link 导航、可扩展性 | 选型决策者 |
28-| [能力规格](architecture/CAPABILITY-SPEC.md) | 性能指标规格、API规格、数据结构(含实现状态) | 框架开发者 |28+| [能力规格](architecture/CAPABILITY-SPEC.md) | 性能指标规格、API 规格、数据结构(含实现状态) | 框架开发者 |
29-| [性能指标规格](architecture/PERFORMANCE-METRICS-SPEC.md) | 16个性能指标定义、采集方式、业界对标 | 性能测试工程师 |29+| [性能指标规格](architecture/PERFORMANCE-METRICS-SPEC.md) | 19 个性能指标定义(17 内置 + 2 插件)、采集方式、业界对标 | 性能测试工程师 |
30-| [设备指标采集设计](architecture/DEVICE-METRICS-COLLECTION-DESIGN.md) | FPS/内存/CPU/指令数采集通道最佳实践(SP_daemon server 模式,真机实测) | 框架开发者 |30+| [设备指标采集设计](architecture/DEVICE-METRICS-COLLECTION-DESIGN.md) | FPS/内存/CPU/指令数采集通道最佳实践(SP_daemon server 模式,真机实测) | 框架开发者 |
31-| [流畅度指标规范 v2](architecture/SMOOTHNESS-METRICS-SPEC.md) | stability/dropRate/fpsP5 业界口径(Vitals/PerfDog/GameBench),fpsMin 已删除 | 框架开发者、性能测试工程师 |31+| [流畅度指标规范 v2](architecture/SMOOTHNESS-METRICS-SPEC.md) | stability/dropRate/fpsP5 业界口径(Vitals/PerfDog/GameBench),fpsMin 已删除 | 框架开发者、性能测试工程师 |
32-| [稳定性检测参数规格](architecture/STABILITY-SPEC.md) | 9 个 detector 参数 + 3 检测器调优 + 与 13 指标关系 | 稳定性测试工程师 |32+| [稳定性检测参数规格](architecture/STABILITY-SPEC.md) | 9 个 detector 参数 + 3 检测器调优 + 与性能指标边界 | 稳定性测试工程师 |
33-| [UITest 连接稳定性设计](architecture/UITEST-STABILITY-DESIGN.md) | TCP 重连/健康检查/布局提供者设计规范(连接层/系统层/编排层修改必读) | 连接层开发者 |33+| [UITest 连接稳定性设计](architecture/UITEST-STABILITY-DESIGN.md) | TCP 重连/健康检查/布局提供者设计规范(连接层/系统层/编排层修改必读) | 连接层开发者 |
34 34 
35## 框架开发35## 框架开发
36 36 
37-| 文档 | 说明 | 适用读者 |37+| 文档 | 说明 | 适用读者 |
38-|------|------|----------|38+| ------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------- |
39-| [扩展开发指南](development/DEVELOPMENT-GUIDE.md) | 新增平台、能力、报告生成器 | 框架开发者 |39+| [扩展开发指南](development/DEVELOPMENT-GUIDE.md) | 新增平台、能力、报告生成器 | 框架开发者 |
40-| [插件开发指南](development/PLUGIN-GUIDE.md) | 新平台/指标/报告/导航/跨平台原生指标桥接 5 大扩展点注册 + iOS Phase 路线图 | 插件作者 |40+| [插件开发指南](development/PLUGIN-GUIDE.md) | 新平台/指标/报告/导航/跨平台原生指标桥接 5 大扩展点注册 + iOS Phase 路线图 | 插件作者 |
41-| [自定义指标采集器指南](development/METRIC-COLLECTOR-GUIDE.md) | 通过 BaseMetricCollector + MetricCollectorFactory 扩展性能指标体系 | 框架扩展者 |41+| [自定义指标采集器指南](development/METRIC-COLLECTOR-GUIDE.md) | 通过 BaseMetricCollector + MetricCollectorFactory 扩展性能指标体系 | 框架扩展者 |
42-| [iOS 扩展方案](design/IOS-EXTENSION-PLAN.md) | iOS 平台接入详细设计(双引擎/WDA/xctrace) | iOS 接入开发者 |42+| [iOS 扩展方案](design/IOS-EXTENSION-PLAN.md) | iOS 平台接入详细设计(双引擎/WDA/xctrace) | iOS 接入开发者 |
43-| [录屏与屏幕分析规划](design/SCREEN-RECORDING-PLAN.md) | 三级录制链(CLI/设备端组件/截图流)+ 分析器演进路线(规划中未实施) | 框架开发者 |43+| [录屏与屏幕分析规划](design/SCREEN-RECORDING-PLAN.md) | 三级录制链(CLI/设备端组件/截图流)+ 分析器演进路线(规划中未实施) | 框架开发者 |
44 44 
45## 运维与迁移45## 运维与迁移
46 46 
47| 文档 | 说明 | 适用读者 |47| 文档 | 说明 | 适用读者 |
48|------|------|----------|48|------|------|----------|
49| [故障排查手册](TROUBLESHOOTING.md) | 设备 / 用例 / 性能 / 对比 / 报告 6 类常见问题 + 排查步骤 | 所有人 |49| [故障排查手册](TROUBLESHOOTING.md) | 设备 / 用例 / 性能 / 对比 / 报告 6 类常见问题 + 排查步骤 | 所有人 |
50-| [迁移指南](MIGRATION.md) | v0.1.x → v0.2.x 破坏性变更 + 升级步骤 | 升级用户 |50+| [迁移指南](MIGRATION.md) | v0.1.x → v0.2.x、v0.2.x → v0.3.x 破坏性变更 + 升级步骤 | 升级用户 |
51-| [Baseline 管理](BASELINE-MANAGEMENT.md) | baseline 存放 / 版本化 / 跨设备共享 / 0.3.0 路线图 | 测试工程师 |51+| [Baseline 管理](BASELINE-MANAGEMENT.md) | baseline 存放 / 版本化(`--rn-version` 分段)/ 跨设备共享 / `.history` 备份 | 测试工程师 |
52| [性能调优指南](PERFORMANCE-TUNING.md) | 采集间隔 / 阈值 / detector 调优 / 自定义 collector | 性能测试工程师 |52| [性能调优指南](PERFORMANCE-TUNING.md) | 采集间隔 / 阈值 / detector 调优 / 自定义 collector | 性能测试工程师 |
53 53 
54## 选型参考54## 选型参考
55 55 
56-| 文档 | 说明 | 适用读者 |56+| 文档 | 说明 | 适用读者 |
57-|------|------|----------|57+| -------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------- |
58-| [框架对比分析](reference/FRAMEWORK-COMPARISON.md) | CPTF vs Jest/Appium/Cypress/Detox/Playwright | 选型决策者 |58+| [框架对比分析](reference/FRAMEWORK-COMPARISON.md) | CPTF vs Jest/Appium/Cypress/Detox/Playwright | 选型决策者 |
59| [五框架深度对比](reference/FIVE-FRAMEWORK-COMPARISON.md) | CPTF vs Detox/Hypium/Maestro/Appium 14 维度详细对比 + 代码示例 + 决策树 | 选型决策者 / 架构师 |59| [五框架深度对比](reference/FIVE-FRAMEWORK-COMPARISON.md) | CPTF vs Detox/Hypium/Maestro/Appium 14 维度详细对比 + 代码示例 + 决策树 | 选型决策者 / 架构师 |
60-| [稳定性对比分析](reference/STABILITY-COMPARISON.md) | 7维度对标业界稳定性方案 | 选型决策者 |60+| [稳定性对比分析](reference/STABILITY-COMPARISON.md) | 7 维度对标业界稳定性方案 | 选型决策者 |
61 61 
62## 内部优化分析62## 内部优化分析
63 63 
64-| 文档 | 说明 | 适用读者 |64+| 文档 | 说明 | 适用读者 |
65-|------|------|----------|65+| ------------------- | ------------------------------------------------------ | ---------- |
66| [待办清单](TODO.md) | 待做事项(性能优化/真机回归/发版)+ 开发约束与不做清单 | 框架维护者 |66| [待办清单](TODO.md) | 待做事项(性能优化/真机回归/发版)+ 开发约束与不做清单 | 框架维护者 |
67 67 
68## 设计记录与调研(历史资料)68## 设计记录与调研(历史资料)
69 69 
70> 设计阶段决策记录与第三方框架调研,反映立项时点的判断;当前架构以 [ARCHITECTURE.md](architecture/ARCHITECTURE.md) 为准。70> 设计阶段决策记录与第三方框架调研,反映立项时点的判断;当前架构以 [ARCHITECTURE.md](architecture/ARCHITECTURE.md) 为准。
71 71 
72-| 文档 | 说明 | 适用读者 |72+| 文档 | 说明 | 适用读者 |
73-|------|------|----------|73+| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------- |
74-| [场景设计](design/01-SCENARIOS.md) | 立项阶段核心场景定位(跨端 UI 自动化/性能/稳定性) | 了解设计初衷 |74+| [场景设计](design/01-SCENARIOS.md) | 立项阶段核心场景定位(跨端 UI 自动化/性能/稳定性) | 了解设计初衷 |
75-| [需求分析](design/02-REQUIREMENTS.md) | 立项阶段功能/非功能需求清单 | 了解设计初衷 |75+| [需求分析](design/02-REQUIREMENTS.md) | 立项阶段功能/非功能需求清单 | 了解设计初衷 |
76-| [Detox 扩展分析](analysis/detox/README.md) | Detox 框架扩展 HarmonyOS 的 6 篇深度分析(能力/双通道架构/集成方案),CPTF 跨平台方案选型的对照参考 | 选型决策者 / 架构师 |76+| [Detox 扩展分析](analysis/detox/README.md) | Detox 框架扩展 HarmonyOS 的 6 篇深度分析(能力/双通道架构/集成方案),CPTF 跨平台方案选型的对照参考 | 选型决策者 / 架构师 |
77-| [@ohos.UiTest API 参考](dependence/UiTest.md) | HarmonyOS UiTest 官方 API 整理(控件查找/注入/截图/dumpLayout),`uitest-transport`/`uitest-driver` 实现的协议依据 | 系统层开发者 |77+| [@ohos.UiTest API 参考](dependence/UiTest.md) | HarmonyOS UiTest 官方 API 整理(控件查找/注入/截图/dumpLayout),`uitest-transport`/`uitest-driver` 实现的协议依据 | 系统层开发者 |
78 78 
79---79---
80 80 
@@ -96,21 +96,21 @@
96 96 
97### 框架架构图97### 框架架构图
98 98 
99-| 图 | 说明 |99+| 图 | 说明 |
100-|----|------|100+| -------------------------------------------------------------- | ----------------------------------------------------- |
101-| [分层架构总览](architecture/architecture-logical.svg) | 五层(对外概览常合并叙述为 3 组:基础设施/系统/调度) |101+| [分层架构总览](architecture/architecture-logical.svg) | 五层(对外概览常合并叙述为 3 组:基础设施/系统/调度) |
102-| [UI自动化双引擎](architecture/architecture-ui-automation.svg) | HTX/ATX通信架构 |102+| [UI 自动化双引擎](architecture/architecture-ui-automation.svg) | HTX/ATX 通信架构 |
103-| [智能导航流程](architecture/architecture-navigation.svg) | autoNavigate + smartNavigateBack |103+| [智能导航流程](architecture/architecture-navigation.svg) | autoNavigate + smartNavigateBack |
104 104 
105### 设计阶段架构图(design/svg)105### 设计阶段架构图(design/svg)
106 106 
107-| 图 | 说明 |107+| 图 | 说明 |
108-|----|------|108+| --------------------------------------------------------------------- | -------------------------------------------------------- |
109-| [01-overview-developmental](design/svg/01-overview-developmental.svg) | 整体发展视角概览 |109+| [01-overview-developmental](design/svg/01-overview-developmental.svg) | 整体发展视角概览 |
110-| [02-layer-base](design/svg/02-layer-base.svg) | 基础层(types/config/utils/workers) |110+| [02-layer-base](design/svg/02-layer-base.svg) | 基础层(types/config/utils/workers) |
111-| [03-layer-system](design/svg/03-layer-system.svg) | 系统层(ISystem 7 子接口) |111+| [03-layer-system](design/svg/03-layer-system.svg) | 系统层(ISystem 7 子接口) |
112-| [04-layer-capability](design/svg/04-layer-capability.svg) | 能力层(ui/measurement/analysis/recorder) |112+| [04-layer-capability](design/svg/04-layer-capability.svg) | 能力层(ui/measurement/analysis/recorder) |
113-| [05-layer-orchestration](design/svg/05-layer-orchestration.svg) | 编排层(TestRunner/ComparisonRunner/ParallelTestRunner) |113+| [05-layer-orchestration](design/svg/05-layer-orchestration.svg) | 编排层(TestRunner/ComparisonRunner/ParallelTestRunner) |
114-| [06-physical-view](design/svg/06-physical-view.svg) | 物理视图(npm 包 + monorepo) |114+| [06-physical-view](design/svg/06-physical-view.svg) | 物理视图(npm 包 + monorepo) |
115-| [07-scenario-view](design/svg/07-scenario-view.svg) | 场景视图(典型执行流程) |115+| [07-scenario-view](design/svg/07-scenario-view.svg) | 场景视图(典型执行流程) |
116-| [08-process-view](design/svg/08-process-view.svg) | 进程视图(事件流 / 异步链) |116+| [08-process-view](design/svg/08-process-view.svg) | 进程视图(事件流 / 异步链) |
@@ -2,27 +2,41 @@
2 2 
3## 版本3## 版本
4 4 
5-- 版本号: 0.2.15+- 版本号: 0.3.1
6-- 更新日期: 2026-07-016+- 更新日期: 2026-10-13
7- 框架: CPTF (Cross-Platform Test Framework)7- 框架: CPTF (Cross-Platform Test Framework)
8 8 
9-### v0.2.1 同步说明9+### v0.3.1 同步说明
10 10 
11-- 同步 3.1 整改后真实采集口径:底层 `PerformanceCollector` 实际只采集 `fps | memory | cpu` 3 个原始指标;`memoryPeak` / `cpuPeak` / `blankScreenRatio` 是 8 个 `BaseMetricCollector` 聚合(`MemoryPeakCollector` / `CPUPeakCollector` / `BlankScreenRatioCollector`)。11+- 同步指标计数口径:`PerfMetricType` 联合 19 成员 = 17 内置 collector + 2 插件(`@cptf/benchmark` 的 renderTime/unmountTime);`fpsMin`/`fpsMax` 已删除(2026-08-25),`stability`/`dropRate`/`fpsP5` 已加入(SMOOTHNESS-METRICS-SPEC v2);`exitLatency` 幽灵成员已从类型联合移除。
12-- 同步 iOS xctrace 真实能力:xctrace FPS 已支持结果缓存(2s TTL)+ 并发 list/export + 2 次重试;录屏已实现(simulator 用 `simctl io recordVideo`,真机用 `idevicerecordvideo`)。12+- 保留 v0.2.1 说明:底层 `PerformanceCollector` 实际只采集 `fps | memory | cpu` 3 个原始指标;`memoryPeak` / `cpuPeak` / `blankScreenRatio` 是 `BaseMetricCollector` 聚合(`MemoryPeakCollector` / `CPUPeakCollector` / `BlankScreenRatioCollector`)。
13+- 保留 v0.2.1 说明:iOS xctrace FPS 已支持结果缓存(2s TTL)+ 并发 list/export + 2 次重试;录屏已实现(simulator 用 `simctl io recordVideo`,真机用 `idevicerecordvideo`)。
13 14 
14---15---
15 16 
16## 一、性能指标规格17## 一、性能指标规格
17 18 
18-### 1.0 实现状态(v0.2.1)19+### 1.0 实现状态(v0.3.1)
19 20 
20-| 类别 | 已实现 | 规划中 | 总计 |21+**计数口径(单一真相 = `src/base/types/index.ts` 的 `PerfMetricType` 联合 + `MetricCollectorFactory` 注册表)**:
21-|------|--------|--------|------|22+ 
22-| **PerfMetricType** | `fps / memory / cpu`(3 个真实采集) + `fpsMin / fpsMax / fpsJank / fpsBigJank / fpsStdDev / memoryPeak / memoryAvg / memoryGrowthRate / cpuPeak / cpuAvg / blankScreenRatio`(11 个聚合) + `startLatency / finishLatency / hotStartLatency / interactionLatency`(4 个 lifecycle 信号) | (无,全部 16 类已落地) | 16 |23+- `PerfMetricType` 联合 **19 成员**
23-| **MetricType**(扩展) | `crashCount`(报告层聚合) | `whiteScreenCount / jankCount / droppedFrames / completionLatency{,Avg,Max}`(6 个) | 7 |24+- 框架内置 collector **17 个**(联合成员中除 2 个插件指标外全部有默认注册)
25+- `@cptf/benchmark` 插件注册 **2 个**(`renderTime` / `unmountTime`)
26+- 已实现合计 **19 个**;**无**「仅声明未实现」成员
27+ 
28+| 类别 | 成员 | 计数 |
29+|------|------|------|
30+| **直接采集**(`PerfMetricType` 成员,每轮一个 sample) | `fps` | 1 |
31+| **聚合指标**(`BaseMetricCollector` 从历史 buffer 计算) | `fpsJank` / `fpsBigJank` / `fpsStdDev` / `stability` / `dropRate` / `fpsP5` / `blankScreenRatio` / `memoryPeak` / `memoryAvg` / `memoryGrowthRate` / `cpuPeak` / `cpuAvg` | 12 |
32+| **lifecycle 信号**(Collector 在 onStart/onFinish 记录时间戳) | `startLatency` / `finishLatency` / `interactionLatency` / `hotStartLatency` | 4 |
33+| **插件扩展**(`@cptf/benchmark` 注册) | `renderTime` / `unmountTime` | 2 |
34+| **合计** | | **19**(1 + 12 + 4 + 2 = 联合 17 内置 + 2 插件) |
35+ 
36+> **原始采集通道 ≠ 联合成员**:`performance-collector.ts` 的 `enabledMetrics` 实际采集 `fps | memory | cpu` 三路原始信号(`MonitoringOptions.metrics`),其中 `memory`/`cpu` **不是 `PerfMetricType` 成员**——它们是 `MemoryPeakCollector`/`CPUAvgCollector` 等聚合 collector 的输入字段,不能出现在 `collect`/`thresholds` 中。上表计数只统计联合成员,故 `fps` 计 1 而非 3。
37+ 
38+> **历史口径**:`fpsMin` / `fpsMax` 已于 2026-08-25 端到端删除(1s 采样粒度下单秒噪声不可控,替代为 `fpsP5`,见 [SMOOTHNESS-METRICS-SPEC.md](SMOOTHNESS-METRICS-SPEC.md));`exitLatency` 曾为「仅声明未实现」成员,已于 v0.3.1 从类型联合移除。
24 39 
25-> **重要**:`performance-collector.ts:61` `enabledMetrics` 实际仅采集 `fps | memory | cpu`(每轮一个 sample)。其余 11 个聚合指标通过 `BaseMetricCollector` 在 `collect()` 阶段从历史 buffer 计算(注册到 `MetricCollectorFactory`,详见 `capability/measurement/metric-collectors.ts`)。
26> 详见 [STABILITY-SPEC.md §6](STABILITY-SPEC.md) — 性能指标与稳定性 detector 边界。40> 详见 [STABILITY-SPEC.md §6](STABILITY-SPEC.md) — 性能指标与稳定性 detector 边界。
27 41 
28### 1.1 PerfMetricType 指标类型42### 1.1 PerfMetricType 指标类型
@@ -34,19 +48,20 @@
34| `interactionLatency` | 延迟 | ms | 交互响应时延 | Step action执行到UI状态变化可验证 | 交互响应测试 | ✅ 实现(lifecycle) |48| `interactionLatency` | 延迟 | ms | 交互响应时延 | Step action执行到UI状态变化可验证 | 交互响应测试 | ✅ 实现(lifecycle) |
35| `hotStartLatency` | 延迟 | ms | 热启动时延 | 后台切前台到页面可交互 | 热启动测试 | ✅ 实现(lifecycle) |49| `hotStartLatency` | 延迟 | ms | 热启动时延 | 后台切前台到页面可交互 | 热启动测试 | ✅ 实现(lifecycle) |
36| `fps` | FPS | Hz | 平均帧率 | `PerformanceCollector` 直接采集 + 聚合 | 滚动、动画测试 | ✅ 实现(`FPSCollector`,实际采集) |50| `fps` | FPS | Hz | 平均帧率 | `PerformanceCollector` 直接采集 + 聚合 | 滚动、动画测试 | ✅ 实现(`FPSCollector`,实际采集) |
37-| `fpsMin` | FPS | Hz | 最小帧率 | `FPSMinCollector` 从历史 buffer 取最小值 | 峰值性能测试 | ✅ 聚合(`FPSMinCollector`) |
38-| `fpsMax` | FPS | Hz | 最大帧率 | `FPSMaxCollector` 从历史 buffer 取最大值 | 渲染能力测试 | ✅ 聚合(`FPSMaxCollector`) |
39| `fpsJank` | FPS | count | 卡顿次数 | `FPSJankCollector` 累计 hitch16ms + hitch33ms 帧数 | 卡顿检测 | ✅ 聚合(`FPSJankCollector`) |51| `fpsJank` | FPS | count | 卡顿次数 | `FPSJankCollector` 累计 hitch16ms + hitch33ms 帧数 | 卡顿检测 | ✅ 聚合(`FPSJankCollector`) |
40| `fpsBigJank` | FPS | count | 严重卡顿次数 | `BigJankCollector` 累计超过 66ms 的帧数 (hitch66ms) | 严重卡顿检测 | ✅ 聚合(`BigJankCollector`) |52| `fpsBigJank` | FPS | count | 严重卡顿次数 | `BigJankCollector` 累计超过 66ms 的帧数 (hitch66ms) | 严重卡顿检测 | ✅ 聚合(`BigJankCollector`) |
41| `fpsStdDev` | FPS | Hz | 帧率标准差 | `FPSStdDevCollector` 从历史 buffer 计算方差根 | 帧率稳定性测试 | ✅ 聚合(`FPSStdDevCollector`) |53| `fpsStdDev` | FPS | Hz | 帧率标准差 | `FPSStdDevCollector` 从历史 buffer 计算方差根 | 帧率稳定性测试 | ✅ 聚合(`FPSStdDevCollector`) |
54+| `stability` | 流畅度 | % | 卡顿率(越低越好) | `StabilityCollector` jankFrames / totalFrames × 100 | 流畅度测试 | ✅ 聚合(`StabilityCollector`,PerfDog 口径) |
55+| `dropRate` | 流畅度 | % | 掉帧率 | `DropRateCollector` 期望帧数与实际帧数偏差比 | 掉帧检测 | ✅ 聚合(`DropRateCollector`) |
56+| `fpsP5` | 流畅度 | Hz | 5% 分位帧率 | `FpsP5Collector` 逐帧间隔倒数序列 P5 分位 | 低帧率尾部分析(fpsMin 替代) | ✅ 聚合(`FpsP5Collector`) |
42| `blankScreenRatio` | 白屏 | ratio | 白屏率 | `BlankScreenRatioCollector` 视频帧亮度分析(>240 且白像素比>85% 判定白屏) | 启动白屏检测 | ✅ 聚合(`BlankScreenRatioCollector`) |57| `blankScreenRatio` | 白屏 | ratio | 白屏率 | `BlankScreenRatioCollector` 视频帧亮度分析(>240 且白像素比>85% 判定白屏) | 启动白屏检测 | ✅ 聚合(`BlankScreenRatioCollector`) |
43| `memoryPeak` | 内存 | KB | 内存峰值 | `MemoryPeakCollector` 从历史 buffer 取最大值 | 内存压力测试 | ✅ 聚合(`MemoryPeakCollector`) |58| `memoryPeak` | 内存 | KB | 内存峰值 | `MemoryPeakCollector` 从历史 buffer 取最大值 | 内存压力测试 | ✅ 聚合(`MemoryPeakCollector`) |
44| `memoryAvg` | 内存 | KB | 内存平均值 | `MemoryAvgCollector` 从历史 buffer 计算平均值 | 内存趋势分析 | ✅ 聚合(`MemoryAvgCollector`) |59| `memoryAvg` | 内存 | KB | 内存平均值 | `MemoryAvgCollector` 从历史 buffer 计算平均值 | 内存趋势分析 | ✅ 聚合(`MemoryAvgCollector`) |
45| `memoryGrowthRate` | 内存 | KB/分钟 | 内存增长率 | `MemoryGrowthRateCollector` 内存采样线性回归斜率 | 内存泄漏检测 | ✅ 聚合(`MemoryGrowthRateCollector`) |60| `memoryGrowthRate` | 内存 | KB/分钟 | 内存增长率 | `MemoryGrowthRateCollector` 内存采样线性回归斜率 | 内存泄漏检测 | ✅ 聚合(`MemoryGrowthRateCollector`) |
46| `cpuPeak` | CPU | % | CPU峰值 | `CPUPeakCollector` 从历史 buffer 取最大值 | CPU压力测试 | ✅ 聚合(`CPUPeakCollector`) |61| `cpuPeak` | CPU | % | CPU峰值 | `CPUPeakCollector` 从历史 buffer 取最大值 | CPU压力测试 | ✅ 聚合(`CPUPeakCollector`) |
47| `cpuAvg` | CPU | % | CPU平均值 | `CPUAvgCollector` 从历史 buffer 计算平均值 | CPU趋势分析 | ✅ 聚合(`CPUAvgCollector`) |62| `cpuAvg` | CPU | % | CPU平均值 | `CPUAvgCollector` 从历史 buffer 计算平均值 | CPU趋势分析 | ✅ 聚合(`CPUAvgCollector`) |
48- 63+| `renderTime` | 渲染 | ms | 组件渲染时间 | `@cptf/benchmark` 插件(`RenderTimeCollector`) | RN 组件渲染性能 | ✅ 实现(插件) |
49-> **对齐实现**:PerfMetricType = 16 个枚举成员已全部有对应 collector 注册。实际底层只采 `fps | memory | cpu` 3 个原始指标,其余 13 个通过 `BaseMetricCollector.computeAllFromHistory` 在 `collect()` 阶段从历史 buffer 计算。64+| `unmountTime` | 渲染 | ms | 组件卸载时间 | `@cptf/benchmark` 插件(`UnmountTimeCollector`) | RN 组件卸载性能 | ✅ 实现(插件) |
50 65 
51### 1.2 MetricType 扩展指标66### 1.2 MetricType 扩展指标
52 67 
@@ -8,10 +8,10 @@
8 8 
9CPTF 性能指标分两类来源:9CPTF 性能指标分两类来源:
10 10 
11-| 来源 | 指标 | 说明 |11+| 来源 | 指标 | 说明 |
12-|------|------|------|12+| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
13-| **框架内置** | `startLatency`、`finishLatency`、`interactionLatency`、`hotStartLatency`、`fps`、`stability`、`dropRate`、`fpsP5`、`fpsJank`、`fpsStdDev`、`fpsBigJank`、`memoryPeak`、`memoryAvg`、`memoryGrowthRate`、`cpuPeak`、`cpuAvg`、`blankScreenRatio` | 由 `@cptf/framework` 的 `PerformanceCollector` + `MetricCollectorFactory` 内置提供,通过系统命令采集 |13+| **框架内置** | `startLatency`、`finishLatency`、`interactionLatency`、`hotStartLatency`、`fps`、`stability`、`dropRate`、`fpsP5`、`fpsJank`、`fpsStdDev`、`fpsBigJank`、`memoryPeak`、`memoryAvg`、`memoryGrowthRate`、`cpuPeak`、`cpuAvg`、`blankScreenRatio` | 由 `@cptf/framework` 的 `PerformanceCollector` + `MetricCollectorFactory` 内置提供,通过系统命令采集 |
14-| **插件扩展** | `renderTime`、`unmountTime` | 由 `@cptf/benchmark` 插件提供,通过 WebSocket 接收设备侧原生测量事件。用例需 `import '@cptf/benchmark'` 并在 `collect` 中声明 |14+| **插件扩展** | `renderTime`、`unmountTime` | 由 `@cptf/benchmark` 插件提供,通过 WebSocket 接收设备侧原生测量事件。用例需 `import '@cptf/benchmark'` 并在 `collect` 中声明 |
15 15 
16> 插件通过 `MetricCollectorFactory.register()` 注册自定义 collector,框架核心零改动。`@cptf/benchmark` 是首个实际落地的指标插件,详见其 README。16> 插件通过 `MetricCollectorFactory.register()` 注册自定义 collector,框架核心零改动。`@cptf/benchmark` 是首个实际落地的指标插件,详见其 README。
17 17 
@@ -7,11 +7,11 @@
7 7 
8CPTF 内置 3 个独立的稳定性检测器(`src/capability/measurement/`):8CPTF 内置 3 个独立的稳定性检测器(`src/capability/measurement/`):
9 9 
10-| 检测器 | 触发场景 | 输出 | 硬编码默认值(v0.2.0) |10+| 检测器 | 触发场景 | 输出 | 硬编码默认值(v0.2.0) |
11-|--------|----------|------|------------------------|11+| --------------------- | ------------------------ | ---------------------- | ------------------------------------------------- |
12-| `WhiteScreenDetector` | `checkWhiteScreen: true` | 截图亮度 / 白像素比 | brightness=240, ratio=0.85, grid=8 |12+| `WhiteScreenDetector` | `checkWhiteScreen: true` | 截图亮度 / 白像素比 | brightness=240, ratio=0.85, grid=8 |
13-| `MemoryLeakDetector` | `checkMemoryLeak: true` | 内存增长率 + R² 置信度 | growth=100 KB/min, minSamples=10, minDuration=60s |13+| `MemoryLeakDetector` | `checkMemoryLeak: true` | 内存增长率 + R² 置信度 | growth=100 KB/min, minSamples=10, minDuration=60s |
14-| `SmartMonkey` | `useSmartMonkey: true` | 元素覆盖率 + 动作分布 | decay=0.5, scroll=0.25, refresh=10 |14+| `SmartMonkey` | `useSmartMonkey: true` | 元素覆盖率 + 动作分布 | decay=0.5, scroll=0.25, refresh=10 |
15 15 
16所有 9 个参数在 v0.2.0 之前硬编码在检测器内部。`StabilityTestConfig`(`TestConfig.stability`)在 v0.2.0 起开放配置入口。16所有 9 个参数在 v0.2.0 之前硬编码在检测器内部。`StabilityTestConfig`(`TestConfig.stability`)在 v0.2.0 起开放配置入口。
17 17 
@@ -21,50 +21,51 @@ CPTF 内置 3 个独立的稳定性检测器(`src/capability/measurement/`)
21 21 
22### 2.1 WhiteScreenDetector22### 2.1 WhiteScreenDetector
23 23 
24-| 参数 | 类型 | 范围 | 默认 | 边界行为 |24+| 参数 | 类型 | 范围 | 默认 | 边界行为 |
25-|------|------|------|------|----------|25+| --------------------------- | ------ | ------- | -------- | --------------------------------------------------- |
26-| `brightnessThreshold` | number | 0-255 | **240** | 0=所有像素都被视为白;255=仅纯白被计为白 |26+| `brightnessThreshold` | number | 0-255 | **240** | 0=所有像素都被视为白;255=仅纯白被计为白 |
27-| `whiteRatioThreshold` | number | 0.0-1.0 | **0.85** | 0=任何白屏率都报警;1=必须 100% 纯白才报警 |27+| `whiteRatioThreshold` | number | 0.0-1.0 | **0.85** | 0=任何白屏率都报警;1=必须 100% 纯白才报警 |
28-| `sampleGridSize` | number | 1+ | **8** | 1=单像素采样;更高=更精确但更慢(8×8=64 采样/截图) |28+| `sampleGridSize` | number | 1+ | **8** | 1=单像素采样;更高=更精确但更慢(8×8=64 采样/截图) |
29-| `minElementsForLayoutCheck` | number | 0+ | 3 | 0=跳过布局校验;3=需要至少 3 个元素 |29+| `minElementsForLayoutCheck` | number | 0+ | 3 | 0=跳过布局校验;3=需要至少 3 个元素 |
30 30 
31**白屏判定**:`brightness > brightnessThreshold AND whitePixelRatio >= whiteRatioThreshold`31**白屏判定**:`brightness > brightnessThreshold AND whitePixelRatio >= whiteRatioThreshold`
32 32 
33### 2.2 MemoryLeakDetector33### 2.2 MemoryLeakDetector
34 34 
35-| 参数 | 类型 | 范围 | 默认 | 边界行为 |35+| 参数 | 类型 | 范围 | 默认 | 边界行为 |
36-|------|------|------|------|----------|36+| ------------------------- | ------ | ---- | --------- | ---------------------------------------- |
37-| `growthThresholdKBPerMin` | number | 0+ | **100** | 0=任何正增长都触发;100=温和(1.6 KB/s) |37+| `growthThresholdKBPerMin` | number | 0+ | **100** | 0=任何正增长都触发;100=温和(1.6 KB/s) |
38-| `minSamples` | number | 2+ | **10** | <2=无法计算回归;<10=显示"样本不足" |38+| `minSamples` | number | 2+ | **10** | <2=无法计算回归;<10=显示"样本不足" |
39-| `minDurationMs` | number | 0+ | **60000** | 0=不限制最短时长 |39+| `minDurationMs` | number | 0+ | **60000** | 0=不限制最短时长 |
40 40 
41**置信度判定**:41**置信度判定**:
42 42 
43-| 置信度 | 条件 | `leakDetected` |43+| 置信度 | 条件 | `leakDetected` |
44-|--------|------|----------------|44+| -------- | --------------------------------------------------------- | -------------- |
45-| `high` | growth > threshold AND R² > 0.7 | YES |45+| `high` | growth > threshold AND R² > 0.7 | YES |
46-| `medium` | growth > threshold AND (R² > 0.5 OR growth > 2×threshold) | YES |46+| `medium` | growth > threshold AND (R² > 0.5 OR growth > 2×threshold) | YES |
47-| `low` | growth > threshold 但 R² < 0.5 | NO |47+| `low` | growth > threshold 但 R² < 0.5 | NO |
48-| - | growth ≤ 0(稳定或下降) | NO |48+| - | growth ≤ 0(稳定或下降) | NO |
49 49 
50### 2.3 SmartMonkey50### 2.3 SmartMonkey
51 51 
52-| 参数 | 类型 | 范围 | 默认 | 边界行为 |52+| 参数 | 类型 | 范围 | 默认 | 边界行为 |
53-|------|------|------|------|----------|53+| ----------------------- | ------ | -------------- | ------------------- | ------------------------------------------ |
54-| `elementWeightDecay` | number | 0.0-1.0 | **0.5** | 0=点击后权重立即归零;1=权重不衰减 |54+| `elementWeightDecay` | number | 0.0-1.0 | **0.5** | 0=点击后权重立即归零;1=权重不衰减 |
55-| `scrollRatio` | number | 0.0-1.0 | **0.25** | 0=从不滚动;1=始终滚动;随覆盖率提高而增加 |55+| `scrollRatio` | number | 0.0-1.0 | **0.25** | 0=从不滚动;1=始终滚动;随覆盖率提高而增加 |
56-| `layoutRefreshInterval` | number | 1+ | **10** | 1=每步都刷新;0=从不刷新(stale 布局) |56+| `layoutRefreshInterval` | number | 1+ | **10** | 1=每步都刷新;0=从不刷新(stale 布局) |
57-| `safeAreaTop` | number | 0-screenHeight | screenHeight × 0.12 | 上方安全区(状态栏) |57+| `safeAreaTop` | number | 0-screenHeight | screenHeight × 0.12 | 上方安全区(状态栏) |
58-| `safeAreaBottom` | number | 0-screenHeight | screenHeight × 0.95 | 下方安全区(导航栏) |58+| `safeAreaBottom` | number | 0-screenHeight | screenHeight × 0.95 | 下方安全区(导航栏) |
59 59 
60**动作分布策略**(基于覆盖率):60**动作分布策略**(基于覆盖率):
61+ 
61- 低覆盖率(< 0.3)→ 更多 click(探索新元素)62- 低覆盖率(< 0.3)→ 更多 click(探索新元素)
62- 高覆盖率(> 0.7)→ 更多 swipe(深入已发现区域)63- 高覆盖率(> 0.7)→ 更多 swipe(深入已发现区域)
63 64 
64### 2.4 StabilityTest 全局阈值65### 2.4 StabilityTest 全局阈值
65 66 
66-| 参数 | 类型 | 范围 | 默认 | 说明 |67+| 参数 | 类型 | 范围 | 默认 | 说明 |
67-|------|------|------|------|------|68+| ---------------------- | ------ | ------- | -------- | -------------------------------------------------------------- |
68| `successRateThreshold` | number | 0.0-1.0 | **0.95** | 整个稳定性运行的成功率门槛。低于此值则 StabilityTest 整体 fail |69| `successRateThreshold` | number | 0.0-1.0 | **0.95** | 整个稳定性运行的成功率门槛。低于此值则 StabilityTest 整体 fail |
69 70 
70---71---
@@ -79,7 +80,7 @@ import { Test, PerformanceTest, StabilityTest } from '@cptf/framework';
79 name: '1小时稳定性测试',80 name: '1小时稳定性测试',
80 module: 'stability',81 module: 'stability',
81 platform: 'harmony',82 platform: 'harmony',
82- repeatTimes: 36000, // 100ms 间隔 × 36000 = 1h83+ repeatTimes: 36000, // 100ms 间隔 × 36000 = 1h
83 checkAppAlive: true,84 checkAppAlive: true,
84 checkWhiteScreen: true,85 checkWhiteScreen: true,
85 checkMemoryLeak: true,86 checkMemoryLeak: true,
@@ -87,23 +88,23 @@ import { Test, PerformanceTest, StabilityTest } from '@cptf/framework';
87 recoverFromCrash: true,88 recoverFromCrash: true,
88 stability: {89 stability: {
89 whiteScreen: {90 whiteScreen: {
90- brightnessThreshold: 250, // 更严格:仅 250+ 才视为白91+ brightnessThreshold: 250, // 更严格:仅 250+ 才视为白
91- whiteRatioThreshold: 0.92, // OLED 暗色更敏感92+ whiteRatioThreshold: 0.92, // OLED 暗色更敏感
92- sampleGridSize: 16, // 16×16 = 256 采样(更慢但更准)93+ sampleGridSize: 16, // 16×16 = 256 采样(更慢但更准)
93 },94 },
94 memoryLeak: {95 memoryLeak: {
95- growthThresholdKBPerMin: 50, // 小应用更严格96+ growthThresholdKBPerMin: 50, // 小应用更严格
96- minSamples: 5, // 5 个样本即可开始回归97+ minSamples: 5, // 5 个样本即可开始回归
97- minDurationMs: 30_000, // 30s 即可开始回归98+ minDurationMs: 30_000, // 30s 即可开始回归
98 },99 },
99 smartMonkey: {100 smartMonkey: {
100- elementWeightDecay: 0.7, // 探索更多(衰减慢)101+ elementWeightDecay: 0.7, // 探索更多(衰减慢)
101- scrollRatio: 0.4, // 滚动更频繁102+ scrollRatio: 0.4, // 滚动更频繁
102- layoutRefreshInterval: 20, // 每 20 步刷新布局103+ layoutRefreshInterval: 20, // 每 20 步刷新布局
103- safeAreaTop: 100, // 自定义状态栏高度104+ safeAreaTop: 100, // 自定义状态栏高度
104- safeAreaBottom: 1800, // 1080×2400 屏幕底部105+ safeAreaBottom: 1800, // 1080×2400 屏幕底部
105 },106 },
106- successRateThreshold: 0.9, // 90% 通过即可(vs 默认 95%)107+ successRateThreshold: 0.9, // 90% 通过即可(vs 默认 95%)
107 },108 },
108})109})
109class LongRunStabilityTest extends StabilityTest {110class LongRunStabilityTest extends StabilityTest {
@@ -197,25 +198,26 @@ test('自定义 whiteScreen 配置生效', () => {
197 198 
198### 5.3 常见误配置199### 5.3 常见误配置
199 200 
200-| 现象 | 原因 | 修复 |201+| 现象 | 原因 | 修复 |
201-|------|------|------|202+| -------------------------------- | -------------------------------- | -------------------- |
202-| 短时压测报"insufficient samples" | `minSamples` 默认 10 太严 | 显式 `minSamples: 3` |203+| 短时压测报"insufficient samples" | `minSamples` 默认 10 太严 | 显式 `minSamples: 3` |
203-| OLED 屏幕不报白屏 | `brightnessThreshold: 240` 太高 | 调到 200 |204+| OLED 屏幕不报白屏 | `brightnessThreshold: 240` 太高 | 调到 200 |
204-| 大型应用内存泄漏永远不报 | `growthThreshold: 100` 偏低 | 调到 500 |205+| 大型应用内存泄漏永远不报 | `growthThreshold: 100` 偏低 | 调到 500 |
205-| SmartMonkey 探索慢 | `elementWeightDecay: 0.5` 衰减快 | 调到 0.7 |206+| SmartMonkey 探索慢 | `elementWeightDecay: 0.5` 衰减快 | 调到 0.7 |
206 207 
207---208---
208 209 
209-## 6. 与 13 个性能指标的关系210+## 6. 与性能指标的关系(19 个 PerfMetricType)
210 211 
211`StabilityTestConfig` 控制的 **detector 参数** 与 `collect: ['fps', 'memory', ...]` 控制的 **指标采集** 是两条独立链路:212`StabilityTestConfig` 控制的 **detector 参数** 与 `collect: ['fps', 'memory', ...]` 控制的 **指标采集** 是两条独立链路:
212 213 
213-| 链路 | 入口 | 默认行为 |214+| 链路 | 入口 | 默认行为 |
214-|------|------|----------|215+| -------------------- | ------------------------------------------- | ----------------------------------------------------- |
215-| 性能指标采集 | `TestConfig.collect` / `monitoring.metrics` | `performance-collector.ts` 真实采集(FPS/memory/CPU) |216+| 性能指标采集 | `TestConfig.collect` / `monitoring.metrics` | `performance-collector.ts` 真实采集(FPS/memory/CPU) |
216-| 稳定性 detector 调优 | `TestConfig.stability` | 9 个 detector 参数(如上) |217+| 稳定性 detector 调优 | `TestConfig.stability` | 9 个 detector 参数(如上) |
217 218 
218两者不重叠:219两者不重叠:
220+ 
219- `collect: ['fps']` 控制 PerformanceCollector 真实采集。221- `collect: ['fps']` 控制 PerformanceCollector 真实采集。
220- `stability.memoryLeak.growthThresholdKBPerMin` 控制 MemoryLeakDetector 报警阈值。222- `stability.memoryLeak.growthThresholdKBPerMin` 控制 MemoryLeakDetector 报警阈值。
221 223 
@@ -235,7 +237,7 @@ test('自定义 whiteScreen 配置生效', () => {
235 237 
236## 7. 相关文档238## 7. 相关文档
237 239 
238-- [CAPABILITY-SPEC.md](CAPABILITY-SPEC.md) — 13 个 `PerfMetricType` 定义240+- [CAPABILITY-SPEC.md](CAPABILITY-SPEC.md) — 19 个 `PerfMetricType` 定义(17 内置 + 2 插件)
239- [PERFORMANCE-METRICS-SPEC.md](PERFORMANCE-METRICS-SPEC.md) — 性能指标采集方式241- [PERFORMANCE-METRICS-SPEC.md](PERFORMANCE-METRICS-SPEC.md) — 性能指标采集方式
240- [METRIC-COLLECTOR-GUIDE.md](../development/METRIC-COLLECTOR-GUIDE.md) — 自定义指标采集器242- [METRIC-COLLECTOR-GUIDE.md](../development/METRIC-COLLECTOR-GUIDE.md) — 自定义指标采集器
241- [PLUGIN-GUIDE.md](../development/PLUGIN-GUIDE.md#3-新增稳定性检测器) — 自定义稳定性检测器243- [PLUGIN-GUIDE.md](../development/PLUGIN-GUIDE.md#3-新增稳定性检测器) — 自定义稳定性检测器
@@ -5,7 +5,7 @@
5 5 
6## 1. 概述6## 1. 概述
7 7 
8-CPTF 框架内置 16 种 `PerfMetricType`(详见 [CAPABILITY-SPEC.md](../architecture/CAPABILITY-SPEC.md#性能指标规格))。8+CPTF 框架内置 17 种 `PerfMetricType`(另 2 种 `renderTime`/`unmountTime` 由 `@cptf/benchmark` 插件提供,共 19 种;详见 [CAPABILITY-SPEC.md](../architecture/CAPABILITY-SPEC.md#性能指标规格))。
9对于以下场景,框架提供完整的扩展机制:9对于以下场景,框架提供完整的扩展机制:
10 10 
11- 业务自定义指标(如首屏渲染、白屏时间、特定业务操作耗时)11- 业务自定义指标(如首屏渲染、白屏时间、特定业务操作耗时)
@@ -16,38 +16,34 @@ CPTF 框架内置 16 种 `PerfMetricType`(详见 [CAPABILITY-SPEC.md](../archi
16 16 
17## 2. 内置 collector 速查17## 2. 内置 collector 速查
18 18 
19-| 名称 | `PerfMetricType` | 采集内容 | 触发时机 |19+| 名称 | `PerfMetricType` | 采集内容 | 触发时机 |
20-|------|------------------|----------|----------|20+| ----------------------------- | -------------------- | ------------------------------------ | -------------------------------------------------------- |
21-| `StartLatencyCollector` | `startLatency` | 启动延迟(ms) | `onStart` |21+| `StartLatencyCollector` | `startLatency` | 启动延迟(ms) | `onStart` |
22-| `FinishLatencyCollector` | `finishLatency` | 完成延迟(ms) | `onFinish` |22+| `FinishLatencyCollector` | `finishLatency` | 完成延迟(ms) | `onFinish` |
23-| `FPSCollector` | `fps` | 平均 FPS | `collect`(依赖 `metricsHistory`) |23+| `FPSCollector` | `fps` | 平均 FPS | `collect`(依赖 `metricsHistory`) |
24-| `FPSMinCollector` | `fpsMin` | 最低 FPS | `collect` |24+| `FPSMinCollector` | `fpsMin` | 最低 FPS | `collect` |
25-| `FPSMaxCollector` | `fpsMax` | 最高 FPS | `collect` |25+| `FPSMaxCollector` | `fpsMax` | 最高 FPS | `collect` |
26-| `FPSJankCollector` | `fpsJank` | 卡顿帧总数 | `collect` |26+| `FPSJankCollector` | `fpsJank` | 卡顿帧总数 | `collect` |
27-| `FPSStdDevCollector` | `fpsStdDev` | FPS 标准差(Welford 算法) | `collect` |27+| `FPSStdDevCollector` | `fpsStdDev` | FPS 标准差(Welford 算法) | `collect` |
28-| `BigJankCollector` | `fpsBigJank` | 大卡顿帧数(>100ms) | `collect` |28+| `BigJankCollector` | `fpsBigJank` | 大卡顿帧数(>100ms) | `collect` |
29-| `InteractionLatencyCollector` | `interactionLatency` | 交互延迟均值(ms) | 通过 `markActionStart/markActionComplete` |29+| `InteractionLatencyCollector` | `interactionLatency` | 交互延迟均值(ms) | 通过 `markActionStart/markActionComplete` |
30-| `BlankScreenRatioCollector` | `blankScreenRatio` | 白屏率(0-1) | 通过 `setBlankScreenRatio` |30+| `BlankScreenRatioCollector` | `blankScreenRatio` | 白屏率(0-1) | 通过 `setBlankScreenRatio` |
31-| `MemoryPeakCollector` | `memoryPeak` | 内存峰值(KB) | `collect` |31+| `MemoryPeakCollector` | `memoryPeak` | 内存峰值(KB) | `collect` |
32-| `MemoryAvgCollector` | `memoryAvg` | 内存均值(KB) | `collect` |32+| `MemoryAvgCollector` | `memoryAvg` | 内存均值(KB) | `collect` |
33-| `MemoryGrowthRateCollector` | `memoryGrowthRate` | 内存增长速率(KB/min,附 R² 置信度) | `collect` + `getRSquared()` |33+| `MemoryGrowthRateCollector` | `memoryGrowthRate` | 内存增长速率(KB/min,附 R² 置信度) | `collect` + `getRSquared()` |
34-| `CPUPeakCollector` | `cpuPeak` | CPU 峰值(%) | `collect` |34+| `CPUPeakCollector` | `cpuPeak` | CPU 峰值(%) | `collect` |
35-| `CPUAvgCollector` | `cpuAvg` | CPU 均值(%) | `collect` |35+| `CPUAvgCollector` | `cpuAvg` | CPU 均值(%) | `collect` |
36-| `HotStartLatencyCollector` | `hotStartLatency` | 热启动延迟(ms) | `onStart`(自动 `sendToBackground → bringToForeground`) |36+| `HotStartLatencyCollector` | `hotStartLatency` | 热启动延迟(ms) | `onStart`(自动 `sendToBackground → bringToForeground`) |
37 37 
38> **写入规则**:38> **写入规则**:
39+>
39> - 依赖时间序列的 collector(FPS / Memory / CPU)通过 `this.metricsHistory` 读取,由 `ExecutionEngine.runIteration` 在 `onFinish` 之后注入。40> - 依赖时间序列的 collector(FPS / Memory / CPU)通过 `this.metricsHistory` 读取,由 `ExecutionEngine.runIteration` 在 `onFinish` 之后注入。
40> - 主动型 collector(StartLatency / InteractionLatency / BlankScreenRatio)通过显式方法在测试执行过程中累积数据。41> - 主动型 collector(StartLatency / InteractionLatency / BlankScreenRatio)通过显式方法在测试执行过程中累积数据。
41 42 
42## 3. 公开 API43## 3. 公开 API
43 44 
44```ts45```ts
45-import {46+import { BaseMetricCollector, MetricCollectorContext, MetricCollectorFactory, PerfMetricType } from '@cptf/framework';
46- BaseMetricCollector,
47- MetricCollectorContext,
48- MetricCollectorFactory,
49- PerfMetricType,
50-} from '@cptf/framework';
51```47```
52 48 
53### `BaseMetricCollector` 抽象基类49### `BaseMetricCollector` 抽象基类
@@ -82,13 +78,13 @@ abstract class BaseMetricCollector {
82 78 
83```ts79```ts
84interface MetricCollectorContext {80interface MetricCollectorContext {
85- ui: UIAutomation; // UI 自动化接口81+ ui: UIAutomation; // UI 自动化接口
86 perf: PerformanceCollector; // 原始性能采集器82 perf: PerformanceCollector; // 原始性能采集器
87- device: DeviceInfo; // 设备元数据(id, screenResolution, model ...)83+ device: DeviceInfo; // 设备元数据(id, screenResolution, model ...)
88- execution: ExecutionContext;// 当前迭代上下文(iteration, isFirstIteration ...)84+ execution: ExecutionContext; // 当前迭代上下文(iteration, isFirstIteration ...)
89 iterationResult: Partial<IterationResult>; // 当前迭代的 startTime / metrics85 iterationResult: Partial<IterationResult>; // 当前迭代的 startTime / metrics
90- bundleId?: string; // 当前测试的 bundleId86+ bundleId?: string; // 当前测试的 bundleId
91- system?: ISystem; // 系统层实例(用于自定义 driver 调用)87+ system?: ISystem; // 系统层实例(用于自定义 driver 调用)
92}88}
93```89```
94 90 
@@ -101,8 +97,8 @@ class MetricCollectorFactory {
101 static register(type: PerfMetricType, factory: () => BaseMetricCollector): void;97 static register(type: PerfMetricType, factory: () => BaseMetricCollector): void;
102 static unregister(type: PerfMetricType): boolean;98 static unregister(type: PerfMetricType): boolean;
103 static isRegistered(type: PerfMetricType): boolean;99 static isRegistered(type: PerfMetricType): boolean;
104- static getRegisteredTypes(): PerfMetricType[]; // 内置 + 自定义100+ static getRegisteredTypes(): PerfMetricType[]; // 内置 + 自定义
105- static getCustomRegisteredTypes(): PerfMetricType[]; // 仅自定义101+ static getCustomRegisteredTypes(): PerfMetricType[]; // 仅自定义
106 static clearCustom(): void;102 static clearCustom(): void;
107}103}
108```104```
@@ -206,11 +202,11 @@ import { Test, Step, PerformanceTest } from '@cptf/framework';
206 scene: 'launch',202 scene: 'launch',
207 monitoring: {203 monitoring: {
208 metrics: ['firstPaint', 'fps', 'memoryPeak', 'anrCount'],204 metrics: ['firstPaint', 'fps', 'memoryPeak', 'anrCount'],
209- interval: 1000, // 采集间隔(ms)205+ interval: 1000, // 采集间隔(ms)
210 },206 },
211 thresholds: [207 thresholds: [
212 { metric: 'firstPaint', operator: '<', value: 800, message: '首屏渲染超过800ms' },208 { metric: 'firstPaint', operator: '<', value: 800, message: '首屏渲染超过800ms' },
213- { metric: 'fps', operator: '>=', value: 55, message: 'FPS低于55' },209+ { metric: 'fps', operator: '>=', value: 55, message: 'FPS低于55' },
214 ],210 ],
215})211})
216class FirstPaintTest extends PerformanceTest {212class FirstPaintTest extends PerformanceTest {
@@ -240,8 +236,14 @@ class FirstPaintTest extends PerformanceTest {
240@Test({ monitoring: { metrics: ['firstPaint'] } })236@Test({ monitoring: { metrics: ['firstPaint'] } })
241class T extends PerformanceTest {237class T extends PerformanceTest {
242 @Step('验证首屏', {238 @Step('验证首屏', {
243- expects: [{ type: 'attribute', name: 'firstPaint', element: 'root',239+ expects: [
244- attribute: { attribute: 'text', expected: '渲染完成' } }],240+ {
241+ type: 'attribute',
242+ name: 'firstPaint',
243+ element: 'root',
244+ attribute: { attribute: 'text', expected: '渲染完成' },
245+ },
246+ ],
245 })247 })
246 async verifyFirstPaint() {248 async verifyFirstPaint() {
247 const history = this.perf?.getMetricsHistory() ?? [];249 const history = this.perf?.getMetricsHistory() ?? [];
@@ -292,12 +294,12 @@ class T extends PerformanceTest {
292 294 
293## 8. 错误处理与边界295## 8. 错误处理与边界
294 296 
295-| 场景 | 行为 |297+| 场景 | 行为 |
296-|------|------|298+| ------------------------------------- | --------------------------------------------------------------------- |
297-| `collect()` 抛错 | 单次迭代标记为失败,`IterationResult.success = false`,不影响其他迭代 |299+| `collect()` 抛错 | 单次迭代标记为失败,`IterationResult.success = false`,不影响其他迭代 |
298-| `metricsHistory` 为空 | 返回 fallback 值(见各 collector 默认分支) |300+| `metricsHistory` 为空 | 返回 fallback 值(见各 collector 默认分支) |
299-| 同一 `PerfMetricType` 多次 `register` | 后注册覆盖前注册 |301+| 同一 `PerfMetricType` 多次 `register` | 后注册覆盖前注册 |
300-| 同一 `type` 既内置又自定义 | 自定义优先 |302+| 同一 `type` 既内置又自定义 | 自定义优先 |
301 303 
302## 9. 调试与排错304## 9. 调试与排错
303 305 
@@ -329,7 +331,7 @@ MetricCollectorFactory.clearCustom();
329## 12. 相关文档331## 12. 相关文档
330 332 
331- [CAPABILITY-SPEC.md](../architecture/CAPABILITY-SPEC.md) — 性能指标规格333- [CAPABILITY-SPEC.md](../architecture/CAPABILITY-SPEC.md) — 性能指标规格
332-- [PERFORMANCE-METRICS-SPEC.md](../architecture/PERFORMANCE-METRICS-SPEC.md) — 16 个指标定义与采集方式334+- [PERFORMANCE-METRICS-SPEC.md](../architecture/PERFORMANCE-METRICS-SPEC.md) — 19 个指标定义与采集方式(17 内置 + 2 插件)
333- [DEVELOPMENT-GUIDE.md](DEVELOPMENT-GUIDE.md) — 框架级扩展(新增平台 / 报告 / 能力)335- [DEVELOPMENT-GUIDE.md](DEVELOPMENT-GUIDE.md) — 框架级扩展(新增平台 / 报告 / 能力)
334- [DECLARATIVE-TEST-GUIDE.md](../getting-started/DECLARATIVE-TEST-GUIDE.md) — `@Test` / `@Step` 用法336- [DECLARATIVE-TEST-GUIDE.md](../getting-started/DECLARATIVE-TEST-GUIDE.md) — `@Test` / `@Step` 用法
335- [EXPECT-GUIDE.md](../advanced/EXPECT-GUIDE.md) — expect 断言体系337- [EXPECT-GUIDE.md](../advanced/EXPECT-GUIDE.md) — expect 断言体系
@@ -2,8 +2,8 @@
2 2 
3## 版本3## 版本
4 4 
5-- 版本号: 0.2.05+- 版本号: 0.3.1
6-- 更新日期: 2026-06-086+- 更新日期: 2026-10-13
7- 适用对象: 测试工程师、自动化测试开发者7- 适用对象: 测试工程师、自动化测试开发者
8 8 
9---9---
@@ -34,32 +34,35 @@ pnpm install
34 34 
35#### 配置文件创建35#### 配置文件创建
36 36 
37-创建 `cptf.config.json`:37+创建 `cptf.config.json`(对齐 `resolveConfigAsync` 当前校验规则,`deepLinkScheme` 必填,缺失时直接报错):
38 38 
39```json39```json
40{40{
41+ "deepLinkScheme": "rnoh-test",
42+ "reportDir": "test-reports",
43+ "reportFormat": "all",
44+ "casesDir": "tests/cases",
41 "platforms": {45 "platforms": {
42 "harmony": {46 "harmony": {
43 "toolPath": "hdc",47 "toolPath": "hdc",
44- "deviceId": "",
45 "bundleId": "com.example.myapp",48 "bundleId": "com.example.myapp",
46- "screenWidth": 1080,49+ "appPath": "harmony/entry/build/default/outputs/default/entry-default-signed.hap",
47- "screenHeight": 234050+ "devices": "auto"
48 },51 },
49 "android": {52 "android": {
50 "toolPath": "adb",53 "toolPath": "adb",
51- "deviceId": "",
52 "bundleId": "com.example.myapp",54 "bundleId": "com.example.myapp",
53- "screenWidth": 1080,55+ "appPath": "android/app/build/outputs/apk/release/app-release.apk",
54- "screenHeight": 234056+ "devices": "auto"
55 }57 }
56- },58+ }
57- "outputDir": "./test-output",
58- "baselineDir": "./baseline",
59- "logLevel": "info"
60}59}
61```60```
62 61 
62+> **`deepLinkScheme` 必填**:v0.3.0 起框架不再提供默认值(见 loader `resolveConfigAsync`),按被测应用的实际 Deep Link scheme 填写(如 `"rnoh-test"`、`"flutter"`、`"myapp"`)。
63+> **`devices: "auto"`**:自动发现已连接设备并探测屏幕尺寸;也可改为数组显式指定(见 2.1)。
64+> **报告目录**:CLI `-o/--output` 优先,其次配置 `reportDir`,缺省 `test-reports`。
65+ 
63#### CLI命令使用66#### CLI命令使用
64 67 
65```bash68```bash
@@ -130,24 +133,53 @@ cptf template stability -n monkey-test -i monkey-stab -c Monkey -s stability-sce
130 133 
131## 二、配置详解134## 二、配置详解
132 135 
133-### 2.1 平台配置136+配置结构以 `CPTFConfigFile`(`src/infrastructure/config/loader.ts`)为单一真相。全局键与 `platforms` 键并列;平台级键只覆盖该平台的差异项。
134 137 
135-| 参数 | 类型 | 必填 | 说明 |138+### 2.1 平台配置(platforms.harmony / android / ios)
136-|------|------|------|------|139+ 
137-| `toolPath` | string | 是 | 平台工具路径(hdc/adb) |140+| 参数 | 类型 | 必填 | 默认值 | 说明 |
138-| `deviceId` | string | 是 | 设备ID(空字符串时自动选择) |141+|------|------|------|--------|------|
139-| `bundleId` | string | 是 | 应用包名/Bundle ID |142+| `toolPath` | string | 否 | 平台默认(harmony=`hdc`,android=`adb`,ios=`xcrun`) | 平台工具路径 |
140-| `screenWidth` | number | 是 | 屏幕宽度 |143+| `bundleId` | string | 否 | `''` | 应用包名/Bundle ID(空则回退全局 `bundleId`) |
141-| `screenHeight` | number | 是 | 屏幕高度 |144+| `appPath` | string | 否 | `''` | 应用包路径(harmony=`.hap`,android=`.apk`,ios=`.ipa`),`--install` / `cptf install` 时使用 |
145+| `statusBarHeight` | number | 否 | 自动探测(探测失败回退密度估算) | 状态栏高度(px),显式配置可跳过探测 |
146+| `devices` | `'auto'` \| `Array<{id?, screenWidth?, screenHeight?}>` | 否 | 自动发现 | 设备选择(见下) |
147+ 
148+**`devices` 三种写法**:
149+ 
150+```jsonc
151+// 1. 自动发现第一台已连接设备 + 探测屏幕尺寸(推荐)
152+"devices": "auto"
153+ 
154+// 2. 显式指定设备与尺寸(尺寸写死后跳过探测)
155+"devices": [{ "id": "7000001234", "screenWidth": 1280, "screenHeight": 2832 }]
156+ 
157+// 3. 数组首项 id 为 "auto" 或缺省 —— 自动发现,但保留后续扩展位
158+"devices": [{ "id": "auto" }]
159+```
160+ 
161+多设备并行与分配策略见 `--device` / `--strategy` CLI 参数(第一章命令速查)。
162+ 
163+iOS 平台额外支持 `wdaProjectPath` / `wdaScheme` / `devTeam` / `wdaPort`(默认 8100)/ `wdaBundleId` / `wdaGitUrl` / `wdaGitCommit` / `wdaAutoDownload`,详见 [iOS 平台说明](../architecture/UITEST-STABILITY-DESIGN.md)。
142 164 
143### 2.2 全局配置165### 2.2 全局配置
144 166 
145| 参数 | 类型 | 必填 | 默认值 | 说明 |167| 参数 | 类型 | 必填 | 默认值 | 说明 |
146|------|------|------|--------|------|168|------|------|------|--------|------|
147-| `outputDir` | string | 否 | `./test-output` | 测试输出目录 |169+| `deepLinkScheme` | string | **是** | 无(缺失直接报错) | Deep Link scheme(如 `"rnoh-test"`),v0.3.0 起必填 |
148-| `baselineDir` | string | 否 | `./baseline` | 基线数据目录 |170+| `bundleId` | string | 否 | — | 全局包名(平台级未配置时的回退) |
149-| `logLevel` | string | 否 | `info` | 日志级别(debug/info/warn/error) |171+| `reportDir` | string | 否 | `test-reports` | 报告根目录(CLI `-o/--output` 优先) |
150-| `reportFormat` | `'html'\|'markdown'\|'json'\|'all'` | 否 | `'all'` | 报告格式 |172+| `reportFormat` | `'html'` \| `'markdown'` \| `'json'` \| `'all'` | 否 | `'all'` | 报告格式(`'all'` = HTML + Markdown + JSON 全部生成) |
173+| `casesDir` | string | 否 | `tests/cases` | 测试用例目录 |
174+| `deviceAllocation` | `'auto'` \| `'round-robin'` \| `'by-module'` \| `'by-category'` \| `'by-case'` \| `'single'` | 否 | — | 多设备用例分配策略(CLI `--strategy` 优先;`'auto'` = 用例带 category 时按 Category 亲和分组,否则 round-robin) |
175+| `rnVersion` | string | 否 | — | 视觉基线按 RN 版本分段存放(`test-baseline/<rnVersion>/...`),缺省不分段 |
176+| `navigationStrategy` | string | 否 | `'default'` | 导航策略 ID(内置 `'default'` / `'native'`,native 驱动模式下自动切 `'native'`) |
177+| `qualityHook` | string | 否 | — | 测试结束后执行的钩子脚本路径(接收 JSON 格式的结果摘要) |
178+| `timezone` | string | 否 | — | 报告时间戳时区(如 `"BeiJing"`) |
179+| `scoring.excludeUnsupported` | boolean | 否 | — | 通过率统计是否排除无法自动化的用例 |
180+| `hostVersions` | `Record<string, string>` | 否 | — | 宿主版本信息(写入报告;也可经环境变量 `CPTF_HOST_VERSIONS` 注入) |
181+| `email` | object | 否 | — | 邮件通知(见 2.3) |
182+| `aiAnalysis` | object | 否 | — | AI 崩溃分析(`enabled`/`apiKey`/`endpoint`/`model`) |
151 183 
152### 2.3 邮件通知配置184### 2.3 邮件通知配置
153 185 
@@ -262,46 +294,36 @@ cptf template stability -n monkey-test -i monkey-stab -c Monkey -s stability-sce
2621. 开启两步验证 → 生成应用专用密码2941. 开启两步验证 → 生成应用专用密码
2632. `host`: `smtp.gmail.com`,`port`: `465`,`secure`: `true`2952. `host`: `smtp.gmail.com`,`port`: `465`,`secure`: `true`
264 296 
265-### 2.4 指标采集配置(monitoring)297+### 2.4 指标采集配置(collect)
266 298 
267-每个测试用例可通过 `monitoring` 配置控制采集哪些性能指标和采集频率。未配置时使用基类默认值。299+每个测试用例通过 `@Test` 装饰器的 `collect` 字段声明采集哪些性能指标。未配置时使用基类默认值。
268 300 
269#### 配置结构301#### 配置结构
270 302 
271```typescript303```typescript
272-interface MonitoringConfig {304+collect?: PerfMetricType[];
273- metrics?: ('fps' | 'memory' | 'cpu' | 'whiteScreen')[];305+// 如:collect: ['fps', 'memoryPeak', 'cpuPeak']
274- interval?: number;
275-}
276```306```
277 307 
278-| 参数 | 类型 | 必填 | 说明 |
279-|------|------|------|------|
280-| `metrics` | string[] | 否 | 采集指标列表,可选值:`fps`、`memory`、`cpu`、`whiteScreen` |
281-| `interval` | number | 否 | 采集间隔(毫秒) |
282- 
283#### 三类测试默认配置308#### 三类测试默认配置
284 309 
285-| 测试类型 | 默认 metrics | 默认 interval | 说明 |310+| 测试类型 | 默认 collect | 其他默认 | 说明 |
286-|---------|-------------|---------------|------|311+|---------|-------------|---------|------|
287-| StabilityTest | `['fps', 'memory', 'cpu', 'whiteScreen']` | 1000ms | 稳定性测试全量采集,含白屏检测 |312+| PerformanceTest | `['startLatency', 'fps', 'blankScreenRatio']` | executeTimes=5, cooldownMs=500, aggregate='average' | 性能测试默认含启动时延/帧率/白屏率 |
288-| PerformanceTest | `['fps', 'memory', 'cpu']` | 500ms | 性能测试高频采集,精确趋势 |313+| StabilityTest | 不采集性能指标 | executeTimes=100, cooldownMs=100,默认启用 appAlive/crashLogs/whiteScreen/memoryLeak 四检测器 | 指标采集与稳定性检测是两条独立链路(见 [STABILITY-SPEC §6](../architecture/STABILITY-SPEC.md)) |
289 314 
290#### 用例级自定义315#### 用例级自定义
291 316 
292-在 `@Test` 装饰器中配置 `monitoring`:317+在 `@Test` 装饰器中配置 `collect`:
293 318 
294```typescript319```typescript
295@Test({320@Test({
296 id: 'my-memory-test',321 id: 'my-memory-test',
297 name: '内存专项测试',322 name: '内存专项测试',
298- module: 'functional',323+ module: 'performance',
299- monitoring: {324+ collect: ['memoryPeak', 'memoryAvg'],
300- metrics: ['memory'],
301- interval: 500,
302- },
303})325})
304-class MyMemoryTest extends FunctionalTest { ... }326+class MyMemoryTest extends PerformanceTest { ... }
305```327```
306 328 
307```typescript329```typescript
@@ -309,26 +331,20 @@ class MyMemoryTest extends FunctionalTest { ... }
309 id: 'high-fps-test',331 id: 'high-fps-test',
310 name: 'FPS高频采集测试',332 name: 'FPS高频采集测试',
311 module: 'performance',333 module: 'performance',
312- monitoring: {334+ collect: ['fps', 'fpsP5', 'stability', 'dropRate'],
313- metrics: ['fps', 'cpu'],
314- interval: 300,
315- },
316})335})
317class HighFpsTest extends PerformanceTest { ... }336class HighFpsTest extends PerformanceTest { ... }
318```337```
319 338 
320-#### 报告展示339+#### 底层采集通道与报告展示
321 340 
322-用例开启了哪些指标,报告detail卡片中就会展示对应Tab:341+- 底层真实采集只有 `fps` / `memory` / `cpu` 三路原始信号,其余指标(`fpsP5`/`stability`/`dropRate`/`memoryPeak`/`cpuAvg` 等)由 `MetricCollectorFactory` 注册的聚合器从历史 buffer 计算;`renderTime`/`unmountTime` 由 `@cptf/benchmark` 插件提供(见 [PERFORMANCE-METRICS-SPEC](../architecture/PERFORMANCE-METRICS-SPEC.md))
323- 342+- 报告 detail 卡片按用例实际 collect 的指标展示 Tab(FPS/内存/CPU/白屏等彩色徽章 + sparkline 趋势)
324-- 顶部彩色徽章标识采集的指标(FPS=蓝、内存=橙、CPU=绿、白屏=紫)
325-- Tab式面板:每个指标独立Tab,含sparkline折线图 + 可滚动数据表格 + 统计摘要
326-- 未采集的指标不显示Tab和metric-box
327 343 
328#### 配置优先级344#### 配置优先级
329 345 
330```346```
331-用例 @Test monitoring 配置 → 基类 getDefaultConfig() 默认值 → 全量采集347+用例 @Test collect 配置 → 基类 getDefaultConfig() 默认值
332```348```
333 349 
334### 2.5 测试用例配置350### 2.5 测试用例配置
@@ -364,10 +380,12 @@ class HighFpsTest extends PerformanceTest { ... }
364 "module": "stability",380 "module": "stability",
365 "platform": "harmony",381 "platform": "harmony",
366 "config": {382 "config": {
367- "iterations": 1000,383+ "executeTimes": 1000,
368- "throttleMs": 500,384+ "cooldownMs": 500,
369- "seed": 12345,385+ "stability": {
370- "actionTypes": ["click", "swipe", "keyEvent"]386+ "enabled": ["appAlive", "crashLogs", "whiteScreen", "memoryLeak"],
387+ "successRateThreshold": 0.95
388+ }
371 }389 }
372}390}
373```391```
@@ -486,27 +504,40 @@ console.log('CPU Peak:', metrics.cpuPeak);
486 504 
487#### 指标类型505#### 指标类型
488 506 
507+`PerfMetricType` 联合共 19 个成员(v0.3.1 口径,以 `src/base/types/index.ts` 为单一真相):
508+ 
489```typescript509```typescript
490// 支持的指标类型510// 支持的指标类型
491-type PerfMetricType = 511+type PerfMetricType =
492- | 'startLatency'512+ // 时延类(lifecycle 信号)
493- | 'finishLatency'513+ | 'startLatency' // 启动时延
494- | 'interactionLatency'514+ | 'finishLatency' // 完成时延
495- | 'hotStartLatency'515+ | 'interactionLatency' // 交互响应时延
496- | 'fps'516+ | 'hotStartLatency' // 热启动时延
497- | 'fpsMin'517+ // 流畅度类
498- | 'fpsMax'518+ | 'fps' // 平均帧率(真实采集)
499- | 'fpsJank'519+ | 'fpsJank' // 卡顿次数
500- | 'fpsBigJank'520+ | 'fpsBigJank' // 严重卡顿次数
501- | 'fpsStdDev'521+ | 'fpsStdDev' // 帧率标准差
502- | 'blankScreenRatio'522+ | 'stability' // 卡顿率(越低越好,PerfDog 口径)
503- | 'memoryPeak'523+ | 'dropRate' // 掉帧率
504- | 'memoryAvg'524+ | 'fpsP5' // 5% 分位帧率(fpsMin 的替代)
505- | 'memoryGrowthRate'525+ // 质量类
506- | 'cpuPeak'526+ | 'blankScreenRatio' // 白屏率
507- | 'cpuAvg';527+ // 内存类
528+ | 'memoryPeak' // 内存峰值
529+ | 'memoryAvg' // 平均内存
530+ | 'memoryGrowthRate' // 内存增长率(泄漏检测)
531+ // CPU 类
532+ | 'cpuPeak' // CPU 峰值
533+ | 'cpuAvg' // 平均 CPU
534+ // 插件扩展(@cptf/benchmark 提供)
535+ | 'renderTime' // 渲染时间
536+ | 'unmountTime'; // 卸载时间
508```537```
509 538 
539+> 框架内置 17 个 collector(上表除 `renderTime`/`unmountTime`),`renderTime`/`unmountTime` 由 `@cptf/benchmark` 插件注册。`fpsMin`/`fpsMax` 已于 v0.2.1 删除(1s 采样粒度下噪声不可控,替代为 `fpsP5`,见 [SMOOTHNESS-METRICS-SPEC](../architecture/SMOOTHNESS-METRICS-SPEC.md));`exitLatency` 是「仅声明未实现」的幽灵成员(`MetricCollectorFactory.create` 会抛 `Unknown metric type`),配套 PR !48 已将其从类型联合移除——若当前代码仍含该成员,以 !48 合入后的 19 成员联合为准。各指标定义详见 [PERFORMANCE-METRICS-SPEC](../architecture/PERFORMANCE-METRICS-SPEC.md)。
540+ 
510### 3.3 稳定性测试541### 3.3 稳定性测试
511 542 
512#### Monkey测试543#### Monkey测试
Apackages/framework/src/__tests__/cli/init-config.test.ts+67-0File content is under review, please try again later
Mpackages/framework/src/cli/commands/init.ts+4-2File content is under review, please try again later