/** * 私信群发功能端到端测试 * 测试私信模板、发送记录、任务管理等功能 */ import { test, expect, Page } from '@playwright/test'; const TEST_CONFIG = { baseURL: 'http://localhost:5173', timeout: 30000, adminUser: { username: 'admin', password: '111111', }, }; // 测试消息模板数据 const TEST_TEMPLATE = { name: '测试模板_' + Date.now(), title: '测试消息标题', content: '这是一条测试私信内容,用于验证系统功能。', type: 'text', category: 'marketing', variables: '${username},${date}', }; // 测试群发任务数据 const TEST_TASK = { name: '测试群发任务_' + Date.now(), targetType: 'all', sendTime: 'immediate', priority: 'normal', }; test.describe('私信群发 - 消息模板管理', () => { test.beforeEach(async ({ page }) => { test.setTimeout(TEST_CONFIG.timeout); await page.goto(TEST_CONFIG.baseURL); await loginAsAdmin(page); // 导航到消息模板页面 await page.click('[data-testid="menu-private-message"]'); await page.click('[data-testid="menu-message-template"]'); await page.waitForURL(/\/private-message\/template/); }); test('应该正确显示消息模板页面', async ({ page }) => { // 检查页面标题 await expect(page.locator('.page-title')).toContainText( /消息模板|模板管理/, ); // 检查搜索表单 await expect(page.locator('[data-testid="search-name"]')).toBeVisible(); await expect(page.locator('[data-testid="search-type"]')).toBeVisible(); await expect(page.locator('[data-testid="search-button"]')).toBeVisible(); // 检查操作按钮 await expect( page.locator('[data-testid="add-template-button"]'), ).toBeVisible(); await expect( page.locator('[data-testid="batch-delete-button"]'), ).toBeVisible(); // 检查模板列表 await expect(page.locator('.template-card, .ant-table')).toBeVisible(); }); test('应该能创建新的消息模板', async ({ page }) => { // 点击添加模板按钮 await page.click('[data-testid="add-template-button"]'); // 等待弹窗出现 await expect(page.locator('.ant-modal')).toBeVisible(); await expect(page.locator('.ant-modal-title')).toContainText( /添加模板|新建模板/, ); // 填写模板信息 await page.fill('[data-testid="form-name"]', TEST_TEMPLATE.name); await page.fill('[data-testid="form-title"]', TEST_TEMPLATE.title); await page.fill('[data-testid="form-content"]', TEST_TEMPLATE.content); await page.selectOption('[data-testid="form-type"]', TEST_TEMPLATE.type); await page.selectOption( '[data-testid="form-category"]', TEST_TEMPLATE.category, ); await page.fill('[data-testid="form-variables"]', TEST_TEMPLATE.variables); // 提交表单 await page.click('[data-testid="form-submit"]'); // 等待成功消息 await expect(page.locator('.ant-message-success')).toBeVisible(); await expect(page.locator('.ant-message-success')).toContainText( /创建成功|添加成功/, ); // 验证模板是否出现在列表中 await page.fill('[data-testid="search-name"]', TEST_TEMPLATE.name); await page.click('[data-testid="search-button"]'); await page.waitForTimeout(1000); const templateCard = page .locator('.template-card, .ant-table-tbody tr') .filter({ hasText: TEST_TEMPLATE.name }); await expect(templateCard).toBeVisible(); }); test('消息模板预览功能应该正常工作', async ({ page }) => { // 搜索刚创建的模板 await page.fill('[data-testid="search-name"]', TEST_TEMPLATE.name); await page.click('[data-testid="search-button"]'); await page.waitForTimeout(1000); // 点击预览按钮 const previewButton = page .locator('.template-card, .ant-table-tbody tr') .filter({ hasText: TEST_TEMPLATE.name }) .locator('[data-testid="preview-button"]'); await previewButton.click(); // 等待预览弹窗出现 await expect(page.locator('.ant-modal')).toBeVisible(); await expect(page.locator('.ant-modal-title')).toContainText( /模板预览|消息预览/, ); // 检查预览内容 await expect(page.locator('[data-testid="preview-title"]')).toContainText( TEST_TEMPLATE.title, ); await expect(page.locator('[data-testid="preview-content"]')).toContainText( TEST_TEMPLATE.content, ); // 关闭预览 await page.click('.ant-modal-close'); }); test('应该能编辑消息模板', async ({ page }) => { // 搜索模板 await page.fill('[data-testid="search-name"]', TEST_TEMPLATE.name); await page.click('[data-testid="search-button"]'); await page.waitForTimeout(1000); // 点击编辑按钮 const editButton = page .locator('.template-card, .ant-table-tbody tr') .filter({ hasText: TEST_TEMPLATE.name }) .locator('[data-testid="edit-button"]'); await editButton.click(); // 等待编辑弹窗 await expect(page.locator('.ant-modal')).toBeVisible(); await expect(page.locator('.ant-modal-title')).toContainText( /编辑模板|修改模板/, ); // 修改内容 const newContent = '已修改的测试私信内容'; await page.fill('[data-testid="form-content"]', newContent); // 提交修改 await page.click('[data-testid="form-submit"]'); // 等待成功消息 await expect(page.locator('.ant-message-success')).toBeVisible(); }); test('变量替换功能应该正常工作', async ({ page }) => { // 搜索模板 await page.fill('[data-testid="search-name"]', TEST_TEMPLATE.name); await page.click('[data-testid="search-button"]'); await page.waitForTimeout(1000); // 点击测试变量按钮 const testButton = page .locator('.template-card, .ant-table-tbody tr') .filter({ hasText: TEST_TEMPLATE.name }) .locator('[data-testid="test-variables-button"]'); await testButton.click(); // 等待测试弹窗 await expect(page.locator('.ant-modal')).toBeVisible(); await expect(page.locator('.ant-modal-title')).toContainText( /变量测试|测试变量/, ); // 输入测试数据 await page.fill('[data-testid="test-username"]', '张三'); await page.fill('[data-testid="test-date"]', '2024-01-01'); // 点击预览 await page.click('[data-testid="preview-with-variables"]'); // 检查变量是否被正确替换 await expect(page.locator('[data-testid="preview-result"]')).toContainText( '张三', ); await expect(page.locator('[data-testid="preview-result"]')).toContainText( '2024-01-01', ); }); }); test.describe('私信群发 - 群发任务管理', () => { test.beforeEach(async ({ page }) => { await page.goto(TEST_CONFIG.baseURL); await loginAsAdmin(page); // 导航到群发任务页面 await page.click('[data-testid="menu-private-message"]'); await page.click('[data-testid="menu-send-task"]'); await page.waitForURL(/\/private-message\/task/); }); test('应该正确显示群发任务页面', async ({ page }) => { // 检查页面标题 await expect(page.locator('.page-title')).toContainText( /群发任务|发送任务/, ); // 检查创建任务按钮 await expect( page.locator('[data-testid="create-task-button"]'), ).toBeVisible(); // 检查任务列表 await expect(page.locator('.task-list, .ant-table')).toBeVisible(); // 检查筛选器 await expect(page.locator('[data-testid="filter-status"]')).toBeVisible(); await expect(page.locator('[data-testid="filter-priority"]')).toBeVisible(); }); test('应该能创建新的群发任务', async ({ page }) => { // 点击创建任务按钮 await page.click('[data-testid="create-task-button"]'); // 等待任务创建页面加载 await page.waitForURL(/\/private-message\/task\/create/); // 第一步:基本信息 await page.fill('[data-testid="task-name"]', TEST_TASK.name); await page.selectOption( '[data-testid="task-priority"]', TEST_TASK.priority, ); await page.click('[data-testid="next-step"]'); // 第二步:选择模板 await page.click('[data-testid="template-selector"]'); await page.click('.template-option:first-child'); await page.click('[data-testid="next-step"]'); // 第三步:选择目标用户 await page.click(`[data-testid="target-${TEST_TASK.targetType}"]`); await page.click('[data-testid="next-step"]'); // 第四步:发送设置 await page.click(`[data-testid="send-${TEST_TASK.sendTime}"]`); await page.click('[data-testid="next-step"]'); // 第五步:确认并创建 await expect(page.locator('.task-summary')).toBeVisible(); await page.click('[data-testid="create-task"]'); // 等待成功消息 await expect(page.locator('.ant-message-success')).toBeVisible(); await expect(page.locator('.ant-message-success')).toContainText( /任务创建成功|创建成功/, ); // 应该跳转回任务列表 await page.waitForURL(/\/private-message\/task$/); }); test('任务状态管理应该正常工作', async ({ page }) => { // 找到一个待发送的任务 const taskRow = page .locator('.task-item, .ant-table-tbody tr') .filter({ hasText: /待发送|pending/ }) .first(); if ((await taskRow.count()) > 0) { // 点击开始发送按钮 await taskRow.locator('[data-testid="start-task"]').click(); // 确认操作 await page.click('[data-testid="confirm-start"]'); // 等待状态更新 await expect(page.locator('.ant-message-success')).toBeVisible(); // 检查任务状态是否变为发送中 await page.reload(); await expect(taskRow.locator('.task-status')).toContainText( /发送中|sending/, ); } }); test('任务详情页面应该正常显示', async ({ page }) => { // 点击第一个任务的详情按钮 const detailButton = page .locator('.task-item, .ant-table-tbody tr') .first() .locator('[data-testid="view-detail"]'); await detailButton.click(); // 等待详情页面加载 await page.waitForURL(/\/private-message\/task\/\d+/); // 检查任务基本信息 await expect(page.locator('.task-info')).toBeVisible(); await expect(page.locator('.task-progress')).toBeVisible(); // 检查发送记录表格 await expect(page.locator('.send-records .ant-table')).toBeVisible(); // 检查统计信息 await expect(page.locator('.task-statistics')).toBeVisible(); }); test('任务暂停和恢复功能应该正常工作', async ({ page }) => { // 找到一个正在发送的任务 const sendingTask = page .locator('.task-item, .ant-table-tbody tr') .filter({ hasText: /发送中|sending/ }) .first(); if ((await sendingTask.count()) > 0) { // 点击暂停按钮 await sendingTask.locator('[data-testid="pause-task"]').click(); // 确认暂停 await page.click('[data-testid="confirm-pause"]'); // 等待状态更新 await expect(page.locator('.ant-message-success')).toBeVisible(); // 检查状态是否变为已暂停 await page.reload(); await expect(sendingTask.locator('.task-status')).toContainText( /已暂停|paused/, ); // 测试恢复功能 await sendingTask.locator('[data-testid="resume-task"]').click(); await page.click('[data-testid="confirm-resume"]'); // 等待状态更新 await expect(page.locator('.ant-message-success')).toBeVisible(); } }); }); test.describe('私信群发 - 发送记录和统计', () => { test.beforeEach(async ({ page }) => { await page.goto(TEST_CONFIG.baseURL); await loginAsAdmin(page); // 导航到发送记录页面 await page.click('[data-testid="menu-private-message"]'); await page.click('[data-testid="menu-send-record"]'); await page.waitForURL(/\/private-message\/record/); }); test('应该正确显示发送记录页面', async ({ page }) => { // 检查页面标题 await expect(page.locator('.page-title')).toContainText( /发送记录|消息记录/, ); // 检查搜索表单 await expect(page.locator('[data-testid="search-keyword"]')).toBeVisible(); await expect(page.locator('[data-testid="search-status"]')).toBeVisible(); await expect( page.locator('[data-testid="search-date-range"]'), ).toBeVisible(); // 检查记录列表 await expect(page.locator('.ant-table')).toBeVisible(); // 检查统计卡片 await expect(page.locator('.statistics-cards')).toBeVisible(); }); test('记录搜索和筛选应该正常工作', async ({ page }) => { // 选择发送状态 await page.selectOption('[data-testid="search-status"]', 'success'); // 选择日期范围 await page.click('[data-testid="search-date-range"]'); await page.click('.ant-picker-today-btn'); // 今天 // 点击搜索 await page.click('[data-testid="search-button"]'); // 等待搜索结果 await page.waitForTimeout(1000); // 检查搜索结果 const records = page.locator('.ant-table-tbody tr'); if ((await records.count()) > 0) { // 验证搜索结果状态是否正确 const statusCells = records .locator('td') .filter({ hasText: /成功|success/ }); expect(await statusCells.count()).toBeGreaterThan(0); } }); test('记录详情查看应该正常工作', async ({ page }) => { // 点击第一条记录的详情按钮 const firstRecord = page.locator('.ant-table-tbody tr').first(); const detailButton = firstRecord.locator('[data-testid="view-detail"]'); if ((await detailButton.count()) > 0) { await detailButton.click(); // 等待详情弹窗 await expect(page.locator('.ant-modal')).toBeVisible(); await expect(page.locator('.ant-modal-title')).toContainText( /消息详情|发送详情/, ); // 检查详情内容 await expect( page.locator('[data-testid="detail-recipient"]'), ).toBeVisible(); await expect( page.locator('[data-testid="detail-content"]'), ).toBeVisible(); await expect( page.locator('[data-testid="detail-send-time"]'), ).toBeVisible(); await expect(page.locator('[data-testid="detail-status"]')).toBeVisible(); } }); test('记录导出功能应该正常工作', async ({ page }) => { const downloadPromise = page.waitForEvent('download'); // 点击导出按钮 await page.click('[data-testid="export-records"]'); const download = await downloadPromise; expect(download.suggestedFilename()).toMatch(/发送记录.*\.xlsx$/); }); test('统计图表应该正常显示', async ({ page }) => { // 导航到统计页面 await page.click('[data-testid="menu-private-message"]'); await page.click('[data-testid="menu-statistics"]'); await page.waitForURL(/\/private-message\/statistics/); // 检查统计图表 await expect(page.locator('.statistics-chart')).toBeVisible(); await expect(page.locator('.echarts-container')).toBeVisible(); // 检查时间范围选择器 await expect( page.locator('[data-testid="time-range-selector"]'), ).toBeVisible(); // 测试时间范围切换 await page.click('[data-testid="range-7days"]'); await page.waitForTimeout(1000); // 图表应该重新加载 await expect(page.locator('.statistics-chart')).toBeVisible(); }); }); test.describe('私信群发 - 实时监控', () => { test.beforeEach(async ({ page }) => { await page.goto(TEST_CONFIG.baseURL); await loginAsAdmin(page); // 导航到实时监控页面 await page.click('[data-testid="menu-private-message"]'); await page.click('[data-testid="menu-monitor"]'); await page.waitForURL(/\/private-message\/monitor/); }); test('实时监控页面应该正常显示', async ({ page }) => { // 检查页面标题 await expect(page.locator('.page-title')).toContainText( /实时监控|发送监控/, ); // 检查实时状态卡片 await expect(page.locator('.status-cards')).toBeVisible(); await expect( page.locator('[data-testid="active-tasks-count"]'), ).toBeVisible(); await expect(page.locator('[data-testid="sending-rate"]')).toBeVisible(); await expect(page.locator('[data-testid="success-rate"]')).toBeVisible(); // 检查实时日志 await expect(page.locator('.realtime-logs')).toBeVisible(); // 检查性能图表 await expect(page.locator('.performance-chart')).toBeVisible(); }); test('实时数据更新应该正常工作', async ({ page }) => { // 记录初始的发送数量 const initialCount = await page .locator('[data-testid="sent-count"]') .textContent(); // 等待数据更新(假设有WebSocket连接) await page.waitForTimeout(5000); // 检查数据是否可能发生变化(在有实际发送任务的情况下) const currentCount = await page .locator('[data-testid="sent-count"]') .textContent(); // 至少UI应该是响应的 await expect(page.locator('[data-testid="sent-count"]')).toBeVisible(); }); test('日志查看和筛选应该正常工作', async ({ page }) => { // 检查日志级别筛选 await page.selectOption('[data-testid="log-level-filter"]', 'error'); // 等待筛选结果 await page.waitForTimeout(1000); // 检查日志内容 const logEntries = page.locator('.log-entry'); if ((await logEntries.count()) > 0) { // 验证筛选结果 const errorLogs = logEntries.filter({ hasText: /错误|error/i }); expect(await errorLogs.count()).toBeGreaterThan(0); } // 测试清空日志功能 await page.click('[data-testid="clear-logs"]'); await page.click('[data-testid="confirm-clear"]'); // 日志应该被清空 await expect(page.locator('.log-entry')).toHaveCount(0); }); }); // 辅助函数:管理员登录 async function loginAsAdmin(page: Page) { await page.fill( '[data-testid="username-input"]', TEST_CONFIG.adminUser.username, ); await page.fill( '[data-testid="password-input"]', TEST_CONFIG.adminUser.password, ); await page.click('[data-testid="login-button"]'); await page.waitForURL(/\/dashboard/, { timeout: 10000 }); }