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
+27 -43
View File
@@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test';
import { test, expect } from './fixtures';
/**
* 安全测试套件
@@ -110,21 +110,20 @@ test.describe('表单安全 - 联系表单验证', { tag: '@security' }, () => {
const formAction = await form.getAttribute('action');
console.log(` Form method: ${formMethod}, action: ${formAction}`);
// 表单可以没有 method 属性(默认 GET),或使用 onSubmit 处理
// 通过拦截 API 请求验证最终提交使用 POST 方法
// 使用 page.route 拦截 API 请求以捕获请求方法
// N-27 修复:原实现把「拦截成功」与「5s 超时」并列为两条通过路径
//(超时分支只查字段可见性)⇒ 表单不再 POST /api/contact 也绿。
// 真实契约:contact-content-v3.tsx:181-182 校验通过后恒定
// fetch('/api/contact', { method: 'POST' })。本例填入的全是合法值
//(zod 校验可通过),因此拦截必须发生且方法必须是 POST,否则变红。
let capturedMethod = '';
const routePromise = new Promise<void>((resolve) => {
page.route('**/api/contact', async (route) => {
capturedMethod = route.request().method();
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: 'true' }),
});
resolve();
await page.route('**/api/contact', async (route) => {
capturedMethod = route.request().method();
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true }),
});
}).catch(() => null);
});
// 填充表单并提交
await page.locator('[data-testid="name-input"]').fill('测试用户');
@@ -142,30 +141,15 @@ test.describe('表单安全 - 联系表单验证', { tag: '@security' }, () => {
// 提交表单
await page.locator('[data-testid="submit-button"]').click();
// 等待路由拦截完成或超时
const routeResult = await Promise.race([
routePromise.then(() => 'intercepted' as const),
page.waitForTimeout(5000).then(() => 'timeout' as const),
]);
if (routeResult === 'intercepted') {
expect(capturedMethod).toBe('POST');
console.log(' ✅ 表单提交使用 POST 方法');
} else {
// 如果 API 路由未触发,可能被客户端验证拦截
console.log(' ⚠️ API 路由未触发,检查客户端验证结果');
const bodyText = await page.locator('body').textContent();
const hasSuccessIndicator =
bodyText!.includes('消息已发送') ||
bodyText!.includes('感谢') ||
bodyText!.includes('发送成功');
if (hasSuccessIndicator) {
console.log(' ✅ 表单提交成功(客户端验证通过)');
}
// 验证表单字段存在且可交互
await expect(page.locator('[data-testid="name-input"]')).toBeVisible();
await expect(page.locator('[data-testid="submit-button"]')).toBeVisible();
}
// 等待拦截发生(10s 内未发生 ⇒ 红),随后硬断言方法为 POST
await expect
.poll(() => capturedMethod, {
timeout: 10000,
message: '提交后 10s 内未拦截到 /api/contact 请求(表单不再向该端点提交?)',
})
.not.toBe('');
expect(capturedMethod).toBe('POST');
console.log(' ✅ 表单提交使用 POST 方法');
});
test('输入字段包含验证属性', async ({ page }) => {
@@ -364,12 +348,12 @@ test.describe('安全传输 - 资源加载', { tag: '@security' }, () => {
test('静态资源通过 HTTPS 加载', async ({ page, baseURL }) => {
// 本地开发环境使用 HTTP,跳过此测试
// 生产环境(HTTPS)下应确保所有资源通过 HTTPS 加载
if (baseURL && baseURL.startsWith('http://localhost')) {
console.log(' ⏭️ 本地开发环境(HTTP),跳过 HTTPS 资源检查');
return;
}
test.skip(
!!baseURL && baseURL.startsWith('http://localhost'),
'本地 harness 恒以 http://localhost 提供站点(playwright.config.ts:27),HTTPS-only 断言只能在真实 HTTPS 部署上执行(N-17)'
);
await page.goto('/', { waitUntil: 'networkidle', timeout: 30000 });
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2000);
// 检查页面中所有资源链接