Files
novalon-website/e2e/uj-11-home-conversion.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

198 lines
9.1 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-11: 首页转化旅程(设计优化专项)
*
* 覆盖首页 Vibe Design 优化后的关键转化路径,作为封版验收依据:
* UJ-11a: 首屏说服链路 — Hero 产品视觉 + 单一 CTA + 数据条 → 信任层(可验证信号)→ 叙事区 → 转化 CTA
* UJ-11b: 信任验证旅程 — 数据来源标注 + 首批客户共创标签 → 联系转化
* UJ-11c: 移动端首屏转化旅程 — 390px 下 CTA 首屏可见、无横向滚动
*
* 关联设计优化计划:docs/plans/2026-08-18-vibe-design-optimization-plan.md
* 关联 critique:.impeccable/critique/
*/
test.setTimeout(90000);
// ==================== 辅助函数 ====================
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);
}
}
/**
* 断言页面无横向滚动(scrollWidth <= clientWidth + 1px 容差)
*/
async function expectNoHorizontalOverflow(page: Page) {
const overflow = await page.evaluate(() => {
const doc = document.documentElement;
return { scrollWidth: doc.scrollWidth, clientWidth: doc.clientWidth };
});
expect(overflow.scrollWidth).toBeLessThanOrEqual(overflow.clientWidth + 1);
}
// ==================== UJ-11a: 首屏说服链路 ====================
test.describe('UJ-11a: 首屏说服链路(Hero → 信任 → 叙事 → CTA)', () => {
test('@journey @critical 首页首屏产品视觉、单一 CTA、数据条渲染正常', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2500);
await closeCookieBanner(page);
// === L1 Hero:浅色工程网格 Hero 可见(v15 已移除产品 Mockup,改为声明先行 + 单一 CTA)===
const heroSection = page.locator('[data-testid="hero-section"]').first();
await expect(heroSection).toBeVisible({ timeout: 15000 });
// === 单一主 CTA ===
const primaryCta = page.locator('[data-testid="hero-primary-cta"]').first();
await expect(primaryCta).toBeVisible();
await expect(primaryCta).toHaveAttribute('href', '/contact');
// 次级 CTA 降级为文字链接
const secondaryLink = page.locator('[data-testid="hero-secondary-link"]').first();
await expect(secondaryLink).toBeVisible();
// === 主标题可见 ===
const heroTitle = page.locator('h1').first();
await expect(heroTitle).toBeVisible();
// 无横向滚动
await expectNoHorizontalOverflow(page);
});
test('@journey @critical 首页信任层展示可验证信号与来源标注', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2500);
await closeCookieBanner(page);
// 滚动到信任层
const trustSection = page.locator('[data-testid="trust-section"]').first();
await trustSection.scrollIntoViewIfNeeded();
await expect(trustSection).toBeVisible({ timeout: 10000 });
const trustText = await trustSection.textContent();
// 可验证信任信号存在(私有化部署 / 本地化 / 全流程 / 共创)
expect(trustText).toContain('100%');
expect(trustText).toContain('成都');
expect(trustText).toContain('全流程');
expect(trustText).toContain('共创');
// 数据来源标注存在(零编造,可追溯)
expect(trustText).toContain('数据来源');
});
test('@journey @critical 首页叙事区按 01/02/03 章节呈现问题→方法→结果', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2500);
await closeCookieBanner(page);
const narrativeSection = page.locator('[data-testid="narrative-section"]').first();
await narrativeSection.scrollIntoViewIfNeeded();
await expect(narrativeSection).toBeVisible({ timeout: 10000 });
const narrativeText = await narrativeSection.textContent();
// 三幕章节编号存在
expect(narrativeText).toContain('01');
expect(narrativeText).toContain('02');
expect(narrativeText).toContain('03');
// 叙事主题存在
expect(narrativeText).toContain('诊断现状');
expect(narrativeText).toContain('设计路径');
expect(narrativeText).toContain('交付成果');
// 每章保留详情入口
const narrativeLinks = narrativeSection.locator('a');
expect(await narrativeLinks.count()).toBeGreaterThanOrEqual(3);
});
});
// ==================== UJ-11b: 信任验证旅程 ====================
test.describe('UJ-11b: 信任验证旅程(共创标签 → 转化)', () => {
test('@journey @critical 首页首批客户共创标签引导至联系转化', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2500);
await closeCookieBanner(page);
// 验收 E2E 修订:early-access-banner 已在复评 P1 中删除——共创主张曾三处重复
// (横幅 / 案例深色卡 / 洞察卡),收敛为案例区深色共创卡唯一载体,
// 由 home-content-v15.test.tsx「consolidates the co-creation pitch」钉住。
// 这里改断言存活的那一处,保持「共创标签 → /contact 转化」的旅程语义。
const coCreationCta = page.locator('[data-testid="cases-co-creation-cta"]').first();
await coCreationCta.scrollIntoViewIfNeeded();
await expect(coCreationCta).toBeVisible({ timeout: 10000 });
await expect(coCreationCta).toHaveAttribute('href', '/contact');
const cardText = await coCreationCta.locator('xpath=ancestor::section[1]').textContent();
// 文案由 CMS pageCopy 可覆盖,故断言主题词而非整句措辞
expect(cardText).toContain('共创');
});
test('@journey @critical 空案例区呈现共创内容块而非裸空态', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2500);
await closeCookieBanner(page);
const bodyText = await page.locator('body').textContent();
// 无「暂无案例数据」裸空态
expect(bodyText).not.toContain('暂无案例数据');
// 出现「共创进行时」前瞻内容块
expect(bodyText).toContain('共创进行时');
// 共创块包含转化 CTA。
// 验收 E2E 修订:原断言的「成为首批共创客户」从未落地为组件文案——
// 预约动作文案由 CTA_LABELS.consult 单一真源约束为「预约咨询」
// (src/lib/constants/cta-labels.ts + DESIGN.md),原测试把设计意图当成了实现。
const coCreationCta = page.locator('[data-testid="cases-co-creation-cta"]').first();
await expect(coCreationCta).toHaveAttribute('href', '/contact');
await expect(coCreationCta).toContainText('预约咨询');
});
});
// ==================== UJ-11c: 移动端首屏转化旅程 ====================
test.describe('UJ-11c: 移动端首屏转化旅程', () => {
test('@mobile @journey @critical 移动端 CTA 首屏可见且无横向滚动', async ({ page }) => {
// 模拟移动端视口
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2500);
await closeCookieBanner(page);
// 无横向滚动
await expectNoHorizontalOverflow(page);
// CTA 首屏可达(在当前视口内可见)
const primaryCta = page.locator('[data-testid="hero-primary-cta"]').first();
await expect(primaryCta).toBeVisible({ timeout: 15000 });
// 首屏 Hero 区块可见(v15 已移除产品 Mockup)
const heroSection = page.locator('[data-testid="hero-section"]').first();
await expect(heroSection).toBeVisible();
});
test('@mobile @journey @critical 移动端信任层与叙事区单列堆叠无溢出', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(2500);
await closeCookieBanner(page);
// 信任层:dev 服务高负载下区块节点会被替换,直接 scrollIntoViewIfNeeded 会撞上
// 「Element is not attached to the DOM」(chain3 实测 firefox),先等可见再滚动。
const trustSection = page.locator('[data-testid="trust-section"]').first();
await expect(trustSection).toBeVisible({ timeout: 15000 });
await trustSection.scrollIntoViewIfNeeded();
await expect(trustSection).toBeVisible();
const trustText = await trustSection.textContent();
expect(trustText).toContain('数据来源');
// 叙事区
const narrativeSection = page.locator('[data-testid="narrative-section"]').first();
await expect(narrativeSection).toBeVisible({ timeout: 15000 });
await narrativeSection.scrollIntoViewIfNeeded();
await expect(narrativeSection).toBeVisible();
expect(await narrativeSection.textContent()).toContain('01');
// 全程无横向滚动
await expectNoHorizontalOverflow(page);
});
});