import { test, expect } from '@playwright/test'; /** * P5: 边缘情况与缺失路径 E2E 测试 * * 覆盖范围: * 1. 404 未找到页面 * 2. 全局错误边界 * 3. 页面加载状态 * 4. 无效路由与特殊字符 */ test.setTimeout(60000); // ==================== 1. 404 页面测试 ==================== test.describe('404 页面 - 未找到页面', () => { 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); const recommendedLinks = [ { text: '产品', expectedPath: '/products' }, { text: '解决方案', expectedPath: '/solutions' }, { text: '关于我们', expectedPath: '/about' }, { text: '联系我们', expectedPath: '/contact' }, ]; for (const { text, expectedPath } of recommendedLinks) { const link = page.locator(`a:has-text("${text}")`).first(); if (await link.count() > 0 && await link.isVisible()) { const href = await link.getAttribute('href'); expect(href).toBe(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('错误边界 - 应用级错误处理', () => { test('错误页面包含重试按钮', async ({ page }) => { // 通过直接访问错误页面组件路径模拟错误状态 await page.goto('/error-test-trigger', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(1500); // 检查是否有返回首页链接(错误边界通用元素) const homeLink = page.locator('a[href="/"]').first(); if (await homeLink.count() > 0) { await expect(homeLink).toBeVisible(); } }); test('错误页面包含返回首页链接', async ({ page }) => { await page.goto('/error-boundary-test', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(1500); const homeLink = page.locator('a[href="/"]').first(); if (await homeLink.count() > 0) { const text = await homeLink.textContent(); expect(text).toContain('返回首页'); } }); }); // ==================== 3. 加载状态测试 ==================== test.describe('加载状态 - 页面过渡', () => { 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); // 点击到产品页面 const productLink = page.locator('a[href="/products"]').first(); if (await productLink.count() > 0 && await productLink.isVisible()) { await productLink.click(); await page.waitForTimeout(1000); // 导航过程中页面仍应包含某些元素 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 处理', () => { test('带特殊字符的路由不崩溃', async ({ page }) => { const specialPaths = [ '/products/', '/about?param=', '/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(); }); });