test(qa): e2e 装配修正 + 集成/门禁测试与工具链重构

- e2e:修死选择器与蜜罐误命中、补断言、GA4/security-headers 用例诚实标注
  skip 边界;新增 assert-helpers/fixtures/hydrated/primary-nav/touch-targets
  等复用桩。
- 集成层:config/test/jest.integration.config.js + tests-integration/ 真库
  一次性 SQLite 用例,teardown 守卫开发库指纹。
- 门禁工具链:jest.setup 归位到根、scripts/accessibility 全站 axe 节点计数、
  check-brand-text-token/check-motion-constraints 机械守卫、stryker/lighthouse
  配置收敛。
This commit is contained in:
2026-09-28 10:48:08 +08:00
parent a366bd1400
commit 6bb7c557ee
142 changed files with 6597 additions and 2653 deletions
+40 -51
View File
@@ -1,61 +1,50 @@
import { test, expect } from '@playwright/test';
import { test, expect } from './fixtures';
test('案例页行业筛选按钮可以正确过滤列表', { tag: '@regression' }, async ({ page }) => {
/**
* N-27 整改(2026-09-23):本文件原实现整段是假绿机——
* 1. 筛选 chip 在组件里是 `<button type="button" aria-pressed>`(cases-content-v3.tsx:266-281),
* 从不带 role="radio" ⇒ 原 `button[role="radio"]` 选择器恒为 0,early-return
* 分支(原 :20-26)只留一个 bodyText 长度断言。
* 2. 且 CMS 种子刻意 0 条案例(prisma/seed.ts:628-631「首批客户共创阶段,案例种子为空」),
* 组件按 hasAnyCases=false 渲染共创空态卡(cases-content-v3.tsx:305-343),
* 筛选器整段缺席是**当前契约**而非缺陷。
*
* 因此这里断言零案例下的确定性契约:
* - 页面骨架(hero/空态卡/底部 CTA)恒定渲染;
* - 行业筛选器组与案例卡片必须不存在(若种子加入案例而未更新本文件,此处变红提醒接线筛选断言);
* - 空态卡给出两条出口链接(/contact 与 /methodology),href 硬断言。
* 真实「点击筛选 → 列表收缩」断言需要 ≥1 条已发布案例研究才能接通,见整改报告。
*/
test('案例页:零案例种子下渲染共创空态且筛选器缺席', { tag: '@regression' }, async ({ page }) => {
await page.goto('/cases', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2000);
// 获取所有案例链接(排除导航、面包屑等非案例链接)
// 案例详情链接通常包含 /cases/ 后跟具体 slug,排除 /cases 本身
const allCaseLinks = page.locator('a[href*="/cases/"]').filter({
has: page.locator('h3, h2, .card-title, [class*="title"]'),
});
const initialCount = await allCaseLinks.count();
console.log('Initial case count:', initialCount);
// Hero 恒定渲染:h1 含「以成果为目标」(cases-content-v3.tsx:142-163)
await expect(page.locator('h1')).toContainText('以成果为目标');
// 如果筛选功能不可用(客户端筛选未实现),跳过验证
const filterButtons = page.locator('button[role="radio"]');
const filterCount = await filterButtons.count();
console.log('Filter buttons found:', filterCount);
// 零案例契约:筛选按钮组(role=group aria-label=按行业筛选)与案例卡片不得存在
await expect(
page.getByRole('group', { name: '按行业筛选' }),
).toHaveCount(0);
await expect(page.locator('a[href*="/cases/"]')).toHaveCount(0);
if (filterCount === 0) {
console.log('No filter buttons found, skipping filter test');
// 验证页面内容存在
const bodyText = await page.locator('body').textContent();
expect(bodyText!.length).toBeGreaterThan(0);
return;
}
// 共创空态卡(hasAnyCases=false 分支)必须渲染,并带两条出口 CTA
const emptyPrimaryCta = page.locator('[data-testid="cases-empty-primary-cta"]');
await expect(emptyPrimaryCta).toBeVisible();
await expect(emptyPrimaryCta).toHaveAttribute('href', '/contact');
await expect(emptyPrimaryCta).toContainText('预约咨询');
// 验证默认显示所有案例
expect(initialCount).toBeGreaterThan(0);
const emptySecondaryCta = page.locator('[data-testid="cases-empty-secondary-cta"]');
await expect(emptySecondaryCta).toBeVisible();
await expect(emptySecondaryCta).toHaveAttribute('href', '/methodology');
await expect(emptySecondaryCta).toContainText('查看方法论');
// 尝试点击制造业筛选
const manufacturingFilter = page.locator('button[role="radio"]:has-text("制造业")').first();
if (await manufacturingFilter.isVisible()) {
await manufacturingFilter.click();
await page.waitForTimeout(1000);
// 空态标题 = EARLY_ACCESS.label(company.ts:133-136)
await expect(page.getByRole('heading', { level: 2, name: '首批客户共创中' })).toBeVisible();
// 获取筛选后的案例链接数量
const filteredLinks = page.locator('a[href*="/cases/"]').filter({
has: page.locator('h3, h2, .card-title, [class*="title"]'),
});
const filteredCount = await filteredLinks.count();
console.log('After manufacturing filter, case count:', filteredCount);
// 软验证:筛选后数量应减少或不变(如果筛选器是客户端行为)
expect(filteredCount).toBeLessThanOrEqual(initialCount);
}
// 切回全部
const allFilter = page.locator('button[role="radio"]:has-text("全部")').first();
if (await allFilter.isVisible()) {
await allFilter.click();
await page.waitForTimeout(1000);
const allLinks = page.locator('a[href*="/cases/"]').filter({
has: page.locator('h3, h2, .card-title, [class*="title"]'),
});
const allCount = await allLinks.count();
console.log('After reset to all, case count:', allCount);
expect(allCount).toBeGreaterThanOrEqual(initialCount - 1);
}
// 底部转化区恒定渲染(cases-content-v3.tsx:348-382)
const bottomPrimaryCta = page.locator('[data-testid="cases-bottom-primary-cta"]');
await expect(bottomPrimaryCta).toBeVisible();
await expect(bottomPrimaryCta).toHaveAttribute('href', '/contact');
});