import { test, expect, type Page } from '@playwright/test'; /** * UJ: 用户旅程 E2E 测试 * * 覆盖范围: * UJ-01: 潜在客户从首页到联系表单的完整旅程 * UJ-02: 行业客户浏览解决方案到产品的旅程 */ 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(); }); });