user-journeys
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUser Journeys Skill
用户旅程技能
Load with: base.md + playwright-testing.md
For defining and testing real user experiences - not just specs, but actual flows humans take through your application.
加载依赖:base.md + playwright-testing.md
用于定义和测试真实用户体验——不仅仅是规格说明,而是用户在应用中实际经历的流程。
Philosophy
设计理念
Specs test features. Journeys test experiences.
A feature can pass all specs but still deliver a terrible experience. User journeys capture:
- How users actually navigate (not how we think they should)
- Emotional states at each step (frustrated, confused, delighted)
- Recovery from mistakes (users will make them)
- Real-world conditions (slow networks, interruptions, distractions)
规格说明测试功能,旅程测试体验。
一个功能可能通过所有规格测试,但仍可能带来糟糕的体验。用户旅程涵盖:
- 用户实际的导航方式(而非我们设想的方式)
- 每个步骤的用户情绪状态(沮丧、困惑、愉悦)
- 错误后的恢复流程(用户必然会犯错)
- 真实场景条件(网络缓慢、中断、干扰)
Journey Documentation Structure
旅程文档结构
_project_specs/
├── journeys/
│ ├── _template.md # Journey template
│ ├── critical/ # Must-work journeys (revenue, core value)
│ │ ├── signup-to-first-value.md
│ │ ├── checkout-purchase.md
│ │ └── login-to-dashboard.md
│ ├── common/ # Frequent user paths
│ │ ├── browse-and-search.md
│ │ ├── update-profile.md
│ │ └── invite-team-member.md
│ └── edge-cases/ # Error recovery, unusual paths
│ ├── payment-failure-retry.md
│ ├── session-timeout-recovery.md
│ └── offline-reconnection.md_project_specs/
├── journeys/
│ ├── _template.md # 旅程模板
│ ├── critical/ # 核心必测旅程(营收、核心价值相关)
│ │ ├── signup-to-first-value.md
│ │ ├── checkout-purchase.md
│ │ └── login-to-dashboard.md
│ ├── common/ # 高频用户路径
│ │ ├── browse-and-search.md
│ │ ├── update-profile.md
│ │ └── invite-team-member.md
│ └── edge-cases/ # 错误恢复、异常路径
│ ├── payment-failure-retry.md
│ ├── session-timeout-recovery.md
│ └── offline-reconnection.mdJourney Template
旅程模板
markdown
undefinedmarkdown
undefinedJourney: [Name]
旅程:[名称]
Overview
概览
| Attribute | Value |
|---|---|
| Priority | Critical / High / Medium |
| User Type | New / Returning / Admin |
| Frequency | Daily / Weekly / One-time |
| Success Metric | Conversion rate, time to complete, drop-off rate |
| 属性 | 值 |
|---|---|
| 优先级 | 核心 / 高 / 中 |
| 用户类型 | 新用户 / 回头客 / 管理员 |
| 发生频率 | 每日 / 每周 / 一次性 |
| 成功指标 | 转化率、完成时长、流失率 |
User Goal
用户目标
What is the user trying to accomplish? Write from their perspective.
"I want to [goal] so that I can [benefit]."
用户想要达成什么?从用户视角描述。
"我想要[达成目标],这样我就能[获得收益]。"
Preconditions
前置条件
- User state (logged in, has subscription, first visit)
- Data state (has items in cart, has team members)
- Environment (mobile, desktop, slow connection)
- 用户状态(已登录、已订阅、首次访问)
- 数据状态(购物车有商品、已有团队成员)
- 环境(移动端、桌面端、网络缓慢)
Journey Steps
旅程步骤
Step 1: [Entry Point]
步骤1:[入口点]
User Action: What the user does
System Response: What they should see/experience
Success Criteria:
- Page loads in < 2 seconds
- Primary CTA is immediately visible
- User understands what to do next
Potential Friction:
- Slow load time → Show skeleton/loader
- Unclear CTA → A/B test copy variations
用户操作: 用户执行的动作
系统响应: 用户应看到/体验到的内容
成功标准:
- 页面加载时长 < 2秒
- 主要CTA(号召性用语)立即可见
- 用户清楚下一步操作
潜在阻碍:
- 加载缓慢 → 显示骨架屏/加载动画
- CTA不清晰 → A/B测试不同文案
Step 2: [Next Action]
步骤2:[下一步操作]
User Action: ...
System Response: ...
Success Criteria:
- ...
Potential Friction:
- ...
用户操作: ...
系统响应: ...
成功标准:
- ...
潜在阻碍:
- ...
Error Scenarios
错误场景
E1: [Error Name]
E1:[错误名称]
Trigger: What causes this error
User Sees: Error message/state
Recovery Path: How user gets back on track
Test: How to verify recovery works
触发条件: 导致错误的原因
用户看到: 错误提示/状态
恢复路径: 用户如何回到正轨
测试方法: 验证恢复流程的有效性
Metrics to Track
跟踪指标
- Time to complete journey
- Drop-off rate at each step
- Error rate and recovery rate
- User satisfaction (if surveyed)
- 旅程完成时长
- 各步骤流失率
- 错误率与恢复率
- 用户满意度(若有调研)
E2E Test Reference
端到端测试引用
Link to Playwright test:
e2e/tests/journeys/[name].spec.ts
---关联Playwright测试:
e2e/tests/journeys/[name].spec.ts
---Critical Journey Examples
核心旅程示例
Signup to First Value
注册至首次价值体验
markdown
undefinedmarkdown
undefinedJourney: Signup to First Value
旅程:注册至首次价值体验
Overview
概览
| Attribute | Value |
|---|---|
| Priority | Critical |
| User Type | New |
| Frequency | One-time |
| Success Metric | % reaching "aha moment" within 5 min |
| 属性 | 值 |
|---|---|
| 优先级 | 核心 |
| 用户类型 | 新用户 |
| 发生频率 | 一次性 |
| 成功指标 | 5分钟内达到"惊喜时刻"的用户占比 |
User Goal
用户目标
"I want to try this product quickly to see if it solves my problem."
"我想快速试用这款产品,看看它能否解决我的问题。"
Preconditions
前置条件
- First visit to site
- No account
- Came from landing page or ad
- 首次访问网站
- 无账号
- 来自落地页或广告
Journey Steps
旅程步骤
Step 1: Landing Page
步骤1:落地页
User Action: Clicks "Get Started Free" or "Try Now"
System Response: Signup form appears (modal or new page)
Success Criteria:
- CTA visible above fold
- No distracting elements
- Clear value proposition visible
Potential Friction:
- Too many form fields → Reduce to email + password only
- Social login missing → Add Google/GitHub options
用户操作: 点击"免费开始"或"立即试用"
系统响应: 弹出注册表单(模态框或新页面)
成功标准:
- CTA在首屏可见
- 无干扰元素
- 清晰的价值主张可见
潜在阻碍:
- 表单字段过多 → 仅保留邮箱+密码
- 缺少社交登录 → 添加Google/GitHub登录选项
Step 2: Account Creation
步骤2:账号创建
User Action: Enters email and password (or uses social login)
System Response:
- Creates account
- Sends verification email (don't block on it)
- Redirects to onboarding
Success Criteria:
- Account created in < 3 seconds
- No email verification wall (verify later)
- Clear next step shown
Potential Friction:
- Email already exists → Offer login link
- Weak password → Show requirements inline, not after submit
用户操作: 输入邮箱和密码(或使用社交登录)
系统响应:
- 创建账号
- 发送验证邮件(不强制等待验证)
- 重定向至引导页
成功标准:
- 账号创建时长 < 3秒
- 无邮箱验证墙(可后续验证)
- 清晰展示下一步操作
潜在阻碍:
- 邮箱已注册 → 提供登录链接
- 密码强度不足 → 实时显示要求,而非提交后提示
Step 3: Onboarding (Quick Win)
步骤3:引导流程(快速见效)
User Action: Completes 1-2 setup questions
System Response:
- Personalizes experience
- Shows progress indicator
- Leads to first action
Success Criteria:
- Max 3 questions
- Skip option available
- < 60 seconds total
Potential Friction:
- Too many questions → User abandons
- No skip option → User feels trapped
用户操作: 完成1-2个设置问题
系统响应:
- 个性化体验
- 显示进度指示器
- 引导至首次核心操作
成功标准:
- 最多3个问题
- 提供跳过选项
- 总时长 < 60秒
潜在阻碍:
- 问题过多 → 用户放弃
- 无跳过选项 → 用户感到受限
Step 4: First Value (Aha Moment)
步骤4:首次价值体验(惊喜时刻)
User Action: Completes core action (creates first X, sees first result)
System Response:
- Celebrates success
- Shows value delivered
- Suggests next step
Success Criteria:
- User experiences core value
- Completion feels rewarding
- Clear path to continue
用户操作: 完成核心操作(创建首个内容、查看首个结果)
系统响应:
- 庆祝成功
- 展示已交付的价值
- 建议下一步操作
成功标准:
- 用户体验到核心价值
- 完成操作有成就感
- 清晰的后续路径
Error Scenarios
错误场景
E1: Email Already Registered
E1:邮箱已注册
Trigger: User tries existing email
User Sees: "Already have an account? Log in or reset password"
Recovery Path: Click to login or reset
Test:
signup-existing-email.spec.ts触发条件: 用户尝试使用已注册的邮箱
用户看到: "已有账号?登录或重置密码"
恢复路径: 点击登录或重置密码
测试:
signup-existing-email.spec.tsE2: Social Login Fails
E2:社交登录失败
Trigger: OAuth provider error
User Sees: "Couldn't connect. Try email signup or try again."
Recovery Path: Email signup form shown as fallback
Test:
social-login-failure.spec.ts触发条件: OAuth提供商错误
用户看到: "连接失败。请尝试邮箱注册或重试。"
恢复路径: 显示邮箱注册表单作为备选
测试:
social-login-failure.spec.tsMetrics to Track
跟踪指标
- Signup → First Value: Target < 5 min
- Drop-off at each step
- Social vs email signup ratio
- Skip rate on onboarding
---- 注册→首次价值体验:目标时长 < 5分钟
- 各步骤流失率
- 社交登录与邮箱登录占比
- 引导流程跳过率
---Checkout Purchase
结账购买
markdown
undefinedmarkdown
undefinedJourney: Checkout Purchase
旅程:结账购买
Overview
概览
| Attribute | Value |
|---|---|
| Priority | Critical (Revenue) |
| User Type | Any |
| Frequency | Variable |
| Success Metric | Checkout completion rate |
| 属性 | 值 |
|---|---|
| 优先级 | 核心(营收相关) |
| 用户类型 | 所有用户 |
| 发生频率 | 不定 |
| 成功指标 | 结账完成率 |
User Goal
用户目标
"I want to pay quickly and securely without surprises."
"我想快速、安全地完成支付,没有意外费用。"
Journey Steps
旅程步骤
Step 1: Cart Review
步骤1:购物车审核
User Action: Views cart before checkout
System Response:
- Shows all items with images, prices
- Shows subtotal, taxes, shipping
- Clear "Checkout" CTA
Success Criteria:
- No hidden fees revealed later
- Easy to modify quantities
- Saved items visible
用户操作: 结账前查看购物车
系统响应:
- 显示所有商品的图片、价格
- 显示小计、税费、运费
- 清晰的"结账"CTA
成功标准:
- 无后续隐藏费用
- 可轻松修改商品数量
- 已保存的商品可见
Step 2: Checkout Start
步骤2:开始结账
User Action: Clicks "Checkout"
System Response:
- Shows checkout form or redirect to payment
- Progress indicator (Step 1 of 3)
- Order summary sidebar
Success Criteria:
- Guest checkout option
- Express checkout (Apple/Google Pay) prominent
- Form fields pre-filled if logged in
用户操作: 点击"结账"
系统响应:
- 显示结账表单或重定向至支付页面
- 进度指示器(第1步,共3步)
- 侧边栏显示订单摘要
成功标准:
- 支持访客结账
- 快捷支付(Apple/Google Pay)突出显示
- 已登录用户自动填充表单字段
Step 3: Payment
步骤3:支付
User Action: Enters payment info
System Response:
- Secure input fields (Stripe/payment provider)
- Real-time validation
- Clear "Pay $XX" button
Success Criteria:
- Card validation inline, not after submit
- Multiple payment options
- Security indicators visible
用户操作: 输入支付信息
系统响应:
- 安全输入字段(Stripe/支付提供商)
- 实时验证
- 清晰的"支付$XX"按钮
成功标准:
- 卡片信息实时验证,而非提交后
- 多种支付选项
- 安全标识可见
Step 4: Confirmation
步骤4:确认
User Action: Submits payment
System Response:
- Processing indicator
- Success page with order details
- Email confirmation sent
Success Criteria:
- Confirmation within 5 seconds
- Order number clearly visible
- Next steps clear (shipping, access, etc.)
用户操作: 提交支付
系统响应:
- 显示处理中指示器
- 成功页面显示订单详情
- 发送确认邮件
成功标准:
- 确认页面加载时长 < 5秒
- 订单号清晰可见
- 后续步骤明确(发货、访问权限等)
Error Scenarios
错误场景
E1: Payment Declined
E1:支付被拒
Trigger: Card declined by processor
User Sees: "Payment declined. Please try another card."
Recovery Path:
- Stay on payment step
- Pre-fill other fields
- Offer alternative payment methods
Test:
payment-declined-recovery.spec.ts
触发条件: 支付处理器拒绝卡片
用户看到: "支付被拒。请尝试其他卡片。"
恢复路径:
- 停留在支付步骤
- 自动填充其他字段
- 提供备选支付方式
测试:
payment-declined-recovery.spec.ts
E2: Session Timeout During Checkout
E2:结账时会话超时
Trigger: User away too long
User Sees: Cart preserved, re-auth required
Recovery Path:
- Quick login
- Return to same checkout step
- Cart contents intact
Test:
checkout-session-timeout.spec.ts
---触发条件: 用户离开时间过长
用户看到: 购物车保留,需重新验证
恢复路径:
- 快速登录
- 返回至同一结账步骤
- 购物车内容完整
测试:
checkout-session-timeout.spec.ts
---Journey Testing with Playwright
使用Playwright进行旅程测试
Journey Test Structure
旅程测试结构
typescript
// e2e/tests/journeys/signup-to-value.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Journey: Signup to First Value', () => {
test.describe.configure({ mode: 'serial' }); // Run in order
test('Step 1: Landing page has clear CTA', async ({ page }) => {
await page.goto('/');
// CTA visible above fold without scrolling
const cta = page.getByRole('button', { name: /get started|try free/i });
await expect(cta).toBeVisible();
await expect(cta).toBeInViewport();
});
test('Step 2: Can create account quickly', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: /get started/i }).click();
// Minimal fields
await expect(page.getByLabel('Email')).toBeVisible();
await expect(page.getByLabel('Password')).toBeVisible();
// Complete signup
const startTime = Date.now();
await page.getByLabel('Email').fill('newuser@example.com');
await page.getByLabel('Password').fill('SecurePass123!');
await page.getByRole('button', { name: /sign up|create/i }).click();
// Should reach onboarding quickly
await expect(page).toHaveURL(/onboarding|welcome|setup/);
expect(Date.now() - startTime).toBeLessThan(5000); // < 5 seconds
});
test('Step 3: Onboarding is skippable', async ({ page }) => {
// ... login as new user ...
await page.goto('/onboarding');
// Skip option exists
const skipButton = page.getByRole('button', { name: /skip/i });
await expect(skipButton).toBeVisible();
});
test('Step 4: Can reach first value in < 5 min', async ({ page }) => {
// Full journey timing
const journeyStart = Date.now();
// ... complete full journey ...
// Verify first value delivered
await expect(page.getByText(/success|created|done/i)).toBeVisible();
// Total time check
const totalTime = (Date.now() - journeyStart) / 1000 / 60; // minutes
expect(totalTime).toBeLessThan(5);
});
});typescript
// e2e/tests/journeys/signup-to-value.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Journey: Signup to First Value', () => {
test.describe.configure({ mode: 'serial' }); // 按顺序执行
test('Step 1: Landing page has clear CTA', async ({ page }) => {
await page.goto('/');
// CTA在首屏可见,无需滚动
const cta = page.getByRole('button', { name: /get started|try free/i });
await expect(cta).toBeVisible();
await expect(cta).toBeInViewport();
});
test('Step 2: Can create account quickly', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: /get started/i }).click();
// 最少字段
await expect(page.getByLabel('Email')).toBeVisible();
await expect(page.getByLabel('Password')).toBeVisible();
// 完成注册
const startTime = Date.now();
await page.getByLabel('Email').fill('newuser@example.com');
await page.getByLabel('Password').fill('SecurePass123!');
await page.getByRole('button', { name: /sign up|create/i }).click();
// 应快速进入引导页
await expect(page).toHaveURL(/onboarding|welcome|setup/);
expect(Date.now() - startTime).toBeLessThan(5000); // < 5秒
});
test('Step 3: Onboarding is skippable', async ({ page }) => {
// ... 以新用户身份登录 ...
await page.goto('/onboarding');
// 存在跳过选项
const skipButton = page.getByRole('button', { name: /skip/i });
await expect(skipButton).toBeVisible();
});
test('Step 4: Can reach first value in < 5 min', async ({ page }) => {
// 完整旅程计时
const journeyStart = Date.now();
// ... 完成完整旅程 ...
// 验证首次价值已交付
await expect(page.getByText(/success|created|done/i)).toBeVisible();
// 总时长检查
const totalTime = (Date.now() - journeyStart) / 1000 / 60; // 分钟
expect(totalTime).toBeLessThan(5);
});
});Error Recovery Tests
错误恢复测试
typescript
// e2e/tests/journeys/checkout-recovery.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Journey: Checkout Error Recovery', () => {
test('recovers from payment decline gracefully', async ({ page }) => {
// Setup: Add item to cart, go to checkout
await page.goto('/products');
await page.getByTestId('add-to-cart').first().click();
await page.getByRole('link', { name: 'Checkout' }).click();
// Use Stripe test card that declines
const stripeFrame = page.frameLocator('iframe[name*="stripe"]');
await stripeFrame.getByPlaceholder('Card number').fill('4000000000000002');
await stripeFrame.getByPlaceholder('MM / YY').fill('12/30');
await stripeFrame.getByPlaceholder('CVC').fill('123');
await page.getByRole('button', { name: /pay/i }).click();
// Verify friendly error
await expect(page.getByText(/declined|try another/i)).toBeVisible();
// Verify still on checkout (not kicked out)
await expect(page).toHaveURL(/checkout/);
// Verify can try again with different card
await stripeFrame.getByPlaceholder('Card number').fill('4242424242424242');
await page.getByRole('button', { name: /pay/i }).click();
// Should succeed now
await expect(page).toHaveURL(/success|confirmation/);
});
test('preserves cart after session timeout', async ({ page, context }) => {
// Add items to cart
await page.goto('/products');
await page.getByTestId('add-to-cart').first().click();
// Clear session (simulate timeout)
await context.clearCookies();
// Return to site
await page.goto('/cart');
// Cart should be preserved (local storage or recovered)
await expect(page.getByTestId('cart-item')).toHaveCount(1);
});
});typescript
// e2e/tests/journeys/checkout-recovery.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Journey: Checkout Error Recovery', () => {
test('recovers from payment decline gracefully', async ({ page }) => {
// 准备:添加商品至购物车,进入结账页
await page.goto('/products');
await page.getByTestId('add-to-cart').first().click();
await page.getByRole('link', { name: 'Checkout' }).click();
// 使用Stripe测试用的拒付卡片
const stripeFrame = page.frameLocator('iframe[name*="stripe"]');
await stripeFrame.getByPlaceholder('Card number').fill('4000000000000002');
await stripeFrame.getByPlaceholder('MM / YY').fill('12/30');
await stripeFrame.getByPlaceholder('CVC').fill('123');
await page.getByRole('button', { name: /pay/i }).click();
// 验证友好的错误提示
await expect(page.getByText(/declined|try another/i)).toBeVisible();
// 验证仍停留在结账页(未被退出)
await expect(page).toHaveURL(/checkout/);
// 验证可更换卡片重试
await stripeFrame.getByPlaceholder('Card number').fill('4242424242424242');
await page.getByRole('button', { name: /pay/i }).click();
// 此次应支付成功
await expect(page).toHaveURL(/success|confirmation/);
});
test('preserves cart after session timeout', async ({ page, context }) => {
// 添加商品至购物车
await page.goto('/products');
await page.getByTestId('add-to-cart').first().click();
// 清除会话(模拟超时)
await context.clearCookies();
// 返回网站
await page.goto('/cart');
// 购物车应被保留(本地存储或恢复)
await expect(page.getByTestId('cart-item')).toHaveCount(1);
});
});User Experience Validation
用户体验验证
UX Checklist per Journey Step
各旅程步骤UX检查清单
markdown
undefinedmarkdown
undefinedUX Validation Checklist
UX验证检查清单
Clarity
清晰度
- User knows where they are (breadcrumbs, progress)
- User knows what to do next (clear CTA)
- User knows what just happened (feedback)
- 用户清楚当前位置(面包屑、进度条)
- 用户清楚下一步操作(清晰CTA)
- 用户清楚刚发生的操作(反馈)
Speed
速度
- Page loads < 2 seconds
- Actions complete < 3 seconds
- Progress shown for longer operations
- 页面加载时长 < 2秒
- 操作完成时长 < 3秒
- 长时操作显示进度
Forgiveness
容错性
- Mistakes are easy to undo
- Errors explain what went wrong
- Recovery path is clear
- 错误易于撤销
- 错误提示说明问题原因
- 恢复路径清晰
Accessibility
可访问性
- Keyboard navigation works
- Screen reader announces changes
- Focus management correct
- Color contrast sufficient
- 支持键盘导航
- 屏幕阅读器播报变更
- 焦点管理正确
- 颜色对比度达标
Mobile
移动端适配
- Touch targets >= 44px
- No horizontal scroll
- Forms don't zoom unexpectedly
- Works on slow 3G
undefined- 触摸目标 >= 44px
- 无横向滚动
- 表单不会意外缩放
- 在3G慢网络下正常工作
undefinedAutomated UX Checks
自动化UX检查
typescript
// e2e/utils/ux-validators.ts
import { Page, expect } from '@playwright/test';
export async function validatePageLoad(page: Page, maxMs = 2000) {
const timing = await page.evaluate(() => {
const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
return nav.loadEventEnd - nav.startTime;
});
expect(timing).toBeLessThan(maxMs);
}
export async function validateCTAVisible(page: Page, ctaText: RegExp) {
const cta = page.getByRole('button', { name: ctaText });
await expect(cta).toBeVisible();
await expect(cta).toBeInViewport();
}
export async function validateNoLayoutShift(page: Page) {
const cls = await page.evaluate(() => {
return new Promise<number>((resolve) => {
let clsValue = 0;
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!(entry as any).hadRecentInput) {
clsValue += (entry as any).value;
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
setTimeout(() => {
observer.disconnect();
resolve(clsValue);
}, 1000);
});
});
expect(cls).toBeLessThan(0.1); // Good CLS score
}
export async function validateAccessibility(page: Page) {
// Check focus visible on interactive elements
const buttons = page.getByRole('button');
const count = await buttons.count();
for (let i = 0; i < Math.min(count, 5); i++) {
await buttons.nth(i).focus();
await expect(buttons.nth(i)).toBeFocused();
}
}typescript
// e2e/utils/ux-validators.ts
import { Page, expect } from '@playwright/test';
export async function validatePageLoad(page: Page, maxMs = 2000) {
const timing = await page.evaluate(() => {
const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
return nav.loadEventEnd - nav.startTime;
});
expect(timing).toBeLessThan(maxMs);
}
export async function validateCTAVisible(page: Page, ctaText: RegExp) {
const cta = page.getByRole('button', { name: ctaText });
await expect(cta).toBeVisible();
await expect(cta).toBeInViewport();
}
export async function validateNoLayoutShift(page: Page) {
const cls = await page.evaluate(() => {
return new Promise<number>((resolve) => {
let clsValue = 0;
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!(entry as any).hadRecentInput) {
clsValue += (entry as any).value;
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
setTimeout(() => {
observer.disconnect();
resolve(clsValue);
}, 1000);
});
});
expect(cls).toBeLessThan(0.1); // 良好的CLS分数
}
export async function validateAccessibility(page: Page) {
// 检查交互元素的焦点可见性
const buttons = page.getByRole('button');
const count = await buttons.count();
for (let i = 0; i < Math.min(count, 5); i++) {
await buttons.nth(i).focus();
await expect(buttons.nth(i)).toBeFocused();
}
}Journey Metrics Dashboard
旅程指标仪表盘
Track journey health with these metrics:
typescript
// lib/journey-metrics.ts
interface JourneyMetric {
journey: string;
step: string;
timestamp: Date;
duration: number;
success: boolean;
userId?: string;
}
// Track in your analytics (PostHog, Mixpanel, etc.)
export function trackJourneyStep(metric: JourneyMetric) {
analytics.track('journey_step', {
journey_name: metric.journey,
step_name: metric.step,
duration_ms: metric.duration,
success: metric.success,
});
}
// Example usage in app
const journeyStart = Date.now();
// ... user completes step ...
trackJourneyStep({
journey: 'signup_to_value',
step: 'account_creation',
timestamp: new Date(),
duration: Date.now() - journeyStart,
success: true,
});通过以下指标跟踪旅程健康状况:
typescript
// lib/journey-metrics.ts
interface JourneyMetric {
journey: string;
step: string;
timestamp: Date;
duration: number;
success: boolean;
userId?: string;
}
// 在分析工具中跟踪(PostHog、Mixpanel等)
export function trackJourneyStep(metric: JourneyMetric) {
analytics.track('journey_step', {
journey_name: metric.journey,
step_name: metric.step,
duration_ms: metric.duration,
success: metric.success,
});
}
// 应用中的示例用法
const journeyStart = Date.now();
// ... 用户完成步骤 ...
trackJourneyStep({
journey: 'signup_to_value',
step: 'account_creation',
timestamp: new Date(),
duration: Date.now() - journeyStart,
success: true,
});Common Journey Patterns
常见旅程模式
Progressive Disclosure Journey
渐进式披露旅程
User sees simple view first, complexity revealed as needed.
markdown
Step 1: Show basic options only
Step 2: "Advanced" expands more options
Step 3: Expert mode unlocks everything用户先看到简单视图,按需展示复杂内容。
markdown
步骤1:仅显示基础选项
步骤2:"高级"选项展开更多功能
步骤3:专家模式解锁全部功能Guided Setup Journey
引导式设置旅程
Hand-hold new users through initial configuration.
markdown
Step 1: Welcome + single choice
Step 2: Core preference
Step 3: Optional integrations (skippable)
Step 4: First action with guidance
Step 5: Success + remove training wheels全程协助新用户完成初始配置。
markdown
步骤1:欢迎页 + 单项选择
步骤2:核心偏好设置
步骤3:可选集成(可跳过)
步骤4:带引导的首次操作
步骤5:成功提示 + 移除引导Recovery Journey
恢复旅程
User returns after failure or abandonment.
markdown
Step 1: Recognize returning user
Step 2: Restore previous state
Step 3: Acknowledge what happened
Step 4: Offer clear path forward
Step 5: Complete original goal用户在失败或放弃后返回。
markdown
步骤1:识别回头用户
步骤2:恢复之前的状态
步骤3:告知用户之前的情况
步骤4:提供清晰的前进路径
步骤5:完成原目标Anti-Patterns
反模式
- Happy path only - Test error recovery, not just success
- Spec-driven testing - Test user goals, not features
- Ignoring time - Measure how long journeys take
- Desktop-only - Test mobile journeys separately
- Skipping emotions - Consider user frustration points
- No metrics - Track journey completion and drop-off
- Static journeys - Update as user behavior evolves
- 仅测试理想路径 - 测试错误恢复,而非仅成功场景
- 规格驱动测试 - 测试用户目标,而非功能
- 忽略时长 - 测量旅程完成时间
- 仅支持桌面端 - 单独测试移动端旅程
- 忽略情绪 - 考虑用户的沮丧点
- 无指标跟踪 - 跟踪旅程完成率与流失率
- 静态旅程 - 随用户行为变化更新旅程
Quick Reference
快速参考
Journey Priorities
旅程优先级
| Priority | Criteria | Test Frequency |
|---|---|---|
| Critical | Revenue, core value | Every deploy |
| High | Daily user actions | Daily |
| Medium | Weekly features | Weekly |
| Low | Edge cases | On change |
| 优先级 | 判定标准 | 测试频率 |
|---|---|---|
| 核心 | 营收、核心价值相关 | 每次部署 |
| 高 | 用户日常操作 | 每日 |
| 中 | 每周使用的功能 | 每周 |
| 低 | 边缘场景 | 变更时测试 |
Package.json Scripts
Package.json脚本
json
{
"scripts": {
"test:journeys": "playwright test e2e/tests/journeys/",
"test:journeys:critical": "playwright test e2e/tests/journeys/critical/",
"test:journeys:report": "playwright show-report"
}
}json
{
"scripts": {
"test:journeys": "playwright test e2e/tests/journeys/",
"test:journeys:critical": "playwright test e2e/tests/journeys/critical/",
"test:journeys:report": "playwright show-report"
}
}Journey Documentation Checklist
旅程文档检查清单
- User goal clearly stated
- All steps documented
- Success criteria per step
- Error scenarios covered
- Recovery paths defined
- Metrics identified
- E2E test linked
- 用户目标清晰表述
- 所有步骤已记录
- 各步骤定义成功标准
- 覆盖错误场景
- 定义恢复路径
- 确定跟踪指标
- 关联端到端测试