|
|
@@ -0,0 +1,432 @@
|
|
|
+# Story 10.4: 编写创建订单测试
|
|
|
+
|
|
|
+Status: ready-for-dev
|
|
|
+
|
|
|
+<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
|
|
+
|
|
|
+## Story
|
|
|
+
|
|
|
+作为测试开发者,
|
|
|
+我想要编写创建订单的 E2E 测试,
|
|
|
+以便验证订单创建功能。
|
|
|
+
|
|
|
+## Acceptance Criteria
|
|
|
+
|
|
|
+**Given** 订单管理 Page Object 已创建
|
|
|
+**When** 编写创建订单测试用例
|
|
|
+**Then** 包含以下测试场景:
|
|
|
+
|
|
|
+1. **基本创建订单**
|
|
|
+ - 填写必填字段(订单名称、预计开始日期)
|
|
|
+ - 验证订单创建成功
|
|
|
+ - 验证订单出现在列表中
|
|
|
+
|
|
|
+2. **创建订单并选择平台**
|
|
|
+ - 选择平台
|
|
|
+ - 使用 `selectRadixOption` 或 `selectRadixOptionAsync`
|
|
|
+ - 验证平台关联正确
|
|
|
+
|
|
|
+3. **创建订单并选择公司**
|
|
|
+ - 选择公司
|
|
|
+ - 验证公司关联正确
|
|
|
+
|
|
|
+4. **创建订单并选择渠道**
|
|
|
+ - 选择渠道
|
|
|
+ - 验证渠道关联正确
|
|
|
+
|
|
|
+5. **创建完整订单(所有字段)**
|
|
|
+ - 填写所有可选字段
|
|
|
+ - 验证所有数据保存正确
|
|
|
+
|
|
|
+6. **表单验证测试**
|
|
|
+ - 未填写必填字段时提交
|
|
|
+ - 验证错误提示显示正确
|
|
|
+
|
|
|
+**测试文件:** `web/tests/e2e/specs/admin/order-create.spec.ts`
|
|
|
+
|
|
|
+## Tasks / Subtasks
|
|
|
+
|
|
|
+- [ ] 创建订单创建测试文件 (AC: When)
|
|
|
+ - [ ] 创建 `web/tests/e2e/specs/admin/order-create.spec.ts`
|
|
|
+ - [ ] 导入必要的测试依赖(Playwright fixtures、OrderManagementPage)
|
|
|
+ - [ ] 配置测试文件的基本结构
|
|
|
+- [ ] 编写基本创建订单测试 (AC: Then #1)
|
|
|
+ - [ ] 测试只填写必填字段创建订单
|
|
|
+ - [ ] 验证创建成功提示
|
|
|
+ - [ ] 验证订单出现在列表中
|
|
|
+- [ ] 编写选择平台创建订单测试 (AC: Then #2)
|
|
|
+ - [ ] 测试创建订单时选择平台
|
|
|
+ - [ ] 验证平台正确保存
|
|
|
+ - [ ] 使用 `selectRadixOption` 工具
|
|
|
+- [ ] 编写选择公司创建订单测试 (AC: Then #3)
|
|
|
+ - [ ] 测试创建订单时选择公司
|
|
|
+ - [ ] 验证公司正确保存
|
|
|
+- [ ] 编写选择渠道创建订单测试 (AC: Then #4)
|
|
|
+ - [ ] 测试创建订单时选择渠道
|
|
|
+ - [ ] 验证渠道正确保存
|
|
|
+- [ ] 编写创建完整订单测试 (AC: Then #5)
|
|
|
+ - [ ] 测试填写所有字段创建订单
|
|
|
+ - [ ] 验证所有数据正确保存
|
|
|
+- [ ] 编写表单验证测试 (AC: Then #6)
|
|
|
+ - [ ] 测试不填写必填字段提交
|
|
|
+ - [ ] 验证错误提示显示
|
|
|
+ - [ ] 测试取消创建操作
|
|
|
+- [ ] 确保所有测试通过 (AC: And)
|
|
|
+
|
|
|
+## Dev Notes
|
|
|
+
|
|
|
+### Epic Context
|
|
|
+
|
|
|
+**Epic 10: 订单管理 E2E 测试 (Epic C - 业务测试 Epic)**
|
|
|
+
|
|
|
+- **目标**: 测试开发者可以为订单管理功能编写完整的 E2E 测试,验证订单的 CRUD、状态流转、人员关联和附件管理功能
|
|
|
+- **业务分组**: Epic C(业务测试 Epic)
|
|
|
+- **背景**: 订单管理是招聘系统的核心业务功能,涉及复杂表单(多选择器联动)、状态流转、人员关联等场景
|
|
|
+- **模式**: 业务测试为主,工具包支持为辅(遵循 Epic A 成功模式)
|
|
|
+
|
|
|
+**依赖:**
|
|
|
+- Epic 1: ✅ 已完成(Select 工具基础框架)
|
|
|
+- Epic 2: ✅ 已完成(Select 工具在真实 E2E 测试中验证)
|
|
|
+- Story 10.1: ✅ 已完成(订单管理 Page Object)
|
|
|
+- Story 10.2: ✅ 已完成(订单列表查看测试)
|
|
|
+- Story 10.3: ✅ 已完成(订单搜索和筛选测试)
|
|
|
+
|
|
|
+### 前序 Story 情报 (Story 10.1, 10.2, 10.3)
|
|
|
+
|
|
|
+**Page Object 已有的创建订单功能:**
|
|
|
+
|
|
|
+`web/tests/e2e/pages/admin/order-management.page.ts` 包含以下方法:
|
|
|
+
|
|
|
+1. **打开创建对话框:**
|
|
|
+ - `openCreateDialog()` - 打开创建订单对话框
|
|
|
+ - `addOrderButton` - 创建订单按钮选择器(`data-testid="create-order-button"`)
|
|
|
+
|
|
|
+2. **填写表单:**
|
|
|
+ - `fillOrderForm(data: OrderData)` - 填写订单表单
|
|
|
+ - 支持字段:name, expectedStartDate, platformName, companyName, channelName, status, workStatus
|
|
|
+
|
|
|
+3. **提交表单:**
|
|
|
+ - `submitForm(): Promise<FormSubmitResult>` - 提交表单
|
|
|
+ - 返回结果包含:success, hasError, hasSuccess, errorMessage, successMessage, responses
|
|
|
+
|
|
|
+4. **完整流程:**
|
|
|
+ - `createOrder(data: OrderData): Promise<FormSubmitResult>` - 创建订单(完整流程)
|
|
|
+ - `orderExists(orderName: string): Promise<boolean>` - 验证订单是否存在
|
|
|
+
|
|
|
+5. **对话框控制:**
|
|
|
+ - `cancelDialog()` - 取消对话框
|
|
|
+ - `waitForDialogClosed()` - 等待对话框关闭
|
|
|
+
|
|
|
+### 订单数据接口
|
|
|
+
|
|
|
+```typescript
|
|
|
+interface OrderData {
|
|
|
+ name: string; // 订单名称(必填)
|
|
|
+ expectedStartDate?: string; // 预计开始日期(必填)
|
|
|
+ platformName?: string; // 平台名称(可选)
|
|
|
+ companyName?: string; // 公司名称(可选)
|
|
|
+ channelName?: string; // 渠道名称(可选)
|
|
|
+ status?: OrderStatus; // 订单状态(编辑模式)
|
|
|
+ workStatus?: WorkStatus; // 工作状态(编辑模式)
|
|
|
+}
|
|
|
+
|
|
|
+// 订单状态常量
|
|
|
+ORDER_STATUS = {
|
|
|
+ DRAFT: 'draft',
|
|
|
+ CONFIRMED: 'confirmed',
|
|
|
+ IN_PROGRESS: 'in_progress',
|
|
|
+ COMPLETED: 'completed',
|
|
|
+}
|
|
|
+
|
|
|
+// 订单状态显示名称
|
|
|
+ORDER_STATUS_LABELS = {
|
|
|
+ draft: '草稿',
|
|
|
+ confirmed: '已确认',
|
|
|
+ in_progress: '进行中',
|
|
|
+ completed: '已完成',
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 测试文件结构参考
|
|
|
+
|
|
|
+参考 Story 10.2(订单列表查看测试)和 Story 10.3(订单筛选测试)的测试结构:
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { test, expect } from '../../utils/test-setup';
|
|
|
+import { readFileSync } from 'fs';
|
|
|
+import { join, dirname } from 'path';
|
|
|
+import { fileURLToPath } from 'url';
|
|
|
+import { selectRadixOption } from '@d8d/e2e-test-utils';
|
|
|
+
|
|
|
+const __filename = fileURLToPath(import.meta.url);
|
|
|
+const __dirname = dirname(__filename);
|
|
|
+const testUsers = JSON.parse(readFileSync(join(__dirname, '../../fixtures/test-users.json'), 'utf-8'));
|
|
|
+
|
|
|
+test.describe.serial('创建订单测试', () => {
|
|
|
+ test.beforeEach(async ({ adminLoginPage, orderManagementPage }) => {
|
|
|
+ await adminLoginPage.goto();
|
|
|
+ await adminLoginPage.login(testUsers.admin.username, testUsers.admin.password);
|
|
|
+ await adminLoginPage.expectLoginSuccess();
|
|
|
+ await orderManagementPage.goto();
|
|
|
+ });
|
|
|
+
|
|
|
+ test.describe('基本创建订单', () => {
|
|
|
+ test('应该能创建只填写必填字段的订单', async ({ orderManagementPage, page }) => {
|
|
|
+ // 生成唯一订单名称
|
|
|
+ const orderName = `测试订单_${Date.now()}`;
|
|
|
+
|
|
|
+ // 创建订单
|
|
|
+ const result = await orderManagementPage.createOrder({
|
|
|
+ name: orderName,
|
|
|
+ expectedStartDate: '2025-01-15',
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证创建成功
|
|
|
+ expect(result.success).toBe(true);
|
|
|
+ expect(result.hasSuccess).toBe(true);
|
|
|
+
|
|
|
+ // 验证订单出现在列表中
|
|
|
+ await expect(async () => {
|
|
|
+ const exists = await orderManagementPage.orderExists(orderName);
|
|
|
+ expect(exists).toBe(true);
|
|
|
+ }).toPass({ timeout: 5000 });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更多测试...
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 创建订单对话框元素(预期)
|
|
|
+
|
|
|
+**创建订单对话框结构(基于 Page Object 分析):**
|
|
|
+- 打开方式:点击"创建订单"按钮(`data-testid="create-order-button"`)
|
|
|
+- 对话框:`role="dialog"`
|
|
|
+- 订单名称输入框:`label` 包含 "订单名称" 或 "名称"
|
|
|
+- 预计开始日期输入框:`label` 包含 "预计开始日期" 或 "开始日期"
|
|
|
+- 平台选择器:Radix UI Select,使用 `selectRadixOption` 工具
|
|
|
+- 公司选择器:Radix UI Select,使用 `selectRadixOption` 工具
|
|
|
+- 渠道选择器:Radix UI Select,使用 `selectRadixOption` 工具
|
|
|
+- 创建按钮:`role="button"`, name="创建"
|
|
|
+- 取消按钮:`role="button"`, name="取消"
|
|
|
+
|
|
|
+### 技术要求
|
|
|
+
|
|
|
+**导入依赖:**
|
|
|
+```typescript
|
|
|
+import { test, expect } from '../../utils/test-setup';
|
|
|
+import { OrderManagementPage, ORDER_STATUS, WORK_STATUS } from '../../pages/admin/order-management.page';
|
|
|
+import { selectRadixOption } from '@d8d/e2e-test-utils';
|
|
|
+```
|
|
|
+
|
|
|
+**测试 Fixtures:**
|
|
|
+- 使用 `adminLoginPage` fixture 进行登录
|
|
|
+- 使用 `orderManagementPage` fixture 操作页面
|
|
|
+- 使用 `page` fixture 直接操作页面元素
|
|
|
+- 使用 `test.describe.serial()` 确保测试按顺序执行
|
|
|
+
|
|
|
+**断言策略:**
|
|
|
+- 验证 `FormSubmitResult.success` 为 `true`
|
|
|
+- 验证 `FormSubmitResult.hasSuccess` 为 `true`
|
|
|
+- 验证订单出现在列表中(使用 `orderExists()` 方法)
|
|
|
+- 验证 Toast 消息显示成功提示
|
|
|
+
|
|
|
+**数据唯一性:**
|
|
|
+- 使用 `Date.now()` 生成唯一订单名称,避免测试数据冲突
|
|
|
+- 示例:`const orderName = '测试订单_' + Date.now();`
|
|
|
+
|
|
|
+### 工具使用
|
|
|
+
|
|
|
+**Select 工具使用(来自 Epic 1, 2):**
|
|
|
+```typescript
|
|
|
+import { selectRadixOption } from '@d8d/e2e-test-utils';
|
|
|
+
|
|
|
+// 静态选择器(平台、公司、渠道)
|
|
|
+await selectRadixOption(page, '平台', '58同城');
|
|
|
+await selectRadixOption(page, '公司', '测试公司');
|
|
|
+await selectRadixOption(page, '渠道', '网络招聘');
|
|
|
+```
|
|
|
+
|
|
|
+**Page Object 使用:**
|
|
|
+```typescript
|
|
|
+// 方式1:使用完整流程方法
|
|
|
+const result = await orderManagementPage.createOrder({
|
|
|
+ name: '测试订单',
|
|
|
+ expectedStartDate: '2025-01-15',
|
|
|
+ platformName: '58同城',
|
|
|
+});
|
|
|
+
|
|
|
+// 方式2:分步操作(适合需要验证中间状态)
|
|
|
+await orderManagementPage.openCreateDialog();
|
|
|
+await orderManagementPage.fillOrderForm({ name: '测试订单' });
|
|
|
+const result = await orderManagementPage.submitForm();
|
|
|
+await orderManagementPage.waitForDialogClosed();
|
|
|
+```
|
|
|
+
|
|
|
+### 测试标准参考
|
|
|
+
|
|
|
+遵循以下测试标准文档:
|
|
|
+- `docs/standards/testing-standards.md` - 测试规范
|
|
|
+- `docs/standards/web-ui-testing-standards.md` - Web UI 测试规范
|
|
|
+- `docs/standards/e2e-radix-testing.md` - Radix UI E2E 测试标准
|
|
|
+
|
|
|
+**选择器优先级:**
|
|
|
+1. `data-testid` - 最高优先级
|
|
|
+2. `aria-label + role`
|
|
|
+3. `text content + role` - 兜底
|
|
|
+
|
|
|
+### Project Structure Notes
|
|
|
+
|
|
|
+**文件位置:**
|
|
|
+- 测试文件: `web/tests/e2e/specs/admin/order-create.spec.ts`
|
|
|
+- Page Object: `web/tests/e2e/pages/admin/order-management.page.ts`
|
|
|
+- Fixtures 目录: `web/tests/e2e/fixtures/`
|
|
|
+
|
|
|
+**对齐统一项目结构:**
|
|
|
+- 遵循 `project-context.md` 中的 TypeScript 严格模式规则
|
|
|
+- 函数参数、返回值必须有明确类型注解
|
|
|
+- 禁止使用 `any` 类型
|
|
|
+- 公共 API 必须包含完整 JSDoc 注释
|
|
|
+
|
|
|
+### 测试运行命令
|
|
|
+
|
|
|
+```bash
|
|
|
+# 在 web 目录下运行单个测试文件
|
|
|
+cd web
|
|
|
+pnpm test:e2e:chromium order-create
|
|
|
+
|
|
|
+# 快速失败模式(推荐调试时使用)
|
|
|
+timeout 60 pnpm test:e2e:chromium order-create
|
|
|
+```
|
|
|
+
|
|
|
+### 测试注意事项
|
|
|
+
|
|
|
+**数据唯一性:**
|
|
|
+- 必须使用 `Date.now()` 或 `crypto.randomUUID()` 生成唯一订单名称
|
|
|
+- 避免测试数据冲突导致测试失败
|
|
|
+
|
|
|
+**必填字段:**
|
|
|
+- 订单名称:必填
|
|
|
+- 预计开始日期:必填(格式:YYYY-MM-DD)
|
|
|
+
|
|
|
+**可选字段:**
|
|
|
+- 平台:可选,需要数据库中存在平台数据
|
|
|
+- 公司:可选,需要数据库中存在公司数据
|
|
|
+- 渠道:可选,需要数据库中存在渠道数据
|
|
|
+
|
|
|
+**表单验证:**
|
|
|
+- 未填写必填字段时提交应显示错误提示
|
|
|
+- 验证错误提示使用 `data-sonner-toast` 属性定位
|
|
|
+
|
|
|
+**Toast 消息:**
|
|
|
+- 成功消息:`[data-sonner-toast][data-type="success"]`
|
|
|
+- 错误消息:`[data-sonner-toast][data-type="error"]`
|
|
|
+
|
|
|
+**网络响应:**
|
|
|
+- `submitForm()` 方法已监听网络响应
|
|
|
+- 使用 `waitForLoadState('networkidle')` 等待网络请求完成
|
|
|
+
|
|
|
+### 测试覆盖场景清单
|
|
|
+
|
|
|
+**基本创建:**
|
|
|
+- [ ] 只填写必填字段创建订单
|
|
|
+- [ ] 验证创建成功提示
|
|
|
+- [ ] 验证订单出现在列表中
|
|
|
+
|
|
|
+**平台选择:**
|
|
|
+- [ ] 创建订单时选择平台
|
|
|
+- [ ] 验证平台正确保存
|
|
|
+
|
|
|
+**公司选择:**
|
|
|
+- [ ] 创建订单时选择公司
|
|
|
+- [ ] 验证公司正确保存
|
|
|
+
|
|
|
+**渠道选择:**
|
|
|
+- [ ] 创建订单时选择渠道
|
|
|
+- [ ] 验证渠道正确保存
|
|
|
+
|
|
|
+**完整订单:**
|
|
|
+- [ ] 填写所有字段创建订单
|
|
|
+- [ ] 验证所有数据正确保存
|
|
|
+
|
|
|
+**表单验证:**
|
|
|
+- [ ] 不填写订单名称提交
|
|
|
+- [ ] 不填写预计开始日期提交
|
|
|
+- [ ] 验证错误提示显示
|
|
|
+- [ ] 测试取消创建操作
|
|
|
+
|
|
|
+### 前序 Story 关键学习点
|
|
|
+
|
|
|
+**从 Story 10.3(订单筛选测试)学到的经验:**
|
|
|
+
|
|
|
+1. **使用辅助函数消除代码重复**:
|
|
|
+ ```typescript
|
|
|
+ // 提取辅助函数
|
|
|
+ async function applyFilters(page) {
|
|
|
+ await page.getByTestId('search-button').click();
|
|
|
+ await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {
|
|
|
+ console.debug('筛选后没有网络请求或请求已完成');
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ async function getTableRowCount(page): Promise<number> {
|
|
|
+ const tbody = page.locator('table tbody');
|
|
|
+ const rows = tbody.locator('tr');
|
|
|
+ return await rows.count();
|
|
|
+ }
|
|
|
+
|
|
|
+ function formatLocalDate(date: Date): string {
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(date.getDate()).padStart(2, '0');
|
|
|
+ return `${year}-${month}-${day}`;
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+2. **使用 try-catch 处理空数据场景**:
|
|
|
+ ```typescript
|
|
|
+ // 如果数据库中没有测试数据,优雅处理
|
|
|
+ try {
|
|
|
+ await selectRadixOption(page, '平台', '58同城');
|
|
|
+ // 验证结果
|
|
|
+ } catch (error) {
|
|
|
+ console.debug('平台数据不存在或选择器无选项:', error);
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+3. **验证实际数据变化,而非仅检查元素可见**:
|
|
|
+ ```typescript
|
|
|
+ // 验证行数变化,而非仅检查表格可见
|
|
|
+ expect(filteredCount).toBeLessThanOrEqual(initialCount);
|
|
|
+ ```
|
|
|
+
|
|
|
+4. **使用 `.toPass()` 进行条件等待**:
|
|
|
+ ```typescript
|
|
|
+ // 等待异步操作完成,最多重试 5 秒
|
|
|
+ await expect(async () => {
|
|
|
+ const exists = await orderManagementPage.orderExists(orderName);
|
|
|
+ expect(exists).toBe(true);
|
|
|
+ }).toPass({ timeout: 5000 });
|
|
|
+ ```
|
|
|
+
|
|
|
+### References
|
|
|
+
|
|
|
+- [Source: _bmad-output/planning-artifacts/epics.md#Story 10.4](../planning-artifacts/epics.md)
|
|
|
+- [Source: _bmad-output/planning-artifacts/prd.md](../planning-artifacts/prd.md)
|
|
|
+- [Source: _bmad-output/planning-artifacts/architecture.md](../planning-artifacts/architecture.md)
|
|
|
+- [Source: web/tests/e2e/pages/admin/order-management.page.ts](../../web/tests/e2e/pages/admin/order-management.page.ts)
|
|
|
+- [Source: _bmad-output/implementation-artifacts/10-1-order-page-object.md](10-1-order-page-object.md)
|
|
|
+- [Source: _bmad-output/implementation-artifacts/10-2-order-list-tests.md](10-2-order-list-tests.md)
|
|
|
+- [Source: _bmad-output/implementation-artifacts/10-3-order-filter-tests.md](10-3-order-filter-tests.md)
|
|
|
+
|
|
|
+## Dev Agent Record
|
|
|
+
|
|
|
+### Agent Model Used
|
|
|
+
|
|
|
+claude-opus-4-5-20251101
|
|
|
+
|
|
|
+### Debug Log References
|
|
|
+
|
|
|
+### Completion Notes List
|
|
|
+
|
|
|
+### File List
|