Files
novalon-website/e2e/p3-compatibility.spec.ts
T
张翔 10404dbb36 chore: sync marketing pages, CMS extensions, tests and project docs
同步工作区剩余变更,主要包括:
- 营销页面组件与布局持续优化(about/news/services/solutions/team 等)
- 详情页四层叙事组件、布局组件、UI 组件调整
- CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展
- 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等)
- ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整
- 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告
- 移除水墨装饰组件与大体积未使用字体文件
2026-07-25 08:04:01 +08:00

254 lines
9.5 KiB
TypeScript

import { test, expect } from '@playwright/test';
/**
* P3: 多浏览器与多设备兼容性测试
*
* 目标:验证网站在不同浏览器和设备上的显示一致性
*
* 测试矩阵:
* - 浏览器:Chrome, Firefox, Safari (WebKit)
* - 设备:Desktop, Tablet, Mobile (多种尺寸)
* - 关键页面:首页、关于、联系、产品等
*/
// 设置超时时间
test.setTimeout(60000);
// ==================== 关键页面的跨浏览器一致性测试 ====================
test.describe('跨浏览器 - 核心页面渲染', () => {
const criticalPages = [
{ path: '/', name: '首页' },
{ path: '/about', name: '关于我们' },
{ path: '/contact', name: '联系我们' },
];
for (const { path, name } of criticalPages) {
test(`${name} (${path}) - 页面基本元素可见`, async ({ page }) => {
await page.goto(path, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2000);
// 基本结构检查
await expect(page.locator('header')).toBeVisible();
await expect(page.locator('main, [role="main"]').first()).toBeVisible();
await expect(page.locator('footer, [data-testid="footer"]')).toBeVisible();
});
test(`${name} (${path}) - Logo 显示正常`, async ({ page }) => {
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();
// 检查 Logo 尺寸合理
const box = await logo.boundingBox();
expect(box).not.toBeNull();
expect(box!.width).toBeGreaterThan(50);
expect(box!.height).toBeGreaterThan(20);
}
});
test(`${name} (${path}) - 导航菜单可交互`, async ({ page }) => {
await page.goto(path, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// 桌面端导航或移动端按钮应该存在
const desktopNav = page.locator('[data-testid="desktop-navigation"]');
const mobileMenuBtn = page.locator('[data-testid="mobile-menu-button"]');
const hasDesktopNav = await desktopNav.count() > 0 && await desktopNav.first().isVisible();
const hasMobileBtn = await mobileMenuBtn.count() > 0 && await mobileMenuBtn.first().isVisible();
expect(hasDesktopNav || hasMobileBtn).toBeTruthy();
});
}
});
// ==================== 多设备视口适配测试 ====================
test.describe('多设备 - 视口响应式布局', () => {
const viewports = [
{ name: 'Desktop XL', width: 1920, height: 1080, type: 'desktop' as const },
{ name: 'Desktop', width: 1280, height: 800, type: 'desktop' as const },
{ name: 'Laptop', width: 1024, height: 768, type: 'laptop' as const },
{ name: 'Tablet Landscape', width: 768, height: 1024, type: 'tablet' as const },
{ name: 'Tablet Portrait', width: 768, height: 1024, type: 'tablet' as const },
{ name: 'Mobile Large', width: 428, height: 926, type: 'mobile' as const },
{ name: 'Mobile Medium', width: 375, height: 667, type: 'mobile' as const },
{ name: 'Mobile Small', width: 320, height: 568, type: 'mobile' as const },
];
for (const viewport of viewports) {
test(`${viewport.name} (${viewport.width}x${viewport.height}) - 首页布局完整`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// Header 可见
await expect(page.locator('header')).toBeVisible();
// Main content 可见
const mainContent = page.locator('main, [role="main"], #main-content').first();
await expect(mainContent).toBeVisible();
// Footer 可见
await expect(page.locator('footer, [data-testid="footer"]')).toBeVisible();
});
test(`${viewport.name} (${viewport.width}x${viewport.height}) - 无水平滚动条`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// 检查是否出现水平滚动条
const hasHorizontalScroll = await page.evaluate(() => {
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
});
expect(hasHorizontalScroll).toBeFalsy();
});
if (viewport.type === 'mobile') {
test(`${viewport.name} - 移动端导航可用`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
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); // 允许小误差
}
});
}
}
});
// ==================== 特定浏览器行为测试 ====================
test.describe('特定浏览器 - 行为差异检查', () => {
test('字体回退机制工作正常', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// 检查 body 的 font-family 是否设置
const fontFamily = await page.evaluate(() => {
return window.getComputedStyle(document.body).fontFamily;
});
expect(fontFamily).toBeTruthy();
expect(fontFamily.length).toBeGreaterThan(0);
// 应该包含中文字体或通用字体族
expect(fontFamily.toLowerCase()).toMatch(/sans-serif|serif|system-ui|pingfang|microsoft|hei/i);
});
test('CSS Grid/Flexbox 兼容性', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// 检查是否有使用现代布局的容器
const modernLayoutUsed = await page.evaluate(() => {
const allElements = document.querySelectorAll('*');
let usesGrid = false;
let usesFlex = false;
for (const el of allElements) {
const style = window.getComputedStyle(el);
if (style.display === 'grid') usesGrid = true;
if (style.display === 'flex') usesFlex = true;
if (usesGrid && usesFlex) break;
}
return { usesGrid, usesFlex };
});
// 网站应该使用现代CSS布局
expect(modernLayoutUsed.usesGrid || modernLayoutUsed.usesFlex).toBeTruthy();
});
test('图片懒加载兼容性', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2000);
// 检查图片是否正确加载
const images = page.locator('img');
const imageCount = await images.count();
let loadedCount = 0;
for (let i = 0; i < Math.min(imageCount, 10); i++) {
const img = images.nth(i);
const naturalWidth = await img.evaluate((el: HTMLImageElement) => el.naturalWidth);
if (naturalWidth > 0) loadedCount++;
}
// 至少应该有一些图片加载成功
if (imageCount > 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');
const inputCount = await inputs.count();
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);
}
}
}
});
});
// ==================== 触摸设备模拟测试 ====================
test.describe('触摸设备 - 手势交互', () => {
test('触摸滑动流畅(无卡顿)', async ({ page, browserName }) => {
// touchscreen API is only available in Chromium with touch emulation
test.skip(browserName !== 'chromium', 'Touchscreen API only available in Chromium');
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// Simulate scroll via mouse wheel instead of touchscreen (cross-browser compatible)
await page.mouse.wheel(0, 300);
await page.waitForTimeout(500);
// 页面不应该崩溃或有错误
const bodyVisible = await page.locator('body').isVisible();
expect(bodyVisible).toBeTruthy();
});
test('双击缩放不会破坏布局', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(1500);
// 模拟双击
const header = page.locator('header').first();
await header.dblclick();
await page.waitForTimeout(500);
// Header 应该仍然可见
await expect(header).toBeVisible();
});
});