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