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'); } }); });