chore(qa): 验收台账与证据入库 + 构建/部署配置同步

- docs/acceptance/qa-tracker.md:跨周期缺陷单一真源台账(§7=第五轮)。
- 周期 1/2 + iPhone SE/axe 验收证据目录、ACCEPTANCE_REVIEW 快照入库。
- 同步 README/CONTEXT/CLAUDE/DESIGN/testing/deployment/lessons-learned 口径;
  next.config/Dockerfile/nginx/Jenkinsfile/docker-compose/sentry/prisma 对齐
  standalone 产物装配与部署形态。
This commit is contained in:
2026-09-28 10:48:09 +08:00
parent 6bb7c557ee
commit a0328a623f
128 changed files with 22455 additions and 504 deletions
+65 -31
View File
@@ -31,7 +31,7 @@
- [x] CMS 全覆盖:法律页、新闻、团队、案例、服务、方案、产品、独立产品、首页运营位(ContentZone)、RBAC/工作流/媒体/通知全部从 CMS 读取并启用 ISR(3600s)
- [x] CMS 管理后台:角色权限界面(`/admin/roles`)、内容模型 × 操作权限矩阵、super_admin 锁定
- [x] 全量测试体系:单元 ~1600 / 功能 E2E ~800 / 视觉回归三端 / 用户旅程 UJ-01~UJ-11 / Lighthouse / k6 负载压力 / 安全扫描,全部通过
- [x] 全量测试体系:单元 **134 suites / 1697 例** / 功能 E2E **1080 例**(270 例 × chromium + chromium-mobile + firefox + webkit 四个 project)/ 视觉回归 **125 例**(25 例 × 桌面·平板·移动·firefox·webkit 五个 project)/ 用户旅程 UJ-01~UJ-11 / Lighthouse / k6 负载压力 / 安全扫描,全部通过。**计数为 2026-09-23 本树复跑实测**(`npm run test:coverage` ⇒ `Test Suites: 134 passed` / `Tests: 1697 passed` / EXIT=0;`cd e2e && npx playwright test --list` ⇒ `Total: 1205 tests in 22 files` = 1080 + 125),非估算;计数随树漂移,引用请附复算命令
- [x] **官网产品模块定位(IHG/字节式品牌矩阵,2026-08-19)**:官网品牌宣传为主,产品矩阵页为聚合入口;成熟产品 `externalUrl` 外链独立站(NovaVis → novavis.p.novalon.cn);未成熟产品详情页占位,独立站上线后切换外链
- [x] **结构性文案全站 CMS 化(2026-08-19)**:新增 `page-copy` 内容模型承载全部营销页面章节标题/眉标/描述/CTA/空状态(首页 + 服务/方案/产品/案例/新闻列表页),「CMS 优先 + 硬编码兜底」不白屏;seed 写入 6 条,本地 db:seed 已生效
- [x] **信任证据补齐·阶段 0(成立 <1 年策略,2026-08-20)**:信任策略从「结果证据」转向「可验证的过程 + 能力 + 治理证据」——首页「首批客户共创计划」板块(共创进行中/产品内测中/成果授权公开三档如实状态 + 招募 CTA)+ 关于页资质「建设中」如实空态,零编造
@@ -87,9 +87,20 @@ npm run build
### 预览生产版本
```bash
npm run preview
npm run build # 先产出 dist/
npm run preview # == npm run start == `next start -p 3000`(不是 `npx serve`)
```
`preview` 是 `start` 的同义别名,两者都是 `next start -p 3000`。项目为 `output: 'standalone'`,Next 16 在此组合下会打印
`"next start" does not work with "output: standalone" configuration. Use "node .next/standalone/server.js" instead.`
——这是**警告而非抛错**,服务仍会起来,但走的是**不受支持的降级路径**,因此它已不再被任何门禁当作产物口径:
- 受支持的启动方式是 `node dist/standalone/server.js`(静态资源由 `package.json:9` 的 `postbuild` 在 `npm run build` 末尾自动拷入 `dist/standalone/`,standalone 产物本身不含 `dist/static` 与 `public`,缺了它们 `/_next/static/**` 全 404)。该启动方式**尚未做成 npm script**,目前由 `check:axe`、`check:headings`、`e2e/playwright.config.ts:134`(`E2E_TARGET=production`)与 `Jenkinsfile` 各自直接 `node` 起。
- `npm run test:e2e:prod` 走的是上面这条受支持路径(`e2e/playwright.config.ts:132-135`),**不是** `next start`。
- `npm run check:headings` 存在产物时直起 standalone,仅在产物缺失时回退 `npm run preview` 并打印告警(`scripts/utils/check-heading-hierarchy.ts:52-67`,验收 N-24③)。
详见 `CLAUDE.md`「Build & Preview」。
## 项目结构
```
@@ -98,31 +109,43 @@ novalon-website/
│ ├── app/ # Next.js App Router
│ │ ├── (marketing)/ # 营销页面路由组
│ │ │ ├── page.tsx # 首页
│ │ │ ├── home-content-v15.tsx # 首页内容装配(V1.5)
│ │ │ ├── about/ # 关于我们
│ │ │ ├── team/ # 团队
│ │ │ ├── cases/ # 成功案例
│ │ │ ├── contact/ # 联系我们
│ │ │ ├── methodology/ # 方法论
│ │ │ ├── news/ # 新闻动态
│ │ │ ├── products/ # 产品服务
│ │ │ ├── services/ # 核心业务
│ │ │ └── solutions/ # 解决方案
│ │ ├── admin/ # CMS 管理后台
│ │ ├── api/ # 服务端接口(表单、CMS、鉴权)
│ │ ├── privacy/ # 隐私政策
│ │ ├── terms/ # 服务条款
│ │ ├── layout.tsx # 根布局
│ │ ├── error.tsx # 错误页面
│ │ ├── error.tsx # 根段错误页面
│ │ ├── global-error.tsx # 全局错误边界
│ │ └── not-found.tsx # 404 页面
│ ├── components/ # React 组件
│ │ ├── ui/ # 基础 UI 组件
│ │ ├── layout/ # 布局组件
│ │ ├── sections/ # 页面区块组件
│ │ ├── effects/ # 视觉效果组件
│ │ ├── content/ # 内容渲染组件(`sections.tsx`)
│ │ ├── detail/ # 详情页组件
│ │ ├── admin/ # 后台组件
│ │ ├── theme/ # 主题组件
│ │ ├── seo/ # SEO 组件
│ │ └── analytics/ # 分析组件
│ ├── hooks/ # 自定义 Hooks
│ └── contexts/ # React Context
├── e2e/ # E2E 测试
├── tests/ # 测试文件
│ ├── performance/ # 性能测试
│ └── styles/ # 样式测试
│ ├── lib/ # 工具与领域逻辑(含 `cms/`、`constants/`、`media/`)
│ └── types/ # 类型声明
├── e2e/ # Playwright E2E + 视觉回归(**无独立 package.json**,依赖装在根目录)
├── tests/ # 非 Playwright 资产
│ ├── performance/ # k6 负载/压力脚本(由 k6 CLI 跑,不在 npm scripts 内)
│ ├── lib/ # 集成测试
│ ├── styles/ # 样式测试
│ └── screenshots/ # 截图基线
├── docs/ # 项目文档
├── scripts/ # 脚本文件
├── config/ # 配置文件
@@ -157,31 +180,38 @@ novalon-website/
| `npm start` | 启动生产服务器 |
| `npm run lint` | 运行 ESLint 检查 |
| `npm run type-check` | TypeScript 类型检查 |
| `npm run test` | 运行 E2E 测试 |
| `npm run test:unit` | 运行单元测试(Jest,120 suites / 1509 tests) |
| `npm run test:coverage` | 运行测试覆盖率(当前 Branches 82.38%, Lines 73.59%) |
| `npm run test` | 全链路 E2E:`test:functional`(4 project ⇒ **1080 实例**)→ `test:visual:all`(5 project ⇒ **125 实例**)串行,`--list` 合计 **1205**。**webServer 默认是 dev server**,见 `test:e2e:prod` |
| `npm run test:unit` | 运行单元测试(Jest,2026-09-23 本树复跑实测 **134 suites / 1697 tests**) |
| `npm run test:coverage` | 运行测试覆盖率。**阈值**单一真源 `config/test/jest.config.js` 的 `coverageThreshold`(全局 statements/lines/functions ≥ 75%、branches ≥ 82%);**实测值**单一记录位见 [docs/development/quality-gates.md](docs/development/quality-gates.md) §3,本文件不再复制百分比以免再次互斥 |
| `npm run test:e2e:fast` | 运行 E2E 快速测试(@smoke + @critical 标签) |
| `npm run test:e2e:standard` | 运行 E2E 标准回归测试(@regression 标签) |
| `npm run test:smoke` | 运行 E2E 冒烟测试(@smoke 标签) |
| `npm run test:critical` | 运行 E2E 关键路径测试(@critical 标签) |
| `npm run test:e2e:journey` | 运行用户旅程测试(@journey 标签,UJ-01/UJ-02) |
| `npm run test:e2e:mobile` | 运行移动端 E2E 测试(@mobile 标签,53 个测试) |
| `npm run test:e2e:prod` | **产物目标** E2E(`E2E_TARGET=production` → harness 新起 `node dist/standalone/server.js`,**不是** `npm run start`——standalone 下 `next start` 不受支持;`@smoke\|@critical\|@journey` × 4 project)。**只有 GA4 的 16 例在这里真正断言**;生产响应头不在此 grep 集合内(它属 `@security` 标签与 `test:security:headers`,勿混)(2026-09-23 实测:排除 2 个写库 spec 后 92 例通过、0 skipped);须先 `npm run build`(`postbuild` 负责装配 `dist/static` + `public`) |
| `npm run test:e2e:mobile` | 运行移动端 E2E 测试(`@mobile` 标签:**55 个用例**(16 `mobile.spec.ts` + 5 `mobile-user-journeys.spec.ts` + 14 `mobile-accessibility.spec.ts` + 18 `mobile-performance.spec.ts` + 2 `uj-11-home-conversion.spec.ts`),4 个功能 project 全跑 ⇒ **220 个实例**;`cd e2e && npx playwright test --list --grep @mobile` 实测 `Total: 220 tests in 5 files`) |
| `npm run test:e2e:mobile:performance` | 运行移动端性能基线测试(FCP/LCP/加载时间) |
| `npm run test:e2e:mobile:accessibility` | 运行移动端可访问性测试(axe-core WCAG 2.1 AA) |
| `npm run test:mutation` | 运行变异测试(Stryker,评估测试质量,当前 36.98%) |
| `npm run test:mutation:quick` | 快速变异测试(仅 utils.ts,91.18%) |
| `npm run test:mutation` | 运行变异测试(Stryker,评估测试质量)。**分数不在此声明**:全量轮耗时以十分钟计且 `--inPlace` 会改动工作树,本轮未复跑;最近一次落盘记录为整体 **36.98%**(`docs/test-strategy-plan.md` P4.5 / 验收清单条目,2026-08 轮次),引用时须附该出处与 `npm run test:mutation` 复算命令 |
| `npm run test:mutation:quick` | 快速变异测试(`--mutate 'src/lib/utils.ts'`,同一落盘记录 **91.18%**,出处同上) |
| `npm run test:security` | 安全扫描(npm audit + 安全响应头检查) |
| `npm run test:security:headers` | 安全响应头检查(X-Content-Type-Options, CSP, HSTS 等) |
| `npm run test:all` | 全量门禁检查(type-check + lint + coverage + fast E2E + security headers) |
| `npm run check:a11y` | 可访问性静态门禁伞:`check:contrast`(令牌对比度,只读 `globals.css`)+ `check:headings`(标题层级,有产物直起 standalone、缺产物才回退 preview)+ `check:brand-token`(双通道红) |
| `npm run check:axe:routes` | 生成全站路由清单:sitemap ∪ 预渲染产物 ∪ 站内链接 BFS → `/tmp/axe-routes.xml`(`scripts/accessibility/crawl-routes.mjs`;退出码 0/2) |
| `npm run check:axe` | 双引擎 × 双主题逐页 axe **节点计数** + 三条规则级通道的分母断言(`scripts/accessibility/axe-node-count.mjs`);退出码 0 通过 / 1 判红 / 2 清单缺失。**需先有 `node dist/standalone/server.js` 在 :3100**;不在 `test:all` 内,CI 由 `Jenkinsfile`「♿♿」阶段(仅 main)执行 |
| `npm run test:all` | 全量门禁检查,`package.json:48` 原文七段:type-check + lint + coverage + **test:integration:real** + check:a11y + fast E2E + security headers;其 E2E 段仍是 dev 目标,GA4 `@critical` 在其中 skipped,需另跑 `test:e2e:prod` |
| `npm run lighthouse` | 运行 Lighthouse 性能测试 |
## 代码质量门禁
项目配置了自动化质量门禁,确保代码提交前通过所有质量检查:
- **ESLint**: 代码风格检查
- **commitlint**: 提交信息规范
- **Jest**: 代码覆盖率检查
- **ESLint**: 代码风格检查(pre-commit 经 husky + lint-staged 跑 `eslint --fix`,CI 跑全仓 `npm run lint`;判定口径 0 error,warning 不判红)
- **commitlint**: 提交信息规范(`.husky/commit-msg`)
- **Jest**: 代码覆盖率检查(阈值单一真源 `config/test/jest.config.js`)
- **可访问性**: `npm run check:a11y`(对比度 / 标题层级 / 双通道红),CI 在「♿ 可访问性门禁」阶段逐条执行同一组
- **TypeScript**: `npm run type-check` —— **不在 pre-commit**(`.lintstagedrc.json` 只跑 `eslint --fix`),推送前须自行执行
- **E2E 目标**: `npm run test` 打 dev server;GA4 的 16 个 `@critical` 实例在该轮为 skipped,产物口径见 `npm run test:e2e:prod`(详见 [docs/development/quality-gates.md](docs/development/quality-gates.md) §5–§7)
### 提交规范
@@ -209,12 +239,12 @@ novalon-website/
| 工具 | 用途 | 配置 |
|------|------|------|
| **Jest** | 单元测试(120 suites / 1509 tests) | `config/test/jest.config.js` |
| **Playwright** | E2E / 视觉回归 / 用户旅程测试 | `e2e/playwright.config.ts` |
| **Jest** | 单元测试(2026-09-23 本树复跑:134 suites / 1697 tests) | `config/test/jest.config.js` |
| **Playwright** | E2E / 视觉回归 / 用户旅程测试(`--list` 实测 1205 例 = 功能 1080 + 视觉 125) | `e2e/playwright.config.ts` |
| **Stryker** | 变异测试(评估测试质量) | `stryker.config.json` |
| **Allure** | 测试报告可视化 | Allure Playwright reporter |
| **k6** | 负载/压力/API 性能测试 | `tests/performance/` |
| **Lighthouse CI** | 性能/可访问性/SEO 审计 | `lighthouserc.js` |
| **Lighthouse CI** | 性能/可访问性/SEO 审计 | `config/test/lighthouserc.json` |
### E2E 测试标签体系
@@ -225,35 +255,39 @@ novalon-website/
| `@smoke` + `@critical` | 快速回归(<5min) | `npm run test:e2e:fast` |
| `@regression` | 全量回归(<15min) | `npm run test:e2e:standard` |
| `@journey` | 用户旅程(UJ-01/UJ-02) | `npm run test:e2e:journey` |
| `@mobile` | 移动端专项测试(53 个:16 基础 + 5 用户旅程 + 14 可访问性 + 18 性能) | `npm run test:e2e:mobile` |
| `@visual` | 视觉回归(105 snapshots × 5 browsers) | `npm run test:visual:all` |
| `@mobile` | 移动端专项测试(55 个用例:16 基础 + 5 用户旅程 + 14 可访问性 + 18 性能 + 2 UJ-11c;× 4 project = 220 实例) | `npm run test:e2e:mobile` |
| (无标签) | 视觉回归:`@visual` 标签**并不存在**(`e2e/` 全量 grep 为 0 命中),视觉用例由 5 个 `visual-*` project 通过 `testMatch` 选中,且只跑 `visual-regression.spec.ts`(25 例 × 5 project = 125 实例) | `npm run test:visual:all` |
### 运行测试
```bash
# 单元测试
npm run test:unit # 1509 tests
npm run test:unit # 134 suites / 1697 tests(2026-09-23 本树复跑)
# 测试覆盖率
npm run test:coverage # Branches 82.38%, Lines 73.59%
npm run test:coverage # 阈值: 全局 statements/lines/functions ≥75%、branches ≥82%
# 实测值见 docs/development/quality-gates.md §3(此处不复制百分比)
# E2E 测试
npm run test # 全量 E2E(631 passed)
npm run test # 全量 E2E = 功能 1080 + 视觉 125 = 1205 实例
# 落盘通过数引自 docs/acceptance/2026-09-21-gates/skipped-tests-final-tree.json
# (totalResults 1195 = 1167 passed / 28 skipped,生成于 2026-09-22 的 chain6 最终树);
# 本轮未重跑全量 E2E(需 dev server + 4 引擎 + 视觉基线,且 28 条 skip 需按该文件逐条解释)
npm run test:e2e:fast # 快速回归(@smoke + @critical)
npm run test:e2e:standard # 标准回归(@regression)
npm run test:e2e:journey # 用户旅程(@journey)
# 移动端测试
npm run test:e2e:mobile # 全量移动端(53 个 @mobile 测试)
npm run test:e2e:mobile # 全量移动端(55 个 @mobile 用例 × 4 project = 220 实例)
npm run test:e2e:mobile:performance # 移动端性能基线
npm run test:e2e:mobile:accessibility # 移动端可访问性
# 变异测试
# 变异测试(分数只引 docs/test-strategy-plan.md 的落盘记录,本仓未复跑)
npm run test:mutation # 全量变异测试
npm run test:mutation:quick # 快速变异(仅 utils.ts)
# 全量门禁检查
npm run test:all # type-check + lint + coverage + fast E2E
npm run test:all # type-check + lint + coverage + integration:real + check:a11y + fast E2E(dev 目标) + security headers
```
## 部署
@@ -263,7 +297,7 @@ npm run test:all # type-check + lint + coverage + fast E2E
项目发布统一通过 `scripts/deploy.sh` 完成,包含构建、发布、回滚、状态查看:
```bash
# 构建静态产物
# 构建产物(`next build`,output=standalone;脚本再把 public/ 同步进 dist、把 dist/static 复制为 dist/_next/static 供 Nginx 直服)
./scripts/deploy.sh build
# 构建并发布到生产服务器(默认命令,等价于 ./scripts/deploy.sh deploy)