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:
+134
-97
@@ -79,11 +79,9 @@ npx playwright test --grep @smoke
|
||||
- `home-page.regression.spec.ts` - 首页回归测试
|
||||
- `navigation.spec.ts` - 导航回归测试
|
||||
|
||||
**运行命令**:
|
||||
**运行命令**(`package.json` 无 `test:regression` 脚本,真实入口是 `test:e2e:standard`):
|
||||
```bash
|
||||
npm run test:regression
|
||||
# 或
|
||||
npx playwright test --grep @regression
|
||||
npm run test:e2e:standard # = cd e2e && npx playwright test --grep @regression
|
||||
```
|
||||
|
||||
### 3. 性能测试 (Performance Tests)
|
||||
@@ -104,12 +102,12 @@ npx playwright test --grep @regression
|
||||
- CLS (Cumulative Layout Shift) < 0.1
|
||||
- TTFB (Time to First Byte) < 600ms
|
||||
|
||||
**运行命令**:
|
||||
**运行命令**(两条入口不是一回事,勿混用):
|
||||
```bash
|
||||
npm run test:performance
|
||||
# 或
|
||||
npx playwright test --grep @performance
|
||||
npm run test:performance # = k6 run tests/performance/load-test.js(压测脚本,不是 Playwright 用例;文件确在磁盘)
|
||||
npx playwright test --grep @performance # 浏览器内性能用例(e2e/mobile-performance.spec.ts)
|
||||
```
|
||||
`test:performance` / `test:stress` / `test:performance:api` / `test:performance:soak` 四条都依赖**本机 `k6` CLI**——`package.json` 里的 `k6@0.0.0` 只是 Grafana/LoadImpact 官方的「Dummy package for autocompleting k6 scripts」编辑器类型包,不含可执行文件(验收 N-10)。
|
||||
|
||||
### 4. 响应式测试 (Responsive Tests)
|
||||
|
||||
@@ -126,11 +124,12 @@ npx playwright test --grep @performance
|
||||
- Tablet: iPad Pro (1024x1366), iPad Air (820x1180)
|
||||
- Mobile: iPhone 12 (390x844), iPhone SE (375x667), Pixel 5 (393x851)
|
||||
|
||||
**运行命令**:
|
||||
**运行命令**(⚠ `@responsive` 这个标签在当前 `e2e/*.spec.ts` 里**零命中**,`--grep @responsive` 会跑到 0 个用例;响应式覆盖由 project 维度承担,真实存在的入口只有这两条):
|
||||
```bash
|
||||
npm run test:responsive
|
||||
# 或
|
||||
npx playwright test --grep @responsive
|
||||
npm run test:visual:tablet # visual-chromium-tablet(平板断点全页)
|
||||
npm run test:visual:mobile # visual-chromium-mobile(窄视口全页)
|
||||
# 功能侧的窄视口执行量在 chromium-mobile project(iPhone 14, 390×844):
|
||||
cd e2e && npx playwright test --project=chromium-mobile
|
||||
```
|
||||
|
||||
### 5. 可访问性测试 (Accessibility Tests)
|
||||
@@ -144,17 +143,21 @@ npx playwright test --grep @responsive
|
||||
- `wcag-compliance.spec.ts` - WCAG 合规测试
|
||||
|
||||
**检查项**:
|
||||
- 颜色对比度 ≥ 4.5:1 (AA 级别)
|
||||
- 颜色对比度 ≥ 4.5:1 (AA 级别);≥24px 大字档(含 `aria-hidden` 的装饰序号)按 1.4.3 的 3:1 判定——axe-core 的 `color-contrast` 不因 `aria-hidden` / `role="none"` / CSS `opacity` 豁免,装饰文字须自带合规对比度(用 `text-text-hint` 令牌)
|
||||
- 键盘导航支持
|
||||
- 屏幕阅读器兼容
|
||||
- ARIA 属性正确性
|
||||
- 焦点顺序合理
|
||||
- 触摸目标:硬门禁为 WCAG 2.2 AA SC 2.5.8 ≥ 24×24px(`e2e/touch-targets.ts` 的 `aaViolations`,须为 0);SC 2.5.5 ≥ 44×44px 属 AAA,仅作为建议清单打印不失败。历史上曾把 44px 误标为 AA,导致门禁长期红色。
|
||||
|
||||
**运行命令**:
|
||||
**运行命令**(`package.json` 中真实存在的脚本;本节其余示例里的 `test:accessibility` / `test:responsive` 并不存在,勿照抄):
|
||||
```bash
|
||||
npm run test:accessibility
|
||||
# 或
|
||||
npx playwright test --grep @accessibility
|
||||
npm run check:a11y # 静态令牌门禁伞:check:contrast + check:headings + check:brand-token(已并入 test:all)
|
||||
npm run check:contrast # 只读 globals.css 令牌表做配对计算(不启服务)
|
||||
npm run check:headings # 自起服务扫描渲染后的标题层级:存在 dist/standalone/server.js 时**直起 standalone**,
|
||||
# 产物缺失才回退 `npm run preview` 并打印告警(验收 N-24③,见 CLAUDE.md「Build & Preview」)
|
||||
npm run check:brand-token # 品牌红文字通道令牌化检查(静态扫描源码)
|
||||
npx playwright test --grep @accessibility # 浏览器内 axe / 触摸目标用例
|
||||
```
|
||||
|
||||
### 6. 安全测试 (Security Tests)
|
||||
@@ -174,39 +177,44 @@ npx playwright test --grep @accessibility
|
||||
- 安全头部配置
|
||||
- 表单验证
|
||||
|
||||
**运行命令**:
|
||||
**运行命令**(两条入口不是一回事,勿混用):
|
||||
```bash
|
||||
npm run test:security
|
||||
# 或
|
||||
npx playwright test --grep @security
|
||||
npm run test:security # = npm audit --audit-level=high && npm run test:security:headers(依赖审计 + 线上响应头脚本,不跑 Playwright 用例)
|
||||
npx playwright test --grep @security # 浏览器侧的安全头用例(e2e/security-headers.spec.ts)
|
||||
```
|
||||
|
||||
### 7. 视觉回归测试 (Visual Tests)
|
||||
|
||||
**目录**: `e2e/src/tests/visual/`
|
||||
**测试文件**: `e2e/visual-regression.spec.ts`(L1 全页 15 页 × 5 project + L2 组件状态 + L3 主题 = `--list` 实测 **25 例/project × 5 project = 125 例**)
|
||||
|
||||
**目的**: 检测 UI 变化
|
||||
> ⚠ 三个口径别混:`VISUAL_TEST_PAGES` 的**页面数** 15、每 project 的**用例数** 25、磁盘上的**参照 PNG 数** 107(5 个 project 目录各 21 张 + 目录外 `manual-acceptance/` 2 张)。用例数 − 参照数 ≠ 0 的那部分是**覆盖空洞**而非「多余用例」——`/products/erp-upgrade` 与 `/about/brand` 在两页于 5 个 project 下均无参照,另有卡片/输入框两例死在选择器上(`[class*="card"]` 零匹配、`input[type="text"]` 的 `.first()` 命中按设计隐藏的反垃圾蜜罐)。定性见 [docs/acceptance/2026-09-23-gates/final-verdict.md](acceptance/2026-09-23-gates/final-verdict.md) N-15 与 A-9。**`@visual` 标签不存在**(`e2e/*.spec.ts` 内零命中),视觉层只按 project / spec 文件选取。
|
||||
|
||||
**测试文件**:
|
||||
- `home-page.visual.spec.ts` - 首页视觉测试
|
||||
- `contact-page.visual.spec.ts` - 联系页视觉测试
|
||||
- `visual-regression.spec.ts` - 视觉回归测试
|
||||
**Project**: `visual-chromium-desktop` / `-tablet` / `-mobile` / `visual-firefox-desktop` / `visual-webkit-desktop`,全部 `reducedMotion: 'reduce'`
|
||||
|
||||
**快照目录**:
|
||||
- `*-snapshots/` - 基线快照
|
||||
**快照目录**: `e2e/visual-snapshots/{projectName}/{testFilePath}/{arg}-{projectName}{ext}`(模板见 `e2e/playwright.config.ts`)
|
||||
|
||||
**运行命令**:
|
||||
```bash
|
||||
npm run test:visual
|
||||
# 或
|
||||
npx playwright test --grep @visual
|
||||
npm run test:visual # 仅 chromium 桌面
|
||||
npm run test:visual:all # 5 个 project 全量
|
||||
npm run test:visual:browsers # 三引擎桌面
|
||||
```
|
||||
|
||||
**更新快照**:
|
||||
```bash
|
||||
npx playwright test --grep @visual --update-snapshots
|
||||
npm run test:visual:all -- --update-snapshots
|
||||
```
|
||||
|
||||
**确定性约束**(2026-09-21 验收 83 例失败的根因,详见 [docs/lessons-learned.md](lessons-learned.md) §5.14 / §5.15):
|
||||
1. 快照与 `E2E_TARGET` 无关——`e2e/fixtures.ts` 移除 `next dev` 注入的 `<nextjs-portal>`(左下角 devtools 指示器),否则 dev 采集的基线在 production 复核时每个全页都多出同一簇。
|
||||
2. 视觉阶段不得与写库用例并发——`npm run test` 已拆为 `test:functional` → `test:visual:all` 两阶段串行。CMS 用例「建条目 → revalidate → 删条目」若与截图并发,生产 server 的预渲染缓存会保留含测试条目的那一版(`x-nextjs-cache: HIT`),数据行删干净了页面仍是脏的。
|
||||
3. dev 指示器由**测试夹具自己剥掉**,不是靠配置关掉——`e2e/fixtures.ts` 的 `context` 夹具注入 init script,用 `document.querySelectorAll('nextjs-portal').forEach(n => n.remove())` 移除 `next dev` 注入的 `<nextjs-portal>`(挂 MutationObserver 于 `document`,因为 document start 时 `documentElement` 还是 null)。它内部 shadow DOM 的 `<footer class="error-overlay-footer">` 会被 Playwright 的 CSS 引擎穿透 open shadow root,使 `locator('footer')` 严格模式解析到 2 个元素(2026-09-21 功能 E2E 68 例失败中的 56 例)。
|
||||
**以下写法是错的,勿再引入**:曾有文档写「`e2e/playwright.config.ts` 的 `webServer.env` 注入 `NEXT_DEV_INDICATORS=off`,`next.config.mjs` 据此设 `devIndicators: false`」——2026-09-23 全仓 grep 证实:**代码里不存在该机制**。`NEXT_DEV_INDICATORS` 仅出现在文档(`docs/lessons-learned.md` §5.16 的历史记录)中,`devIndicators` 在代码里的唯一出现是 `e2e/fixtures.ts:8` 的一句注释(自述「实测 `devIndicators:false` 也拿不掉」);`next.config.mjs` 与 `e2e/playwright.config.ts` 两个字符串都没有(后者 `webServer.env` 只注入 `CMS_REVALIDATE_SECRET`,`:142-144`)。该方案当年被实测回滚:设了变量后 `nextjs-portal` 数量仍为 1,因为官方文档写明 `false` 只隐藏指示器本身,而 dev 下 CSP 禁 `eval` 的报错把 overlay 常驻挂住(详见 §5.16)。
|
||||
4. 交互客户端组件前先 `expectHydrated()`(`e2e/hydrated.ts`)——SSR HTML 一到 DOM 就 visible,但 `onClick` / `onMouseEnter` 要等 hydration(实测 `/contact` 提交按钮 first visible 后 1735ms);窗口内的指针事件被丢弃且不重放,表现为动作成功、状态永不变、轮询到 timeout。
|
||||
5. Cookie 同意条只在「未存偏好」时渲染,而 `storageState.json` 预置了 `novalon-cookie-preferences`——需要覆盖同意条的用例必须先 `addInitScript` 清 localStorage,再用 `[data-testid="cookie-consent-banner"]` 定位(`a[href="/privacy"]` 会先命中 footer 的同名链接)。
|
||||
6. 视觉基线口径固定为「已表达 Cookie 偏好」——同意条在挂载 2s 后才出现,而 `visual-firefox-desktop` / `visual-webkit-desktop` 用空 storage state(见 `5a4d310`),慢引擎会把它画进首屏、快引擎赶不上。`visual-regression.spec.ts` 顶部用 `addInitScript` 预置偏好(时间戳为常量)消除该竞态,详见 §5.19。
|
||||
7. 重采基线前先确认 `ContentItem` 无残留测试数据:`sqlite3 "file:prisma/dev.db?mode=ro" "select modelCode,count(*) from ContentItem group by 1;"`。
|
||||
|
||||
### 8. 移动端测试 (Mobile Tests)
|
||||
|
||||
**目录**: `e2e/src/tests/mobile/`
|
||||
@@ -221,6 +229,39 @@ npx playwright test --grep @visual --update-snapshots
|
||||
- `pwa/pwa-functionality.spec.ts` - PWA 功能测试
|
||||
- `mobile-ux.spec.ts` - 移动端 UX 测试
|
||||
|
||||
**视口约定**:`mobile-*.spec.ts` 顶部必须显式 `test.use({ ...devices['iPhone 14'] })`。只有 `chromium-mobile` project 在配置里设了移动视口,而这两个 spec 同时被 `chromium` / `firefox` / `webkit` 桌面 project 复用——不在 spec 内钉住视口,同一份断言会有一半执行量到桌面布局(断点类名、抽屉是否挂载、触摸目标尺寸全都不同),却被当成移动端口径。
|
||||
|
||||
**E2E 目标(默认是 dev,不是产物)**:`e2e/playwright.config.ts:132-135` 按 `E2E_TARGET` 切换 webServer——`production` 走 `HOSTNAME=localhost PORT=3000 node dist/standalone/server.js`(服务 `output: 'standalone'` 产物,`next.config.mjs:44`),否则走 `npm run dev`。**不是 `npm run start`**:Next 16 在 standalone 输出下明确告警 `next start` 不可用,走它即测的是不受支持的降级路径(配置内注释 `:128-131`)。`npm run test` / `test:functional` / `test:e2e` **都不设该变量**,即默认打 dev server;dev 下 Next 的安全头、压缩、预取与 ISR 行为与线上不同。要走产物口径用 `npm run test:e2e:prod`(脚本本身已带 `E2E_TARGET=production`,`--grep '@smoke|@critical|@journey'` × 4 project),且**须先自行 `npm run build`**——webServer 只负责起服务,不代跑构建,且 standalone 目录不自含 `dist/static` 与 `public`,须按 `Jenkinsfile:353-355` 的装配方式先拷进去(缺了它们 `/_next/static/**` 全 404)。`reuseExistingServer` 在 production 目标下恒为 false(`:139`),:3000 被占用会直接中止本轮,避免静默复用陈旧服务。
|
||||
|
||||
> ⚠ **dev 目标下的 skipped 口径(不可忽略)**:`ga4-event-tracking.spec.ts` 的 4 个 `@critical` 用例(TC-GA4-001..004)在默认 dev 轮次里**每个 project 都 skip**,4 project 共 **16 个 skipped**(不是 passed)。链路:`NEXT_PUBLIC_GA_MEASUREMENT_ID` 只写在 `.env.production`(`.env` / `.env.local` 里没有)→ dev 下 `src/components/analytics/GoogleAnalytics.tsx:81,119` 提前 `return null` → 无 `gtag/js` 请求 → 用例内 `test.skip(measurementId === null, '构建未注入 GA 脚本…')` 触发。证据:`docs/acceptance/2026-09-21-gates/skipped-tests-final-tree.json`(最终树 `npm run test` 全量 1195 结果 = 1167 passed / 28 skipped,其中该组 `count: 16`;另两组为 cms-workflow「仅 chromium 执行」9、p3「Touchscreen API 仅 Chromium」3)。产物口径的 4 passed 见同目录 `ga4-production-run.txt`。
|
||||
> 结论:**报告 GA4 覆盖时必须引用 `test:e2e:prod` 的结果**,把 `npm run test` 的「全绿」当作 GA4 已验证即为口径错误。CI 侧的对应关系:`Jenkinsfile`「🌐 E2E 测试」阶段(仅 `branch 'main'`)先 `npm run build` 再 `npm run test:e2e:prod`,即产物口径在 main 上被真实执行;本地开发轮的 `npm run test` 不覆盖它。
|
||||
|
||||
**iPhone SE 口径复核(验收 §8-⑥)**:375×667 / `deviceScaleFactor: 2` / `isMobile` + `hasTouch` / `colorScheme` 强制深·浅 × 同意条 pending·dismissed × 首页 / 新闻详情 / 新闻列表 / 联系页 = 16 组,产物在 `docs/acceptance/2026-09-21-iphone-se/`(32 图 + `measurements.json` + 探针脚本 `iphone-se-audit.mjs`)。口径与阈值:`scrollWidth == clientWidth`(无横向溢出)、`footerBottomGap`(备案行到 `<footer>` 底边的留白,R-1 修复前 192–226,修复后恒为 64)、`bannerOverlapsFooter`(首次访客的 fixed 浮层覆盖页脚属设计行为)。复跑:`OUT=/tmp/se node --input-type=module -e "$(cat docs/acceptance/2026-09-21-iphone-se/iphone-se-audit.mjs)"`(需在仓库目录内执行,脚本要 `import 'playwright'`;且需一个跑在 3000 端口的 dev server)。**滚到底必须逐帧轮询**:首页入场分级会持续增高文档,单次 `scrollTo(0, scrollHeight)` 会停在半页并测出无效 `footer` 位置。该口径是浏览器仿真,不替代真机走查。
|
||||
|
||||
**axe 全站节点计数门禁(验收 §8-⑤,2026-09-23 起为常驻门禁)**:既有 `e2e/mobile-accessibility.spec.ts` 不满足该条口径——它只断言 critical/serious 的**规则条数** = 0(`:67`),覆盖 9 个路由、单一主题,因此「同一规则下 20 个节点不达标」与「0 个节点不达标」在它眼里等价。为此单独建 `scripts/accessibility/axe-node-count.mjs`(由 `docs/acceptance/2026-09-21-axe/axe-contrast-evidence.mjs` 平移而来,证据 JSON 仍留在原目录):在 **chromium + firefox** 两个引擎 × **light + dark** 两个主题下逐页跑 `axe-core` 4.11.4(`wcag2a/wcag2aa/wcag21a/wcag21aa`),按**节点数**记账并落 `axe-evidence.json`。闸口同时成立才算过:`violationNodes` / `color-contrast` 节点数 = 0(四个组合任一 > 0 即判红)、`themeMismatch` = 0(回读 `document.documentElement[data-theme]`,防「以为在量深色其实还是浅色」)、`bgMismatch` = 0(`body` 底色须等于令牌值 light `rgb(255,255,255)` / dark `rgb(10,14,20)`,防样式未加载导致的「对比度意外达标」),外加规则级第二次 `runOnly` 的**分母断言**:`autocomplete-valid` / `presentation-role-conflict` / `svg-img-alt` 三条必须在**每一页 × 每一个组合**都被判定过(`Σ = 路由 × 4 × 3`,且 `extraRulePagesUnderCovered = 0`)——脚本里另有一份写死的下限清单,删掉运行列表中的一条规则会当场判红而不是悄悄少测。
|
||||
|
||||
**分母口径(2026-09-22 升级,勿再退回单取 sitemap)**:清单来源是 **`/sitemap.xml` ∪ 预渲染产物(`dist/standalone/dist/server/app/**/*.html`)∪ 站内链接 BFS(`scripts/accessibility/crawl-routes.mjs`,原 `docs/acceptance/2026-09-21-axe/crawl-sitemap.mjs`)**,最终树实测 **34 条路由**(`axe-evidence.json`:`routeCount=34`、`rows=136 = 34 × 4 组合`、`passed=true`)。`sitemap.xml` 只覆盖「希望被收录的页」,单独取用它即为口径错误——扩目录首轮当场挖出 `/_not-found` 与 `/_global-error` 两个真实缺口(见 `docs/lessons-learned.md` §5.24、`CONTEXT.md` 决策行)。历史对照:`axe-evidence-sitemap29.json`(29 页版,当时的 0 是真 0,但分母不足)。
|
||||
|
||||
**退出码即门禁判定**(不再只是打印 `PASSED`):`0` 通过 / `1` 判红(violation 节点 > 0、主题或底色不匹配、规则级通道分母不闭合、整组响应全非 2xx 的「你在扫 404」保险丝、扫描中途异常)/ `2` 输入不可用(路由清单缺失或解析出 0 条;爬虫侧同样在清单为空或 BASE 完全不可达时退出 2 且**不覆盖**上一份清单)。
|
||||
|
||||
复跑(两条脚本可零参数直跑,env 有默认值):
|
||||
```bash
|
||||
npm run build # 产出 dist/standalone,并由 postbuild 自动装配静态资源(见下)
|
||||
PORT=3100 HOSTNAME=127.0.0.1 node dist/standalone/server.js & # 不用 npm run start:Next 对 output:'standalone' 下的 next start 明确告警不支持
|
||||
npm run check:axe:routes # → /tmp/axe-routes.xml(BASE 默认 http://localhost:3100)
|
||||
npm run check:axe # 读同一份清单,证据写 docs/acceptance/2026-09-21-axe/axe-evidence.json
|
||||
```
|
||||
装配这一步现在由 `package.json` 的 `postbuild` 代做,等价于手工执行:
|
||||
```bash
|
||||
mkdir -p dist/standalone/dist/static dist/standalone/public
|
||||
cp -R dist/static/. dist/standalone/dist/static/ && cp -R public/. dist/standalone/public/ # 与 Dockerfile.prod 同一套装配
|
||||
```
|
||||
静态资源必须在**起服务之前**并入 standalone 根目录:`server.js` 启动时缓存静态索引,缺 `dist/static` 会让 `/_next/static/**` 全 404,页面落在默认黑白底上——对比度会「意外达标」,正是 `bgMismatch` 判据要拦的情形。脚本要 `import 'playwright'` 与读取 `node_modules/axe-core/axe.min.js`,两者已按仓库根解析(不再依赖 cwd)。注入 axe 依赖站点 CSP 含 `script-src 'unsafe-inline'`(`next.config.mjs:29-31`);若收紧该指令,此扫描与既有 axe 用例会同时失效。
|
||||
|
||||
> ✅ **CI 接线(2026-09-23 落地)**:`Jenkinsfile` 新增「♿♿ 全站 axe 节点计数」阶段(仅 `branch 'main'`,与 E2E / Lighthouse 同档,因为 34 路由 × 4 组合 ≈ 14 分钟),复用前一阶段构建好的 `dist/standalone`,按上面的顺序装配静态资源 → 起 `node dist/standalone/server.js`(专用端口 `AXE_PORT`,默认 3100)→ 依次 `npm run check:axe:routes` 与 `npm run check:axe`,`set -e` 下任一非 0 退出即判红、无 `|| echo`;服务进程按记录到的 PID 收服(含 `trap … EXIT`),**只 kill 本阶段自己启动的进程**——历史事故是只 kill 包装进程留下 `next-server` 孤儿占住 :3000,被 `reuseExistingServer` 静默复用而毒化整轮验证(`docs/acceptance/2026-09-21-gates/final-tree-results.md`「已知口径限制」1)。证据 `axe-evidence.json` 在 `post { always }` 归档,判红时同样留档。
|
||||
|
||||
> 📊 **最近一次全链路实测(2026-09-23,装配后的 standalone 产物)**:axe `PASSED=true`(`chromium`/`firefox` × `light`/`dark` 各 34 页,`bgMismatch` / `contrastNodes` / `violationNodes` 全为 0;唯一 `non2xxResponses=1` 是 `/_not-found` 的正确 404),Lighthouse 27 次运行断言全通过、a11y 9/9 URL 均 100。这一格在修 N-9(standalone 缺 `dist/static` ⇒ 此前两条门禁实际是在 CSS 全 404 的裸 HTML 上出数)之前是**不可信**的。完整判定、未闭环项与待授权清单见 [docs/acceptance/2026-09-23-gates/final-verdict.md](acceptance/2026-09-23-gates/final-verdict.md)。
|
||||
|
||||
### 9. 部署就绪测试 (Deployment Tests)
|
||||
|
||||
**目录**: `e2e/src/tests/deployment/`
|
||||
@@ -231,6 +272,24 @@ npx playwright test --grep @visual --update-snapshots
|
||||
- `deployment-readiness.spec.ts` - 部署就绪检查
|
||||
- `quick-check.spec.ts` - 快速检查
|
||||
|
||||
## 门禁命令对照(2026-09-23 与 `package.json` 逐条核对)
|
||||
|
||||
只列 `package.json` 中**真实存在**的脚本;本节之外的示例命令(如 `test:accessibility`、`test:responsive`、`test:regression`、`test:ui`、`test:debug`、`test:headed`、`test:allure*`、`test:ci`)在当前脚本表中不存在,属历史参考;`test:performance` 与 `test:security` 虽然存在,但含义与下方各节的字面期待不同(见 §3、§6 的运行命令块)。
|
||||
|
||||
| 脚本 | 实际命令 | 目标 / 口径 | CI 阶段 |
|
||||
|---|---|---|---|
|
||||
| `npm run test` | `test:functional` + `test:visual:all` 串行 | webServer = **dev**(`npm run dev`);GA4 的 16 个 `@critical` 实例 skipped | — |
|
||||
| `npm run test:functional` / `test:e2e` | 4 功能 project | 同上(dev 目标) | — |
|
||||
| `npm run test:e2e:prod` | `E2E_TARGET=production` + `--grep '@smoke\|@critical\|@journey'` × 4 project | **产物目标**(`node dist/standalone/server.js`,非 `next start`);须先 `npm run build` 并装配 `dist/static` + `public` | 🌐 E2E 测试(仅 main) |
|
||||
| `npm run test:critical` / `test:smoke` / `test:e2e:fast` / `test:e2e:standard` / `test:e2e:journey` | 按标签 grep | 未设 `E2E_TARGET` ⇒ 仍是 dev 目标 | — |
|
||||
| `npm run test:visual` / `:all` / `:mobile` / `:tablet` / `:browsers` | 视觉 project 子集 | 快照与目标无关(见 §7 约束 1) | 👁️ 视觉回归(仅 main,跑 `test:visual`) |
|
||||
| `npm run check:a11y` | `check:contrast` → `check:headings` → `check:brand-token` | 令牌/标题/双通道红三条静态门禁;**已并入 `test:all`** | ♿ 可访问性门禁(逐条调用,全分支) |
|
||||
| `npm run check:axe` / `check:axe:routes` | `node scripts/accessibility/{axe-node-count,crawl-routes}.mjs` | 全站路由清单(三源并集)+ 双引擎双主题 axe **节点计数**与三条规则级通道的分母断言;退出码 0/1/2(2 = 清单缺失或 0 条)。需先有 `node dist/standalone/server.js` 在 :3100;未被 `test:all` 引用 | ♿♿ 全站 axe 节点计数(仅 main,复用「🏗️ 构建 dist」的产物) |
|
||||
| `npm run test:all` | type-check + lint + test:coverage + **test:integration:real** + **check:a11y** + test:e2e:fast + test:security:headers(`package.json:48` 逐字) | 一键本地门禁(其 E2E 段仍是 dev 目标);CI 不按此脚本跑,见上表「CI 集成」 | — |
|
||||
| `npm run test:unit` / `test:coverage` / `test:coverage:check` | jest(阈值单一真源 `config/test/jest.config.js`,根 `jest.config.js` 仅转发) | 单元 + 覆盖率棘轮 | 🧪 单元测试 |
|
||||
| `npm run lighthouse` | `lhci autorun --config=config/test/lighthouserc.json` | 性能 + axe 断言 | ⚡ Lighthouse(仅 main,先 build) |
|
||||
| `npm run test:security` / `test:security:headers` | `npm audit --audit-level=high` + 头部检查 | 依赖与响应头 | 🔒 安全扫描 |
|
||||
|
||||
## Page Object Model
|
||||
|
||||
### 基础页面类
|
||||
@@ -398,32 +457,24 @@ export function getEnvironment(): Environment {
|
||||
### 本地运行
|
||||
|
||||
```bash
|
||||
# 进入测试目录
|
||||
cd e2e
|
||||
# 依赖装在仓库根:e2e/ 下**没有** package.json(`cd e2e && npm install` 会报 ENOENT / 装出空目录),
|
||||
# Playwright 与全部 reporter 都是根 package.json 的 devDependencies
|
||||
npm ci # 或 npm install
|
||||
npx playwright install # 安装 chromium / firefox / webkit 浏览器二进制
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 安装浏览器
|
||||
npx playwright install
|
||||
|
||||
# 运行所有测试
|
||||
# 运行所有测试(功能 4 project → 视觉 5 project 串行;webServer 默认是 dev server)
|
||||
npm run test
|
||||
|
||||
# 运行特定测试
|
||||
# 以下 playwright 命令须在 e2e/ 目录内执行(配置文件与 spec 同目录,根目录直接跑找不到 config)
|
||||
cd e2e
|
||||
npx playwright test path/to/test.spec.ts
|
||||
|
||||
# 运行带标签的测试
|
||||
npx playwright test --grep @smoke
|
||||
|
||||
# UI 模式
|
||||
npm run test:ui
|
||||
|
||||
# 调试模式
|
||||
npm run test:debug
|
||||
|
||||
# 有头模式
|
||||
npm run test:headed
|
||||
# UI / 调试 / 有头模式:package.json 里没有 test:ui / test:debug / test:headed,
|
||||
# 这三条此前是「读起来像真的」的假命令,跑必报 Missing script;正确写法是 Playwright 原生 flag
|
||||
npx playwright test --ui
|
||||
npx playwright test --debug
|
||||
npx playwright test --headed
|
||||
```
|
||||
|
||||
### CI 环境运行
|
||||
@@ -444,14 +495,12 @@ npx playwright show-report
|
||||
### Allure 报告
|
||||
|
||||
```bash
|
||||
# 生成报告
|
||||
npm run test:allure
|
||||
|
||||
# 打开报告
|
||||
npm run test:allure:open
|
||||
|
||||
# 实时服务
|
||||
npm run test:allure:serve
|
||||
# Allure 报告:注意 package.json 未定义任何 test:allure* 脚本,
|
||||
# 且 allure-commandline **不在 dependencies/devDependencies 里**(node_modules/allure-commandline 不存在),
|
||||
# 因此旧版文档给的三条 `test:allure*` 脚本全部不可执行(package.json 里查无此名)。当前仓库真实产出的是
|
||||
# e2e/allure-results/*.json(由 Playwright reporter 写盘)。要看报告需先自行安装 CLI:
|
||||
npx -y allure-commandline generate e2e/allure-results -o e2e/allure-report --clean
|
||||
npx -y allure-commandline open e2e/allure-report
|
||||
```
|
||||
|
||||
### JUnit 报告
|
||||
@@ -524,46 +573,34 @@ test.describe('测试组', () => {
|
||||
});
|
||||
```
|
||||
|
||||
## CI 集成
|
||||
## CI 集成(本仓库的真实流水线)
|
||||
|
||||
### Woodpecker CI 配置
|
||||
**本仓库的 CI 只有一套:根目录 `Jenkinsfile`(Jenkins 声明式 Pipeline)。** 以下系统在本仓库**不存在**,历史文档若引用即为假事实,已删除:
|
||||
|
||||
```yaml
|
||||
# .woodpecker.yml
|
||||
pipeline:
|
||||
e2e-tests:
|
||||
image: node:18-alpine
|
||||
environment:
|
||||
NODE_ENV: test
|
||||
CI: true
|
||||
commands:
|
||||
- cd e2e
|
||||
- npm ci
|
||||
- npx playwright install --with-deps chromium
|
||||
- npm run test:ci
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
```
|
||||
- `.woodpecker.yml` / Woodpecker CI —— 磁盘上无此文件
|
||||
- GitLab CI(`pipeline:` / `image:` 语法)—— 无 `.gitlab-ci.yml`;本节此前那段 `image: node:18-alpine` 且调用一个不存在的脚本(`test:ci`)的片段三重过期(CI 系统、node 版本、脚本名都不对),已整段移除
|
||||
- GitHub Actions —— `.github/` 目录不存在
|
||||
- `.gitea/` **存在但只有 `PULL_REQUEST_TEMPLATE.md`**(PR 模板,供 `scripts/check-pr-checklist.sh` 校验),不是 workflow 定义
|
||||
|
||||
### 测试命令
|
||||
`Jenkinsfile` 实际阶段与命令(`when { branch 'main' }` 标 ★,其余全分支执行;失败处理已统一为 `set -e` / `|| exit 1`,历史上吞错的 `|| echo` 只剩环境探测块,见验收 A-5):
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "playwright test",
|
||||
"test:smoke": "playwright test --grep @smoke",
|
||||
"test:regression": "playwright test --grep @regression",
|
||||
"test:performance": "playwright test --grep @performance",
|
||||
"test:responsive": "playwright test --grep @responsive",
|
||||
"test:visual": "playwright test --grep @visual",
|
||||
"test:accessibility": "playwright test --grep @accessibility",
|
||||
"test:security": "playwright test --grep @security",
|
||||
"test:ci": "playwright test --reporter=html,json,junit"
|
||||
}
|
||||
}
|
||||
```
|
||||
| 阶段 | 命令 | 说明 |
|
||||
|---|---|---|
|
||||
| 🔧 环境检测与准备 | `node -v` / `npm -v` / `rsync`、`ssh`、`curl` 探测 | 探测类 `|| echo` 合法 |
|
||||
| 📥 安装依赖 | `npm ci` →(失败兜底)`npm install --legacy-peer-deps` | ⚠ 该块先 `rm -rf node_modules package-lock.json` 再 `npm ci`,故 lockfile 从未真正生效(验收 N-26②,未修待决策) |
|
||||
| 🔍 代码质量检查 → ESLint / TypeScript | `npm run lint` / `npm run type-check` | 判定口径 0 error(warning 不判红) |
|
||||
| 🧪 单元测试 | `CI=true npm run test:coverage:check` | 覆盖率阈值真源 `config/test/jest.config.js` |
|
||||
| 🌐 E2E 测试 ★ | `npm run build` → `npm run test:e2e:prod` | 产物目标;GA4 的 16 例只在这里断言 |
|
||||
| 👁️ 视觉回归测试 ★ | `npm run test:visual` | **只跑桌面 chromium 一个 project**,另四个视觉 project 在 CI 从不执行(验收 N-28) |
|
||||
| ♿ 可访问性门禁 | `check:contrast` → `check:brand-token` → `check:headings` | 三条静态门禁,全分支 |
|
||||
| ⚡ Lighthouse 性能与无障碍 ★ | `npm run build` → `npm run lighthouse` | lhci 自起产物服务并断言 |
|
||||
| 🧬 变异测试 ★ | `npm run test:mutation:quick` | 仅 `src/lib/utils.ts`;`post` 清 `.stryker-tmp` |
|
||||
| 🔒 安全扫描 ★ | `npm audit --audit-level=high` → `npm run test:security:headers` | ⚠ 该脚本**默认打线上** `https://novalon.cn`,与阶段上方「检查本分支构建产物」的打印相反(验收 N-23,未修待决策) |
|
||||
| 🏗️ 构建 dist | `npm run build:clean` + 产物存在性/文件数校验 | `post { success }` 归档 `dist/**` |
|
||||
| ♿♿ 全站 axe 节点计数 ★ | `node dist/standalone/server.js`(`AXE_PORT`,默认 3100)→ `OUT=$ROUTES npm run check:axe:routes` → `SITEMAP=$ROUTES npm run check:axe` | 复用上一阶段产物;按记录 PID 收服,只 kill 本阶段自起的进程 |
|
||||
| 🚀 部署到生产环境 ★ | `./scripts/deploy.sh deploy --skip-build`(失败自动 `rollback`) | 需 `DEPLOY_TO_PRODUCTION` 参数 |
|
||||
|
||||
本地一键门禁对应关系:`npm run test:all` = `type-check` + `lint` + `test:coverage` + `test:integration:real` + `check:a11y` + `test:e2e:fast` + `test:security:headers`;CI **不跑** `test:all`(它按上表逐阶段拆开跑),两者集合不同——`test:all` 里的 E2E 是 dev 目标,CI 的 E2E 是产物目标。
|
||||
|
||||
## 调试技巧
|
||||
|
||||
|
||||
Reference in New Issue
Block a user