- 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 配置收敛。
206 lines
9.5 KiB
TypeScript
206 lines
9.5 KiB
TypeScript
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: 服务探索者旅程测试完成');
|
||
});
|
||
}); |