|
@@ -0,0 +1,626 @@
|
|
|
|
|
+# Story 11.5: 创建测试公司(需要先有平台)
|
|
|
|
|
+
|
|
|
|
|
+Status: ready-for-dev
|
|
|
|
|
+
|
|
|
|
|
+<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
|
|
|
|
+
|
|
|
|
|
+## Story
|
|
|
|
|
+
|
|
|
|
|
+作为测试开发者,
|
|
|
|
|
+我想要编写公司创建功能的 E2E 测试,
|
|
|
|
|
+以便验证公司管理模块的创建功能正常工作。
|
|
|
|
|
+
|
|
|
|
|
+## Acceptance Criteria
|
|
|
|
|
+
|
|
|
|
|
+1. **AC1: 创建测试文件**
|
|
|
|
|
+ - 文件路径: `web/tests/e2e/specs/admin/company-create.spec.ts`
|
|
|
|
|
+ - 使用 Playwright test 框架
|
|
|
|
|
+ - 使用 Story 11.4 创建的 `CompanyManagementPage` Page Object
|
|
|
|
|
+ - 定义测试夹具(fixtures)包含 Page Object
|
|
|
|
|
+
|
|
|
|
|
+2. **AC2: 测试基本创建流程(不选择平台)**
|
|
|
|
|
+ - 导航到公司管理页面
|
|
|
|
|
+ - 点击创建公司按钮
|
|
|
|
|
+ - 仅填写必填字段(公司名称)
|
|
|
|
|
+ - 提交表单
|
|
|
|
|
+ - 验证创建成功(API 响应 + 列表中显示新公司)
|
|
|
|
|
+
|
|
|
|
|
+3. **AC3: 测试完整表单字段(包含平台选择)**
|
|
|
|
|
+ - 首先创建测试用的平台(使用 Page Object 方法)
|
|
|
|
|
+ - 填写所有字段(平台、公司名称、联系人、联系电话、联系邮箱、地址)
|
|
|
|
|
+ - 验证所有数据保存正确
|
|
|
|
|
+ - 验证公司出现在列表中,平台关联正确
|
|
|
|
|
+
|
|
|
|
|
+4. **AC4: 测试 PlatformSelector 集成**
|
|
|
|
|
+ - 使用 `@d8d/e2e-test-utils` 的 `selectRadixOptionAsync` 选择平台
|
|
|
|
|
+ - 验证异步加载的平台选项正确显示
|
|
|
|
|
+ - 验证选择平台后表单提交成功
|
|
|
|
|
+
|
|
|
|
|
+5. **AC5: 测试表单验证**
|
|
|
|
|
+ - 未填写公司名称时提交
|
|
|
|
|
+ - 验证内联错误提示显示(对话框保持打开)
|
|
|
|
|
+ - 验证公司未被创建
|
|
|
|
|
+
|
|
|
|
|
+6. **AC6: 测试数据唯一性**
|
|
|
|
|
+ - 创建公司时使用时间戳确保公司名称唯一
|
|
|
|
|
+ - 验证不同测试之间数据不冲突
|
|
|
|
|
+
|
|
|
|
|
+7. **AC7: 测试取消和关闭操作**
|
|
|
|
|
+ - 测试点击取消按钮关闭创建对话框
|
|
|
|
|
+ - 测试点击对话框外部关闭对话框
|
|
|
|
|
+ - 测试按 ESC 键关闭对话框
|
|
|
|
|
+ - 验证取消后公司未被创建
|
|
|
|
|
+
|
|
|
|
|
+8. **AC8: 代码质量标准**
|
|
|
|
|
+ - TypeScript 类型检查通过(无类型错误)
|
|
|
|
|
+ - 测试用例有清晰的描述
|
|
|
|
|
+
|
|
|
|
|
+## Tasks / Subtasks
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 任务 1: 创建测试文件和基础结构 (AC: 1, 8)
|
|
|
|
|
+ - [ ] 创建文件 `web/tests/e2e/specs/admin/company-create.spec.ts`
|
|
|
|
|
+ - [ ] 导入 Playwright test 和 CompanyManagementPage
|
|
|
|
|
+ - [ ] 定义测试夹具(adminLoginPage, companyManagementPage)
|
|
|
|
|
+ - [ ] 设置测试基础配置
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 任务 2: 实现测试前置条件 (AC: 1, 2)
|
|
|
|
|
+ - [ ] 实现 `test.beforeEach()` 登录后台
|
|
|
|
|
+ - [ ] 导航到公司管理页面
|
|
|
|
|
+ - [ ] 验证页面加载完成
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 任务 3: 实现基本创建流程测试(不选择平台) (AC: 2, 6)
|
|
|
|
|
+ - [ ] 编写测试用例:应该成功创建公司(仅填写公司名称)
|
|
|
|
|
+ - [ ] 使用时间戳生成唯一公司名称
|
|
|
|
|
+ - [ ] 验证 API 响应成功
|
|
|
|
|
+ - [ ] 验证公司出现在列表中
|
|
|
|
|
+ - [ ] 清理测试数据
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 任务 4: 实现完整表单字段测试(包含平台选择) (AC: 3, 4, 6)
|
|
|
|
|
+ - [ ] 编写测试用例:应该成功创建公司(选择平台并填写所有字段)
|
|
|
|
|
+ - [ ] 使用 `createPlatform` 方法先创建测试平台
|
|
|
|
|
+ - [ ] 使用 `selectRadixOptionAsync` 选择平台
|
|
|
|
|
+ - [ ] 填写所有字段(公司名称、联系人、联系电话、联系邮箱、地址)
|
|
|
|
|
+ - [ ] 验证所有数据保存正确
|
|
|
|
|
+ - [ ] 验证平台关联正确显示
|
|
|
|
|
+ - [ ] 清理测试数据和平台
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 任务 5: 实现表单验证测试 (AC: 5)
|
|
|
|
|
+ - [ ] 编写测试用例:未填写公司名称时应显示内联错误
|
|
|
|
|
+ - [ ] 尝试提交空表单
|
|
|
|
|
+ - [ ] 验证对话框保持打开(表单验证阻止了提交)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 任务 6: 实现取消和关闭操作测试 (AC: 7)
|
|
|
|
|
+ - [ ] 编写测试用例:应该能取消创建公司操作
|
|
|
|
|
+ - [ ] 编写测试用例:应该能通过关闭对话框取消创建
|
|
|
|
|
+ - [ ] 编写测试用例:应该能通过 ESC 键取消创建
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 任务 7: 实现测试后清理 (AC: 6)
|
|
|
|
|
+ - [ ] 每个测试用例内清理测试数据
|
|
|
|
|
+ - [ ] 删除测试创建的公司
|
|
|
|
|
+ - [ ] 删除测试创建的平台
|
|
|
|
|
+ - [ ] 验证清理成功
|
|
|
|
|
+
|
|
|
|
|
+- [ ] 任务 8: 运行测试并验证 (AC: 2, 3, 4, 5, 6, 7, 8)
|
|
|
|
|
+ - [ ] 运行测试: `pnpm test:e2e:chromium company-create.spec.ts`
|
|
|
|
|
+ - [ ] TypeScript 类型检查通过
|
|
|
|
|
+ - [ ] 修复发现的问题
|
|
|
|
|
+
|
|
|
|
|
+## Dev Notes
|
|
|
|
|
+
|
|
|
|
|
+### Epic 11 背景和目标
|
|
|
|
|
+
|
|
|
|
|
+**Epic 11: 基础配置管理测试 (Epic F)**
|
|
|
|
|
+
|
|
|
|
|
+为平台、公司、渠道配置管理编写 E2E 测试,为后续用户管理和跨端测试提供必要的测试数据。
|
|
|
|
|
+
|
|
|
|
|
+**实体关系链:**
|
|
|
|
|
+```
|
|
|
|
|
+Platform (平台)
|
|
|
|
|
+ ↓ 1:N
|
|
|
|
|
+Company (公司) - 必须 platformId
|
|
|
|
|
+ ↓ 1:N
|
|
|
|
|
+Order (订单) - 必须 companyId
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**Story 11.5 在 Epic 中的位置:**
|
|
|
|
|
+- Story 11.1 → Story 11.2 → Story 11.3 → Story 11.4 (已完成) → Story 11.5 (当前) → Story 11.6
|
|
|
|
|
+- Story 11.4 创建了 CompanyManagementPage Page Object
|
|
|
|
|
+- Story 11.5 将编写公司创建功能的 E2E 测试
|
|
|
|
|
+- Story 11.6 将编写公司列表显示验证测试
|
|
|
|
|
+
|
|
|
|
|
+### 架构模式和约束
|
|
|
|
|
+
|
|
|
|
|
+**测试文件结构参考:**
|
|
|
|
|
+
|
|
|
|
|
+参考 Story 11.2(平台创建测试)的结构模式:
|
|
|
|
|
+- `web/tests/e2e/specs/admin/platform-create.spec.ts`
|
|
|
|
|
+- `web/tests/e2e/specs/admin/company-create.spec.ts`(当前)
|
|
|
|
|
+
|
|
|
|
|
+**标准测试文件结构:**
|
|
|
|
|
+```typescript
|
|
|
|
|
+import { test, expect } from '@playwright/test';
|
|
|
|
|
+import { CompanyManagementPage } from '../../pages/admin/company-management.page';
|
|
|
|
|
+
|
|
|
|
|
+test.describe('公司创建功能', () => {
|
|
|
|
|
+ test.beforeEach(async ({ adminLoginPage, companyManagementPage }) => {
|
|
|
|
|
+ // 登录后台
|
|
|
|
|
+ await adminLoginPage.goto();
|
|
|
|
|
+ await adminLoginPage.login('admin', 'admin123');
|
|
|
|
|
+
|
|
|
|
|
+ // 导航到公司管理页面
|
|
|
|
|
+ await companyManagementPage.goto();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test.afterEach(async ({ companyManagementPage }) => {
|
|
|
|
|
+ // 清理测试数据
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('应该成功创建公司', async ({ companyManagementPage }) => {
|
|
|
|
|
+ // 测试逻辑
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**测试夹具配置:**
|
|
|
|
|
+
|
|
|
|
|
+Story 11.4 已添加 `companyManagementPage` fixture 到 `web/tests/e2e/utils/test-setup.ts`:
|
|
|
|
|
+```typescript
|
|
|
|
|
+export const test = test.extend<{
|
|
|
|
|
+ adminLoginPage: AdminLoginPage;
|
|
|
|
|
+ companyManagementPage: CompanyManagementPage;
|
|
|
|
|
+}>({
|
|
|
|
|
+ adminLoginPage: async ({ page }, use) => {
|
|
|
|
|
+ await use(new AdminLoginPage(page));
|
|
|
|
|
+ },
|
|
|
|
|
+ companyManagementPage: async ({ page }, use) => {
|
|
|
|
|
+ await use(new CompanyManagementPage(page));
|
|
|
|
|
+ },
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### CompanyManagementPage API 参考
|
|
|
|
|
+
|
|
|
|
|
+**来自 Story 11.4 的可用方法:**
|
|
|
|
|
+
|
|
|
|
|
+| 方法 | 描述 | 返回值 |
|
|
|
|
|
+|------|------|--------|
|
|
|
|
|
+| `goto()` | 导航到公司管理页面 | `Promise<void>` |
|
|
|
|
|
+| `createCompany(data, platformName)` | 创建公司(完整流程) | `Promise<FormSubmitResult>` |
|
|
|
|
|
+| `editPlatform(name, data)` | 编辑公司 | `Promise<FormSubmitResult>` |
|
|
|
|
|
+| `deleteCompany(name)` | 删除公司 | `Promise<boolean>` |
|
|
|
|
|
+| `companyExists(name)` | 验证公司是否存在 | `Promise<boolean>` |
|
|
|
|
|
+| `createPlatform(data)` | 创建平台(用于测试数据准备) | `Promise<FormSubmitResult>` |
|
|
|
|
|
+| `deletePlatform(name)` | 删除平台 | `Promise<boolean>` |
|
|
|
|
|
+
|
|
|
|
|
+**数据接口:**
|
|
|
|
|
+```typescript
|
|
|
|
|
+interface CompanyData {
|
|
|
|
|
+ platformId?: number; // 可选,平台ID
|
|
|
|
|
+ companyName: string; // 必填
|
|
|
|
|
+ contactPerson?: string;
|
|
|
|
|
+ contactPhone?: string;
|
|
|
|
|
+ contactEmail?: string;
|
|
|
|
|
+ address?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface PlatformData {
|
|
|
|
|
+ platformName: string; // 必填
|
|
|
|
|
+ contactPerson?: string;
|
|
|
|
|
+ contactPhone?: string;
|
|
|
|
|
+ contactEmail?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface FormSubmitResult {
|
|
|
|
|
+ success: boolean;
|
|
|
|
|
+ hasError: boolean;
|
|
|
|
|
+ hasSuccess: boolean;
|
|
|
|
|
+ errorMessage?: string;
|
|
|
|
|
+ successMessage?: string;
|
|
|
|
|
+ responses?: NetworkResponse[];
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**关键方法签名(来自 Story 11.4):**
|
|
|
|
|
+```typescript
|
|
|
|
|
+/**
|
|
|
|
|
+ * 创建公司(完整流程)
|
|
|
|
|
+ * @param data - 公司数据
|
|
|
|
|
+ * @param platformName - 平台名称(用于选择平台)
|
|
|
|
|
+ */
|
|
|
|
|
+async createCompany(data: CompanyData, platformName?: string): Promise<FormSubmitResult>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Company 实体结构
|
|
|
|
|
+
|
|
|
|
|
+**Company Entity (来自 company.entity.ts):**
|
|
|
|
|
+```typescript
|
|
|
|
|
+{
|
|
|
|
|
+ id: number; // 主键ID
|
|
|
|
|
+ platformId: number | null; // 平台ID(外键,可选)
|
|
|
|
|
+ companyName: string; // 公司名称(必填,最大100字符)
|
|
|
|
|
+ contactPerson: string | null; // 联系人(可选,最大50字符)
|
|
|
|
|
+ contactPhone: string | null; // 联系电话(可选,最大20字符)
|
|
|
|
|
+ contactEmail: string | null; // 联系邮箱(可选,最大100字符)
|
|
|
|
|
+ address: string | null; // 地址(可选,最大200字符)
|
|
|
|
|
+ status: number; // 状态:1-正常,0-禁用(默认1)
|
|
|
|
|
+ createTime: Date; // 创建时间
|
|
|
|
|
+ updateTime: Date; // 更新时间
|
|
|
|
|
+ platform?: Platform; // 关联的平台信息(eager加载)
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**唯一约束:**
|
|
|
|
|
+- 同一平台下公司名称唯一:`idx_company_name_platform (companyName, platformId)`
|
|
|
|
|
+- 如果不选择平台,公司名称全局唯一
|
|
|
|
|
+
|
|
|
|
|
+### CompanyManagement UI 组件分析
|
|
|
|
|
+
|
|
|
|
|
+**页面路径:** `/admin/companies`
|
|
|
|
|
+
|
|
|
|
|
+**创建公司对话框字段(来自 CompanyManagement.tsx):**
|
|
|
|
|
+
|
|
|
|
|
+1. **platformId** - PlatformSelector(可选)
|
|
|
|
|
+ - 使用 Radix UI Select 组件
|
|
|
|
|
+ - 异步加载平台选项
|
|
|
|
|
+ - data-testid: `create-company-platform-selector`
|
|
|
|
|
+ - 需要使用 `selectRadixOptionAsync` 选择
|
|
|
|
|
+
|
|
|
|
|
+2. **companyName** - Input(必填)
|
|
|
|
|
+ - 占位符: "请输入公司名称"
|
|
|
|
|
+ - data-testid: `create-company-name-input`
|
|
|
|
|
+
|
|
|
|
|
+3. **contactPerson** - Input(可选)
|
|
|
|
|
+ - 占位符: "请输入联系人"
|
|
|
|
|
+ - data-testid: `create-company-contact-person-input`
|
|
|
|
|
+
|
|
|
|
|
+4. **contactPhone** - Input(可选)
|
|
|
|
|
+ - 占位符: "请输入联系电话"
|
|
|
|
|
+ - data-testid: `create-company-contact-phone-input`
|
|
|
|
|
+
|
|
|
|
|
+5. **contactEmail** - Input(可选,email 类型)
|
|
|
|
|
+ - 占位符: "请输入联系邮箱"
|
|
|
|
|
+ - data-testid: `create-company-contact-email-input`
|
|
|
|
|
+
|
|
|
|
|
+6. **address** - Input(可选)
|
|
|
|
|
+ - 占位符: "请输入地址"
|
|
|
|
|
+ - data-testid: `create-company-address-input`
|
|
|
|
|
+
|
|
|
|
|
+**按钮:**
|
|
|
|
|
+- 取消按钮: `data-testid="cancel-company-button"`
|
|
|
|
|
+- 提交按钮: `data-testid="submit-create-company-button"`
|
|
|
|
|
+
|
|
|
|
|
+**对话框标题:**
|
|
|
|
|
+- data-testid: `company-modal-title`
|
|
|
|
|
+- 标题文本: "创建公司"
|
|
|
|
|
+
|
|
|
|
|
+### 测试用例设计
|
|
|
|
|
+
|
|
|
|
|
+**测试用例 1: 基本创建流程(不选择平台)**
|
|
|
|
|
+```typescript
|
|
|
|
|
+test('应该成功创建公司(不选择平台)', async ({ companyManagementPage }) => {
|
|
|
|
|
+ const timestamp = Date.now();
|
|
|
|
|
+ const companyName = `测试公司_${timestamp}`;
|
|
|
|
|
+
|
|
|
|
|
+ // 创建公司(不选择平台)
|
|
|
|
|
+ const result = await companyManagementPage.createCompany({
|
|
|
|
|
+ companyName
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 验证 API 响应成功
|
|
|
|
|
+ const createResponse = result.responses?.find(r => r.url.includes('createCompany'));
|
|
|
|
|
+ expect(createResponse?.ok).toBe(true);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证公司出现在列表中
|
|
|
|
|
+ const exists = await companyManagementPage.companyExists(companyName);
|
|
|
|
|
+ expect(exists).toBe(true);
|
|
|
|
|
+
|
|
|
|
|
+ // 清理
|
|
|
|
|
+ await companyManagementPage.deleteCompany(companyName);
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**测试用例 2: 完整表单字段(包含平台选择)**
|
|
|
|
|
+```typescript
|
|
|
|
|
+test('应该成功创建公司(选择平台并填写所有字段)', async ({ companyManagementPage }) => {
|
|
|
|
|
+ const timestamp = Date.now();
|
|
|
|
|
+
|
|
|
|
|
+ // 首先创建测试平台
|
|
|
|
|
+ const platformName = `测试平台_${timestamp}`;
|
|
|
|
|
+ await companyManagementPage.createPlatform({
|
|
|
|
|
+ platformName,
|
|
|
|
|
+ contactPerson: '测试联系人',
|
|
|
|
|
+ contactPhone: '13800138000',
|
|
|
|
|
+ contactEmail: `test_${timestamp}@example.com`
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 创建公司(选择平台)
|
|
|
|
|
+ const companyName = `测试公司_${timestamp}`;
|
|
|
|
|
+ const result = await companyManagementPage.createCompany({
|
|
|
|
|
+ companyName,
|
|
|
|
|
+ contactPerson: '张三',
|
|
|
|
|
+ contactPhone: '13900139000',
|
|
|
|
|
+ contactEmail: `company_${timestamp}@example.com`,
|
|
|
|
|
+ address: '北京市朝阳区'
|
|
|
|
|
+ }, platformName);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证 API 响应成功
|
|
|
|
|
+ const createResponse = result.responses?.find(r => r.url.includes('createCompany'));
|
|
|
|
|
+ expect(createResponse?.ok).toBe(true);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证公司出现在列表中
|
|
|
|
|
+ const exists = await companyManagementPage.companyExists(companyName);
|
|
|
|
|
+ expect(exists).toBe(true);
|
|
|
|
|
+
|
|
|
|
|
+ // 清理(先删除公司,再删除平台)
|
|
|
|
|
+ await companyManagementPage.deleteCompany(companyName);
|
|
|
|
|
+ await companyManagementPage.deletePlatform(platformName);
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**测试用例 3: 表单验证(空公司名称)**
|
|
|
|
|
+```typescript
|
|
|
|
|
+test('未填写公司名称时应显示内联验证错误', async ({ companyManagementPage }) => {
|
|
|
|
|
+ // 打开创建对话框
|
|
|
|
|
+ await companyManagementPage.openCreateDialog();
|
|
|
|
|
+
|
|
|
|
|
+ // 不填写任何字段,直接提交
|
|
|
|
|
+ const submitButton = companyManagementPage.page.locator('[data-testid="submit-create-company-button"]');
|
|
|
|
|
+ await submitButton.click();
|
|
|
|
|
+
|
|
|
|
|
+ // 验证对话框仍然打开(表单验证阻止了提交)
|
|
|
|
|
+ const dialog = companyManagementPage.page.locator('[role="dialog"]');
|
|
|
|
|
+ await expect(dialog).toBeVisible();
|
|
|
|
|
+
|
|
|
|
|
+ // 验证内联错误消息显示
|
|
|
|
|
+ const errorMessage = companyManagementPage.page.locator('[data-testid="create-company-name-input"] + div[data-testid="form-message"]');
|
|
|
|
|
+ await expect(errorMessage).toBeVisible();
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭对话框
|
|
|
|
|
+ await companyManagementPage.cancelDialog();
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**测试用例 4: 取消操作**
|
|
|
|
|
+```typescript
|
|
|
|
|
+test('应该能取消创建公司操作', async ({ companyManagementPage }) => {
|
|
|
|
|
+ const timestamp = Date.now();
|
|
|
|
|
+ const companyName = `测试公司_${timestamp}`;
|
|
|
|
|
+
|
|
|
|
|
+ // 打开创建对话框并填写表单
|
|
|
|
|
+ await companyManagementPage.openCreateDialog();
|
|
|
|
|
+ await companyManagementPage.page.locator('[data-testid="create-company-name-input"]').fill(companyName);
|
|
|
|
|
+
|
|
|
|
|
+ // 点击取消按钮
|
|
|
|
|
+ await companyManagementPage.cancelDialog();
|
|
|
|
|
+
|
|
|
|
|
+ // 验证对话框关闭
|
|
|
|
|
+ const dialog = companyManagementPage.page.locator('[role="dialog"]');
|
|
|
|
|
+ await expect(dialog).not.toBeVisible();
|
|
|
|
|
+
|
|
|
|
|
+ // 验证公司未被创建
|
|
|
|
|
+ const exists = await companyManagementPage.companyExists(companyName);
|
|
|
|
|
+ expect(exists).toBe(false);
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 数据唯一性策略
|
|
|
|
|
+
|
|
|
|
|
+**使用时间戳确保唯一性:**
|
|
|
|
|
+```typescript
|
|
|
|
|
+const timestamp = Date.now();
|
|
|
|
|
+const uniqueId = `company_test_${timestamp}`;
|
|
|
|
|
+const companyName = `测试公司_${uniqueId}`;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**测试数据清理:**
|
|
|
|
|
+- 每个测试用例结束后清理自己创建的数据
|
|
|
|
|
+- 如果创建了平台,先删除公司,再删除平台
|
|
|
|
|
+- 验证删除成功后再结束测试
|
|
|
|
|
+
|
|
|
|
|
+### PlatformSelector 集成要点
|
|
|
|
|
+
|
|
|
|
|
+**关键注意事项(来自 Story 11.4):**
|
|
|
|
|
+
|
|
|
|
|
+1. **使用 `@d8d/e2e-test-utils` 的 `selectRadixOptionAsync`:**
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ import { selectRadixOptionAsync } from '@d8d/e2e-test-utils';
|
|
|
|
|
+
|
|
|
|
|
+ // 在 Page Object 的 fillCompanyForm 中选择平台
|
|
|
|
|
+ if (platformName) {
|
|
|
|
|
+ await selectRadixOptionAsync(this.page, '平台', platformName);
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+2. **测试数据准备顺序:**
|
|
|
|
|
+ - 先创建测试平台(使用 `createPlatform`)
|
|
|
|
|
+ - 然后创建公司并指定平台名称
|
|
|
|
|
+ - 清理时先删除公司,再删除平台
|
|
|
|
|
+
|
|
|
|
|
+3. **PlatformSelector 标签:**
|
|
|
|
|
+ - 标签文本: "平台" 或 "选择平台"
|
|
|
|
|
+ - 使用时需要确认实际的标签文本
|
|
|
|
|
+
|
|
|
|
|
+### 项目结构约束
|
|
|
|
|
+
|
|
|
|
|
+**测试文件存放路径:**
|
|
|
|
|
+```
|
|
|
|
|
+web/tests/e2e/specs/admin/
|
|
|
|
|
+├── platform-create.spec.ts # 平台创建测试(已完成)
|
|
|
|
|
+├── platform-list.spec.ts # 平台列表测试(已完成)
|
|
|
|
|
+├── company-create.spec.ts # 公司创建测试(当前)
|
|
|
|
|
+└── company-list.spec.ts # 公司列表测试(后续 Story)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**依赖文件:**
|
|
|
|
|
+- `web/tests/e2e/pages/admin/company-management.page.ts` - Story 11.4 创建
|
|
|
|
|
+- `web/tests/e2e/utils/test-setup.ts` - 已包含 companyManagementPage fixture
|
|
|
|
|
+
|
|
|
|
|
+### 测试运行命令
|
|
|
|
|
+
|
|
|
|
|
+**运行单个测试文件:**
|
|
|
|
|
+```bash
|
|
|
|
|
+cd web
|
|
|
|
|
+pnpm test:e2e:chromium company-create.spec.ts
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**运行单个测试用例:**
|
|
|
|
|
+```bash
|
|
|
|
|
+cd web
|
|
|
|
|
+pnpm test:e2e:chromium company-create.spec.ts -g "应该成功创建公司"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**快速失败模式(调试):**
|
|
|
|
|
+```bash
|
|
|
|
|
+cd web
|
|
|
|
|
+timeout 60 pnpm test:e2e:chromium company-create.spec.ts
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 依赖关系
|
|
|
|
|
+
|
|
|
|
|
+**Epic 11 内部依赖:**
|
|
|
|
|
+- Story 11.1: ✅ 已完成(PlatformManagementPage 作为参考)
|
|
|
|
|
+- Story 11.2: ✅ 已完成(平台创建测试参考)
|
|
|
|
|
+- Story 11.3: ✅ 已完成(平台列表测试参考)
|
|
|
|
|
+- Story 11.4: ✅ 已完成(CompanyManagementPage)
|
|
|
|
|
+- Story 11.5: 公司创建测试(当前)
|
|
|
|
|
+- Story 11.6: 公司列表测试(依赖当前 Story)
|
|
|
|
|
+
|
|
|
|
|
+**外部依赖:**
|
|
|
|
|
+- Epic 1, 2: `@d8d/e2e-test-utils` 包(已存在)
|
|
|
|
|
+- `web/tests/e2e/utils/test-setup.ts`: 已包含 companyManagementPage 夹具
|
|
|
|
|
+
|
|
|
|
|
+### 测试标准和规范
|
|
|
|
|
+
|
|
|
|
|
+遵循项目测试标准:
|
|
|
|
|
+- `docs/standards/testing-standards.md`
|
|
|
|
|
+- `docs/standards/web-ui-testing-standards.md`
|
|
|
|
|
+
|
|
|
|
|
+**关键测试原则:**
|
|
|
|
|
+1. 测试独立性:每个测试用例独立运行
|
|
|
|
|
+2. 数据清理:每个测试结束后清理自己创建的数据
|
|
|
|
|
+3. 清晰断言:使用 expect() 明确断言预期结果
|
|
|
|
|
+4. 等待策略:使用 Playwright 的 auto-waiting
|
|
|
|
|
+
|
|
|
|
|
+### 前序 Story (11.1-11.4) 关键经验
|
|
|
|
|
+
|
|
|
|
|
+从 Story 11.1-11.4 中学到的关键经验:
|
|
|
|
|
+
|
|
|
|
|
+1. **Toast 检测不可靠:**
|
|
|
|
|
+ - Toast 消息有时出现得很快
|
|
|
|
|
+ - 已改用 API 响应验证作为主要验证方式
|
|
|
|
|
+
|
|
|
|
|
+2. **表格列顺序:**
|
|
|
|
|
+ - Company 表格列顺序:公司名称(0), 平台(1), 联系人(2), 联系电话(3), 状态(4), 创建时间(5), 操作(6)
|
|
|
|
|
+ - 使用 `nth(0)` 检查公司名称列
|
|
|
|
|
+
|
|
|
|
|
+3. **测试数据要求:**
|
|
|
|
|
+ - 后端 Zod schema 要求 contactEmail 必须是有效邮箱(如果填写)
|
|
|
|
|
+ - 空字符串会被拒绝,所以测试不填的字段应该设为 undefined
|
|
|
|
|
+
|
|
|
|
|
+4. **页面刷新:**
|
|
|
|
|
+ - 创建/删除数据后需要刷新页面或重新导航
|
|
|
|
|
+ - 使用 `page.reload()` 或 `goto()` 刷新
|
|
|
|
|
+
|
|
|
|
|
+5. **选择器优先级:**
|
|
|
|
|
+ - 优先使用 data-testid(最稳定)
|
|
|
|
|
+ - 其次使用 role + name(较稳定)
|
|
|
|
|
+ - 避免使用文本选择器(可能变化)
|
|
|
|
|
+
|
|
|
|
|
+6. **API 删除策略(来自 Story 11.2):**
|
|
|
|
|
+ - 使用 API 直接删除,绕过 UI 的不可靠性
|
|
|
|
|
+ - 删除成功后刷新页面确保列表更新
|
|
|
|
|
+
|
|
|
|
|
+### 已知问题和注意事项
|
|
|
|
|
+
|
|
|
|
|
+1. **Platform 异步加载:**
|
|
|
|
|
+ - PlatformSelector 选项是异步加载的
|
|
|
|
|
+ - 需要等待选项加载完成再选择
|
|
|
|
|
+ - 使用 `selectRadixOptionAsync` 确保选项加载完成
|
|
|
|
|
+
|
|
|
|
|
+2. **公司名称唯一性:**
|
|
|
|
|
+ - 同一平台下公司名称唯一
|
|
|
|
|
+ - 测试使用时间戳确保唯一性
|
|
|
|
|
+ - 如果不选择平台,公司名称全局唯一
|
|
|
|
|
+
|
|
|
|
|
+3. **测试数据清理顺序:**
|
|
|
|
|
+ - 必须先删除公司,再删除平台
|
|
|
|
|
+ - 因为公司依赖平台(外键关系)
|
|
|
|
|
+
|
|
|
|
|
+4. **内联验证消息:**
|
|
|
|
|
+ - 使用 `FormMessage` 组件显示
|
|
|
|
|
+ - data-testid 格式: `{fieldId}-form-message` 或类似
|
|
|
|
|
+ - 需要验证实际的 data-testid
|
|
|
|
|
+
|
|
|
|
|
+### 开发顺序建议
|
|
|
|
|
+
|
|
|
|
|
+1. 创建测试文件和基础结构
|
|
|
|
|
+2. 实现测试前置条件(登录、导航)
|
|
|
|
|
+3. 实现基本创建流程测试(不选择平台)
|
|
|
|
|
+4. 实现完整表单字段测试(包含平台选择)
|
|
|
|
|
+5. 实现表单验证测试
|
|
|
|
|
+6. 实现取消和关闭操作测试
|
|
|
|
|
+7. 实现测试后清理
|
|
|
|
|
+8. 运行测试并验证
|
|
|
|
|
+9. TypeScript 类型检查
|
|
|
|
|
+
|
|
|
|
|
+### References
|
|
|
|
|
+
|
|
|
|
|
+- [Epic 11 基础配置管理测试](../planning-artifacts/epics.md#epic-11-基础配置管理测试-epic-f)
|
|
|
|
|
+- [Story 11.1 Platform Page Object](./11-1-platform-page-object.story.md)
|
|
|
|
|
+- [Story 11.2 平台创建测试](./11-2-platform-create-test.story.md)
|
|
|
|
|
+- [Story 11.4 Company Page Object](./11-4-company-page-object.story.md)
|
|
|
|
|
+- [Company Entity](../../allin-packages/company-module/src/entities/company.entity.ts)
|
|
|
|
|
+- [Company Schema](../../allin-packages/company-module/src/schemas/company.schema.ts)
|
|
|
|
|
+- [CompanyManagement UI](../../allin-packages/company-management-ui/src/components/CompanyManagement.tsx)
|
|
|
|
|
+- [测试标准](../../docs/standards/testing-standards.md)
|
|
|
|
|
+- [Web UI 测试标准](../../docs/standards/web-ui-testing-standards.md)
|
|
|
|
|
+- [Architecture Decision - TypeScript + Playwright 陷阱](../planning-artifacts/architecture.md#typescript--playwright-常见陷阱)
|
|
|
|
|
+
|
|
|
|
|
+### Project Structure Notes
|
|
|
|
|
+
|
|
|
|
|
+**测试文件存放路径:**
|
|
|
|
|
+```
|
|
|
|
|
+web/tests/e2e/
|
|
|
|
|
+├── pages/admin/
|
|
|
|
|
+│ ├── platform-management.page.ts # 平台管理 Page Object(已完成)
|
|
|
|
|
+│ └── company-management.page.ts # 公司管理 Page Object(已完成)
|
|
|
|
|
+├── specs/admin/
|
|
|
|
|
+│ ├── platform-create.spec.ts # 平台创建测试(已完成)
|
|
|
|
|
+│ ├── platform-list.spec.ts # 平台列表测试(已完成)
|
|
|
|
|
+│ └── company-create.spec.ts # 公司创建测试(当前)
|
|
|
|
|
+└── utils/
|
|
|
|
|
+ └── test-setup.ts # 测试夹具配置(已包含 companyManagementPage)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Dev Agent Record
|
|
|
|
|
+
|
|
|
|
|
+### Agent Model Used
|
|
|
|
|
+
|
|
|
|
|
+Claude (d8d-model)
|
|
|
|
|
+
|
|
|
|
|
+### Debug Log References
|
|
|
|
|
+
|
|
|
|
|
+### Completion Notes List
|
|
|
|
|
+
|
|
|
|
|
+**Story 创建完成:**
|
|
|
|
|
+1. ✅ 分析 Story 11.5 需求:编写公司创建功能的 E2E 测试
|
|
|
|
|
+2. ✅ 创建完整的 Story 文档
|
|
|
|
|
+3. ✅ 包含所有验收标准和任务分解
|
|
|
|
|
+4. ✅ 提供详细的 Dev Notes 指导开发者
|
|
|
|
|
+5. ✅ 参考 Story 11.2(平台创建测试)的模式
|
|
|
|
|
+6. ✅ 分析 CompanyManagement.tsx UI 组件结构
|
|
|
|
|
+7. ✅ 提供完整的测试用例示例
|
|
|
|
|
+8. ✅ 说明 PlatformSelector 集成方法
|
|
|
|
|
+9. ✅ 强调测试数据准备和清理顺序(平台 → 公司)
|
|
|
|
|
+
|
|
|
|
|
+### File List
|
|
|
|
|
+
|
|
|
|
|
+**创建的文件:**
|
|
|
|
|
+- `_bmad-output/implementation-artifacts/11-5-company-create-test.story.md`
|
|
|
|
|
+
|
|
|
|
|
+**依赖的已有文件:**
|
|
|
|
|
+- `web/tests/e2e/pages/admin/company-management.page.ts` - Story 11.4 创建
|
|
|
|
|
+- `web/tests/e2e/pages/admin/platform-management.page.ts` - Story 11.1 创建(参考)
|
|
|
|
|
+- `allin-packages/company-management-ui/src/components/CompanyManagement.tsx` - UI 组件参考
|
|
|
|
|
+- `allin-packages/company-module/src/entities/company.entity.ts` - 实体参考
|
|
|
|
|
+- `allin-packages/company-module/src/schemas/company.schema.ts` - Schema 参考
|