|
|
@@ -0,0 +1,531 @@
|
|
|
+# Story 9.1: 照片上传功能完整测试
|
|
|
+
|
|
|
+Status: ready-for-dev
|
|
|
+
|
|
|
+<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
|
|
+
|
|
|
+## Story
|
|
|
+
|
|
|
+作为测试开发者,
|
|
|
+我想要编写照片上传功能的完整测试,
|
|
|
+以便验证照片上传的真实业务逻辑。
|
|
|
+
|
|
|
+## Acceptance Criteria
|
|
|
+
|
|
|
+**Given** 文件上传工具 (Epic 3, Story 3.1) 已完成
|
|
|
+**And** `uploadFileToField()` 函数已可用
|
|
|
+**When** 编写照片上传功能测试
|
|
|
+**Then** 包含以下测试场景:
|
|
|
+
|
|
|
+1. **单张照片上传**
|
|
|
+ - 上传身份证正面
|
|
|
+ - 上传身份证反面
|
|
|
+ - 上传残疾证照片
|
|
|
+ - 验证预览显示正确
|
|
|
+
|
|
|
+2. **多张照片上传**
|
|
|
+ - 同时上传身份证正反面
|
|
|
+ - 同时上传多张照片
|
|
|
+ - 验证所有照片都显示
|
|
|
+
|
|
|
+3. **照片格式支持**
|
|
|
+ - JPG 格式上传
|
|
|
+ - PNG 格式上传
|
|
|
+ - WEBP 格式上传
|
|
|
+
|
|
|
+4. **照片删除**
|
|
|
+ - 删除已上传的照片
|
|
|
+ - 验证删除后预览消失
|
|
|
+
|
|
|
+5. **照片大小限制**
|
|
|
+ - 验证超大文件的处理
|
|
|
+ - 验证不支持的格式
|
|
|
+
|
|
|
+## Tasks / Subtasks
|
|
|
+
|
|
|
+- [ ] **Task 1: 创建测试 fixtures 文件** (AC: #1, #3)
|
|
|
+ - [ ] Subtask 1.1: 在 `web/tests/fixtures/images/` 创建测试图片文件
|
|
|
+ - [ ] Subtask 1.2: 准备不同格式的测试图片 (JPG, PNG, WEBP)
|
|
|
+ - [ ] Subtask 1.3: 准备超大文件用于边界测试
|
|
|
+ - [ ] Subtask 1.4: 准备不支持的格式文件用于验证
|
|
|
+
|
|
|
+- [ ] **Task 2: 创建照片上传测试文件** (AC: #1, #2, #3, #4, #5)
|
|
|
+ - [ ] Subtask 2.1: 创建 `web/tests/e2e/specs/admin/disability-person-photo.spec.ts`
|
|
|
+ - [ ] Subtask 2.2: 编写单张照片上传测试
|
|
|
+ - [ ] Subtask 2.3: 编写多张照片上传测试
|
|
|
+ - [ ] Subtask 2.4: 编写照片格式支持测试
|
|
|
+ - [ ] Subtask 2.5: 编写照片删除测试
|
|
|
+ - [ ] Subtask 2.6: 编写照片大小限制测试
|
|
|
+
|
|
|
+- [ ] **Task 3: 更新 Page Object 使用新工具** (AC: #1, #2)
|
|
|
+ - [ ] Subtask 3.1: 更新 `DisabilityPersonManagementPage.uploadPhoto()` 使用 `uploadFileToField()`
|
|
|
+ - [ ] Subtask 3.2: 移除旧的 `evaluateHandle` + temp files 代码
|
|
|
+
|
|
|
+- [ ] **Task 4: 运行测试并验证通过** (AC: #1, #2, #3, #4, #5)
|
|
|
+ - [ ] Subtask 4.1: 使用 `pnpm test:e2e:chromium disability-person-photo` 运行测试
|
|
|
+ - [ ] Subtask 4.2: 修复发现的问题
|
|
|
+ - [ ] Subtask 4.3: 验证所有测试通过
|
|
|
+
|
|
|
+## Dev Notes
|
|
|
+
|
|
|
+### Epic 9 背景与目标
|
|
|
+
|
|
|
+**Epic 9: 残疾人管理完整 E2E 测试覆盖(含并行隔离)**
|
|
|
+
|
|
|
+为残疾人管理功能编写完整的、真正验证业务功能的 E2E 测试,并确保测试可以与未来的区域管理测试并行运行。
|
|
|
+
|
|
|
+**当前问题:**
|
|
|
+- 现有 `disability-person-complete.spec.ts` 只是组件验证,没有真正测试业务功能
|
|
|
+- 照片上传等业务逻辑缺乏完整的测试覆盖
|
|
|
+- 测试隔离性未验证,可能与区域管理测试冲突
|
|
|
+
|
|
|
+**Epic 9 的 Story 依赖关系:**
|
|
|
+- **Story 9.1(本故事)**:照片上传功能测试(优先级最高,因为 Epic 3 已完成文件上传工具)
|
|
|
+- Story 9.2:银行卡管理功能测试
|
|
|
+- Story 9.3:备注管理功能测试
|
|
|
+- Story 9.4:回访记录管理测试
|
|
|
+- Story 9.5:完整流程测试(CRUD)
|
|
|
+- Story 9.6:测试隔离与并行执行验证
|
|
|
+- Story 9.7:稳定性验证(10次连续运行)
|
|
|
+
|
|
|
+### 业务功能分析
|
|
|
+
|
|
|
+**照片上传功能概述:**
|
|
|
+
|
|
|
+残疾人管理表单包含以下照片类型:
|
|
|
+1. **身份证照片**:正面、反面(必需)
|
|
|
+2. **残疾证照片**:照片(必需)
|
|
|
+3. **个人照片**:可选
|
|
|
+
|
|
|
+**组件架构(从 Story 3.1 分析):**
|
|
|
+
|
|
|
+```
|
|
|
+PhotoUploadField (allin-packages/disability-person-management-ui)
|
|
|
+ └── FileSelector (packages/file-management-ui)
|
|
|
+ └── MinioUploader (packages/file-management-ui)
|
|
|
+ └── <input type="file" data-testid="photo-upload-{index}">
|
|
|
+```
|
|
|
+
|
|
|
+**关键发现(Story 3.1 ):**
|
|
|
+- `MinioUploader` 组件已添加 `testId` prop
|
|
|
+- `PhotoUploadField` 生成唯一的 `data-testid="photo-upload-{index}"`
|
|
|
+- 每个照片项都有独立的隐藏文件输入框
|
|
|
+- 可以直接使用 `uploadFileToField(page, '[data-testid="photo-upload-0"]', 'file.jpg')` 上传
|
|
|
+
|
|
|
+### 技术规范
|
|
|
+
|
|
|
+#### 可用工具函数
|
|
|
+
|
|
|
+从 `@d8d/e2e-test-utils` 导入(Epic 1-3 已完成):
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { uploadFileToField } from '@d8d/e2e-test-utils';
|
|
|
+import { selectRadixOption } from '@d8d/e2e-test-utils';
|
|
|
+import { selectProvinceCity } from '@d8d/e2e-test-utils';
|
|
|
+
|
|
|
+// 文件上传工具 (Epic 3, Story 3.1)
|
|
|
+await uploadFileToField(
|
|
|
+ page,
|
|
|
+ selector, // 例如: '[data-testid="photo-upload-0"]'
|
|
|
+ fileName, // 相对于 fixtures 目录: 'id-card-front.jpg'
|
|
|
+ options? // 可选配置
|
|
|
+);
|
|
|
+
|
|
|
+// Radix UI Select 工具 (Epic 1)
|
|
|
+await selectRadixOption(page, label, value);
|
|
|
+
|
|
|
+// 省市区级联选择工具 (Epic 2/3)
|
|
|
+await selectProvinceCity(page, province, city);
|
|
|
+```
|
|
|
+
|
|
|
+#### 测试文件结构
|
|
|
+
|
|
|
+```
|
|
|
+web/tests/e2e/
|
|
|
+├── fixtures/
|
|
|
+│ └── images/ # 测试图片目录(需创建)
|
|
|
+│ ├── id-card-front.jpg # 身份证正面
|
|
|
+│ ├── id-card-back.jpg # 身份证反面
|
|
|
+│ ├── disability-card.jpg # 残疾证照片
|
|
|
+│ ├── photo.jpg # 个人照片
|
|
|
+│ ├── photo.png # PNG 格式测试
|
|
|
+│ ├── photo.webp # WEBP 格式测试
|
|
|
+│ ├── large-file.jpg # 超大文件 (>5MB)
|
|
|
+│ └── invalid.txt # 不支持的格式
|
|
|
+├── specs/
|
|
|
+│ └── admin/
|
|
|
+│ └── disability-person-photo.spec.ts # 本测试文件(需创建)
|
|
|
+└── pages/
|
|
|
+ └── admin/
|
|
|
+ └── disability-person.page.ts # Page Object(需更新)
|
|
|
+```
|
|
|
+
|
|
|
+#### Fixtures 文件要求
|
|
|
+
|
|
|
+**图片文件创建指南:**
|
|
|
+
|
|
|
+由于 CI 环境中可能没有真实图片,创建占位图片:
|
|
|
+
|
|
|
+```bash
|
|
|
+# 方法1: 使用 ImageMagick(如果可用)
|
|
|
+convert -size 100x100 xc:blue web/tests/fixtures/images/id-card-front.jpg
|
|
|
+
|
|
|
+# 方法2: 使用 Python(快速创建占位图)
|
|
|
+python3 << 'EOF'
|
|
|
+from PIL import Image
|
|
|
+import os
|
|
|
+
|
|
|
+fixtures_dir = 'web/tests/fixtures/images'
|
|
|
+os.makedirs(fixtures_dir, exist_ok=True)
|
|
|
+
|
|
|
+# 创建占位图片
|
|
|
+images = [
|
|
|
+ ('id-card-front.jpg', (200, 300), 'blue'),
|
|
|
+ ('id-card-back.jpg', (200, 300), 'green'),
|
|
|
+ ('disability-card.jpg', (200, 300), 'red'),
|
|
|
+ ('photo.jpg', (150, 200), 'yellow'),
|
|
|
+ ('photo.png', (150, 200), 'cyan'),
|
|
|
+ ('photo.webp', (150, 200), 'magenta'),
|
|
|
+]
|
|
|
+
|
|
|
+for filename, size, color in images:
|
|
|
+ img = Image.new('RGB', size, color)
|
|
|
+ img.save(os.path.join(fixtures_dir, filename))
|
|
|
+
|
|
|
+# 创建超大文件(用于边界测试)
|
|
|
+large_img = Image.new('RGB', (2000, 2000), 'purple')
|
|
|
+large_img.save(os.path.join(fixtures_dir, 'large-file.jpg'), quality=100)
|
|
|
+
|
|
|
+# 创建无效格式文件
|
|
|
+with open(os.path.join(fixtures_dir, 'invalid.txt'), 'w') as f:
|
|
|
+ f.write('This is not an image')
|
|
|
+
|
|
|
+print('✅ Fixtures created')
|
|
|
+EOF
|
|
|
+```
|
|
|
+
|
|
|
+**临时方案:复制现有图片**
|
|
|
+```bash
|
|
|
+# 如果项目中已有图片,可以复制
|
|
|
+mkdir -p web/tests/fixtures/images
|
|
|
+# 复制或创建测试图片...
|
|
|
+```
|
|
|
+
|
|
|
+### Page Object 更新
|
|
|
+
|
|
|
+**当前 `uploadPhoto()` 方法(需替换):**
|
|
|
+
|
|
|
+当前代码(`disability-person.page.ts:179-204`)使用 `evaluateHandle` + 临时文件,不稳定:
|
|
|
+
|
|
|
+```typescript
|
|
|
+// ❌ 旧方法(不稳定)
|
|
|
+async uploadPhoto(photoType: string, fileName: string) {
|
|
|
+ const photoSection = this.page.locator('text=' + photoType).first();
|
|
|
+ const uploadButton = photoSection.locator('xpath=ancestor::div...').first()
|
|
|
+ .getByRole('button', { name: /上传/ }).first();
|
|
|
+
|
|
|
+ const fileInput = await uploadButton.evaluateHandle((el: any) => {
|
|
|
+ const input = el.querySelector('input[type="file"]');
|
|
|
+ return input;
|
|
|
+ });
|
|
|
+
|
|
|
+ const file = {
|
|
|
+ name: fileName,
|
|
|
+ mimeType: 'image/jpeg',
|
|
|
+ buffer: Buffer.from('fake image content') // ❌ 假数据
|
|
|
+ };
|
|
|
+
|
|
|
+ await fileInput.uploadFile(file as any); // ❌ 不稳定
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**新方法(使用 `uploadFileToField`):**
|
|
|
+
|
|
|
+```typescript
|
|
|
+// ✅ 新方法(推荐)
|
|
|
+import { uploadFileToField } from '@d8d/e2e-test-utils';
|
|
|
+
|
|
|
+/**
|
|
|
+ * 上传照片
|
|
|
+ * @param photoIndex 照片索引(0=身份证正面, 1=身份证反面, 2=残疾证照片)
|
|
|
+ * @param fileName fixtures 目录中的文件名
|
|
|
+ */
|
|
|
+async uploadPhoto(photoIndex: number, fileName: string) {
|
|
|
+ const selector = `[data-testid="photo-upload-${photoIndex}"]`;
|
|
|
+ await uploadFileToField(this.page, selector, fileName);
|
|
|
+ console.debug(` ✓ 上传照片 [${photoIndex}]: ${fileName}`);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 测试用例设计
|
|
|
+
|
|
|
+#### 测试文件模板
|
|
|
+
|
|
|
+```typescript
|
|
|
+// web/tests/e2e/specs/admin/disability-person-photo.spec.ts
|
|
|
+import { test, expect } from '@playwright/test';
|
|
|
+import { DisabilityPersonManagementPage } from '../../pages/admin/disability-person.page';
|
|
|
+
|
|
|
+test.describe('残疾人管理 - 照片上传功能', () => {
|
|
|
+ let pageObject: DisabilityPersonManagementPage;
|
|
|
+
|
|
|
+ test.beforeEach(async ({ page }) => {
|
|
|
+ pageObject = new DisabilityPersonManagementPage(page);
|
|
|
+ await pageObject.goto();
|
|
|
+ await pageObject.openCreateDialog();
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该成功上传身份证正面照片', async ({ page }) => {
|
|
|
+ // 使用 uploadFileToField 工具
|
|
|
+ await pageObject.uploadPhoto(0, 'id-card-front.jpg');
|
|
|
+
|
|
|
+ // 验证预览显示
|
|
|
+ const preview = page.locator('[data-testid="photo-upload-0"] img');
|
|
|
+ await expect(preview).toBeVisible();
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该成功上传身份证正反面', async ({ page }) => {
|
|
|
+ // 顺序上传
|
|
|
+ await pageObject.uploadPhoto(0, 'id-card-front.jpg');
|
|
|
+ await pageObject.uploadPhoto(1, 'id-card-back.jpg');
|
|
|
+
|
|
|
+ // 验证两个预览都显示
|
|
|
+ const previews = page.locator('[data-testid^="photo-upload-"] img');
|
|
|
+ await expect(previews).toHaveCount(2);
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该支持 JPG 格式', async ({ page }) => {
|
|
|
+ await pageObject.uploadPhoto(0, 'photo.jpg');
|
|
|
+ const preview = page.locator('[data-testid="photo-upload-0"] img');
|
|
|
+ await expect(preview).toBeVisible();
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该支持 PNG 格式', async ({ page }) => {
|
|
|
+ await pageObject.uploadPhoto(0, 'photo.png');
|
|
|
+ const preview = page.locator('[data-testid="photo-upload-0"] img');
|
|
|
+ await expect(preview).toBeVisible();
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该支持 WEBP 格式', async ({ page }) => {
|
|
|
+ await pageObject.uploadPhoto(0, 'photo.webp');
|
|
|
+ const preview = page.locator('[data-testid="photo-upload-0"] img');
|
|
|
+ await expect(preview).toBeVisible();
|
|
|
+ });
|
|
|
+
|
|
|
+ test('应该能够删除已上传的照片', async ({ page }) => {
|
|
|
+ // 先上传
|
|
|
+ await pageObject.uploadPhoto(0, 'id-card-front.jpg');
|
|
|
+
|
|
|
+ // 点击删除按钮(需要确认选择器)
|
|
|
+ const deleteButton = page.locator('[data-testid="photo-upload-0"]')
|
|
|
+ .getByRole('button', { name: /删除/ });
|
|
|
+ await deleteButton.click();
|
|
|
+
|
|
|
+ // 验证预览消失
|
|
|
+ const preview = page.locator('[data-testid="photo-upload-0"] img');
|
|
|
+ await expect(preview).not.toBeVisible();
|
|
|
+ });
|
|
|
+
|
|
|
+ test('超大文件应该有合理处理', async ({ page }) => {
|
|
|
+ // 上传超大文件
|
|
|
+ await pageObject.uploadPhoto(0, 'large-file.jpg');
|
|
|
+
|
|
|
+ // 验证:要么成功上传,要么显示友好的错误提示
|
|
|
+ const preview = page.locator('[data-testid="photo-upload-0"] img');
|
|
|
+ const hasPreview = await preview.count() > 0;
|
|
|
+
|
|
|
+ if (!hasPreview) {
|
|
|
+ // 验证错误提示
|
|
|
+ const errorToast = page.locator('[data-sonner-toast][data-type="error"]');
|
|
|
+ await expect(errorToast).toBeVisible();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ test('不支持的格式应该显示错误', async ({ page }) => {
|
|
|
+ // 尝试上传 TXT 文件
|
|
|
+ await pageObject.uploadPhoto(0, 'invalid.txt');
|
|
|
+
|
|
|
+ // 验证错误提示
|
|
|
+ const errorToast = page.locator('[data-sonner-toast][data-type="error"]');
|
|
|
+ await expect(errorToast).toBeVisible();
|
|
|
+ const errorMessage = await errorToast.textContent();
|
|
|
+ expect(errorMessage).toContain('格式');
|
|
|
+ });
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 项目结构说明
|
|
|
+
|
|
|
+**测试目录组织:**
|
|
|
+- **fixtures/**: 测试资源(图片、数据文件)
|
|
|
+ - 集中管理,避免散落在代码中
|
|
|
+ - 支持版本控制(小文件)
|
|
|
+- **specs/**: 测试用例文件
|
|
|
+ - 按功能模块组织
|
|
|
+ - `.spec.ts` 后缀(Playwright 约定)
|
|
|
+- **pages/**: Page Object 封装
|
|
|
+ - 页面元素和操作方法
|
|
|
+ - 复用性强
|
|
|
+
|
|
|
+**无冲突检测:**
|
|
|
+- 新增测试文件,不影响现有代码
|
|
|
+- Page Object 更新是增强,非破坏性修改
|
|
|
+- Fixtures 目录新建,无冲突
|
|
|
+
|
|
|
+### 测试隔离策略
|
|
|
+
|
|
|
+**数据隔离(为 Story 9.6 准备):**
|
|
|
+
|
|
|
+```typescript
|
|
|
+test.describe.configure({ mode: 'serial' }); // 串行执行(如果需要)
|
|
|
+
|
|
|
+test.afterEach(async ({ page }) => {
|
|
|
+ // 清理测试数据
|
|
|
+ // TODO: 实现数据清理逻辑
|
|
|
+ // 选项1: 使用测试账号 + 软删除
|
|
|
+ // 选项2: 数据库事务回滚
|
|
|
+ // 选项3: API 删除创建的数据
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+**唯一 ID 生成:**
|
|
|
+```typescript
|
|
|
+const timestamp = Date.now();
|
|
|
+const uniqueId = `test_${timestamp}`;
|
|
|
+```
|
|
|
+
|
|
|
+### 参考文档
|
|
|
+
|
|
|
+**架构文档:**
|
|
|
+- [Source: _bmad-output/planning-artifacts/architecture.md] - 测试策略、错误处理、TypeScript+Playwright 陷阱
|
|
|
+
|
|
|
+**E2E 测试标准:**
|
|
|
+- [Source: docs/standards/e2e-radix-testing.md] - 文件上传测试标准
|
|
|
+
|
|
|
+**Epic 9 完整需求:**
|
|
|
+- [Source: _bmad-output/planning-artifacts/epics.md#Epic-9-Story-9.1] - 业务需求和验收标准
|
|
|
+
|
|
|
+**前置 Epic 完成:**
|
|
|
+- [Source: _bmad-output/implementation-artifacts/3-1-file-upload-tool.md] - 文件上传工具实现
|
|
|
+- [Source: _bmad-output/implementation-artifacts/epic-2-retrospective.md] - DOM 结构假设必须验证
|
|
|
+
|
|
|
+**现有代码参考:**
|
|
|
+- [Source: packages/e2e-test-utils/src/file-upload.ts] - `uploadFileToField()` 实现
|
|
|
+- [Source: packages/file-management-ui/src/components/MinioUploader.tsx] - 组件的 testId 支持
|
|
|
+- [Source: allin-packages/disability-person-management-ui/src/components/PhotoUploadField.tsx] - testId 生成逻辑
|
|
|
+- [Source: web/tests/e2e/pages/admin/disability-person.page.ts] - Page Object(需更新)
|
|
|
+
|
|
|
+### Project Structure Notes
|
|
|
+
|
|
|
+**Monorepo 结构对齐:**
|
|
|
+- 测试位于 `web/tests/e2e/` 目录
|
|
|
+- 使用 pnpm workspace 协议引用 `@d8d/e2e-test-utils`
|
|
|
+- 与现有 Page Object 模式保持一致
|
|
|
+
|
|
|
+**文件组织:**
|
|
|
+- 新建测试 specs 文件:`web/tests/e2e/specs/admin/disability-person-photo.spec.ts`
|
|
|
+- 新建 fixtures 目录:`web/tests/fixtures/images/`
|
|
|
+- 更新现有 Page Object:`web/tests/e2e/pages/admin/disability-person.page.ts`
|
|
|
+
|
|
|
+**检测到的冲突或差异:**
|
|
|
+- 现有 `uploadPhoto()` 方法使用 `evaluateHandle` + 临时文件(不稳定)
|
|
|
+- 本故事将其替换为 `uploadFileToField()` 工具(更稳定)
|
|
|
+- **理由:** Epic 3 已完成文件上传工具,应统一使用标准工具
|
|
|
+
|
|
|
+**遵循的项目标准:**
|
|
|
+- 文件命名:`.spec.ts` 后缀(Playwright 测试)
|
|
|
+- 测试目录:`specs/` 分离,`pages/` Page Object,`fixtures/` 测试资源
|
|
|
+- 使用 `@d8d/e2e-test-utils` 工具函数
|
|
|
+- 遵循 `docs/standards/e2e-radix-testing.md` 标准
|
|
|
+
|
|
|
+### TypeScript + Playwright 陷阱(关键)
|
|
|
+
|
|
|
+基于架构文档的陷阱章节(architecture.md 第 533-657 行):
|
|
|
+
|
|
|
+**陷阱 1: DOM 结构假设必须验证** ⚠️
|
|
|
+- 本故事中:`MinioUploader` 的 DOM 结构已在 Story 3.1 中验证
|
|
|
+- 使用 `data-testid="photo-upload-{index}"` 选择器(最稳定)
|
|
|
+- 不依赖动态查找(如 evaluateHandle)
|
|
|
+
|
|
|
+**陷阱 2: 精确文本匹配**
|
|
|
+- 使用 `page.locator('[data-testid="photo-upload-0"]')` 而非文本匹配
|
|
|
+- 避免使用 `:has-text()` 进行部分匹配
|
|
|
+
|
|
|
+**陷阱 3: 网络空闲等待**
|
|
|
+- 上传后使用合理的等待策略
|
|
|
+- 考虑使用 `waitForLoadState('networkidle')` 或 `waitForSelector()` 等待预览显示
|
|
|
+
|
|
|
+**陷阱 4: 避免使用 page.evaluate()**
|
|
|
+- 旧代码使用 `evaluateHandle` 查找文件输入框(不稳定)
|
|
|
+- 新方法直接使用选择器 + `setInputFiles()`(更稳定)
|
|
|
+
|
|
|
+### References
|
|
|
+
|
|
|
+**源文档引用:**
|
|
|
+- [Source: _bmad-output/planning-artifacts/epics.md#Epic-9-Story-9.1] - 完整业务需求
|
|
|
+- [Source: _bmad-output/planning-artifacts/architecture.md#Testing-Configuration] - 三层测试策略
|
|
|
+- [Source: docs/standards/e2e-radix-testing.md#文件上传测试] - 文件上传测试标准
|
|
|
+
|
|
|
+**前置 Story 参考:**
|
|
|
+- [Source: _bmad-output/implementation-artifacts/3-1-file-upload-tool.md] - 文件上传工具实现
|
|
|
+- [Source: _bmad-output/implementation-artifacts/3-6-upload-stability-test] - 文件上传稳定性验证
|
|
|
+
|
|
|
+**相关组件源码:**
|
|
|
+- [Source: packages/e2e-test-utils/src/index.ts] - 可用工具导出
|
|
|
+- [Source: packages/e2e-test-utils/src/file-upload.ts] - uploadFileToField 实现
|
|
|
+- [Source: packages/e2e-test-utils/src/types.ts] - FileUploadOptions 类型定义
|
|
|
+- [Source: packages/file-management-ui/src/components/MinioUploader.tsx] - 上传组件
|
|
|
+- [Source: allin-packages/disability-person-management-ui/src/components/PhotoUploadField.tsx] - 照片上传字段
|
|
|
+
|
|
|
+**现有测试代码:**
|
|
|
+- [Source: web/tests/e2e/pages/admin/disability-person.page.ts:179-204] - 旧的 uploadPhoto 方法(需替换)
|
|
|
+
|
|
|
+## Dev Agent Record
|
|
|
+
|
|
|
+### Agent Model Used
|
|
|
+
|
|
|
+Claude Opus 4 (claude-opus-4-5-20251101)
|
|
|
+
|
|
|
+### Debug Log References
|
|
|
+
|
|
|
+### Completion Notes List
|
|
|
+
|
|
|
+1. ✅ 加载并分析 Epic 9 Story 9.1 需求(从 epics.md)
|
|
|
+2. ✅ 加载并分析架构文档(architecture.md)
|
|
|
+3. ✅ 加载并分析项目上下文(project-context.md)
|
|
|
+4. ✅ 分析前置 Story 3.1 的实现和组件修改
|
|
|
+5. ✅ 分析现有 Page Object 和测试基础设施
|
|
|
+6. ✅ 创建完整的 Story 9.1 文档,包含:
|
|
|
+ - Story 和验收标准
|
|
|
+ - 详细的任务分解
|
|
|
+ - Epic 9 背景和目标
|
|
|
+ - 业务功能分析
|
|
|
+ - 技术规范(可用工具、文件结构、fixtures 要求)
|
|
|
+ - Page Object 更新指南
|
|
|
+ - 完整的测试用例模板
|
|
|
+ - 项目结构说明
|
|
|
+ - 测试隔离策略
|
|
|
+ - TypeScript + Playwright 陷阱警告
|
|
|
+ - 完整的参考文档列表
|
|
|
+
|
|
|
+### File List
|
|
|
+
|
|
|
+**创建的文件:**
|
|
|
+- `_bmad-output/implementation-artifacts/9-1-photo-upload-tests.md` - 本 story 文档
|
|
|
+
|
|
|
+**需要创建的文件(开发者任务):**
|
|
|
+- `web/tests/fixtures/images/` - 测试图片目录
|
|
|
+ - `id-card-front.jpg`
|
|
|
+ - `id-card-back.jpg`
|
|
|
+ - `disability-card.jpg`
|
|
|
+ - `photo.jpg`
|
|
|
+ - `photo.png`
|
|
|
+ - `photo.webp`
|
|
|
+ - `large-file.jpg`
|
|
|
+ - `invalid.txt`
|
|
|
+
|
|
|
+- `web/tests/e2e/specs/admin/disability-person-photo.spec.ts` - 测试文件
|
|
|
+
|
|
|
+**需要修改的文件(开发者任务):**
|
|
|
+- `web/tests/e2e/pages/admin/disability-person.page.ts` - 更新 uploadPhoto() 方法
|
|
|
+
|