Selaa lähdekoodia

feat(e2e-test-utils): 完成 Story 1.2 类型定义和错误处理,通过代码审查

- 实现 types.ts: BaseOptions, ErrorContext, AsyncSelectOptions, FileUploadOptions, FormStepOptions, DialogOptions
- 实现 errors.ts: E2ETestError 类和 throwError 辅助函数
- 实现 constants.ts: DEFAULT_TIMEOUTS, SELECTOR_STRATEGIES 和 SelectorStrategy 类型
- 所有导出都有完整的 JSDoc 注释和示例代码
- 修复 package.json exports 配置,移除冗余 types 条件
- 更新 Story File List 文档,澄清 index.ts 和测试文件的来源
- 类型检查通过,13 个单元测试全部通过

验收标准:
✅ types.ts 导出所有要求的类型
✅ errors.ts 导出 E2ETestError 和 ErrorContext
✅ constants.ts 定义正确的超时值 (static: 2000, async: 5000, networkIdle: 10000)
✅ 零 any 类型,TypeScript 严格模式
✅ 完整 JSDoc 注释
✅ 类型检查通过

Story 状态: review → done

🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 viikko sitten
vanhempi
sitoutus
9a5a40b1fa

+ 54 - 29
_bmad-output/implementation-artifacts/1-2-implement-types-errors.md

@@ -1,6 +1,6 @@
 # Story 1.2: 实现类型定义和错误处理
 
-Status: ready-for-dev
+Status: done
 
 <!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
 
@@ -23,31 +23,31 @@ Status: ready-for-dev
 
 ## Tasks / Subtasks
 
-- [ ] 实现 `src/types.ts` - 共享和特定类型定义 (AC: 1, 2, 4, 5)
-  - [ ] 导入并重新导出 Playwright `Page` 类型
-  - [ ] 定义 `BaseOptions` 接口(timeout 等通用配置)
-  - [ ] 定义 `ErrorContext` 接口(operation, target, expected, actual, available, suggestion)
-  - [ ] 定义 `AsyncSelectOptions` 接口(extends BaseOptions)
-  - [ ] 定义 `FileUploadOptions` 接口(extends BaseOptions)
-  - [ ] 定义 `FormStepOptions` 接口(extends BaseOptions)
-  - [ ] 定义 `DialogOptions` 接口(extends BaseOptions)
-  - [ ] 为所有导出类型添加完整 JSDoc 注释
-- [ ] 实现 `src/errors.ts` - 错误类和错误处理 (AC: 2, 5)
-  - [ ] 实现 `E2ETestError` 类(extends Error)
-  - [ ] 实现 `formatErrorMessage` 私有函数(生成友好错误消息)
-  - [ ] 实现 `throwError` 辅助函数(抛出结构化错误)
-  - [ ] 为 `E2ETestError` 添加完整 JSDoc 注释
-- [ ] 实现 `src/constants.ts` - 超时和选择器策略常量 (AC: 3)
-  - [ ] 定义 `DEFAULT_TIMEOUTS` 常量(static: 2000, async: 5000, networkIdle: 10000)
-  - [ ] 定义 `SELECTOR_STRATEGIES` 常量(testid → ARIA → text)
-  - [ ] 为所有常量添加 JSDoc 注释
-- [ ] 更新 `src/index.ts` 导出新增类型和错误类 (AC: 1, 2, 3)
-  - [ ] 导出 `types.ts` 中的所有类型
-  - [ ] 导出 `errors.ts` 中的 `E2ETestError`
-  - [ ] 导出 `constants.ts` 中的所有常量
-- [ ] 验证类型检查通过 (AC: 6)
-  - [ ] 运行 `pnpm typecheck` 确保无类型错误
-  - [ ] 验证所有类型都有正确的 JSDoc 注释
+- [x] 实现 `src/types.ts` - 共享和特定类型定义 (AC: 1, 2, 4, 5)
+  - [x] 导入并重新导出 Playwright `Page` 类型
+  - [x] 定义 `BaseOptions` 接口(timeout 等通用配置)
+  - [x] 定义 `ErrorContext` 接口(operation, target, expected, actual, available, suggestion)
+  - [x] 定义 `AsyncSelectOptions` 接口(extends BaseOptions)
+  - [x] 定义 `FileUploadOptions` 接口(extends BaseOptions)
+  - [x] 定义 `FormStepOptions` 接口(extends BaseOptions)
+  - [x] 定义 `DialogOptions` 接口(extends BaseOptions)
+  - [x] 为所有导出类型添加完整 JSDoc 注释
+- [x] 实现 `src/errors.ts` - 错误类和错误处理 (AC: 2, 5)
+  - [x] 实现 `E2ETestError` 类(extends Error)
+  - [x] 实现 `formatErrorMessage` 私有函数(生成友好错误消息)
+  - [x] 实现 `throwError` 辅助函数(抛出结构化错误)
+  - [x] 为 `E2ETestError` 添加完整 JSDoc 注释
+- [x] 实现 `src/constants.ts` - 超时和选择器策略常量 (AC: 3)
+  - [x] 定义 `DEFAULT_TIMEOUTS` 常量(static: 2000, async: 5000, networkIdle: 10000)
+  - [x] 定义 `SELECTOR_STRATEGIES` 常量(testid → ARIA → text)
+  - [x] 为所有常量添加 JSDoc 注释
+- [x] 更新 `src/index.ts` 导出新增类型和错误类 (AC: 1, 2, 3)
+  - [x] 导出 `types.ts` 中的所有类型
+  - [x] 导出 `errors.ts` 中的 `E2ETestError`
+  - [x] 导出 `constants.ts` 中的所有常量
+- [x] 验证类型检查通过 (AC: 6)
+  - [x] 运行 `pnpm typecheck` 确保无类型错误
+  - [x] 验证所有类型都有正确的 JSDoc 注释
 
 ## Dev Notes
 
@@ -447,16 +447,41 @@ Claude (d8d-model) via create-story workflow
 - 为后续工具函数提供类型基础设施
 - 包含架构约束、项目标准对齐、前一个故事集成上下文
 
+**实现完成 (2026-01-08):**
+- ✅ 完成 `src/types.ts`: 实现了 `BaseOptions`, `ErrorContext`, `AsyncSelectOptions`, `FileUploadOptions`, `FormStepOptions`, `DialogOptions` 类型
+- ✅ 完成 `src/errors.ts`: 实现了 `E2ETestError` 类和 `throwError` 辅助函数
+- ✅ 完成 `src/constants.ts`: 实现了 `DEFAULT_TIMEOUTS`, `SELECTOR_STRATEGIES` 和 `SelectorStrategy` 类型
+- ✅ 所有导出类型都有完整的 JSDoc 注释和示例代码
+- ✅ 类型检查通过 (`pnpm typecheck`)
+- ✅ 遵循 TypeScript 严格模式,无 `any` 类型
+
 ### File List
 
-**本故事将创建/修改的文件:**
+**本故事创建/修改的文件:**
 - `packages/e2e-test-utils/src/types.ts` - 完整实现共享和特定类型定义
 - `packages/e2e-test-utils/src/errors.ts` - 完整实现错误类和错误处理
 - `packages/e2e-test-utils/src/constants.ts` - 完整实现超时和选择器策略常量
-- `packages/e2e-test-utils/src/index.ts` - 更新主导出,导出新增类型和错误类
+- `packages/e2e-test-utils/package.json` - 修复 exports 配置(移除冗余 types 条件)
+
+**相关文件(已在 Story 1.1 中完成,本故事无需修改):**
+- `packages/e2e-test-utils/src/index.ts` - 主导出(已在 Story 1.1 正确配置)
+- `packages/e2e-test-utils/tests/unit/index.test.ts` - 单元测试(已在 Story 1.1 创建,测试本故事所有新功能)
 
-**相关文件(只读,用于参考):**
+**只读参考文件:**
 - `_bmad-output/implementation-artifacts/1-1-create-package-structure.md` - 前一个故事
 - `_bmad-output/planning-artifacts/epics.md` - Epic 和故事定义
 - `_bmad-output/planning-artifacts/architecture.md` - 架构决策和模式
 - `_bmad-output/project-context.md` - 项目技术栈和规则
+
+## Change Log
+
+### 2026-01-08 - Story 1.2 实现
+- ✅ 实现 `src/types.ts`: `BaseOptions`, `ErrorContext`, `AsyncSelectOptions`, `FileUploadOptions`, `FormStepOptions`, `DialogOptions`
+- ✅ 实现 `src/errors.ts`: `E2ETestError` 类, `throwError` 辅助函数
+- ✅ 实现 `src/constants.ts`: `DEFAULT_TIMEOUTS`, `SELECTOR_STRATEGIES`, `SelectorStrategy` 类型
+- ✅ 所有导出都有完整 JSDoc 注释和示例
+- ✅ 类型检查通过 (`pnpm typecheck`)
+
+### 2026-01-08 - 代码审查修复
+- 🔧 修复 `package.json`: 移除 exports 中冗余的 "types" 条件
+- 📝 更新 File List: 澄清 `src/index.ts` 和 `tests/unit/index.test.ts` 在 Story 1.1 中已完成

+ 1 - 1
_bmad-output/implementation-artifacts/sprint-status.yaml

@@ -43,7 +43,7 @@ development_status:
   # Epic 1: 测试工具包基础框架与 Select 支持
   epic-1: in-progress
   1-1-create-package-structure: done
-  1-2-implement-types-errors: ready-for-dev
+  1-2-implement-types-errors: done
   1-3-static-select-tool: backlog
   1-4-async-select-tool: backlog
   1-5-main-export-docs: backlog

+ 1 - 2
packages/e2e-test-utils/package.json

@@ -8,8 +8,7 @@
   "exports": {
     ".": {
       "import": "./src/index.ts",
-      "require": "./src/index.ts",
-      "types": "./src/index.ts"
+      "require": "./src/index.ts"
     }
   },
   "files": [

+ 43 - 3
packages/e2e-test-utils/src/constants.ts

@@ -1,20 +1,60 @@
 /**
  * 默认超时配置(毫秒)
+ *
+ * @description
+ * 定义各种操作的默认超时时间。所有工具函数都应该使用这些常量而不是硬编码超时值。
+ *
+ * @example
+ * ```ts
+ * const timeout = options.timeout ?? DEFAULT_TIMEOUTS.static;
+ * ```
  */
 export const DEFAULT_TIMEOUTS = {
-  /** 静态选项超时 */
+  /** 静态选项超时(2秒)*/
   static: 2000,
-  /** 异步选项超时 */
+  /** 异步选项超时(5秒)*/
   async: 5000,
-  /** 网络空闲超时 */
+  /** 网络空闲超时(10秒)*/
   networkIdle: 10000
 } as const;
 
 /**
  * 选择器策略常量
+ *
+ * @description
+ * 定义选择器查找的优先级顺序,用于应对 Radix UI 版本升级。
+ *
+ * 优先级顺序:
+ * 1. `data-testid` - 最稳定,推荐用于测试
+ * 2. `aria-label + role` - 无障碍属性,较为稳定
+ * 3. `text content + role` - 文本内容,最不稳定但最通用
+ *
+ * @example
+ * ```ts
+ * // 按优先级尝试选择器
+ * for (const strategy of SELECTOR_STRATEGIES) {
+ *   const element = await findByStrategy(strategy, label);
+ *   if (element) return element;
+ * }
+ * ```
  */
 export const SELECTOR_STRATEGIES = [
   'data-testid',
   'aria-label + role',
   'text content + role'
 ] as const;
+
+/**
+ * 选择器策略类型
+ *
+ * @description
+ * 选择器策略的联合类型,用于类型检查。
+ *
+ * @example
+ * ```ts
+ * function findByStrategy(strategy: SelectorStrategy, label: string) {
+ *   // ...
+ * }
+ * ```
+ */
+export type SelectorStrategy = typeof SELECTOR_STRATEGIES[number];

+ 55 - 3
packages/e2e-test-utils/src/errors.ts

@@ -2,9 +2,27 @@ import type { ErrorContext } from './types';
 
 /**
  * E2E 测试专用错误类
- * 提供结构化的错误上下文信息
+ *
+ * @description
+ * 提供结构化的错误上下文信息,帮助开发者快速定位问题。
+ *
+ * @example
+ * ```ts
+ * throw new E2ETestError({
+ *   operation: 'selectRadixOption',
+ *   target: '残疾类型',
+ *   expected: '视力残疾',
+ *   available: ['听力残疾', '言语残疾', '肢体残疾']
+ * });
+ * ```
  */
 export class E2ETestError extends Error {
+  /**
+   * 创建一个 E2E 测试错误实例
+   *
+   * @param context - 结构化的错误上下文
+   * @param message - 自定义错误消息(可选,默认自动生成)
+   */
   constructor(
     public readonly context: ErrorContext,
     message?: string
@@ -15,10 +33,18 @@ export class E2ETestError extends Error {
 }
 
 /**
- * 格式化错误消息
+ * 格式化错误消息为友好格式
+ *
+ * @description
+ * 内部函数,用于将 ErrorContext 转换为易读的错误消息。
+ *
+ * @internal
+ *
+ * @param context - 错误上下文
+ * @returns 格式化后的错误消息
  */
 function formatErrorMessage(context: ErrorContext): string {
-  const parts = [
+  const parts: string[] = [
     `❌ ${context.operation} failed`,
     `Target: ${context.target}`
   ];
@@ -41,3 +67,29 @@ function formatErrorMessage(context: ErrorContext): string {
 
   return parts.join('\n');
 }
+
+/**
+ * 抛出 E2E 测试错误的辅助函数
+ *
+ * @description
+ * 便捷函数,用于抛出带上下文的 E2E 测试错误。永远抛出错误,返回类型为 `never`。
+ *
+ * @example
+ * ```ts
+ * if (!optionFound) {
+ *   throwError({
+ *     operation: 'selectRadixOption',
+ *     target: label,
+ *     expected: value,
+ *     available: availableOptions,
+ *     suggestion: '检查选项值是否正确'
+ *   });
+ * }
+ * ```
+ *
+ * @param context - 错误上下文
+ * @throws {E2ETestError} 永远抛出错误
+ */
+export function throwError(context: ErrorContext): never {
+  throw new E2ETestError(context);
+}

+ 104 - 3
packages/e2e-test-utils/src/types.ts

@@ -1,7 +1,15 @@
-// 导出类型定义
-
 /**
  * 基础配置选项,所有配置对象的基类
+ *
+ * @description
+ * 提供通用的配置选项,所有工具函数的配置对象都应该继承此接口。
+ *
+ * @example
+ * ```ts
+ * interface MyOptions extends BaseOptions {
+ *   customOption?: string;
+ * }
+ * ```
  */
 export interface BaseOptions {
   /** 超时时间(毫秒)*/
@@ -9,7 +17,22 @@ export interface BaseOptions {
 }
 
 /**
- * 错误上下文接口
+ * 错误上下文接口,用于提供结构化的错误信息
+ *
+ * @description
+ * 包含操作失败时所需的完整上下文信息,帮助开发者快速定位问题。
+ *
+ * @example
+ * ```ts
+ * const errorContext: ErrorContext = {
+ *   operation: 'selectRadixOption',
+ *   target: '残疾类型',
+ *   expected: '视力残疾',
+ *   actual: '未找到选项',
+ *   available: ['听力残疾', '言语残疾'],
+ *   suggestion: '检查选项值是否正确'
+ * };
+ * ```
  */
 export interface ErrorContext {
   /** 操作类型(如 'selectRadixOption')*/
@@ -26,5 +49,83 @@ export interface ErrorContext {
   suggestion?: string;
 }
 
+/**
+ * 异步 Select 选项配置
+ *
+ * @description
+ * 用于异步加载选项的 Radix UI Select 组件。
+ *
+ * @example
+ * ```ts
+ * await selectRadixOptionAsync(page, '省份', '广东省', {
+ *   timeout: 10000,
+ *   waitForOption: true
+ * });
+ * ```
+ */
+export interface AsyncSelectOptions extends BaseOptions {
+  /** 是否等待选项加载完成(默认:true)*/
+  waitForOption?: boolean;
+  /** 等待网络空闲后再操作(默认:false)*/
+  waitForNetworkIdle?: boolean;
+}
+
+/**
+ * 文件上传选项配置
+ *
+ * @description
+ * 用于文件上传工具函数的配置选项。
+ *
+ * @example
+ * ```ts
+ * await uploadFile(page, fileInput, '/path/to/file.jpg', {
+ *   timeout: 5000,
+ *   verifyUpload: true
+ * });
+ * ```
+ */
+export interface FileUploadOptions extends BaseOptions {
+  /** 验证文件是否成功上传(默认:true)*/
+  verifyUpload?: boolean;
+}
+
+/**
+ * 表单步骤配置
+ *
+ * @description
+ * 用于表单填写工具函数的配置选项。
+ *
+ * @example
+ * ```ts
+ * await fillFormField(page, selector, 'value', {
+ *   timeout: 3000,
+ *   scrollToElement: true
+ * });
+ * ```
+ */
+export interface FormStepOptions extends BaseOptions {
+  /** 是否在填写前滚动到元素(默认:true)*/
+  scrollToElement?: boolean;
+}
+
+/**
+ * 对话框操作配置
+ *
+ * @description
+ * 用于对话框工具函数的配置选项。
+ *
+ * @example
+ * ```ts
+ * await closeDialog(page, {
+ *   timeout: 2000,
+ *   closeWaitTime: 500
+ * });
+ * ```
+ */
+export interface DialogOptions extends BaseOptions {
+  /** 对话框关闭后的等待时间(毫秒,默认:500)*/
+  closeWaitTime?: number;
+}
+
 // 重新导出 Playwright 类型以便使用者使用
 export type { Page } from '@playwright/test';