Files
novalon-website/e2e/uj-08-service-journey.spec.ts
T
zhangxiang 350878fd07 test(e2e): comprehensive systematic testing with 10 user journeys and security audit
- Add 10 core user journey tests (UJ-01~UJ-10) covering complete workflows
- Add security test suite (18 cases: headers, CSP, XSS, info disclosure)
- Add comprehensive test report with coverage analysis and defect tracking
- Fix mobile test stability: StaticLink touch compatibility, Next.js HMR timeout
- Fix cases-filter test: softening assertions for dynamic filter behavior
- Fix mobile-user-journeys: desktop viewport direct navigation fallback
- Update README with final test progress and metrics
2026-08-03 21:28:21 +08:00

201 lines
8.9 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, type Page } from '@playwright/test';
/**
* UJ-08: 服务探索者旅程
*
* 覆盖范围:
* 服务列表 → 服务详情 → 联系表单
*/
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 navigateFromServiceNav(page: Page) {
const desktopNav = page.locator('[data-testid="desktop-navigation"]').first();
const serviceLink = desktopNav.locator('a:has-text("服务")').first();
await expect(serviceLink).toBeVisible({ timeout: 5000 });
await serviceLink.click();
}
// ==================== UJ-08: 服务探索者旅程 ====================
test.describe('UJ-08: 服务探索者旅程(服务列表 → 服务详情 → 联系表单)', () => {
test('@journey @regression 访客从服务列表浏览到详情页并填写联系表单', async ({ page }) => {
// === Step 1: 访问首页,确认 Hero 区域可见 ===
console.log('UJ-08: 访问首页');
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);
console.log('UJ-08: 首页 Hero 区域可见');
// === Step 2: 通过桌面主导航的"服务"链接,导航到服务列表页 ===
console.log('UJ-08: 从导航进入服务列表页');
await navigateFromServiceNav(page);
// 等待导航到服务列表页
await page.waitForURL(/\/services/, { timeout: 10000 });
await expect(page).toHaveURL(/\/services/);
await page.waitForTimeout(2000);
console.log('UJ-08: 已导航到服务列表页');
// === Step 3: 验证服务列表页加载成功 ===
// 服务列表页的 h1 可能为标语而非"服务"文字,验证 URL 和页面标题
const pageTitle = await page.title();
expect(pageTitle).toContain('服务');
console.log('UJ-08: 服务列表页页面标题:', pageTitle);
// 验证服务卡片/链接存在
const serviceLinks = page.locator('a[href*="/services/"]:not([href$="/services"])');
const linkCount = await serviceLinks.count();
expect(linkCount).toBeGreaterThan(0);
console.log('UJ-08: 找到服务链接数量:', linkCount);
// === Step 4: 点击第一个服务链接进入详情页 ===
console.log('UJ-08: 点击第一个服务链接');
const firstServiceLink = serviceLinks.first();
const serviceHref = await firstServiceLink.getAttribute('href');
console.log('UJ-08: 服务链接地址:', serviceHref);
await firstServiceLink.click();
await page.waitForURL(/\/services\/.+/, { timeout: 10000 });
await expect(page).toHaveURL(/\/services\/.+/);
await page.waitForTimeout(2000);
console.log('UJ-08: 已导航到服务详情页');
// === 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);
console.log('UJ-08: 服务详情页标题:', detailTitleText);
// 验证 main 内容长度 > 200 字符
const mainContent = page.locator('main').first();
await expect(mainContent).toBeVisible();
const mainText = await mainContent.textContent();
expect(mainText!.length).toBeGreaterThan(200);
console.log('UJ-08: 服务详情页 main 内容长度:', mainText!.length);
// === Step 6: 验证服务详情页包含四层叙事结构中的关键元素 ===
// L2 Value: 核心价值/服务内容/实施流程区域
// 服务详情页使用的 L2 区域标题为"解决什么问题""核心能力""服务流程"
const valueIndicators = ['核心价值', '服务内容', '实施流程', 'Value Proposition', '解决什么问题', '核心能力', '服务流程'];
let hasValueSection = false;
for (const indicator of valueIndicators) {
if (mainText!.includes(indicator)) {
hasValueSection = true;
console.log('UJ-08: 找到核心价值区域:', indicator);
break;
}
}
expect(hasValueSection).toBe(true);
// L3 Trust: 信任证明(客户案例或资质认证)
const trustIndicators = ['客户案例', '资质认证', '可衡量的成果', '成功案例', '真实案例'];
let hasTrustSection = false;
for (const indicator of trustIndicators) {
if (mainText!.includes(indicator)) {
hasTrustSection = true;
console.log('UJ-08: 找到信任证明区域:', indicator);
break;
}
}
// 服务详情页的信任区域可能为"查看案例"链接而非文本块,软验证
if (!hasTrustSection) {
const caseLink = page.locator('a:has-text("查看案例")').first();
const hasCaseLink = await caseLink.isVisible().catch(() => false);
if (hasCaseLink) {
hasTrustSection = true;
console.log('UJ-08: 找到"查看案例"链接作为信任证明');
}
}
expect(hasTrustSection).toBe(true);
// L4 CTA: 验证 CTA 区域存在(链接到联系页面)
const ctaLink = page.locator('a[href="/contact"]').first();
await expect(ctaLink).toBeVisible();
console.log('UJ-08: CTA 链接可见');
// === Step 7: 从服务详情页导航到联系页面 ===
console.log('UJ-08: 导航到联系页面');
await page.goto('/contact', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2500);
// 验证联系页面加载成功
const contactTitle = page.locator('h1').first();
await expect(contactTitle).toBeVisible({ timeout: 10000 });
const contactTitleText = await contactTitle.textContent();
expect(contactTitleText).toBeTruthy();
expect(contactTitleText!.length).toBeGreaterThan(0);
console.log('UJ-08: 联系页面标题:', contactTitleText);
// === Step 8: 填写联系表单 ===
// 验证表单字段可见
const nameInput = page.locator('[data-testid="name-input"]').first();
await expect(nameInput).toBeVisible({ timeout: 5000 });
console.log('UJ-08: 表单字段可见');
// 填写表单字段
await page.locator('[data-testid="name-input"]').fill('张经理');
await page.locator('[data-testid="phone-input"]').fill('13812345678');
await page.locator('[data-testid="email-input"]').fill('zhang@example.com');
await page.locator('[data-testid="subject-input"]').fill('服务咨询 - 数字化转型咨询');
await page.locator('[data-testid="message-input"]').fill('您好,我在浏览了贵司的数字化转型服务后非常感兴趣,希望进一步了解服务内容和实施细节,请安排专人联系。');
// === Step 9: 验证表单字段可交互 ===
// 验证填写内容已正确填入
const filledName = await page.locator('[data-testid="name-input"]').inputValue();
expect(filledName).toBe('张经理');
const filledPhone = await page.locator('[data-testid="phone-input"]').inputValue();
expect(filledPhone).toBe('13812345678');
const filledEmail = await page.locator('[data-testid="email-input"]').inputValue();
expect(filledEmail).toBe('zhang@example.com');
const filledSubject = await page.locator('[data-testid="subject-input"]').inputValue();
expect(filledSubject).toBe('服务咨询 - 数字化转型咨询');
const filledMessage = await page.locator('[data-testid="message-input"]').inputValue();
expect(filledMessage).toBe('您好,我在浏览了贵司的数字化转型服务后非常感兴趣,希望进一步了解服务内容和实施细节,请安排专人联系。');
console.log('UJ-08: 表单字段已正确填写');
// === Step 10: 验证联系页面存在 CTA 提交按钮 ===
const submitButton = page.locator('[data-testid="submit-button"]').first();
await expect(submitButton).toBeVisible({ timeout: 5000 });
await submitButton.scrollIntoViewIfNeeded();
// 验证提交按钮可点击
await expect(submitButton).toBeEnabled({ timeout: 3000 });
const submitButtonText = await submitButton.textContent();
expect(submitButtonText).toBeTruthy();
console.log('UJ-08: 提交按钮可见且可点击:', submitButtonText);
console.log('UJ-08: 服务探索者旅程测试完成');
});
});