import { test, expect, type Page } from '@playwright/test'; /** * UJ: 用户旅程 E2E 测试 * * 覆盖范围: * UJ-01: 潜在客户从首页到联系表单的完整旅程 * UJ-02: 行业客户浏览解决方案到产品的旅程 * UJ-04: 新闻读者浏览旅程(列表 → 详情 → 返回) * UJ-05: 案例浏览者筛选旅程(列表 → 筛选 → 详情) * UJ-10: 深度搜索者旅程(分类浏览 → 逐篇阅读 → 内容发现) * * 环境依赖(需 admin 认证环境,封版前标记为 ⏳): * UJ-03: 内容管理员完整旅程(登录 → 创建 → 审核 → 发布 → 前台可见) * UJ-06: 多角色管理员权限旅程(超级管理员 → 角色 → 权限 → 用户) * UJ-07: 媒体管理员上传旅程 */ test.setTimeout(90000); // ==================== 辅助函数 ==================== /** * 关闭 Cookie 同意横幅(如果存在) */ async function closeCookieBanner(page: Page) { const acceptAllButton = page.locator('button:has-text("接受所有")').first(); if (await acceptAllButton.count() > 0 && await acceptAllButton.isVisible()) { await acceptAllButton.click(); await page.waitForTimeout(500); } } /** * 从桌面主导航的产品下拉菜单中导航到指定链接 */ async function navigateFromProductDropdown(page: Page, linkText: string) { const desktopNav = page.locator('[data-testid="desktop-navigation"]').first(); const productDropdown = desktopNav.locator('button:has-text("产品")').first(); await expect(productDropdown).toBeVisible({ timeout: 5000 }); await productDropdown.hover(); await page.waitForTimeout(300); const targetLink = desktopNav.locator(`a:has-text("${linkText}")`).first(); await expect(targetLink).toBeVisible({ timeout: 5000 }); // 鼠标悬停在链接上,防止下拉菜单因 mouseleave 关闭 await targetLink.hover(); await page.waitForTimeout(200); await targetLink.click(); } // ==================== UJ-01: 潜在客户从首页到联系表单的完整旅程 ==================== test.describe('UJ-01: 潜在客户从首页到联系表单的完整旅程', () => { test('@journey @regression 访客从首页浏览产品并提交联系表单', async ({ page }) => { // === Step 1: 访问首页,验证 Hero 区域 === await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(2500); await closeCookieBanner(page); // 验证 Hero 区域可见 const heroTitle = page.locator('h1').first(); await expect(heroTitle).toBeVisible({ timeout: 15000 }); const titleText = await heroTitle.textContent(); expect(titleText).toBeTruthy(); expect(titleText!.length).toBeGreaterThan(0); // 验证 Hero 描述段落可见 const heroDescription = page.locator('main p').first(); await expect(heroDescription).toBeVisible(); // === Step 2: 通过导航下拉菜单进入产品页面 === await navigateFromProductDropdown(page, '查看全部产品'); // === Step 3: 验证产品页面加载成功 === await page.waitForURL(/\/products/, { timeout: 10000 }); await expect(page).toHaveURL(/\/products/); await page.waitForTimeout(2000); // 验证产品页面有标题 const productsTitle = page.locator('h1, h2').first(); await expect(productsTitle).toBeVisible(); const productsTitleText = await productsTitle.textContent(); expect(productsTitleText).toContain('产品'); // 验证产品卡片存在 const productLinks = page.locator('a[href*="/products/"]:not([href$="/products"])'); const linkCount = await productLinks.count(); expect(linkCount).toBeGreaterThan(0); // === Step 4: 点击产品进入详情页 === const firstProductLink = productLinks.first(); await firstProductLink.click(); await page.waitForURL(/\/products\/.+/, { timeout: 10000 }); await expect(page).toHaveURL(/\/products\/.+/); await page.waitForTimeout(2000); // === Step 5: 验证产品详情页的四层叙事结构 === // L1 Hero: 产品标题可见 const detailTitle = page.locator('h1').first(); await expect(detailTitle).toBeVisible({ timeout: 10000 }); const detailTitleText = await detailTitle.textContent(); expect(detailTitleText).toBeTruthy(); expect(detailTitleText!.length).toBeGreaterThan(0); // L2 Value: 产品详情页应包含核心功能或产品价值区域 const mainContent = page.locator('main').first(); await expect(mainContent).toBeVisible(); const mainText = await mainContent.textContent(); expect(mainText!.length).toBeGreaterThan(200); // 验证功能/价值相关内容存在(核心功能或产品价值标签) const valueIndicators = ['核心功能', '产品价值', '实施流程']; let hasValueSection = false; for (const indicator of valueIndicators) { if (mainText!.includes(indicator)) { hasValueSection = true; break; } } expect(hasValueSection).toBe(true); // L3 Trust: 验证信任相关内容(客户案例或资质认证) const trustIndicators = ['客户案例', '资质认证', '可衡量的成果']; let hasTrustSection = false; for (const indicator of trustIndicators) { if (mainText!.includes(indicator)) { hasTrustSection = true; break; } } expect(hasTrustSection).toBe(true); // L4 CTA: 验证 CTA 区域存在(包含"预约演示"或"开始使用"链接) const ctaLink = page.locator('a[href="/contact"]').first(); await expect(ctaLink).toBeVisible(); // === Step 6: 导航到联系页面 === await page.goto('/contact', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(2500); // === Step 7: 填写联系表单 === // 验证表单字段可见 const nameInput = page.locator('[data-testid="name-input"]').first(); await expect(nameInput).toBeVisible({ timeout: 5000 }); await page.locator('[data-testid="name-input"]').fill('潜在客户'); await page.locator('[data-testid="phone-input"]').fill('13912345678'); await page.locator('[data-testid="email-input"]').fill('prospect@example.com'); await page.locator('[data-testid="subject-input"]').fill('产品咨询 - ERP 管理系统'); await page.locator('[data-testid="message-input"]').fill('您好,我在浏览了贵司的 ERP 产品后非常感兴趣,希望进一步了解产品功能和实施细节,请安排专人联系。'); // === Step 8: 拦截 API 并提交表单 === let routeHit = false; await page.route('/api/contact', async (route) => { routeHit = true; await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ success: true, message: '发送成功' }), }); }); const submitButton = page.locator('[data-testid="submit-button"]').first(); await submitButton.scrollIntoViewIfNeeded(); await page.waitForTimeout(300); // 隐藏可能覆盖底部元素的固定返回顶部按钮 await page.evaluate(() => { const backToTop = document.querySelector('[aria-label="返回顶部"]') as HTMLElement | null; if (backToTop) backToTop.style.display = 'none'; }); // 使用原生 DOM click() 触发提交 await page.evaluate(() => { const btn = document.querySelector('[data-testid="submit-button"]') as HTMLButtonElement | null; btn?.click(); }); // === Step 9: 验证成功消息 === await page.waitForTimeout(2000); console.log('UJ-01 route hit:', routeHit); // 验证成功提示存在 const bodyText = await page.locator('body').textContent(); const hasSuccessIndicator = bodyText!.includes('消息已发送') || bodyText!.includes('感谢') || bodyText!.includes('发送成功') || bodyText!.includes('提交成功'); expect(hasSuccessIndicator).toBe(true); }); }); // ==================== UJ-02: 行业客户浏览解决方案到产品的旅程 ==================== test.describe('UJ-02: 行业客户浏览解决方案到产品的旅程', () => { test('@journey @regression 客户从解决方案浏览到产品详情', async ({ page }) => { // === Step 1: 访问首页 === await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(2000); await closeCookieBanner(page); // 验证首页加载成功 const heroTitle = page.locator('h1').first(); await expect(heroTitle).toBeVisible({ timeout: 10000 }); // === Step 2: 导航到解决方案页面 === // 通过桌面导航的解决方案下拉菜单 const desktopNav = page.locator('[data-testid="desktop-navigation"]').first(); const solutionsDropdown = desktopNav.locator('button:has-text("解决方案")').first(); await expect(solutionsDropdown).toBeVisible({ timeout: 5000 }); await solutionsDropdown.hover(); await page.waitForTimeout(300); const viewAllSolutions = desktopNav.locator('a:has-text("查看全部解决方案")').first(); await expect(viewAllSolutions).toBeVisible({ timeout: 5000 }); await viewAllSolutions.hover(); await page.waitForTimeout(200); await viewAllSolutions.click(); await page.waitForURL(/\/solutions/, { timeout: 10000 }); await expect(page).toHaveURL(/\/solutions/); await page.waitForTimeout(2000); // 验证解决方案页面加载成功 const solutionsTitle = page.locator('h1, h2').first(); await expect(solutionsTitle).toBeVisible(); // === Step 3: 点击一个解决方案 === const solutionLinks = page.locator('a[href*="/solutions/"]:not([href$="/solutions"])'); const solutionLinkCount = await solutionLinks.count(); expect(solutionLinkCount).toBeGreaterThan(0); const firstSolutionLink = solutionLinks.first(); const solutionHref = await firstSolutionLink.getAttribute('href'); console.log('UJ-02 clicking solution link:', solutionHref); await firstSolutionLink.click(); await page.waitForURL(/\/solutions\/.+/, { timeout: 10000 }); await expect(page).toHaveURL(/\/solutions\/.+/); await page.waitForTimeout(2000); // === Step 4: 验证解决方案详情页 === const solutionDetailTitle = page.locator('h1').first(); await expect(solutionDetailTitle).toBeVisible({ timeout: 10000 }); const solutionDetailText = await solutionDetailTitle.textContent(); expect(solutionDetailText).toBeTruthy(); expect(solutionDetailText!.length).toBeGreaterThan(0); // 验证解决方案详情页包含核心内容区域 const solutionMain = page.locator('main').first(); await expect(solutionMain).toBeVisible(); const solutionMainText = await solutionMain.textContent(); expect(solutionMainText!.length).toBeGreaterThan(200); // 验证包含行业痛点/解决方案/价值主张区域 const solutionSections = ['行业痛点', '解决方案', 'Value Proposition']; let hasSolutionSection = false; for (const section of solutionSections) { if (solutionMainText!.includes(section)) { hasSolutionSection = true; break; } } expect(hasSolutionSection).toBe(true); // === Step 5: 通过"查看产品"链接进入产品页面 === // 解决方案详情页的 Hero 区有"查看产品"链接 const viewProductsLink = page.locator('a[href="/products"]').first(); await expect(viewProductsLink).toBeVisible({ timeout: 5000 }); await viewProductsLink.click(); await page.waitForURL(/\/products/, { timeout: 10000 }); await expect(page).toHaveURL(/\/products/); await page.waitForTimeout(2000); // 验证产品页面加载成功 const productsPageTitle = page.locator('h1, h2').first(); await expect(productsPageTitle).toBeVisible(); // === Step 6: 点击产品进入详情页 === const productDetailLinks = page.locator('a[href*="/products/"]:not([href$="/products"])'); const productDetailLinkCount = await productDetailLinks.count(); expect(productDetailLinkCount).toBeGreaterThan(0); const firstProductDetailLink = productDetailLinks.first(); const productHref = await firstProductDetailLink.getAttribute('href'); console.log('UJ-02 clicking product link:', productHref); await firstProductDetailLink.click(); await page.waitForURL(/\/products\/.+/, { timeout: 10000 }); await expect(page).toHaveURL(/\/products\/.+/); await page.waitForTimeout(2000); // 验证产品详情页 const productDetailTitle = page.locator('h1').first(); await expect(productDetailTitle).toBeVisible({ timeout: 10000 }); const productDetailTitleText = await productDetailTitle.textContent(); expect(productDetailTitleText).toBeTruthy(); expect(productDetailTitleText!.length).toBeGreaterThan(0); // 验证产品详情页包含完整内容 const productDetailMain = page.locator('main').first(); const productDetailText = await productDetailMain.textContent(); expect(productDetailText!.length).toBeGreaterThan(200); // 验证存在 CTA 链接 const contactLink = page.locator('a[href="/contact"]').first(); await expect(contactLink).toBeVisible(); }); }); // ==================== UJ-04: 新闻读者浏览旅程 ==================== test.describe('UJ-04: 新闻读者浏览旅程(列表 → 详情 → 返回)', () => { test('@journey @regression 读者从新闻列表浏览到详情页并返回', async ({ page }) => { // === Step 1: 访问新闻列表 === await page.goto('/news', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(2000); await closeCookieBanner(page); // 验证新闻列表页加载成功 const newsTitle = page.locator('h1').first(); await expect(newsTitle).toBeVisible({ timeout: 10000 }); const newsTitleText = await newsTitle.textContent(); expect(newsTitleText).toBeTruthy(); // 验证新闻列表存在文章条目 const newsListItems = page.locator('a[href*="/news/"]').first(); await expect(newsListItems).toBeVisible({ timeout: 5000 }); // 获取新闻列表中的文章链接数量 const articleLinks = page.locator('a[href*="/news/"]'); const articleCount = await articleLinks.count(); // 可能有些链接是导航/面包屑类,应该有至少1个文章链接 console.log('UJ-04 article links found:', articleCount); if (articleCount > 0) { // === Step 2: 点击第一篇新闻进入详情页 === const firstArticle = articleLinks.first(); await firstArticle.click(); // 等待导航到新闻详情页 await page.waitForURL(/\/news\/.+/, { timeout: 10000 }); await page.waitForTimeout(2000); // === Step 3: 验证新闻详情页 === const detailTitle = page.locator('h1').first(); await expect(detailTitle).toBeVisible({ timeout: 10000 }); const detailTitleText = await detailTitle.textContent(); expect(detailTitleText).toBeTruthy(); expect(detailTitleText!.length).toBeGreaterThan(0); // 验证新闻详情页有发布时间或内容区域 const detailContent = page.locator('main').first(); await expect(detailContent).toBeVisible(); const detailText = await detailContent.textContent(); expect(detailText!.length).toBeGreaterThan(50); // === Step 4: 返回新闻列表 === // 通过面包屑导航返回 const breadcrumbNewsLink = page.locator('nav a[href="/news"], [aria-label*="新闻"] a, a:has-text("新闻")').first(); if (await breadcrumbNewsLink.count() > 0 && await breadcrumbNewsLink.isVisible()) { await breadcrumbNewsLink.click(); await page.waitForURL(/\/news\/?$/, { timeout: 10000 }); await page.waitForTimeout(1000); } else { // 直接导航回新闻列表 await page.goto('/news', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(1000); } // 验证已返回新闻列表 await expect(page).toHaveURL(/\/news\/?$/); } else { // 没有文章链接时,验证页面内容存在 console.log('UJ-04: No article links found, verifying page content'); const bodyText = await page.locator('body').textContent(); expect(bodyText!.length).toBeGreaterThan(0); } }); }); // ==================== UJ-05: 案例浏览者筛选旅程 ==================== test.describe('UJ-05: 案例浏览者筛选旅程(列表 → 筛选 → 详情)', () => { test('@journey @regression 客户从案例列表筛选并查看详情', async ({ page }) => { // === Step 1: 访问案例列表 === await page.goto('/cases', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(2000); await closeCookieBanner(page); // 验证案例列表页加载成功 const casesTitle = page.locator('h1').first(); await expect(casesTitle).toBeVisible({ timeout: 10000 }); const casesTitleText = await casesTitle.textContent(); expect(casesTitleText).toBeTruthy(); console.log('UJ-05 cases page title:', casesTitleText); // === Step 2: 尝试使用行业筛选(如果有筛选功能) === const industryFilters = page.locator( 'button:has-text("全部"), button:has-text("金融"), button:has-text("制造"), ' + 'button:has-text("医疗"), button:has-text("零售"), button:has-text("教育"), ' + 'button:has-text("科技"), [data-testid*="filter"], [data-testid*="industry"]' ); if (await industryFilters.count() > 0) { // 点击第一个可见的筛选按钮(非"全部") const filterButtons = page.locator( 'button:has-text("金融"), button:has-text("制造"), ' + 'button:has-text("医疗"), button:has-text("零售"), ' + 'button:has-text("科技")' ); const filterCount = await filterButtons.count(); if (filterCount > 0) { const firstFilter = filterButtons.first(); const filterText = await firstFilter.textContent(); console.log('UJ-05 clicking filter:', filterText); await firstFilter.click(); await page.waitForTimeout(1500); } } // === Step 3: 点击案例进入详情页 === const caseLinks = page.locator('a[href*="/cases/"]'); const caseLinkCount = await caseLinks.count(); console.log('UJ-05 case links found:', caseLinkCount); if (caseLinkCount > 0) { const firstCase = caseLinks.first(); await firstCase.click(); await page.waitForURL(/\/cases\/.+/, { timeout: 10000 }); await page.waitForTimeout(2000); // === Step 4: 验证案例详情页 === const detailTitle = page.locator('h1').first(); await expect(detailTitle).toBeVisible({ timeout: 10000 }); const detailTitleText = await detailTitle.textContent(); expect(detailTitleText).toBeTruthy(); expect(detailTitleText!.length).toBeGreaterThan(0); // 验证案例详情页包含内容 const detailContent = page.locator('main').first(); await expect(detailContent).toBeVisible(); const detailText = await detailContent.textContent(); expect(detailText!.length).toBeGreaterThan(100); // 验证包含案例相关标识 const hasCaseIndicator = detailText!.includes('案例') || detailText!.includes('成果') || detailText!.includes('挑战') || detailText!.includes('解决方案'); expect(hasCaseIndicator).toBe(true); } else { // 没有案例链接时,验证页面内容存在 console.log('UJ-05: No case links found, verifying page content'); const bodyText = await page.locator('body').textContent(); expect(bodyText!.length).toBeGreaterThan(0); } }); }); // ==================== UJ-10: 深度搜索者旅程 ==================== test.describe('UJ-10: 深度搜索者旅程(分类浏览 → 逐篇阅读 → 内容发现)', () => { test('@journey @regression 用户通过分类筛选浏览多篇文章并发现相关内容', async ({ page }) => { // === Step 1: 访问新闻列表 === await page.goto('/news', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(2000); await closeCookieBanner(page); // 验证新闻列表页加载成功 const newsTitle = page.locator('h1').first(); await expect(newsTitle).toBeVisible({ timeout: 10000 }); const newsTitleText = await newsTitle.textContent(); expect(newsTitleText).toBeTruthy(); console.log('UJ-10 news title:', newsTitleText); // 验证分类筛选按钮存在 const categoryButtons = page.locator('button:has-text("公司新闻"), button:has-text("研发动态")'); const categoryCount = await categoryButtons.count(); console.log('UJ-10 category buttons found:', categoryCount); expect(categoryCount).toBeGreaterThan(0); // === Step 2: 点击"公司新闻"分类筛选 === const companyNewsBtn = page.locator('button:has-text("公司新闻")').first(); if (await companyNewsBtn.isVisible()) { await companyNewsBtn.click(); await page.waitForTimeout(1000); // 验证筛选后文章列表更新 const filteredArticles = page.locator('a[href*="/news/"]'); const filteredCount = await filteredArticles.count(); console.log('UJ-10 company news articles:', filteredCount); } // === Step 3: 阅读第一篇新闻文章 === const firstArticle = page.locator('a[href*="/news/"]').first(); if (await firstArticle.count() > 0 && await firstArticle.isVisible()) { const firstArticleHref = await firstArticle.getAttribute('href'); console.log('UJ-10 reading first article:', firstArticleHref); await firstArticle.click(); // 等待导航到新闻详情页 await page.waitForURL(/\/news\/.+/, { timeout: 10000 }); await page.waitForTimeout(2000); // 验证新闻详情页加载成功 const detailTitle = page.locator('h1').first(); await expect(detailTitle).toBeVisible({ timeout: 10000 }); const detailTitleText = await detailTitle.textContent(); expect(detailTitleText).toBeTruthy(); expect(detailTitleText!.length).toBeGreaterThan(0); console.log('UJ-10 first article title:', detailTitleText); // 验证详情页有正文内容 const detailContent = page.locator('main').first(); await expect(detailContent).toBeVisible(); const detailText = await detailContent.textContent(); expect(detailText!.length).toBeGreaterThan(100); // 验证页面包含发布日期 const dateIndicator = page.locator('time, [datetime], [aria-label*="date"], text=/\\d{4}[-/]\\d{1,2}[-/]\\d{1,2}/').first(); if (await dateIndicator.count() > 0) { await expect(dateIndicator).toBeVisible(); } // 验证内容发现:相关新闻或推荐阅读区域 const relatedSection = page.locator( 'text=/相关新闻|推荐阅读|相关文章|RELATED|more news/i' ).first(); if (await relatedSection.count() > 0) { await expect(relatedSection).toBeVisible(); console.log('UJ-10 related news section found'); } } // === Step 4: 返回新闻列表,切换分类浏览 === await page.goto('/news', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(1500); // 切换到"研发动态"分类 const devNewsBtn = page.locator('button:has-text("研发动态")').first(); if (await devNewsBtn.isVisible()) { await devNewsBtn.click(); await page.waitForTimeout(1000); } // === Step 5: 阅读第二篇新闻文章 === const secondArticle = page.locator('a[href*="/news/"]').first(); if (await secondArticle.count() > 0 && await secondArticle.isVisible()) { const secondArticleHref = await secondArticle.getAttribute('href'); console.log('UJ-10 reading second article:', secondArticleHref); await secondArticle.click(); await page.waitForURL(/\/news\/.+/, { timeout: 10000 }); await page.waitForTimeout(2000); // 验证第二篇新闻详情页 const secondDetailTitle = page.locator('h1').first(); await expect(secondDetailTitle).toBeVisible({ timeout: 10000 }); const secondDetailTitleText = await secondDetailTitle.textContent(); expect(secondDetailTitleText).toBeTruthy(); expect(secondDetailTitleText!.length).toBeGreaterThan(0); console.log('UJ-10 second article title:', secondDetailTitleText); // 验证详情页有正文内容 const secondDetailContent = page.locator('main').first(); await expect(secondDetailContent).toBeVisible(); const secondDetailText = await secondDetailContent.textContent(); expect(secondDetailText!.length).toBeGreaterThan(100); } // === Step 6: 返回全部新闻列表,验证内容发现能力 === await page.goto('/news', { waitUntil: 'domcontentloaded', timeout: 30000 }); await page.waitForTimeout(1500); // 点击"全部"恢复显示所有新闻 const allBtn = page.locator('button:has-text("全部")').first(); if (await allBtn.isVisible()) { await allBtn.click(); await page.waitForTimeout(1000); } // 验证所有新闻文章显示 const allArticles = page.locator('a[href*="/news/"]'); const allCount = await allArticles.count(); console.log('UJ-10 all articles count:', allCount); expect(allCount).toBeGreaterThanOrEqual(1); // 验证搜索功能(内容发现工具) const searchInput = page.locator('input[placeholder*="搜索"]').first(); if (await searchInput.isVisible()) { await searchInput.fill('数字化'); await page.waitForTimeout(1000); const searchResults = page.locator('a[href*="/news/"]'); const searchResultCount = await searchResults.count(); console.log('UJ-10 search results:', searchResultCount); // 搜索功能正常工作,返回结果 expect(searchResultCount).toBeGreaterThanOrEqual(0); } // 最终验证:页面加载正常,内容可发现 const bodyText = await page.locator('body').textContent(); expect(bodyText!.length).toBeGreaterThan(0); console.log('UJ-10 completed successfully'); }); });