10404dbb36
同步工作区剩余变更,主要包括: - 营销页面组件与布局持续优化(about/news/services/solutions/team 等) - 详情页四层叙事组件、布局组件、UI 组件调整 - CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展 - 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等) - ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整 - 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告 - 移除水墨装饰组件与大体积未使用字体文件
721 lines
25 KiB
TypeScript
721 lines
25 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
||
|
||
/**
|
||
* P2: 全页面功能E2E测试套件
|
||
*
|
||
* 测试范围:覆盖网站所有主要功能和用户流程
|
||
*
|
||
* 模块划分:
|
||
* 1. 首页功能测试(Hero区、产品展示、解决方案、CTA等)
|
||
* 2. 导航与路由测试
|
||
* 3. 产品中心测试
|
||
* 4. 解决方案测试
|
||
* 5. 服务介绍测试
|
||
* 6. 关于我们测试
|
||
* 7. 联系我们表单测试
|
||
* 8. 新闻动态测试
|
||
* 9. 团队介绍测试
|
||
* 10. 法律页面测试(隐私政策、服务条款)
|
||
* 11. 响应式布局测试
|
||
* 12. 无障碍访问测试
|
||
*/
|
||
|
||
// 设置超时时间
|
||
test.setTimeout(60000);
|
||
|
||
// ==================== 1. 首页功能测试 ====================
|
||
test.describe('首页 - 核心功能区', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(2000);
|
||
});
|
||
|
||
test('Hero区域正确显示', async ({ page }) => {
|
||
// 检查主标题
|
||
const heroTitle = page.locator('h1').first();
|
||
await expect(heroTitle).toBeVisible();
|
||
const titleText = await heroTitle.textContent();
|
||
expect(titleText).toBeTruthy();
|
||
expect(titleText!.length).toBeGreaterThan(0);
|
||
|
||
// 检查副标题或描述文本
|
||
const heroDescription = page.locator('main p').first();
|
||
await expect(heroDescription).toBeVisible();
|
||
});
|
||
|
||
test('产品矩阵卡片显示正常', async ({ page }) => {
|
||
// 查找产品卡片容器
|
||
const productSection = page.locator('text=产品矩阵').first();
|
||
if (await productSection.isVisible()) {
|
||
// 等待产品卡片加载
|
||
await page.waitForTimeout(1000);
|
||
|
||
// 检查至少有产品卡片存在
|
||
const productCards = page.locator('a[href*="/products/"]');
|
||
const cardCount = await productCards.count();
|
||
expect(cardCount).toBeGreaterThan(0);
|
||
}
|
||
});
|
||
|
||
test('挑战与解决方案区块显示', async ({ page }) => {
|
||
const challengesSection = page.locator('text=您的挑战').first();
|
||
if (await challengesSection.isVisible()) {
|
||
// 检查挑战项
|
||
const challengeItems = page.locator('text=/数据孤岛|增长瓶颈|合规风险/');
|
||
const itemCount = await challengeItems.count();
|
||
expect(itemCount).toBeGreaterThan(0);
|
||
}
|
||
});
|
||
|
||
test('信任标识区块显示', async ({ page }) => {
|
||
const trustSection = page.locator('text=值得信赖').first();
|
||
if (await trustSection.isVisible()) {
|
||
// 检查信任标识项(私有化部署、资深团队等)
|
||
const trustItems = page.locator('text=/私有化部署|资深团队|全栈自研|长期陪跑/');
|
||
const itemCount = await trustItems.count();
|
||
expect(itemCount).toBeGreaterThan(0);
|
||
}
|
||
});
|
||
|
||
test('CTA按钮可见且可点击', async ({ page }) => {
|
||
const ctaButton = page.locator('text=开始合作').first();
|
||
if (await ctaButton.isVisible()) {
|
||
await expect(ctaButton).toBeEnabled();
|
||
}
|
||
});
|
||
});
|
||
|
||
// ==================== 2. 导航与路由测试 ====================
|
||
test.describe('导航系统 - 路由与菜单', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(2000);
|
||
});
|
||
|
||
test('主导航菜单包含所有主要链接', async ({ page }) => {
|
||
const nav = page.locator('nav, [data-testid="desktop-navigation"]').first();
|
||
await expect(nav).toBeVisible();
|
||
|
||
// 检查关键导航项
|
||
const expectedLinks = ['产品', '解决方案', '服务', '关于我们', '联系我们'];
|
||
for (const linkText of expectedLinks) {
|
||
const link = nav.locator(`text=${linkText}`).first();
|
||
// 某些链接可能在下拉菜单中,所以只检查是否存在
|
||
const linkExists = await link.count();
|
||
expect(linkExists).toBeGreaterThan(0);
|
||
}
|
||
});
|
||
|
||
test('Logo点击返回首页', async ({ page }) => {
|
||
const logo = page.locator('header a img, header img[alt*="睿新致远"]').first();
|
||
if (await logo.count() > 0) {
|
||
const logoLink = logo.locator('..');
|
||
await logoLink.click();
|
||
await page.waitForTimeout(1000);
|
||
expect(page.url()).toContain('/');
|
||
}
|
||
});
|
||
|
||
test('导航链接可点击且跳转正确', async ({ page }) => {
|
||
const navLinks = [
|
||
{ text: '关于我们', expectedPath: '/about' },
|
||
{ text: '联系我们', expectedPath: '/contact' },
|
||
{ text: '服务', expectedPath: '/services' },
|
||
];
|
||
|
||
for (const { text, expectedPath } of navLinks) {
|
||
const link = page.locator(`text=${text}`).first();
|
||
if (await link.isVisible() && await link.isEnabled()) {
|
||
await Promise.all([
|
||
page.waitForURL(expectedPath, { timeout: 5000 }).catch(() => {}),
|
||
link.click()
|
||
]);
|
||
await page.waitForTimeout(500);
|
||
|
||
// 返回首页继续测试其他链接
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(1000);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// ==================== 3. 产品中心测试 ====================
|
||
test.describe('产品中心 - 页面功能', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/products', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(2000);
|
||
});
|
||
|
||
test('产品列表页面加载成功', async ({ page }) => {
|
||
await expect(page).toHaveURL(/\/products/);
|
||
|
||
// 检查页面标题包含"产品"
|
||
const pageTitle = page.locator('h1, h2').first();
|
||
await expect(pageTitle).toBeVisible();
|
||
const titleText = await pageTitle.textContent();
|
||
expect(titleText).toContain('产品');
|
||
});
|
||
|
||
test('产品卡片可点击跳转到详情', async ({ page }) => {
|
||
// 等待产品卡片加载
|
||
await page.waitForTimeout(1000);
|
||
|
||
const productLinks = page.locator('a[href*="/products/"]:not([href*="/products$"])');
|
||
const linkCount = await productLinks.count();
|
||
|
||
if (linkCount > 0) {
|
||
// 点击第一个产品链接
|
||
const firstProduct = productLinks.first();
|
||
await firstProduct.click();
|
||
await page.waitForTimeout(1000);
|
||
|
||
// 应该跳转到产品详情页
|
||
expect(page.url()).toMatch(/\/products\/[^/]+$/);
|
||
}
|
||
});
|
||
|
||
test('ERP升级专题页面可访问', async ({ page }) => {
|
||
await page.goto('/products/erp-upgrade', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(1500);
|
||
|
||
// 页面应正常加载
|
||
const content = page.locator('main').first();
|
||
await expect(content).toBeVisible();
|
||
});
|
||
});
|
||
|
||
// ==================== 4. 解决方案测试 ====================
|
||
test.describe('解决方案 - 行业方案展示', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/solutions', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(2000);
|
||
});
|
||
|
||
test('解决方案列表页面加载成功', async ({ page }) => {
|
||
await expect(page).toHaveURL(/\/solutions/);
|
||
|
||
const pageTitle = page.locator('h1, h2').first();
|
||
await expect(pageTitle).toBeVisible();
|
||
});
|
||
|
||
test('行业分类标签可见', async ({ page }) => {
|
||
// 检查是否有行业分类(制造业、零售业、教育等)
|
||
const industries = ['制造业', '零售业', '教育', '医疗'];
|
||
|
||
for (const industry of industries) {
|
||
const industryElement = page.locator(`text=${industry}`).first();
|
||
if (await industryElement.count() > 0) {
|
||
await expect(industryElement.first()).toBeVisible();
|
||
}
|
||
}
|
||
});
|
||
|
||
test('解决方案详情可查看', async ({ page }) => {
|
||
const solutionLink = page.locator('a[href*="/solutions/"]').first();
|
||
if (await solutionLink.count() > 0 && await solutionLink.isVisible()) {
|
||
await solutionLink.click();
|
||
await page.waitForTimeout(1000);
|
||
expect(page.url()).toMatch(/\/solutions\/[^/]+$/);
|
||
}
|
||
});
|
||
});
|
||
|
||
// ==================== 5. 服务介绍测试 ====================
|
||
test.describe('服务介绍 - 服务内容展示', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/services', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(2000);
|
||
});
|
||
|
||
test('服务页面加载成功', async ({ page }) => {
|
||
await expect(page).toHaveURL(/\/services/);
|
||
|
||
const pageTitle = page.locator('h1, h2').first();
|
||
await expect(pageTitle).toBeVisible();
|
||
});
|
||
|
||
test('服务项目列表显示', async ({ page }) => {
|
||
// 检查是否有服务项目展示
|
||
const serviceCards = page.locator('article, [class*="card"], [class*="service"]');
|
||
if (await serviceCards.count() > 0) {
|
||
const cardCount = await serviceCards.count();
|
||
expect(cardCount).toBeGreaterThan(0);
|
||
}
|
||
});
|
||
|
||
test('服务详情可访问', async ({ page }) => {
|
||
const serviceLink = page.locator('a[href*="/services/"]').first();
|
||
if (await serviceLink.count() > 0 && await serviceLink.isVisible()) {
|
||
await serviceLink.click();
|
||
await page.waitForTimeout(1000);
|
||
expect(page.url()).toMatch(/\/services\/[^/]+$/);
|
||
}
|
||
});
|
||
});
|
||
|
||
// ==================== 6. 关于我们测试 ====================
|
||
test.describe('关于我们 - 公司信息展示', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/about', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(2000);
|
||
});
|
||
|
||
test('关于页面加载成功', async ({ page }) => {
|
||
await expect(page).toHaveURL(/\/about/);
|
||
|
||
// Check that the page renders meaningful content (CMS-driven title varies)
|
||
const mainContent = page.locator('main').first();
|
||
await expect(mainContent).toBeVisible();
|
||
const contentText = await mainContent.textContent();
|
||
expect(contentText!.length).toBeGreaterThan(100);
|
||
});
|
||
|
||
test('公司信息展示完整', async ({ page }) => {
|
||
// 检查公司描述或使命愿景等内容
|
||
const mainContent = page.locator('main').first();
|
||
await expect(mainContent).toBeVisible();
|
||
|
||
const contentText = await mainContent.textContent();
|
||
expect(contentText!.length).toBeGreaterThan(100); // 应有足够的内容
|
||
});
|
||
|
||
test('不显示敏感联系信息(电话号码)', async ({ page }) => {
|
||
// Check main content only (exclude footer with ICP/police numbers)
|
||
const mainContent = page.locator('main').first();
|
||
const mainText = await mainContent.textContent();
|
||
// Only check for explicit phone patterns like 028-XXXXXXXX
|
||
const phonePattern = /028-\d{8}/;
|
||
expect(mainText).not.toMatch(phonePattern);
|
||
});
|
||
});
|
||
|
||
// ==================== 7. 联系我们表单测试 ====================
|
||
test.describe('联系我们 - 表单交互', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/contact', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(2500); // 表单可能需要更长的渲染时间
|
||
});
|
||
|
||
test('联系表单所有字段可见', async ({ page }) => {
|
||
// 检查表单字段
|
||
const formFields = [
|
||
'[data-testid="name-input"]',
|
||
'[data-testid="phone-input"]',
|
||
'[data-testid="email-input"]',
|
||
'[data-testid="subject-input"]',
|
||
'[data-testid="message-input"]',
|
||
'[data-testid="submit-button"]',
|
||
];
|
||
|
||
for (const selector of formFields) {
|
||
const field = page.locator(selector).first();
|
||
await expect(field).toBeVisible({ timeout: 5000 });
|
||
}
|
||
});
|
||
|
||
test('表单必填验证工作正常', async ({ page }) => {
|
||
const submitButton = page.locator('[data-testid="submit-button"]').first();
|
||
await expect(submitButton).toBeVisible({ timeout: 5000 });
|
||
|
||
// 直接提交空表单
|
||
await submitButton.click();
|
||
|
||
// 等待验证错误出现
|
||
await page.waitForTimeout(500);
|
||
|
||
// 检查是否出现错误提示
|
||
const errorMessages = page.locator('[data-testid="error-message"], .error-message, [role="alert"]');
|
||
const errorCount = await errorMessages.count();
|
||
expect(errorCount).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('表单输入功能正常', async ({ page }) => {
|
||
const testData = {
|
||
name: '测试用户',
|
||
phone: '13800138000',
|
||
email: 'test@example.com',
|
||
subject: '测试咨询',
|
||
message: '这是一条测试消息,用于验证表单功能。',
|
||
};
|
||
|
||
// 填写姓名
|
||
const nameInput = page.locator('[data-testid="name-input"]').first();
|
||
await nameInput.fill(testData.name);
|
||
expect(await nameInput.inputValue()).toBe(testData.name);
|
||
|
||
// 填写邮箱
|
||
const emailInput = page.locator('[data-testid="email-input"]').first();
|
||
await emailInput.fill(testData.email);
|
||
expect(await emailInput.inputValue()).toBe(testData.email);
|
||
|
||
// 填写消息
|
||
const messageInput = page.locator('[data-testid="message-input"]').first();
|
||
await messageInput.fill(testData.message);
|
||
expect(await messageInput.inputValue()).toBe(testData.message);
|
||
});
|
||
|
||
test('邮箱格式验证', async ({ page }) => {
|
||
const emailInput = page.locator('[data-testid="email-input"]').first();
|
||
const submitButton = page.locator('[data-testid="submit-button"]').first();
|
||
|
||
// 输入无效邮箱
|
||
await emailInput.fill('invalid-email');
|
||
|
||
// 填写其他必填字段以通过基本验证
|
||
const nameInput = page.locator('[data-testid="name-input"]').first();
|
||
await nameInput.fill('测试用户');
|
||
|
||
await submitButton.click();
|
||
await page.waitForTimeout(500);
|
||
|
||
// 检查邮箱相关的错误信息
|
||
const emailError = page.locator('[data-testid="error-message"]').first();
|
||
// 可能会显示邮箱格式错误
|
||
const errorExists = await emailError.count();
|
||
if (errorExists > 0) {
|
||
const errorText = await emailError.textContent();
|
||
expect(errorText).toBeTruthy();
|
||
}
|
||
});
|
||
});
|
||
|
||
// ==================== 8. 新闻动态测试 ====================
|
||
test.describe('新闻动态 - 内容展示', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/news', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(2000);
|
||
});
|
||
|
||
test('新闻列表页面加载成功', async ({ page }) => {
|
||
await expect(page).toHaveURL(/\/news/);
|
||
|
||
const pageTitle = page.locator('h1, h2').first();
|
||
await expect(pageTitle).toBeVisible();
|
||
});
|
||
|
||
test('新闻文章可点击查看详情', async ({ page }) => {
|
||
const newsLinks = page.locator('a[href*="/news/"]');
|
||
const linkCount = await newsLinks.count();
|
||
|
||
if (linkCount > 0) {
|
||
const firstNews = newsLinks.first();
|
||
await firstNews.click();
|
||
await page.waitForTimeout(1000);
|
||
expect(page.url()).toMatch(/\/news\/[^/]+$/);
|
||
}
|
||
});
|
||
});
|
||
|
||
// ==================== 9. 团队介绍测试 ====================
|
||
test.describe('团队介绍 - 成员展示', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/team', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(2000);
|
||
});
|
||
|
||
test('团队页面加载成功', async ({ page }) => {
|
||
await expect(page).toHaveURL(/\/team/);
|
||
|
||
const content = page.locator('main').first();
|
||
await expect(content).toBeVisible();
|
||
});
|
||
|
||
test('团队成员信息展示', async ({ page }) => {
|
||
// 检查是否有团队成员卡片或列表
|
||
const teamMembers = page.locator('[class*="member"], [class*="team"], article');
|
||
if (await teamMembers.count() > 0) {
|
||
const memberCount = await teamMembers.count();
|
||
expect(memberCount).toBeGreaterThan(0);
|
||
}
|
||
});
|
||
});
|
||
|
||
// ==================== 10. 法律页面测试 ====================
|
||
test.describe('法律页面 - 隐私政策与服务条款', () => {
|
||
|
||
test('隐私政策页面内容完整', async ({ page }) => {
|
||
await page.goto('/privacy', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(1500);
|
||
|
||
await expect(page).toHaveURL(/\/privacy/);
|
||
|
||
// 检查页面标题
|
||
const pageTitle = page.locator('h1').first();
|
||
await expect(pageTitle).toBeVisible();
|
||
const titleText = await pageTitle.textContent();
|
||
expect(titleText).toContain('隐私');
|
||
|
||
// 检查正文内容足够长(法律文档应该有较多内容)
|
||
const bodyText = await page.locator('body').textContent();
|
||
expect(bodyText!.length).toBeGreaterThan(500);
|
||
});
|
||
|
||
test('服务条款页面内容完整', async ({ page }) => {
|
||
await page.goto('/terms', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(1500);
|
||
|
||
await expect(page).toHaveURL(/\/terms/);
|
||
|
||
const pageTitle = page.locator('h1').first();
|
||
await expect(pageTitle).toBeVisible();
|
||
const titleText = await pageTitle.textContent();
|
||
expect(titleText).toContain('条款') || expect(titleText).toContain('服务');
|
||
|
||
const bodyText = await page.locator('body').textContent();
|
||
expect(bodyText!.length).toBeGreaterThan(500);
|
||
});
|
||
});
|
||
|
||
// ==================== 11. 响应式布局测试 ====================
|
||
test.describe('响应式设计 - 多设备适配', () => {
|
||
|
||
test('桌面端布局正常', async ({ page }) => {
|
||
await page.setViewportSize({ width: 1280, height: 800 });
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(1500);
|
||
|
||
// Header 和 Footer 都应可见
|
||
await expect(page.locator('header')).toBeVisible();
|
||
await expect(page.locator('footer, [data-testid="footer"]')).toBeVisible();
|
||
|
||
// 主导航应水平显示
|
||
const desktopNav = page.locator('[data-testid="desktop-navigation"]');
|
||
if (await desktopNav.count() > 0) {
|
||
await expect(desktopNav.first()).toBeVisible();
|
||
}
|
||
});
|
||
|
||
test('平板端布局正常', async ({ page }) => {
|
||
await page.setViewportSize({ width: 768, height: 1024 });
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(1500);
|
||
|
||
await expect(page.locator('header')).toBeVisible();
|
||
await expect(page.locator('footer, [data-testid="footer"]')).toBeVisible();
|
||
});
|
||
|
||
test('移动端布局正常', async ({ page }) => {
|
||
await page.setViewportSize({ width: 375, height: 667 });
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(1500);
|
||
|
||
// Header 和 Footer 可见
|
||
await expect(page.locator('header')).toBeVisible();
|
||
await expect(page.locator('footer, [data-testid="footer"]')).toBeVisible();
|
||
|
||
// 移动端菜单按钮应可见
|
||
const mobileMenuBtn = page.locator('[data-testid="mobile-menu-button"]');
|
||
if (await mobileMenuBtn.count() > 0) {
|
||
await expect(mobileMenuBtn.first()).toBeVisible({ timeout: 5000 });
|
||
}
|
||
});
|
||
|
||
test('移动端菜单展开/收起功能', async ({ page }) => {
|
||
await page.setViewportSize({ width: 375, height: 667 });
|
||
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()) {
|
||
// 点击打开菜单
|
||
await mobileMenuBtn.click();
|
||
await page.waitForTimeout(500);
|
||
|
||
const mobileNav = page.locator('[data-testid="mobile-navigation"]').first();
|
||
await expect(mobileNav).toBeVisible({ timeout: 5000 });
|
||
|
||
// 再次点击关闭菜单
|
||
await mobileMenuBtn.click();
|
||
await page.waitForTimeout(500);
|
||
|
||
// 菜单应该隐藏
|
||
await expect(mobileNav).not.toBeVisible();
|
||
}
|
||
});
|
||
});
|
||
|
||
// ==================== 12. 无障碍访问测试 ====================
|
||
test.describe('无障碍性 - A11y 基础检查', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(1500);
|
||
});
|
||
|
||
test('HTML lang 属性设置正确', async ({ page }) => {
|
||
const htmlLang = await page.locator('html').getAttribute('lang');
|
||
expect(htmlLang).toBe('zh-CN');
|
||
});
|
||
|
||
test('页面标题非空', async ({ page }) => {
|
||
const title = await page.title();
|
||
expect(title).toBeTruthy();
|
||
expect(title.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('跳转链接存在', async ({ page }) => {
|
||
const skipLink = page.locator('[data-skip-to-content], a[href="#main-content"]').first();
|
||
if (await skipLink.count() > 0) {
|
||
await expect(skipLink).toBeVisible();
|
||
}
|
||
});
|
||
|
||
test('图片都有 alt 属性', async ({ page }) => {
|
||
const images = page.locator('img');
|
||
const count = await images.count();
|
||
|
||
let imagesWithoutAlt = 0;
|
||
for (let i = 0; i < count; i++) {
|
||
const alt = await images.nth(i).getAttribute('alt');
|
||
if (!alt) imagesWithoutAlt++;
|
||
}
|
||
|
||
// 允许少量装饰性图片没有 alt(使用 alt="" 或 role="presentation")
|
||
expect(imagesWithoutAlt).toBeLessThanOrEqual(2);
|
||
});
|
||
|
||
test('Heading 层级结构合理', async ({ page }) => {
|
||
// 检查页面有 h1 标签
|
||
const h1 = page.locator('h1').first();
|
||
await expect(h1).toBeVisible();
|
||
|
||
// 不应出现从 h1 直接跳到 h3+ 的情况(简化检查)
|
||
const headings = await page.evaluate(() => {
|
||
const levels = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'))
|
||
.map(el => parseInt(el.tagName.substring(1)));
|
||
return levels;
|
||
});
|
||
|
||
if (headings.length > 1) {
|
||
// 基本检查:不应该有太多级别跳跃
|
||
let maxJump = 0;
|
||
for (let i = 1; i < headings.length; i++) {
|
||
const jump = headings[i] - headings[i - 1];
|
||
if (jump > maxJump) maxJump = jump;
|
||
}
|
||
// 允许最多跳跃2级(如 h1 -> h3)
|
||
expect(maxJump).toBeLessThanOrEqual(2);
|
||
}
|
||
});
|
||
|
||
test('颜色对比度基础检查(确保文字可读)', async ({ page }) => {
|
||
// 检查主要文本元素不是白色背景上的浅色文字
|
||
const body = page.locator('body').first();
|
||
const bgColor = await body.evaluate((el) => {
|
||
return window.getComputedStyle(el).backgroundColor;
|
||
});
|
||
|
||
// 背景应该是浅色或深色(不能是透明导致无法判断)
|
||
expect(bgColor).not.toBe('rgba(0, 0, 0, 0)');
|
||
});
|
||
});
|
||
|
||
// ==================== 13. Footer 功能测试 ====================
|
||
test.describe('Footer - 全局底部区域', () => {
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.waitForTimeout(1500);
|
||
|
||
// 滚动到底部
|
||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||
await page.waitForTimeout(500);
|
||
});
|
||
|
||
test('Footer 包含公司名称', async ({ page }) => {
|
||
const footer = page.locator('footer, [data-testid="footer"]').first();
|
||
await expect(footer).toBeVisible();
|
||
|
||
const footerText = await footer.textContent();
|
||
expect(footerText).toContain('睿新致远');
|
||
});
|
||
|
||
test('Footer 包含ICP备案号', async ({ page }) => {
|
||
const footer = page.locator('footer, [data-testid="footer"]').first();
|
||
const footerText = await footer.textContent();
|
||
expect(footerText).toContain('蜀ICP备');
|
||
});
|
||
|
||
test('Footer 导航链接可点击', async ({ page }) => {
|
||
const footer = page.locator('footer, [data-testid="footer"]').first();
|
||
|
||
// 检查隐私政策和服务条款链接
|
||
const privacyLink = footer.locator('a:has-text("隐私政策")');
|
||
if (await privacyLink.count() > 0 && await privacyLink.isVisible()) {
|
||
await privacyLink.click();
|
||
await page.waitForTimeout(1000);
|
||
expect(page.url()).toContain('/privacy');
|
||
}
|
||
|
||
// 返回首页检查服务条款
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||
await page.waitForTimeout(500);
|
||
|
||
const termsLink = footer.locator('a:has-text("服务条款")');
|
||
if (await termsLink.count() > 0 && await termsLink.isVisible()) {
|
||
await termsLink.click();
|
||
await page.waitForTimeout(1000);
|
||
expect(page.url()).toContain('/terms');
|
||
}
|
||
});
|
||
|
||
test('Footer 包含联系方式(邮箱)', async ({ page }) => {
|
||
const footer = page.locator('footer, [data-testid="footer"]').first();
|
||
const footerText = await footer.textContent();
|
||
// 应包含 @ 符号的邮箱地址
|
||
expect(footerText).toMatch(/@.*\.cn/);
|
||
});
|
||
});
|
||
|
||
// ==================== 14. SEO 元数据测试 ====================
|
||
test.describe('SEO - 搜索引擎优化基础', () => {
|
||
|
||
test('首页 Meta Description 存在', async ({ page }) => {
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
|
||
const metaDescription = page.locator('meta[name="description"]').first();
|
||
const content = await metaDescription.getAttribute('content');
|
||
expect(content).toBeTruthy();
|
||
expect(content!.length).toBeGreaterThan(50);
|
||
});
|
||
|
||
test('各页面有唯一标题', async ({ context }) => {
|
||
const urls = ['/', '/about', '/contact', '/products', '/services', '/solutions'];
|
||
const titles: string[] = [];
|
||
|
||
for (const url of urls) {
|
||
const newPage = await context.newPage();
|
||
await newPage.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
const title = await newPage.title();
|
||
titles.push(title);
|
||
await newPage.close();
|
||
}
|
||
|
||
// 所有标题应该唯一(或者至少不完全相同)
|
||
const uniqueTitles = new Set(titles);
|
||
expect(uniqueTitles.size).toBeGreaterThanOrEqual(urls.length / 2);
|
||
});
|
||
|
||
test('Canonical URL 设置', async ({ page }) => {
|
||
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||
|
||
const canonical = page.locator('link[rel="canonical"]').first();
|
||
if (await canonical.count() > 0) {
|
||
const href = await canonical.getAttribute('href');
|
||
expect(href).toBeTruthy();
|
||
expect(href).toContain('novalon.cn');
|
||
}
|
||
});
|
||
});
|