Files
novalon-website/e2e/uj-08-service-journey.spec.ts
T
zhangxiang 6bb7c557ee test(qa): e2e 装配修正 + 集成/门禁测试与工具链重构
- e2e:修死选择器与蜜罐误命中、补断言、GA4/security-headers 用例诚实标注
  skip 边界;新增 assert-helpers/fixtures/hydrated/primary-nav/touch-targets
  等复用桩。
- 集成层:config/test/jest.integration.config.js + tests-integration/ 真库
  一次性 SQLite 用例,teardown 守卫开发库指纹。
- 门禁工具链:jest.setup 归位到根、scripts/accessibility 全站 axe 节点计数、
  check-brand-text-token/check-motion-constraints 机械守卫、stryker/lighthouse
  配置收敛。
2026-09-28 10:48:08 +08:00

206 lines
9.5 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 './fixtures';
/**
* UJ-08: 服务探索者旅程
*
* 覆盖范围:
* 服务列表 → 服务详情 → 联系表单
*/
test.setTimeout(90000);
// 本套件走桌面导航(desktop-navigation 在 md 断点以下 display:none):
// 固定桌面视口,使 chromium-mobile 项目也按用例真实语义验证引擎而非布局。
test.use({ viewport: { width: 1440, height: 900 } });
// ==================== 辅助函数 ====================
/**
* 关闭 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;
}
}
// 服务详情页的信任区域可能为"查看案例"链接而非文本块。
// N-27 修复:原 `isVisible().catch(() => false)` 把定位器/基础设施异常吞成
// 「链接不存在」,回退校验只在异常路径之外才被读到 ⇒ 改为无条件执行路径上的
// 硬断言:链接消失 ⇒ toBeVisible 超时判红;基础设施异常 ⇒ 原样抛出,不再伪装。
if (!hasTrustSection) {
const caseLink = page.locator('a:has-text("查看案例")').first();
await expect(caseLink, '服务详情页信任区域应含文本关键词或"查看案例"链接').toBeVisible({ timeout: 5000 });
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: 服务探索者旅程测试完成');
});
});