- Add missing-paths E2E spec for desktop/mobile navigation and contrast - Add local Playwright config for dev server testing - Expand unit tests for CMS data server, products, contact and UI components - Adjust jest config for new test patterns
183 lines
6.4 KiB
TypeScript
183 lines
6.4 KiB
TypeScript
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/<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();
|
|
});
|
|
});
|