Open
docs(framework): fix stale roadmap labels, cross-repo dead links, skill version tag #2
liu_wentaoAdd on 27 days ago
docs(framework): fix stale roadmap labels, cross-repo dead links, skill version tag #2
Open
Merged fromdocs/p1-readme-cli-migrationintofeature/device-native-driver
Total: 34 files changed+3155-2156
| @@ -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 Scope | 17 | ## 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 Displaying | 27 | ## 1. Images Not Displaying |
| 28 | 28 | ||
| 29 | Covers: 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`). | 29 | Covers: 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 | ||
| 33 | 1. Run the check script (recommended): | 33 | 1. Run the check script (recommended): |
| 34 | ```bash | 34 | ```bash |
| @@ -55,7 +55,7 @@ Covers: reader-facing Chinese paragraphs in `docs/en/**`; inappropriate large En | |||
| 55 | 55 | ||
| 56 | Covers: external 404s, invalid anchors, outdated repo/branch references, wrong relative paths between docs, and historical version links not labeled with applicable version. | 56 | Covers: 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 | ||
| 60 | 1. Collect links (markdown links and bare URLs) in `docs/**/*.md`. | 60 | 1. Collect links (markdown links and bare URLs) in `docs/**/*.md`. |
| 61 | 2. 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: | 61 | 2. 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 | ||
| 126 | 1. Confirm scope: all categories, or user-specified categories/subtrees. | 126 | 1. Confirm scope: all categories, or user-specified categories/subtrees. |
| 127 | 2. Audit by category → fix → bilingual pairing. | 127 | 2. 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. |
| 129 | 4. Output summary. | 129 | 4. Output summary. |
| 130 | 130 | ||
| 131 | ## Output Summary | 131 | ## 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 | |||
| 10 | Audit and fix **spelling correctness** in `docs/` Markdown, using the repo's existing **cspell** (root `.cspell.json`) as the authority. | 10 | Audit 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 Scope | 19 | ## 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 Truth | 31 | ## 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 | ||
| 63 | For each warning / regex hit, pick one (do **not** blindly dump everything into `words`): | 64 | For 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 | ||
| 97 | Handle hits per step 3. | 98 | Handle 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 wording | 101 | ### 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-split | 105 | - 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 | ```markdown | 117 | ```markdown |
| 117 | ## docs/ Spelling Audit Summary | 118 | ## 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 | ### Pending | 134 | ### Pending |
| 135 | + | ||
| 132 | | file:line | word | note | | 136 | | file:line | word | note | |
| 133 | 137 | ||
| 134 | ### Bilingual gaps / not synced | 138 | ### 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 Practices | 8 | # 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 | ||
| 12 | Use Husky to manage Git hooks, and lint-staged to run targeted checks on staged files. Do not use the Python `pre-commit` framework. | 24 | Use 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 Layout | 28 | ## 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 Rules | 50 | ## commitlint Custom Rules |
| 36 | 51 | ||
| 37 | The 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: | 52 | The 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 | ||
| 50 | All custom rule error messages can carry documentation links so developers can quickly find the spec (corresponding specs are under this repository's `standards/`). | 65 | All 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 Files | 67 | ## 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 Coverage | 85 | ## 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 | ## Verification | 106 | ## Verification |
| 93 | 107 | ||
| 94 | ```bash | 108 | ```bash |
| 95 | pnpm exec lint-staged --diff="HEAD" --verbose | 109 | pnpm exec lint-staged --diff="HEAD" --verbose |
| 96 | pnpm exec cspell --no-must-find-files README.md | 110 | pnpm exec cspell --no-must-find-files README.md |
| 97 | -sh ./tools/pre-commit-checks/check-license-header.sh src/App.tsx | 111 | +pnpm exec ts-node ./tools/gitleaks/gitleaks-check.ts |
| 98 | -sh ./tools/gitleaks/gitleaks-check.sh | ||
| 99 | node -e "require('./commitlint.config.js'); console.log('commitlint OK')" | 112 | node -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). | ||
| @@ -7,11 +7,21 @@ mode: subagent | |||
| 7 | 7 | ||
| 8 | # Submit PRs | 8 | # 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 | ## Overview | 20 | ## Overview |
| 11 | 21 | ||
| 12 | Automate 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`. | 22 | Automate 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 Inputs | 26 | ## Required Inputs |
| 17 | 27 | ||
| @@ -34,7 +44,7 @@ Default primary language is `zh-CN`. If no language config is found, generate co | |||
| 34 | Before drafting commit, PR, or Issue text, run: | 44 | Before drafting commit, PR, or Issue text, run: |
| 35 | 45 | ||
| 36 | ```bash | 46 | ```bash |
| 37 | -python3 <skill>/scripts/submit.py language | 47 | +python3 .quality-toolkit/tools/submit-prs/scripts/submit_prs.py language |
| 38 | ``` | 48 | ``` |
| 39 | 49 | ||
| 40 | Use the returned `primary_language` for all generated text. Language priority: | 50 | Use the returned `primary_language` for all generated text. Language priority: |
| @@ -91,10 +101,10 @@ Issue rules from this workflow: | |||
| 91 | 101 | ||
| 92 | The skill supports two modes. Infer the mode from the user's instruction; when ambiguous, ask. | 102 | The 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 | ||
| 99 | In **commit-push** mode, stop after all branches are pushed. Do not create Issue, PR, or comment `start build`. | 109 | In **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`. |
| 118 | 3. For each pushed branch, create a PR to the matching target branch. | 128 | 3. 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 | |||
| 121 | 6. Comment `start build` on each PR. | 131 | 6. Comment `start build` on each PR. |
| 122 | 7. Verify by querying PR URL, Issue URL, linked Issue list, and comment list. | 132 | 7. 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 Guidance | 136 | ## Branch Matrix Guidance |
| 127 | 137 | ||
| @@ -172,10 +182,10 @@ Commit flow requirements: | |||
| 172 | Manual command for maintainers: | 182 | Manual command for maintainers: |
| 173 | 183 | ||
| 174 | ```bash | 184 | ```bash |
| 175 | -python3 <skill>/scripts/static_check.py --staged --fix | 185 | +python3 .quality-toolkit/tools/static-check/scripts/static_check.py --staged --fix |
| 176 | clang-format --style=file:.clang-format -i <touched-cpp-or-h-files> | 186 | clang-format --style=file:.clang-format -i <touched-cpp-or-h-files> |
| 177 | git add <files> | 187 | git add <files> |
| 178 | -python3 <skill>/scripts/static_check.py --staged | 188 | +python3 .quality-toolkit/tools/static-check/scripts/static_check.py --staged |
| 179 | git diff --check --cached | 189 | git 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 | ||
| 219 | Expected matches should only be documentation examples or generic token variable names, not token values. | 229 | Expected 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 | --- |
| 2 | name: cptf-test-api | 2 | name: 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 Reference | 6 | # 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 | ### Platform | 50 | ### Platform |
| 51 | + | ||
| 50 | ```typescript | 52 | ```typescript |
| 51 | type Platform = 'harmony' | 'android' | 'ios'; | 53 | type Platform = 'harmony' | 'android' | 'ios'; |
| 52 | ``` | 54 | ``` |
| 55 | + | ||
| 53 | Default: `'harmony'` (when device platform unspecified) | 56 | Default: `'harmony'` (when device platform unspecified) |
| 54 | 57 | ||
| 55 | ### TestModule | 58 | ### TestModule |
| 59 | + | ||
| 56 | ```typescript | 60 | ```typescript |
| 57 | type TestModule = 'functional' | 'performance' | 'stability'; | 61 | type 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 | ### TestPlatform | 70 | ### TestPlatform |
| 71 | + | ||
| 66 | ```typescript | 72 | ```typescript |
| 67 | type TestPlatform = Platform | 'all' | Platform[]; | 73 | type TestPlatform = Platform | 'all' | Platform[]; |
| 68 | ``` | 74 | ``` |
| 75 | + | ||
| 69 | Default: `'all'` | 76 | Default: `'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 | ```typescript | 88 | ```typescript |
| 81 | type PerfMetricType = | 89 | type 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 | ```typescript | 123 | ```typescript |
| 106 | type MetricType = | 124 | type 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 | ### AggregateStrategy | 143 | ### AggregateStrategy |
| 144 | + | ||
| 115 | ```typescript | 145 | ```typescript |
| 116 | type AggregateStrategy = 'average' | 'median' | 'max' | 'min'; | 146 | type 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 | ### AlertLevel | 156 | ### AlertLevel |
| 157 | + | ||
| 126 | ```typescript | 158 | ```typescript |
| 127 | type AlertLevel = 'warning' | 'error' | 'critical'; | 159 | type AlertLevel = 'warning' | 'error' | 'critical'; |
| 128 | ``` | 160 | ``` |
| 129 | 161 | ||
| 130 | ### ReportFormat | 162 | ### ReportFormat |
| 163 | + | ||
| 131 | ```typescript | 164 | ```typescript |
| 132 | type ReportFormat = 'html' | 'markdown' | 'json' | 'all'; | 165 | type ReportFormat = 'html' | 'markdown' | 'json' | 'all'; |
| 133 | ``` | 166 | ``` |
| 167 | + | ||
| 134 | Default: `'all'` (test-runner.ts); `'html'` (cptf.config.json init) | 168 | Default: `'all'` (test-runner.ts); `'html'` (cptf.config.json init) |
| 135 | 169 | ||
| 136 | ### LoggerLevel | 170 | ### LoggerLevel |
| 171 | + | ||
| 137 | ```typescript | 172 | ```typescript |
| 138 | type LoggerLevel = 'debug' | 'info' | 'warn' | 'error'; | 173 | type LoggerLevel = 'debug' | 'info' | 'warn' | 'error'; |
| 139 | ``` | 174 | ``` |
| 140 | 175 | ||
| 141 | ### AppState.state | 176 | ### AppState.state |
| 177 | + | ||
| 142 | ```typescript | 178 | ```typescript |
| 143 | type AppStateValue = 'foreground' | 'background' | 'crashed' | 'not_running'; | 179 | type AppStateValue = 'foreground' | 'background' | 'crashed' | 'not_running'; |
| 144 | ``` | 180 | ``` |
| 145 | 181 | ||
| 146 | ### DeviceInfo.status | 182 | ### DeviceInfo.status |
| 183 | + | ||
| 147 | ```typescript | 184 | ```typescript |
| 148 | type DeviceStatus = 'connected' | 'disconnected' | 'error'; | 185 | type DeviceStatus = 'connected' | 'disconnected' | 'error'; |
| 149 | ``` | 186 | ``` |
| @@ -199,60 +236,63 @@ export interface TestConfig { | |||
| 199 | 236 | ||
| 200 | ### TestConfig Complete Property Reference | 237 | ### 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 symbols | 283 | - 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 IDs | 284 | - 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-find | 291 | - Navigation strategy: search box input → close keyboard → click result; fallback: scroll-to-find |
| 253 | - Page transitions clear search state automatically | 292 | - 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 Reference | 348 | ### 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 Comparison | 392 | ### 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 | ```typescript | 437 | ```typescript |
| 395 | attribute: 'text' | 'placeholder' | 'type' | 'value' | 'enabled' | 'visible' | 'focused' | 'checked' | 'selected' | 'clickable' | 'scrollable'; | 438 | attribute: 'text' | 'placeholder' | 'type' | 'value' | 'enabled' | 'visible' | 'focused' | 'checked' | 'selected' | 'clickable' | 'scrollable'; |
| 396 | expected?: unknown; | 439 | expected?: unknown; |
| @@ -407,7 +450,7 @@ ignoreCase?: boolean; | |||
| 407 | 450 | ||
| 408 | ```typescript | 451 | ```typescript |
| 409 | // L0 dump 签名检测(零开销,推荐) | 452 | // L0 dump 签名检测(零开销,推荐) |
| 410 | -const overlays = await this.getOverlays(); // BaseTest 门面 | 453 | +const overlays = await this.getOverlays(); // BaseTest 门面 |
| 411 | const kbUp = overlays.some(o => o.kind === 'keyboard'); | 454 | const 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 }); | |||
| 417 | await this.ui.dismissOverlayAndWait('keyboard', 3000); | 460 | await 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 | ```typescript | 469 | ```typescript |
| 426 | type OverlayKind = 'keyboard' | 'dialog' | 'systemBar'; | 470 | type OverlayKind = 'keyboard' | 'dialog' | 'systemBar'; |
| 427 | type OverlayOcclusion = 'resize' | 'cover' | 'replace' | 'none'; | 471 | type OverlayOcclusion = 'resize' | 'cover' | 'replace' | 'none'; |
| 428 | interface OverlayInfo { | 472 | interface OverlayInfo { |
| 429 | kind: OverlayKind; | 473 | kind: OverlayKind; |
| 430 | - name?: string; // L0=dump节点id / L1=hidumper WindowName | 474 | + 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 | ### ExpectStateConfig | 485 | ### ExpectStateConfig |
| 486 | + | ||
| 442 | ```typescript | 487 | ```typescript |
| 443 | focused?: boolean; | 488 | focused?: boolean; |
| 444 | enabled?: boolean; | 489 | enabled?: boolean; |
| @@ -451,6 +496,7 @@ customState?: string; | |||
| 451 | ``` | 496 | ``` |
| 452 | 497 | ||
| 453 | ### ExpectInteractionConfig | 498 | ### ExpectInteractionConfig |
| 499 | + | ||
| 454 | ```typescript | 500 | ```typescript |
| 455 | interactionType: 'click' | 'input' | 'scroll' | 'swipe' | 'longpress' | 'drag' | 'pinch'; | 501 | interactionType: 'click' | 'input' | 'scroll' | 'swipe' | 'longpress' | 'drag' | 'pinch'; |
| 456 | clickSuccess?: boolean; | 502 | clickSuccess?: boolean; |
| @@ -469,6 +515,7 @@ eventPayload?: unknown; | |||
| 469 | ``` | 515 | ``` |
| 470 | 516 | ||
| 471 | ### ExpectResponseConfig | 517 | ### ExpectResponseConfig |
| 518 | + | ||
| 472 | ```typescript | 519 | ```typescript |
| 473 | maxResponseTime?: number; // min=0, ms | 520 | maxResponseTime?: number; // min=0, ms |
| 474 | avgResponseTime?: number; // min=0, ms | 521 | avgResponseTime?: number; // min=0, ms |
| @@ -478,4 +525,3 @@ measureIterations?: number; // min=1 | |||
| 478 | ``` | 525 | ``` |
| 479 | 526 | ||
| 480 | --- | 527 | --- |
| 481 | - | ||
| @@ -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 | ``` |
| 46 | cptf/ | 49 | cptf/ |
| 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 | |||
| 65 | pnpm build | 68 | pnpm build |
| 66 | ``` | 69 | ``` |
| 67 | 70 | ||
| 68 | -### CLI命令 | 71 | +### CLI 命令 |
| 69 | 72 | ||
| 70 | ```bash | 73 | ```bash |
| 71 | cptf devices # 列出设备 | 74 | cptf devices # 列出设备 |
| 72 | cptf devices -p harmony # 列出HarmonyOS设备 | 75 | cptf devices -p harmony # 列出HarmonyOS设备 |
| 73 | cptf scan # 扫描测试用例 | 76 | cptf scan # 扫描测试用例 |
| 77 | +cptf index # 生成测试用例索引文件 | ||
| 74 | cptf run # 执行所有测试(自动平台选择) | 78 | cptf run # 执行所有测试(自动平台选择) |
| 75 | cptf run -p harmony # HarmonyOS测试 | 79 | cptf run -p harmony # HarmonyOS测试 |
| 76 | cptf run -m performance # 指定模块测试 | 80 | cptf run -m performance # 指定模块测试 |
| 81 | +cptf run -C Button # 按分类测试 | ||
| 77 | cptf run -t all-metrics-performance # 指定用例测试 | 82 | cptf 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/) | ||
| 78 | cptf compare # 跨平台对比测试 | 88 | cptf compare # 跨平台对比测试 |
| 89 | +cptf compare --driver native # 对比测试同样支持驱动模式 | ||
| 90 | +cptf index # 生成测试用例索引(test-cases-index.ts) | ||
| 91 | +cptf install # 安装应用到连接设备(--atx 同时装 Android ATX Agent) | ||
| 79 | cptf init # 初始化配置 | 92 | cptf init # 初始化配置 |
| 80 | cptf check-env # 检查环境依赖 | 93 | cptf check-env # 检查环境依赖 |
| 81 | cptf template performance -n my-test # 生成测试模板 | 94 | cptf 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 | ```bash | 170 | ```bash |
| 160 | cptf devices # 列出设备 | 171 | cptf devices # 列出设备 |
| 161 | cptf devices -p harmony # 列出平台设备 | 172 | cptf devices -p harmony # 列出平台设备 |
| 173 | +cptf scan # 扫描用例 | ||
| 174 | +cptf index # 生成测试索引文件 | ||
| 162 | cptf run # 执行所有测试(自动平台选择) | 175 | cptf run # 执行所有测试(自动平台选择) |
| 163 | cptf run -p harmony # HarmonyOS测试 | 176 | cptf run -p harmony # HarmonyOS测试 |
| 164 | cptf run -m performance # 指定模块 | 177 | cptf run -m performance # 指定模块 |
| 178 | +cptf run -C Button # 指定分类 | ||
| 165 | cptf run -t calendar-perf-declarative # 指定用例 | 179 | cptf 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 # 包含不可自动化用例 | ||
| 166 | cptf run -p harmony --rn-version rn84 --update-baseline # 按 RN 版本分段建立/验证视觉基线 | 184 | cptf run -p harmony --rn-version rn84 --update-baseline # 按 RN 版本分段建立/验证视觉基线 |
| 185 | +cptf run -p harmony --rn-version rn84 --force-baseline # 强制覆盖视觉基线(自动备份旧基线) | ||
| 167 | cptf compare # 跨平台对比测试 | 186 | cptf compare # 跨平台对比测试 |
| 187 | +cptf compare --driver native # 对比测试同样支持驱动模式 | ||
| 188 | +cptf index # 生成测试用例索引 | ||
| 189 | +cptf install # 安装应用到设备 | ||
| 168 | cptf init # 初始化配置文件 | 190 | cptf init # 初始化配置文件 |
| 169 | cptf check-env # 检查环境依赖 | 191 | cptf check-env # 检查环境依赖 |
| 170 | cptf template <module> -n <name> # 生成测试用例模板 | 192 | cptf 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 | ## License | 212 | ## License |
| 191 | 213 | ||
| 192 | -MIT | 214 | +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"], |
| @@ -12,6 +12,7 @@ | |||
| 12 | CPTF(Cross-Platform Test Framework)是一个跨平台自动化测试框架,支持 HarmonyOS、Android、iOS 平台的自动化测试和跨平台对比测试。 | 12 | CPTF(Cross-Platform Test Framework)是一个跨平台自动化测试框架,支持 HarmonyOS、Android、iOS 平台的自动化测试和跨平台对比测试。 |
| 13 | 13 | ||
| 14 | 适用于多种跨平台技术栈: | 14 | 适用于多种跨平台技术栈: |
| 15 | + | ||
| 15 | - React Native / RN | 16 | - React Native / RN |
| 16 | - Flutter | 17 | - 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 | - ✅ 场景自动恢复 - autoRecoverScene | 29 | - ✅ 场景自动恢复 - 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 | - ✅ 自动扫描 baseline | 63 | - ✅ 自动扫描 baseline |
| 57 | - ✅ 像素级 diff.png 生成 | 64 | - ✅ 像素级 diff.png 生成 |
| 58 | - ✅ ignoreTestID / ignoreTestIDPatterns | 65 | - ✅ 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 | |||
| 166 | pnpm typecheck | 175 | pnpm 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 | ```bash | 182 | ```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 | ```typescript | 225 | ```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 | }) |
| 226 | class ListScroll extends PerformanceTest { | 234 | class 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 | ``` |
| 259 | orchestration/core/ | 268 | orchestration/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/Playwright | 286 | - [框架对比](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 | ``` |
| 296 | app/tests/ | 308 | app/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 | ```bash | 321 | ```bash |
| 308 | # 在项目根目录执行 | 322 | # 在项目根目录执行 |
| 309 | node packages/framework/dist/cli.js run -p harmony -m performance | 323 | node 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 | |||
| 314 | cptf run -m performance | 328 | cptf run -m performance |
| 315 | cptf compare -t textinput-placeholder-compatibility | 329 | cptf compare -t textinput-placeholder-compatibility |
| 316 | 330 | ||
| 317 | -# 应用层执行(cd app) | 331 | +# 应用层执行(cd app,位于 rnoh_test 仓) |
| 318 | pnpm functional:run # 功能模块批量运行(app 侧脚本) | 332 | pnpm functional:run # 功能模块批量运行(app 侧脚本) |
| 319 | pnpm test:compare # 委托给 cptf compare | 333 | pnpm test:compare # 委托给 cptf compare |
| 320 | cptf compare -t <case-id> # 单用例对比 | 334 | cptf 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行) |
| 364 | class IosSystem extends BaseSystem implements ISystem { | 379 | class 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 | ```typescript | 405 | ```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 | ## License | 411 | ## 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,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 | ```ts | 30 | ```ts |
| 31 | import { globalConfig } from '@cptf/framework'; | 31 | import { 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 LCD | 91 | ### 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. 自定义 collector | 108 | ## 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 | ```ts | 141 | ```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 | ```ts | 159 | ```ts |
| 164 | import { PerformanceCollector } from '@cptf/framework'; | 160 | import { PerformanceCollector } from '@cptf/framework'; |
| 165 | const collector = new PerformanceCollector(system, historySize, logger, { | 161 | const 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 | ```ts | 179 | ```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 | ### Harmony | 189 | ### 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 | ### Android | 197 | ### 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 | ### iOS | 205 | ### 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.1 | 5 | +- 版本号: 0.3.1 |
| 6 | -- 更新日期: 2026-07-01 | 6 | +- 更新日期: 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 | ||
Mpackages/framework/docs/architecture/PERFORMANCE-METRICS-SPEC.md+225-225File content is under review, please try again later
Mpackages/framework/docs/architecture/STABILITY-SPEC.md+58-56File content is under review, please try again later
Mpackages/framework/docs/development/METRIC-COLLECTOR-GUIDE.md+45-43File content is under review, please try again later
| @@ -2,8 +2,8 @@ | |||
| 2 | 2 | ||
| 3 | ## 版本 | 3 | ## 版本 |
| 4 | 4 | ||
| 5 | -- 版本号: 0.2.0 | 5 | +- 版本号: 0.3.1 |
| 6 | -- 更新日期: 2026-06-08 | 6 | +- 更新日期: 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 | ```json | 39 | ```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": 2340 | 50 | + "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": 2340 | 56 | + "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 | ```bash | 68 | ```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 | |||
| 262 | 1. 开启两步验证 → 生成应用专用密码 | 294 | 1. 开启两步验证 → 生成应用专用密码 |
| 263 | 2. `host`: `smtp.gmail.com`,`port`: `465`,`secure`: `true` | 295 | 2. `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 | ```typescript | 303 | ```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 | ```typescript | 319 | ```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 | ```typescript | 329 | ```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 | }) |
| 317 | class HighFpsTest extends PerformanceTest { ... } | 336 | class 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 | ```typescript | 509 | ```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测试 |
Mpackages/framework/src/__tests__/capability/metric-collectors.test.ts+649-104File content is under review, please try again later
Mpackages/framework/src/__tests__/system/app-installer.test.ts+26-25File content is under review, please try again later
Mpackages/framework/src/orchestration/parallel-test-runner.ts+1-1File content is under review, please try again later
Mpackages/framework/src/system/app-installer.ts+42-37File content is under review, please try again later