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:
@@ -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);
|
||||
|
||||
// 检查页面中所有资源链接
|
||||
|
||||
Reference in New Issue
Block a user