11-5-company-create-test.story.md 22 KB

Story 11.5: 创建测试公司(需要先有平台)

Status: review

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-utilsselectRadixOptionAsync 选择平台
    • 验证异步加载的平台选项正确显示
    • 验证选择平台后表单提交成功
  5. AC5: 测试表单验证

    • 未填写公司名称时提交
    • 验证内联错误提示显示(对话框保持打开)
    • 验证公司未被创建
  6. AC6: 测试数据唯一性

    • 创建公司时使用时间戳确保公司名称唯一
    • 验证不同测试之间数据不冲突
  7. AC7: 测试取消和关闭操作

    • 测试点击取消按钮关闭创建对话框
    • 测试点击对话框外部关闭对话框
    • 测试按 ESC 键关闭对话框
    • 验证取消后公司未被创建
  8. AC8: 代码质量标准

    • TypeScript 类型检查通过(无类型错误)
    • 测试用例有清晰的描述

Tasks / Subtasks

  • [x] 任务 1: 创建测试文件和基础结构 (AC: 1, 8)

    • 创建文件 web/tests/e2e/specs/admin/company-create.spec.ts
    • 导入 Playwright test 和 CompanyManagementPage
    • 定义测试夹具(adminLoginPage, companyManagementPage)
    • 设置测试基础配置
  • [x] 任务 2: 实现测试前置条件 (AC: 1, 2)

    • 实现 test.beforeEach() 登录后台
    • 导航到公司管理页面
    • 验证页面加载完成
  • [x] 任务 3: 实现基本创建流程测试(不选择平台) (AC: 2, 6)

    • 编写测试用例:应该成功创建公司(仅填写公司名称)
    • 使用时间戳生成唯一公司名称
    • 验证 API 响应成功
    • 验证公司出现在列表中
    • 清理测试数据
  • [x] 任务 4: 实现完整表单字段测试(包含平台选择) (AC: 3, 4, 6)

    • 编写测试用例:应该成功创建公司(选择平台并填写所有字段)
    • 使用 createPlatform 方法先创建测试平台
    • 使用 selectRadixOptionAsync 选择平台
    • 填写所有字段(公司名称、联系人、联系电话、联系邮箱、地址)
    • 验证所有数据保存正确
    • 验证平台关联正确显示
    • 清理测试数据和平台
  • [x] 任务 5: 实现表单验证测试 (AC: 5)

    • 编写测试用例:未填写公司名称时应显示内联错误
    • 尝试提交空表单
    • 验证对话框保持打开(表单验证阻止了提交)
  • [x] 任务 6: 实现取消和关闭操作测试 (AC: 7)

    • 编写测试用例:应该能取消创建公司操作
    • 编写测试用例:应该能通过关闭对话框取消创建
    • 编写测试用例:应该能通过 ESC 键取消创建
  • [x] 任务 7: 实现测试后清理 (AC: 6)

    • 每个测试用例内清理测试数据
    • 删除测试创建的公司
    • 删除测试创建的平台
    • 验证清理成功
  • [x] 任务 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(当前)

标准测试文件结构:

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

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>

数据接口:

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):

/**
 * 创建公司(完整流程)
 * @param data - 公司数据
 * @param platformName - 平台名称(用于选择平台)
 */
async createCompany(data: CompanyData, platformName?: string): Promise<FormSubmitResult>

Company 实体结构

Company Entity (来自 company.entity.ts):

{
  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: 基本创建流程(不选择平台)

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: 完整表单字段(包含平台选择)

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: 表单验证(空公司名称)

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: 取消操作

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);
});

数据唯一性策略

使用时间戳确保唯一性:

const timestamp = Date.now();
const uniqueId = `company_test_${timestamp}`;
const companyName = `测试公司_${uniqueId}`;

测试数据清理:

  • 每个测试用例结束后清理自己创建的数据
  • 如果创建了平台,先删除公司,再删除平台
  • 验证删除成功后再结束测试

PlatformSelector 集成要点

关键注意事项(来自 Story 11.4):

  1. 使用 @d8d/e2e-test-utilsselectRadixOptionAsync

    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

测试运行命令

运行单个测试文件:

cd web
pnpm test:e2e:chromium company-create.spec.ts

运行单个测试用例:

cd web
pnpm test:e2e:chromium company-create.spec.ts -g "应该成功创建公司"

快速失败模式(调试):

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

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. ✅ 强调测试数据准备和清理顺序(平台 → 公司)

Story 实施完成 (2026-01-12):

  1. ✅ 创建测试文件 web/tests/e2e/specs/admin/company-create.spec.ts
  2. ✅ 实现所有 17 个测试用例:
    • 基本创建流程测试(不选择平台) - 2 个测试
    • 完整表单字段测试(包含平台选择) - 3 个测试
    • 表单验证测试 - 2 个测试
    • 取消和关闭操作测试 - 3 个测试
    • 对话框元素验证 - 1 个测试
    • 数据唯一性测试 - 2 个测试
    • 测试后清理验证 - 2 个测试
    • PlatformSelector 集成测试 - 2 个测试
  3. ✅ 所有测试通过(17/17 passed)
  4. ✅ 修复 CompanyManagementPage 中的 platformSelector 选择器
    • 原问题:使用了不存在的 data-testid="create-company-platform-selector"
    • 解决方案:改为使用 page.getByRole('dialog').getByText(/^平台/) 定位平台标签
  5. ✅ 使用 platformManagementPage fixture 创建测试平台(因为 CompanyManagementPage 没有 createPlatform 方法)
  6. ✅ 正确实现测试数据清理顺序(先删除公司,再删除平台)

File List

新创建的文件:

  • web/tests/e2e/specs/admin/company-create.spec.ts - 公司创建 E2E 测试文件

修改的文件:

  • web/tests/e2e/pages/admin/company-management.page.ts - 修复 platformSelector 选择器

依赖的已有文件:

  • web/tests/e2e/pages/admin/company-management.page.ts - Story 11.4 创建
  • web/tests/e2e/pages/admin/platform-management.page.ts - Story 11.1 创建(用于创建测试平台)
  • web/tests/e2e/utils/test-setup.ts - 已包含 companyManagementPage 和 platformManagementPage fixtures