import { test, expect, type Page } from '@playwright/test'; /** * 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); // 首批客户共创横幅存在(无虚构客户,如实标注) const earlyAccessBanner = page.locator('[data-testid="early-access-banner"]').first(); await earlyAccessBanner.scrollIntoViewIfNeeded(); await expect(earlyAccessBanner).toBeVisible({ timeout: 10000 }); const bannerText = await earlyAccessBanner.textContent(); expect(bannerText).toContain('首批客户共创中'); // 引导至联系转化 const cta = earlyAccessBanner.locator('a[href="/contact"]').first(); await expect(cta).toBeVisible(); await expect(cta).toHaveAttribute('href', '/contact'); }); 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 expect(bodyText).toContain('成为首批共创客户'); }); }); // ==================== 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); // 信任层 const trustSection = page.locator('[data-testid="trust-section"]').first(); 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 narrativeSection.scrollIntoViewIfNeeded(); await expect(narrativeSection).toBeVisible(); expect(await narrativeSection.textContent()).toContain('01'); // 全程无横向滚动 await expectNoHorizontalOverflow(page); }); });