|
|
@@ -0,0 +1,392 @@
|
|
|
+# Story 010.007: 租户后台统一广告管理UI交互E2E测试
|
|
|
+
|
|
|
+## Status
|
|
|
+Approved
|
|
|
+
|
|
|
+## Story
|
|
|
+
|
|
|
+**As a** 超级管理员,
|
|
|
+**I want** 通过E2E测试验证租户后台统一广告管理的完整UI交互流程,
|
|
|
+**so that** 确保租户后台的广告管理功能在实际浏览器环境中能够正常工作,包括登录、导航、CRUD操作、表单验证等所有交互场景。
|
|
|
+
|
|
|
+## Acceptance Criteria
|
|
|
+
|
|
|
+1. 创建UI交互E2E测试文件:`web/tests/e2e/specs/tenant-advertisement-ui.spec.ts`
|
|
|
+2. 测试登录流程:超级管理员登录租户后台
|
|
|
+3. 测试导航:验证广告管理菜单项可点击,页面正确跳转
|
|
|
+4. 测试广告列表:验证广告列表正确显示,包含正确数据
|
|
|
+5. 测试创建广告:打开创建表单,填写字段,提交,验证创建成功
|
|
|
+6. 测试编辑广告:点击编辑按钮,修改数据,保存,验证更新成功
|
|
|
+7. 测试删除广告:点击删除按钮,确认删除,验证数据删除
|
|
|
+8. 测试广告类型管理:验证类型列表、创建、编辑、删除
|
|
|
+9. 测试分页功能:验证翻页功能正常工作
|
|
|
+10. 测试搜索功能:验证按标题/代码搜索功能正常
|
|
|
+11. 测试表单验证:验证必填字段、格式验证、错误提示
|
|
|
+12. 测试图片选择器:验证图片选择器集成正常工作
|
|
|
+13. 测试响应式布局:验证页面在不同屏幕尺寸下正常显示
|
|
|
+14. 更新E2E测试规范文档,添加UI交互测试示例
|
|
|
+
|
|
|
+## Tasks / Subtasks
|
|
|
+
|
|
|
+- [ ] **任务1: 创建测试基础结构** (AC: 1, 2)
|
|
|
+ - [ ] 在 `web/tests/e2e/specs/` 目录创建 `tenant-advertisement-ui.spec.ts`
|
|
|
+ - [ ] 创建 Page Object:`web/tests/e2e/pages/tenant/tenant-login.page.ts`
|
|
|
+ - [ ] 创建 Page Object:`web/tests/e2e/pages/tenant/tenant-advertisement.page.ts`
|
|
|
+ - [ ] 创建测试 fixtures:`web/tests/e2e/fixtures/test-advertisements.json`
|
|
|
+ - [ ] 设置测试工具函数
|
|
|
+
|
|
|
+- [ ] **任务2: 实现登录流程测试** (AC: 2)
|
|
|
+ - [ ] 创建租户后台登录 Page Object
|
|
|
+ - [ ] 测试超级管理员登录(username=admin, password=admin123, tenantId=1)
|
|
|
+ - [ ] 验证登录成功后跳转到租户后台首页
|
|
|
+ - [ ] 验证登录失败场景(错误密码)
|
|
|
+
|
|
|
+- [ ] **任务3: 实现导航测试** (AC: 3)
|
|
|
+ - [ ] 验证广告管理菜单项存在且可点击
|
|
|
+ - [ ] 验证广告类型管理菜单项存在且可点击
|
|
|
+ - [ ] 验证点击菜单项后正确跳转到对应页面
|
|
|
+
|
|
|
+- [ ] **任务4: 实现广告列表测试** (AC: 4)
|
|
|
+ - [ ] 验证广告列表页面正确显示
|
|
|
+ - [ ] 验证广告列表数据正确渲染(标题、类型、状态、排序等)
|
|
|
+ - [ ] 验证列表操作按钮(编辑、删除)存在
|
|
|
+
|
|
|
+- [ ] **任务5: 实现创建广告测试** (AC: 5, 11, 12)
|
|
|
+ - [ ] 测试点击"新建"按钮打开创建对话框
|
|
|
+ - [ ] 测试填写表单字段(标题、类型、代码、URL、排序等)
|
|
|
+ - [ ] 测试图片选择器交互
|
|
|
+ - [ ] 测试表单验证(必填字段、格式验证)
|
|
|
+ - [ ] 测试提交创建成功
|
|
|
+ - [ ] 验证创建成功后列表中显示新广告
|
|
|
+
|
|
|
+- [ ] **任务6: 实现编辑广告测试** (AC: 6)
|
|
|
+ - [ ] 测试点击"编辑"按钮打开编辑对话框
|
|
|
+ - [ ] 测试修改广告数据
|
|
|
+ - [ ] 测试保存更新
|
|
|
+ - [ ] 验证更新成功后列表中数据已更新
|
|
|
+
|
|
|
+- [ ] **任务7: 实现删除广告测试** (AC: 7)
|
|
|
+ - [ ] 测试点击"删除"按钮
|
|
|
+ - [ ] 测试确认删除对话框
|
|
|
+ - [ ] 验证删除成功后列表中数据已移除
|
|
|
+
|
|
|
+- [ ] **任务8: 实现广告类型管理测试** (AC: 8)
|
|
|
+ - [ ] 测试广告类型列表页面显示
|
|
|
+ - [ ] 测试创建广告类型
|
|
|
+ - [ ] 测试编辑广告类型
|
|
|
+ - [ ] 测试删除广告类型
|
|
|
+
|
|
|
+- [ ] **任务9: 实现分页功能测试** (AC: 9)
|
|
|
+ - [ ] 测试分页组件显示
|
|
|
+ - [ ] 测试点击下一页/上一页
|
|
|
+ - [ ] 测试跳转到指定页码
|
|
|
+ - [ ] 验证分页数据正确加载
|
|
|
+
|
|
|
+- [ ] **任务10: 实现搜索功能测试** (AC: 10)
|
|
|
+ - [ ] 测试搜索输入框
|
|
|
+ - [ ] 测试按标题搜索
|
|
|
+ - [ ] 测试按代码搜索
|
|
|
+ - [ ] 验证搜索结果正确过滤
|
|
|
+
|
|
|
+- [ ] **任务11: 实现表单验证测试** (AC: 11)
|
|
|
+ - [ ] 测试必填字段验证(标题、类型等)
|
|
|
+ - [ ] 测试格式验证(URL格式、排序必须是数字等)
|
|
|
+ - [ ] 测试长度限制验证
|
|
|
+ - [ ] 验证错误提示正确显示
|
|
|
+
|
|
|
+- [ ] **任务12: 实现图片选择器测试** (AC: 12)
|
|
|
+ - [ ] 测试图片选择器按钮可点击
|
|
|
+ - [ ] 测试图片选择对话框打开
|
|
|
+ - [ ] 测试选择图片后确认
|
|
|
+ - [ ] 验证图片预览正确显示
|
|
|
+
|
|
|
+- [ ] **任务13: 实现响应式布局测试** (AC: 13)
|
|
|
+ - [ ] 测试桌面视图(Desktop Chrome)
|
|
|
+ - [ ] 测试移动端视图(Mobile Chrome/iPhone)
|
|
|
+ - [ ] 验证页面布局在不同尺寸下正常显示
|
|
|
+
|
|
|
+- [ ] **任务14: 更新E2E测试规范文档** (AC: 14)
|
|
|
+ - [ ] 在 `docs/architecture/e2e-testing-standards.md` 中添加UI交互测试示例
|
|
|
+ - [ ] 添加Page Object模式示例
|
|
|
+ - [ ] 添加租户后台测试规范
|
|
|
+
|
|
|
+## Dev Notes
|
|
|
+
|
|
|
+### 前一故事关键要点(来自 010.006)
|
|
|
+
|
|
|
+**测试成果**:
|
|
|
+- 创建了 `web/tests/e2e/unified-advertisement-api.spec.ts` API兼容性测试
|
|
|
+- 50个E2E测试通过,5个跳过
|
|
|
+- 添加了JWT认证逻辑到E2E测试
|
|
|
+- 创建了E2E测试规范文档
|
|
|
+
|
|
|
+**测试基础**:
|
|
|
+- **Playwright配置**: `web/tests/e2e/playwright.config.ts`(testDir: '.')
|
|
|
+- **测试命令**: `cd web && pnpm test:e2e:chromium`
|
|
|
+- **认证方式**: 通过 `/api/v1/auth/login?tenantId=1` 登录获取JWT token
|
|
|
+- **测试用户**: 超级管理员(username=admin, password=admin123, tenantId=1)
|
|
|
+
|
|
|
+### 租户后台结构
|
|
|
+
|
|
|
+**路由配置** [Source: web/src/client/tenant/routes.tsx]:
|
|
|
+```typescript
|
|
|
+// 广告管理路由
|
|
|
+{
|
|
|
+ path: 'unified-advertisements',
|
|
|
+ element: <UnifiedAdvertisementManagement />,
|
|
|
+}
|
|
|
+
|
|
|
+// 广告类型管理路由
|
|
|
+{
|
|
|
+ path: 'unified-advertisement-types',
|
|
|
+ element: <UnifiedAdvertisementTypeManagement />,
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**菜单配置** [Source: web/src/client/tenant/menu.tsx]:
|
|
|
+- 菜单项使用 Megaphone 图标
|
|
|
+- 菜单路径:`/tenant/unified-advertisements` 和 `/tenant/unified-advertisement-types`
|
|
|
+
|
|
|
+**统一广告管理UI包**:
|
|
|
+- 组件:`@d8d/unified-advertisement-management-ui`
|
|
|
+- 包含:广告列表、创建表单、编辑表单、删除确认对话框
|
|
|
+- 图片选择器:使用 `@d8d/file-management-ui-mt` 的文件选择器
|
|
|
+
|
|
|
+### API端点
|
|
|
+
|
|
|
+**管理员API(需要JWT认证)**:
|
|
|
+```typescript
|
|
|
+// 广告管理
|
|
|
+GET /api/v1/admin/unified-advertisements // 列表
|
|
|
+POST /api/v1/admin/unified-advertisements // 创建
|
|
|
+PUT /api/v1/admin/unified-advertisements/:id // 更新
|
|
|
+DELETE /api/v1/admin/unified-advertisements/:id // 删除
|
|
|
+
|
|
|
+// 广告类型管理
|
|
|
+GET /api/v1/admin/unified-advertisement-types // 列表
|
|
|
+POST /api/v1/admin/unified-advertisement-types // 创建
|
|
|
+PUT /api/v1/admin/unified-advertisement-types/:id // 更新
|
|
|
+DELETE /api/v1/admin/unified-advertisement-types/:id // 删除
|
|
|
+```
|
|
|
+
|
|
|
+### E2E测试框架配置
|
|
|
+
|
|
|
+**Playwright配置** [Source: web/tests/e2e/playwright.config.ts]:
|
|
|
+```typescript
|
|
|
+export default defineConfig({
|
|
|
+ testDir: '.', // 当前目录,扫描.spec.ts文件
|
|
|
+ fullyParallel: true,
|
|
|
+ forbidOnly: !!process.env.CI,
|
|
|
+ retries: process.env.CI ? 2 : 0,
|
|
|
+ workers: process.env.CI ? 1 : undefined,
|
|
|
+ use: {
|
|
|
+ baseURL: process.env.E2E_BASE_URL || 'http://localhost:8080',
|
|
|
+ trace: 'on-first-retry',
|
|
|
+ screenshot: 'only-on-failure',
|
|
|
+ video: 'retain-on-failure',
|
|
|
+ },
|
|
|
+ projects: [
|
|
|
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
|
+ { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
|
|
|
+ ],
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### Page Object模式
|
|
|
+
|
|
|
+**登录页面对象示例** [Source: docs/architecture/e2e-testing-standards.md]:
|
|
|
+```typescript
|
|
|
+// pages/tenant/tenant-login.page.ts
|
|
|
+import { Page, expect } from '@playwright/test';
|
|
|
+
|
|
|
+export class TenantLoginPage {
|
|
|
+ readonly page: Page;
|
|
|
+ readonly usernameInput = this.page.locator('input[name="username"]');
|
|
|
+ readonly passwordInput = this.page.locator('input[name="password"]');
|
|
|
+ readonly submitButton = this.page.locator('button[type="submit"]');
|
|
|
+
|
|
|
+ constructor(page: Page) {
|
|
|
+ this.page = page;
|
|
|
+ }
|
|
|
+
|
|
|
+ async goto() {
|
|
|
+ await this.page.goto('/tenant/login');
|
|
|
+ }
|
|
|
+
|
|
|
+ async login(username: string, password: string) {
|
|
|
+ await this.usernameInput.fill(username);
|
|
|
+ await this.passwordInput.fill(password);
|
|
|
+ await this.submitButton.click();
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**广告管理页面对象示例**:
|
|
|
+```typescript
|
|
|
+// pages/tenant/tenant-advertisement.page.ts
|
|
|
+import { Page, expect } from '@playwright/test';
|
|
|
+
|
|
|
+export class TenantAdvertisementPage {
|
|
|
+ readonly page: Page;
|
|
|
+ readonly createButton = this.page.locator('[data-testid="create-advertisement-button"]');
|
|
|
+ readonly searchInput = this.page.locator('input[placeholder*="搜索"]');
|
|
|
+ readonly table = this.page.locator('[data-testid="advertisement-table"]');
|
|
|
+
|
|
|
+ constructor(page: Page) {
|
|
|
+ this.page = page;
|
|
|
+ }
|
|
|
+
|
|
|
+ async goto() {
|
|
|
+ await this.page.goto('/tenant/unified-advertisements');
|
|
|
+ }
|
|
|
+
|
|
|
+ async clickCreate() {
|
|
|
+ await this.createButton.click();
|
|
|
+ }
|
|
|
+
|
|
|
+ async search(keyword: string) {
|
|
|
+ await this.searchInput.fill(keyword);
|
|
|
+ await this.page.waitForTimeout(300); // 等待搜索防抖
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 测试选择器规范
|
|
|
+
|
|
|
+**使用 data-testid** [Source: docs/architecture/ui-package-standards.md]:
|
|
|
+- 关键交互元素必须添加 `data-testid` 属性
|
|
|
+- 命名约定:`{action}-{element}-{purpose}`
|
|
|
+- 示例:
|
|
|
+ - `data-testid="create-advertisement-button"`
|
|
|
+ - `data-testid="edit-advertisement-button-1"`
|
|
|
+ - `data-testid="delete-confirm-dialog-title"`
|
|
|
+
|
|
|
+### 测试数据准备
|
|
|
+
|
|
|
+**测试用户数据** [Source: docs/architecture/e2e-testing-standards.md]:
|
|
|
+```sql
|
|
|
+-- 测试租户
|
|
|
+INSERT INTO tenant_mt (id, name, code, status, created_at, updated_at)
|
|
|
+VALUES (1, '测试租户', 'test-tenant', 1, NOW(), NOW());
|
|
|
+
|
|
|
+-- 测试超级管理员 (密码: admin123)
|
|
|
+INSERT INTO users_mt (id, tenant_id, username, password, registration_source, is_disabled, is_deleted, created_at, updated_at)
|
|
|
+VALUES (1, 1, 'admin', '$2b$10$x3t2kofPmACnk6y6lfL6ouU836LBEuZE9BinQ3ZzA4Xd04izyY42K', 'web', 0, 0, NOW(), NOW());
|
|
|
+```
|
|
|
+
|
|
|
+**测试广告数据**:
|
|
|
+```typescript
|
|
|
+// fixtures/test-advertisements.json
|
|
|
+{
|
|
|
+ "testAdvertisement": {
|
|
|
+ "title": "测试广告",
|
|
|
+ "typeId": 1,
|
|
|
+ "code": "TEST_AD",
|
|
|
+ "url": "https://example.com",
|
|
|
+ "sort": 1,
|
|
|
+ "status": 1,
|
|
|
+ "actionType": 1
|
|
|
+ },
|
|
|
+ "testAdvertisementType": {
|
|
|
+ "name": "测试类型",
|
|
|
+ "code": "TEST_TYPE",
|
|
|
+ "description": "测试广告类型",
|
|
|
+ "status": 1,
|
|
|
+ "sortOrder": 1
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 统一广告管理UI组件结构
|
|
|
+
|
|
|
+**组件来源** [Source: packages/unified-advertisement-management-ui/src/components/]:
|
|
|
+- `UnifiedAdvertisementManagement.tsx` - 主管理组件
|
|
|
+- `AdvertisementList.tsx` - 广告列表
|
|
|
+- `AdvertisementForm.tsx` - 广告表单(创建/编辑)
|
|
|
+- `AdvertisementTypeManagement.tsx` - 广告类型管理
|
|
|
+- `AdvertisementTypeForm.tsx` - 广告类型表单
|
|
|
+
|
|
|
+**表单字段**:
|
|
|
+- 广告表单:title(必填)、typeId(必填)、code、url、imageFileId、sort、status、actionType
|
|
|
+- 广告类型表单:name(必填)、code(必填)、description、status、sortOrder
|
|
|
+
|
|
|
+### 响应式布局测试
|
|
|
+
|
|
|
+**设备配置** [Source: docs/architecture/e2e-testing-standards.md]:
|
|
|
+- Desktop Chrome (1920x1080)
|
|
|
+- Pixel 5 (移动端,393x851)
|
|
|
+- iPhone 12 (移动端,390x844)
|
|
|
+
|
|
|
+### 测试执行命令
|
|
|
+
|
|
|
+```bash
|
|
|
+# 运行所有E2E测试
|
|
|
+cd web && pnpm test:e2e:chromium
|
|
|
+
|
|
|
+# 运行特定测试文件
|
|
|
+pnpm test:e2e tenant-advertisement-ui
|
|
|
+
|
|
|
+# 调试模式
|
|
|
+pnpm test:e2e:debug
|
|
|
+
|
|
|
+# 查看测试列表
|
|
|
+pnpm exec playwright test --config=tests/e2e/playwright.config.ts --list
|
|
|
+```
|
|
|
+
|
|
|
+### 关键注意事项
|
|
|
+
|
|
|
+1. **使用page对象**: 本故事的重点是使用 `page` 对象进行真正的浏览器UI交互测试,而不是像故事010.006那样使用 `request` 对象
|
|
|
+2. **Page Object模式**: 必须使用Page Object模式封装页面交互,提高测试可维护性
|
|
|
+3. **test-id选择器**: 优先使用 `data-testid` 而不是文本选择器,避免国际化导致的测试不稳定
|
|
|
+4. **数据清理**: 每个测试后应清理创建的测试数据,避免影响其他测试
|
|
|
+5. **异步等待**: 使用 `waitForSelector` 或 `waitForTimeout` 确保元素加载完成
|
|
|
+6. **测试隔离**: 每个测试应该独立运行,不依赖其他测试的状态
|
|
|
+
|
|
|
+### Testing
|
|
|
+
|
|
|
+**测试文件位置**:
|
|
|
+- 主测试文件: `web/tests/e2e/specs/tenant-advertisement-ui.spec.ts`
|
|
|
+- Page Objects: `web/tests/e2e/pages/tenant/`
|
|
|
+- 测试Fixtures: `web/tests/e2e/fixtures/`
|
|
|
+
|
|
|
+**测试框架**:
|
|
|
+- Playwright (chromium) [Source: docs/architecture/tech-stack.md]
|
|
|
+- 支持 Desktop 和 Mobile 视口测试
|
|
|
+
|
|
|
+**测试标准**:
|
|
|
+- 关键用户流程 100% 覆盖
|
|
|
+- 主要用户流程 80% 覆盖
|
|
|
+- 使用 Page Object 模式
|
|
|
+- 使用 data-testid 选择器
|
|
|
+
|
|
|
+**测试覆盖范围**:
|
|
|
+- 登录和导航流程
|
|
|
+- 广告CRUD操作(创建、读取、更新、删除)
|
|
|
+- 广告类型CRUD操作
|
|
|
+- 表单验证和错误处理
|
|
|
+- 分页和搜索功能
|
|
|
+- 图片选择器集成
|
|
|
+- 响应式布局验证
|
|
|
+
|
|
|
+## Change Log
|
|
|
+
|
|
|
+| Date | Version | Description | Author |
|
|
|
+|------|---------|-------------|--------|
|
|
|
+| 2026-01-03 | 1.0 | 初始故事创建 | Bob (Scrum Master) |
|
|
|
+
|
|
|
+## Dev Agent Record
|
|
|
+
|
|
|
+### Agent Model Used
|
|
|
+_待开发代理填写_
|
|
|
+
|
|
|
+### Debug Log References
|
|
|
+_待开发代理填写_
|
|
|
+
|
|
|
+### Completion Notes List
|
|
|
+_待开发代理填写_
|
|
|
+
|
|
|
+### File List
|
|
|
+_待开发代理填写_
|
|
|
+
|
|
|
+## QA Results
|
|
|
+_QA代理待填写_
|