Files
novalon-website/e2e/p2-functional-e2e.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

721 lines
25 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 '@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');
}
});
});