Files
novalon-website/e2e/p5-edge-cases.spec.ts
T
zhangxiang 6bb7c557ee 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
  配置收敛。
2026-09-28 10:48:08 +08:00

197 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from './fixtures';
/**
* P5: 边缘情况与缺失路径 E2E 测试
*
* 覆盖范围:
* 1. 404 未找到页面
* 2. 全局错误边界
* 3. 页面加载状态
* 4. 无效路由与特殊字符
*/
test.setTimeout(60000);
// ==================== 1. 404 页面测试 ====================
test.describe('404 页面 - 未找到页面', { tag: '@regression' }, () => {
test('访问不存在的页面显示 404', async ({ page }) => {
await page.goto('/this-page-does-not-exist', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// URL 应保持为请求的路径或重定向到 404 处理
expect(page.url()).toContain('this-page-does-not-exist');
// 检查 404 页面关键元素
const heading = page.locator('h1').first();
await expect(heading).toBeVisible();
const headingText = await heading.textContent();
expect(headingText).toMatch(/404|页面未找到/);
});
test('404 页面包含返回首页按钮', async ({ page }) => {
await page.goto('/non-existent-page', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
const homeButton = page.locator('a[href="/"]').first();
await expect(homeButton).toBeVisible();
expect(await homeButton.textContent()).toContain('返回首页');
});
test('404 页面推荐链接可点击', async ({ page }) => {
await page.goto('/missing-page', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// N-27 修复:四个推荐链接在 src/app/not-found-content.tsx:41-81 中恒定渲染,
// 原 `if (count > 0 && isVisible)` 守卫会让本例在链接缺失时静默通过。
// 改为主内容区内作用域的轮询断言——任何一条缺失/隐藏即失败。
const recommendedLinks = [
{ text: '产品', expectedPath: '/products' },
{ text: '解决方案', expectedPath: '/solutions' },
{ text: '关于我们', expectedPath: '/about' },
{ text: '联系我们', expectedPath: '/contact' },
];
for (const { text, expectedPath } of recommendedLinks) {
const link = page.locator(`main a:has-text("${text}")`).first();
await expect(link, `404 页缺少推荐链接「${text}」`).toBeVisible();
await expect(link).toHaveAttribute('href', expectedPath);
}
});
test('404 页面标题设置正确', async ({ page }) => {
await page.goto('/not-found-test', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
const title = await page.title();
expect(title).toContain('页面未找到');
});
});
// ==================== 2. 错误边界测试 ====================
test.describe('错误边界 - 应用级错误处理', { tag: '@regression' }, () => {
test('错误页面包含重试按钮', async ({ page }) => {
// 未知路径渲染 src/app/not-found-content.tsx:其恢复动作是
// 「返回上一页」<button>(not-found-content.tsx:30-33)+「返回首页」CTA。
// N-27 修复:原守卫 `if (count > 0)` ⇒ 页面空了也绿。改为硬断言两者可见。
await page.goto('/error-test-trigger', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
const backButton = page.locator('main button', { hasText: '返回上一页' }).first();
await expect(backButton).toBeVisible();
const homeLink = page.locator('main a[href="/"]').first();
await expect(homeLink).toBeVisible();
});
test('错误页面包含返回首页链接', async ({ page }) => {
await page.goto('/error-boundary-test', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// N-27 修复:守卫改为硬断言,并验证链接真的能回到首页
// (与 :32 的差异:此例覆盖点击后的导航行为,不只是文案存在)。
const homeLink = page.locator('main a[href="/"]').first();
await expect(homeLink).toBeVisible();
await expect(homeLink).toContainText('返回首页');
await Promise.all([
page.waitForURL((url) => new URL(url).pathname === '/', { waitUntil: 'commit', timeout: 15000 }),
homeLink.click(),
]);
expect(new URL(page.url()).pathname).toBe('/');
});
});
// ==================== 3. 加载状态测试 ====================
test.describe('加载状态 - 页面过渡', { tag: '@regression' }, () => {
test('页面加载时显示主要内容', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
// 页面加载后 header 应可见
await expect(page.locator('header')).toBeVisible({ timeout: 10000 });
// main 内容应存在
const mainContent = page.locator('main, [role="main"]').first();
await expect(mainContent).toBeVisible({ timeout: 10000 });
});
test('页面导航不应完全空白', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1000);
// N-27 修复:原守卫 `if (count > 0 && isVisible)` 依赖 header 巨型下拉里的
// `a[href="/products"]`(未 hover 时隐藏 ⇒ 断言常被跳过)。
// 改用主内容区恒定渲染的叙事区行动链接(home-content-v15 NARRATIVE_ACTS 第三幕,
// p2-functional-e2e 亦以同选择器作硬断言),导航与内容都变硬断言。
const productLink = page
.locator('[data-testid="narrative-section"] a[href="/products"]')
.first();
await expect(productLink).toBeVisible();
await productLink.click();
await expect(page).toHaveURL((url) => url.pathname === '/products');
// 导航完成后页面仍应包含实际内容
const bodyText = await page.locator('body').textContent();
expect(bodyText!.length).toBeGreaterThan(50);
});
test('刷新页面后内容仍然加载', async ({ page }) => {
await page.goto('/products', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2000);
// 刷新页面
await page.reload({ waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// 页面应仍然加载成功
const mainContent = page.locator('main, [role="main"]').first();
await expect(mainContent).toBeVisible();
});
});
// ==================== 4. 无效路由与特殊字符测试 ====================
test.describe('无效路由 - 边界 URL 处理', { tag: '@regression' }, () => {
test('带特殊字符的路由不崩溃', async ({ page }) => {
const specialPaths = [
'/products/<script>alert(1)</script>',
'/about?param=<invalid>',
'/contact#%20%20',
];
for (const path of specialPaths) {
await page.goto(path, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1000);
// 页面不应崩溃,body 应存在
const body = page.locator('body').first();
await expect(body).toBeVisible();
}
});
test('过长路径不导致页面异常', async ({ page }) => {
const longPath = '/a'.repeat(200);
await page.goto(longPath, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// 页面应正常渲染(404 页面或正常页面)
const body = page.locator('body').first();
await expect(body).toBeVisible();
const bodyText = await body.textContent();
expect(bodyText!.length).toBeGreaterThan(0);
});
test('查询参数不影响页面渲染', async ({ page }) => {
await page.goto('/?utm_source=test&utm_medium=email', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// 首页应正常渲染
const header = page.locator('header').first();
await expect(header).toBeVisible();
});
});