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,5 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { test, expect } from './fixtures';
|
||||
import { pollBoundingBox } from './assert-helpers';
|
||||
|
||||
/**
|
||||
* P3: 多浏览器与多设备兼容性测试
|
||||
@@ -38,16 +39,17 @@ test.describe('跨浏览器 - 核心页面渲染', { tag: '@regression' }, () =>
|
||||
await page.goto(path, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
const logo = page.locator('header img, header svg').first();
|
||||
if (await logo.count() > 0) {
|
||||
await expect(logo).toBeVisible();
|
||||
// N-27 修复:原守卫 `if (logo.count() > 0)` 让 Logo 消失时静默通过。
|
||||
// 证据:header.tsx:96-121 恒定渲染 a[aria-label="返回首页"],内含明暗两版
|
||||
// logo(按 html[data-theme] 显隐,恰有一个可见),CSS h-8 md:h-10 ⇒
|
||||
// 高度 32/40px、宽度随 160:40 比例自动缩放,桌面与移动断点均成立。
|
||||
const logo = page.locator('header a[aria-label="返回首页"] img:visible').first();
|
||||
await expect(logo).toBeVisible();
|
||||
|
||||
// 检查 Logo 尺寸合理
|
||||
const box = await logo.boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
expect(box!.width).toBeGreaterThan(50);
|
||||
expect(box!.height).toBeGreaterThan(20);
|
||||
}
|
||||
// 检查 Logo 尺寸合理
|
||||
const box = await pollBoundingBox(logo);
|
||||
expect(box.width).toBeGreaterThan(50);
|
||||
expect(box.height).toBeGreaterThan(20);
|
||||
});
|
||||
|
||||
test(`${name} (${path}) - 导航菜单可交互`, async ({ page }) => {
|
||||
@@ -116,15 +118,17 @@ test.describe('多设备 - 视口响应式布局', { tag: '@regression' }, () =>
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// N-27 修复:本组用例显式设到移动视口(≤428px < md 断点),
|
||||
// header.tsx:192 的 mobile-menu-button(min 44×44px)必须可见。
|
||||
// 原守卫 `if (isVisible)` ⇒ 按钮消失时静默通过;改为硬断言 + 轮询盒模型。
|
||||
const mobileMenuBtn = page.locator('[data-testid="mobile-menu-button"]').first();
|
||||
if (await mobileMenuBtn.isVisible()) {
|
||||
// 按钮应在可视区域内
|
||||
const box = await mobileMenuBtn.boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
expect(box!.x).toBeGreaterThanOrEqual(0);
|
||||
expect(box!.y).toBeGreaterThanOrEqual(0);
|
||||
expect(box!.x + box!.width).toBeLessThanOrEqual(viewport.width + 10); // 允许小误差
|
||||
}
|
||||
await expect(mobileMenuBtn).toBeVisible();
|
||||
|
||||
// 按钮应在可视区域内(pollBoundingBox 等待节点稳定,超时即红)
|
||||
const box = await pollBoundingBox(mobileMenuBtn);
|
||||
expect(box.x).toBeGreaterThanOrEqual(0);
|
||||
expect(box.y).toBeGreaterThanOrEqual(0);
|
||||
expect(box.x + box.width).toBeLessThanOrEqual(viewport.width + 10); // 允许小误差
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -181,6 +185,10 @@ test.describe('特定浏览器 - 行为差异检查', { tag: '@regression' }, ()
|
||||
const images = page.locator('img');
|
||||
const imageCount = await images.count();
|
||||
|
||||
// N-27 修复:原 `if (imageCount > 0)` 守卫——首页 img 数为 0 时本例静默通过。
|
||||
// header.tsx:103-121 恒渲染两枚 logo <img>(priority/eager),img 数为 0 即真实回归。
|
||||
expect(imageCount).toBeGreaterThan(0);
|
||||
|
||||
let loadedCount = 0;
|
||||
|
||||
for (let i = 0; i < Math.min(imageCount, 10); i++) {
|
||||
@@ -189,30 +197,27 @@ test.describe('特定浏览器 - 行为差异检查', { tag: '@regression' }, ()
|
||||
if (naturalWidth > 0) loadedCount++;
|
||||
}
|
||||
|
||||
// 至少应该有一些图片加载成功
|
||||
if (imageCount > 0) {
|
||||
expect(loadedCount).toBeGreaterThan(0);
|
||||
}
|
||||
// 至少应该有一些图片加载成功(无条件断言)
|
||||
expect(loadedCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('表单元素样式一致', async ({ page }) => {
|
||||
await page.goto('/contact', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Check for any visible form inputs (selectors vary by page implementation)
|
||||
const inputs = page.locator('input:visible, textarea:visible');
|
||||
// N-27 修复:原双守卫 `if (inputCount > 0)` + `if (box)` 使本例在表单消失时
|
||||
// 静默通过。联系页表单为恒定契约(contact-content-v3 渲染必填输入),
|
||||
// 改为主内容区内作用域的硬断言 + 轮询盒模型。
|
||||
const inputs = page.locator('main input:visible, main textarea:visible');
|
||||
const inputCount = await inputs.count();
|
||||
expect(inputCount).toBeGreaterThan(0);
|
||||
|
||||
if (inputCount > 0) {
|
||||
for (let i = 0; i < Math.min(inputCount, 5); i++) {
|
||||
const input = inputs.nth(i);
|
||||
// Verify input is visible and has reasonable dimensions
|
||||
const box = await input.boundingBox();
|
||||
if (box) {
|
||||
expect(box.width).toBeGreaterThan(50);
|
||||
expect(box.height).toBeGreaterThan(20);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < Math.min(inputCount, 5); i++) {
|
||||
const input = inputs.nth(i);
|
||||
// Verify input is visible and has reasonable dimensions
|
||||
const box = await pollBoundingBox(input);
|
||||
expect(box.width).toBeGreaterThan(50);
|
||||
expect(box.height).toBeGreaterThan(20);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user