Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
566 lines
18 KiB
TypeScript
566 lines
18 KiB
TypeScript
/**
|
||
* 私信群发功能端到端测试
|
||
* 测试私信模板、发送记录、任务管理等功能
|
||
*/
|
||
|
||
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 });
|
||
}
|