- 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 配置收敛。
198 lines
9.1 KiB
TypeScript
198 lines
9.1 KiB
TypeScript
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);
|
||
});
|
||
});
|