Files
novalon-website/e2e/user-journey.spec.ts
T
zhangxiang 3e629bd9ee test(e2e): add GA4 tracking, mobile, user journey tests and update visual baselines
- Add GA4 event tracking E2E tests (4 cases: page view, form submit, button click, product page)
- Add mobile-specific E2E tests (16 cases: navigation, form, product browsing)
- Add user journey tests (UJ-01: homepage to contact, UJ-02: product discovery)
- Fix E2E test selectors and assertions for consistency
- Update visual regression baselines across all browsers (chromium/firefox/webkit)
- Update Playwright config for new test files
2026-07-31 20:24:57 +08:00

304 lines
12 KiB
TypeScript

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