2
0
Эх сурвалжийг харах

docs(e2e-test-utils): 创建 Story 3.1 - 开发文件上传工具函数

- 创建完整的 story 文档,包含用户故事、验收标准、任务分解
- 应用 Epic 2 成功经验:DOM 结构假设必须验证
- 更新 Epic 3 状态为 in-progress,Story 3.1 状态为 ready-for-dev

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 долоо хоног өмнө
parent
commit
8cb26d7d05

+ 289 - 0
_bmad-output/implementation-artifacts/3-1-file-upload-tool.md

@@ -0,0 +1,289 @@
+# Story 3.1: 开发文件上传工具函数
+
+Status: ready-for-dev
+
+<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
+
+## Story
+
+作为测试开发者,
+我想要使用 `uploadFileToField()` 函数上传文件,
+以便测试照片上传、文档上传等功能。
+
+## Acceptance Criteria
+
+**Given** Epic 1 的类型定义已存在(`BaseOptions`, `E2ETestError`, `ErrorContext`, `DEFAULT_TIMEOUTS`)
+
+**When** 实现 `src/file-upload.ts` 中的 `uploadFileToField(page, selector, fileName)` 函数
+
+**Then** 验收标准如下:
+
+1. **函数从 fixtures 目录加载测试文件**
+   - 支持默认 fixtures 目录:`web/tests/fixtures/`
+   - 支持通过 `FileUploadOptions.fixturesDir` 自定义 fixtures 目录
+   - 文件名相对于 fixtures 目录解析
+
+2. **使用 Playwright 的 `setInputFiles()` API**
+   - 使用 `page.locator(selector).setInputFiles()` 方法
+   - 支持 `data-testid`, `aria-label`, CSS 选择器等多种选择器
+
+3. **支持相对路径(相对于 fixtures 目录)**
+   - 文件名如 `'sample-id-card.jpg'` 自动在 fixtures 目录中查找
+   - 支持子目录如 `'images/sample-id-card.jpg'`
+
+4. **错误时提供清晰消息**
+   - 使用 `E2ETestError` 和 `ErrorContext`
+   - 错误消息包含:文件路径、选择器、失败原因、修复建议
+   - 区分文件不存在和选择器无效两种错误场景
+
+5. **操作在 5 秒内完成(NFR9)**
+   - 默认超时 5000ms,可通过 `FileUploadOptions.timeout` 自定义
+
+6. **配置对象继承 `BaseOptions`**
+   - `FileUploadOptions extends BaseOptions`
+   - 支持 `timeout` 配置
+
+## Tasks / Subtasks
+
+- [ ] **Task 1: 更新 types.ts 中的 FileUploadOptions 定义** (AC: #6)
+  - [ ] Subtask 1.1: 修改 `FileUploadOptions` 接口,添加 `fixturesDir` 和 `waitForUpload` 选项
+  - [ ] Subtask 1.2: 移除 `@beta` 标记,因为此功能即将实现
+
+- [ ] **Task 2: 实现 `src/file-upload.ts` 核心函数** (AC: #1, #2, #3, #4, #5)
+  - [ ] Subtask 2.1: 实现 `uploadFileToField()` 主函数
+  - [ ] Subtask 2.2: 实现 fixtures 路径解析逻辑
+  - [ ] Subtask 2.3: 实现文件存在性检查
+  - [ ] Subtask 2.4: 实现错误处理(文件不存在、选择器无效、超时)
+  - [ ] Subtask 2.5: 添加 `console.debug()` 日志
+
+- [ ] **Task 3: 更新 `src/index.ts` 导出** (AC: #2)
+  - [ ] Subtask 3.1: 导出 `uploadFileToField` 函数
+  - [ ] Subtask 3.2: 导出 `FileUploadOptions` 类型
+
+- [ ] **Task 4: 添加 JSDoc 注释** (NFR25-NFR40)
+  - [ ] Subtask 4.1: 添加完整的 JSDoc(@param, @throws, @example)
+  - [ ] Subtask 4.2: 内部辅助函数使用 `@internal` 标记
+
+- [ ] **Task 5: 创建 fixtures 测试文件** (AC: #1)
+  - [ ] Subtask 5.1: 在 `packages/e2e-test-utils/tests/fixtures/images/` 创建测试图片占位文件
+  - [ ] Subtask 5.2: 在 `web/tests/fixtures/images/` 创建测试图片占位文件
+
+## Dev Notes
+
+### Epic 3 背景与目标
+
+**Epic 3: 文件上传工具开发与验证**
+
+遵循 Epic 2 的成功模式,开发文件上传工具并在真实 E2E 测试中验证,解决当前测试超时阻塞问题。
+
+**模式:** 工具开发 → 真实 E2E 测试验证 → 问题修复 → 稳定性验证
+
+**Epic 2 关键经验(必须应用):**
+1. 单元测试无法发现真实 DOM 问题,必须添加集成测试
+2. DOM 结构假设必须验证,不能基于理想模型开发
+3. 真实 E2E 测试不可替代
+
+**当前测试超时问题:**
+- 残疾人管理测试在文件上传阶段超时(60秒)
+- 现有的 `uploadPhoto()` 方法使用复杂的 DOM 操作
+- 需要简化文件上传流程
+
+### 技术规范
+
+#### 函数签名
+
+```typescript
+export async function uploadFileToField(
+  page: Page,
+  selector: string,
+  fileName: string,
+  options?: FileUploadOptions
+): Promise<void>
+
+export interface FileUploadOptions extends BaseOptions {
+  /** fixtures 目录路径,默认为 'tests/fixtures' */
+  fixturesDir?: string;
+  /** 是否等待上传完成,默认为 true */
+  waitForUpload?: boolean;
+}
+```
+
+#### 实现要点
+
+1. **Fixtures 路径解析**
+   - 默认 fixtures 目录:`tests/fixtures/`(相对于测试运行目录)
+   - 支持 `options.fixturesDir` 自定义路径
+   - 使用 Node.js `path.join()` 和 `path.resolve()` 解析路径
+
+2. **文件存在性检查**
+   - 使用 `fs.existsSync()` 检查文件是否存在
+   - 如果文件不存在,抛出 `E2ETestError` 包含完整路径和建议
+
+3. **文件上传**
+   - 使用 `page.locator(selector).setInputFiles(filePath)`
+   - 支持多种选择器策略:`data-testid` > `aria-label` > CSS selector
+
+4. **错误处理**
+   - 文件不存在:提供完整文件路径和可能的原因
+   - 选择器无效:提供选择器字符串和页面当前状态
+   - 超时:使用 `options.timeout` 或默认 5000ms
+
+5. **日志输出**
+   - 使用 `console.debug()` 输出调试信息(仅 Vitest 中可见)
+   - 包括:文件路径、选择器、上传状态
+
+### 项目结构说明
+
+**包结构:**
+```
+packages/e2e-test-utils/
+├── src/
+│   ├── index.ts                    # 主导出,需要更新
+│   ├── types.ts                    # 共享类型定义,需要更新 FileUploadOptions
+│   ├── errors.ts                   # 错误类和错误处理
+│   ├── constants.ts                # 常量定义(超时)
+│   ├── radix-select.ts             # Radix UI Select 工具(参考实现)
+│   └── file-upload.ts              # 文件上传工具(需要实现)
+├── tests/
+│   ├── fixtures/
+│   │   └── images/
+│   │       ├── sample-id-card.jpg  # 测试图片占位文件
+│   │       └── sample-disability-card.jpg
+│   └── unit/
+│       └── file-upload.test.ts     # 单元测试(下个 story)
+```
+
+**web/tests/fixtures 结构(需要创建):**
+```
+web/tests/fixtures/
+├── images/
+│   ├── sample-id-card.jpg
+│   └── sample-disability-card.jpg
+```
+
+### 代码模式参考
+
+参考 `radix-select.ts` 的实现模式:
+
+```typescript
+import type { Page } from "@playwright/test";
+import type { FileUploadOptions } from "./types";
+import { throwError } from "./errors";
+import { DEFAULT_TIMEOUTS } from "./constants";
+
+/**
+ * 上传文件到指定输入框
+ *
+ * @description
+ * 从 fixtures 目录加载测试文件并上传到指定的文件输入框。
+ * ...
+ */
+export async function uploadFileToField(
+  page: Page,
+  selector: string,
+  fileName: string,
+  options?: FileUploadOptions
+): Promise<void> {
+  console.debug(`[uploadFileToField] 开始上传: selector="${selector}", fileName="${fileName}"`);
+
+  // 1. 合并默认配置
+  const config = {
+    timeout: options?.timeout ?? DEFAULT_TIMEOUTS.static,
+    fixturesDir: options?.fixturesDir ?? 'tests/fixtures',
+    waitForUpload: options?.waitForUpload ?? true
+  };
+
+  // 2. 解析文件路径
+  const filePath = resolveFixturePath(fileName, config.fixturesDir);
+
+  // 3. 检查文件是否存在
+  if (!fs.existsSync(filePath)) {
+    throwError({
+      operation: 'uploadFileToField',
+      target: `文件 "${fileName}"`,
+      expected: `文件存在于 ${filePath}`,
+      suggestion: '检查文件名是否正确,或确认文件已添加到 fixtures 目录'
+    });
+  }
+
+  // 4. 查找文件输入框并上传
+  // ... 实现
+
+  console.debug(`[uploadFileToField] 上传完成`);
+}
+```
+
+### 测试场景(用于 Story 3.3)
+
+将在 `web/tests/e2e/specs/admin/disability-person-complete.spec.ts` 中验证:
+
+1. **身份证照片上传** - 测试基本文件上传功能
+2. **残疾证照片上传** - 测试不同文件类型
+3. **个人照片上传** - 测试多文件场景
+4. **文件不存在场景** - 测试错误处理
+
+### 参考文档
+
+**架构文档:**
+- `_bmad-output/planning-artifacts/architecture.md` - 包结构、API 设计模式、错误处理策略
+
+**E2E 测试标准:**
+- `docs/standards/e2e-radix-testing.md` - 文件上传测试标准(第 155-198 行)
+
+**Epic 3 完整需求:**
+- `_bmad-output/planning-artifacts/epics.md` - Epic 3 和 Story 3.1 详细需求(第 710-777 行)
+
+**Epic 2 回顾(关键经验):**
+- `_bmad-output/implementation-artifacts/epic-2-retrospective.md` - DOM 结构假设必须验证
+
+**TypeScript + Playwright 陷阱:**
+- `architecture.md` 第 533-657 行 - DOM 结构假设必须验证、选择器策略优先级
+
+### Project Structure Notes
+
+**Monorepo 结构对齐:**
+- 包位于 `packages/e2e-test-utils/`
+- 使用 pnpm workspace 协议
+- 与现有 `@d8d/shared-test-util`(后端集成测试)分离
+
+**文件组织:**
+- 源文件按功能分组:`file-upload.ts` 用于文件上传
+- 共享代码集中在:`types.ts`, `errors.ts`, `constants.ts`
+- 主导出使用 `index.ts`,支持 tree-shaking
+
+**无冲突检测:**
+- 无命名冲突(新文件 `file-upload.ts`)
+- 无类型冲突(扩展现有 `FileUploadOptions`)
+- 无导出冲突(新增导出)
+
+### References
+
+**源文档引用:**
+- [Source: _bmad-output/planning-artifacts/epics.md#Epic-3-Story-3.1]
+- [Source: _bmad-output/planning-artifacts/architecture.md#Package-Structure]
+- [Source: docs/standards/e2e-radix-testing.md#文件上传测试]
+
+**现有代码参考:**
+- [Source: packages/e2e-test-utils/src/radix-select.ts] - 实现模式参考
+- [Source: packages/e2e-test-utils/src/types.ts] - 类型定义
+- [Source: packages/e2e-test-utils/src/errors.ts] - 错误处理
+- [Source: packages/e2e-test-utils/src/constants.ts] - 超时常量
+- [Source: web/tests/e2e/pages/admin/disability-person.page.ts#uploadPhoto] - 现有文件上传方法(第 176-205 行)
+
+## Dev Agent Record
+
+### Agent Model Used
+
+Claude Opus 4 (claude-opus-4-5-20251101)
+
+### Debug Log References
+
+### Completion Notes List
+
+1. Epic 3 状态已更新为 `in-progress`
+2. Story 文件已创建在 `_bmad-output/implementation-artifacts/3-1-file-upload-tool.md`
+
+### File List
+
+- `_bmad-output/implementation-artifacts/3-1-file-upload-tool.md` (新建)
+- `_bmad-output/implementation-artifacts/sprint-status.yaml` (已更新 epic-3 状态)

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

@@ -63,8 +63,8 @@ development_status:
   # Epic 3: 文件上传工具开发与验证
   # 目标: 遵循"先验证再扩展"策略,优先开发文件上传工具,解决当前测试超时阻塞问题
   # 模式: 工具开发 → 真实 E2E 测试验证 → 问题修复 → 稳定性验证
-  epic-3: backlog
-  3-1-file-upload-tool: backlog          # 开发文件上传工具函数
+  epic-3: in-progress
+  3-1-file-upload-tool: ready-for-dev   # 开发文件上传工具函数
   3-2-upload-unit-tests: backlog         # 编写文件上传工具的单元测试
   3-3-upload-e2e-integration: backlog    # 在 web/tests/e2e 中验证文件上传工具
   3-4-collect-feedback-fix: backlog      # 收集反馈并修复问题