first commit

This commit is contained in:
2025-11-04 16:29:07 +08:00
commit 8ee74625c7
19 changed files with 4562 additions and 0 deletions

472
顾问管理测试.spec.ts Normal file
View File

@ -0,0 +1,472 @@
import { test, expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
import Imap from 'imap';
import { simpleParser, ParsedMail } from 'mailparser';
test('counselor registration and login test', async ({ page }) => {
test.setTimeout(600000); // 设置超时时间为10分钟
// 错误收集数组
const errors: { step: string; error: string; timestamp: string; pageUrl: string }[] = [];
// 管理员登录信息
const adminInfo = {
email: 'xdf.admin@applify.ai',
password: 'b9#0!;+{Tx4649op'
};
// 新顾问信息
const counselorInfo = {
name: '黄子旭顾问测试',
email: 'dev.test@prodream.cn',
branch: '5257',
line: '11',
employeeId: '11',
password: '' // 将从邮件中提取
};
// 企业邮箱 IMAP 配置
const imapConfig = {
user: 'dev.test@prodream.cn',
password: 'phhuma3UKGHvZPwo', // 企业邮箱授权码IMAP/SMTP服务授权码
host: 'smtp.exmail.qq.com',
port: 993,
tls: true,
tlsOptions: { rejectUnauthorized: false }
};
// 辅助函数:执行步骤并捕获错误
const executeStep = async (stepName: string, stepFunction: () => Promise<void>) => {
try {
console.log(`\n[执行] ${stepName}`);
await stepFunction();
console.log(`[成功] ${stepName}`);
} catch (error) {
let errorMessage = error instanceof Error ? error.message : String(error);
const firstLine = errorMessage.split('\n')[0];
const cleanError = firstLine.replace(/\s+\(.*?\)/, '').trim();
const timestamp = new Date().toISOString();
const pageUrl = page.url();
errors.push({ step: stepName, error: cleanError, timestamp, pageUrl });
console.error(`[失败] ${stepName}: ${cleanError}`);
console.error(`[页面URL] ${pageUrl}`);
}
};
// 辅助函数通过IMAP读取最新邮件密码
const getPasswordFromEmail = (): Promise<string> => {
return new Promise((resolve, reject) => {
console.log('正在连接邮箱服务器...');
const imap = new Imap(imapConfig);
imap.once('ready', () => {
console.log('邮箱连接成功');
imap.openBox('INBOX', false, (err, box) => {
if (err) {
imap.end();
reject(err);
return;
}
console.log(`收件箱邮件数: ${box.messages.total}`);
// 搜索所有欢迎邮件
imap.search([['FROM', 'no-reply'], ['SUBJECT', 'Welcome to Prodream']], (err, results) => {
if (err) {
imap.end();
reject(err);
return;
}
if (results.length === 0) {
imap.end();
reject(new Error('未找到欢迎邮件'));
return;
}
console.log(`找到 ${results.length} 封相关邮件`);
// 收集所有邮件的日期和ID
const emailDates: { id: number; date: Date }[] = [];
let processedCount = 0;
const fetchHeaders = imap.fetch(results, { bodies: 'HEADER.FIELDS (DATE)' });
fetchHeaders.on('message', (msg, seqno) => {
msg.on('body', (stream: NodeJS.ReadableStream) => {
let buffer = '';
stream.on('data', (chunk: Buffer) => {
buffer += chunk.toString('utf8');
});
stream.once('end', () => {
// 从头部提取日期
const dateMatch = buffer.match(/Date:\s*(.+?)(?:\r?\n|$)/i);
if (dateMatch) {
try {
const dateString = dateMatch[1].trim();
const emailDate = new Date(dateString);
// 验证日期是否有效
if (!isNaN(emailDate.getTime())) {
emailDates.push({ id: seqno, date: emailDate });
console.log(`邮件 ${seqno} 的日期: ${emailDate.toISOString()}`);
} else {
console.log(`⚠️ 邮件 ${seqno} 日期无效: ${dateString}`);
}
} catch (e) {
console.log(`⚠️ 邮件 ${seqno} 日期解析失败`);
}
} else {
console.log(`⚠️ 邮件 ${seqno} 未找到日期头部`);
}
});
});
msg.once('end', () => {
processedCount++;
// 所有邮件头部都处理完后,选择最新的
if (processedCount === results.length) {
console.log(`\n处理完成共收集到 ${emailDates.length} 封邮件的日期信息`);
// 如果没有收集到任何日期信息使用最大的邮件ID通常是最新的
let latestEmail: { id: number; date: Date };
if (emailDates.length === 0) {
console.log('⚠️ 未能从邮件头部提取日期使用邮件ID排序');
const maxId = Math.max(...results);
latestEmail = { id: maxId, date: new Date() };
console.log(`选择邮件 ID: ${maxId}`);
} else {
// 按日期排序,最新的在最后
emailDates.sort((a, b) => a.date.getTime() - b.date.getTime());
latestEmail = emailDates[emailDates.length - 1];
console.log(`选择最新邮件: ID=${latestEmail.id}, 日期=${latestEmail.date.toISOString()}`);
}
// 获取最新邮件的完整内容
const fetchBody = imap.fetch([latestEmail.id], { bodies: '' });
fetchBody.on('message', (msg2) => {
msg2.on('body', (stream: any) => {
simpleParser(stream, (err: any, parsed: ParsedMail) => {
if (err) {
imap.end();
reject(err);
return;
}
console.log('邮件解析成功');
// 尝试从文本版本和 HTML 版本提取
const textBody = parsed.text || '';
const htmlBody = parsed.html || '';
console.log('=== 邮件文本内容 ===');
console.log(textBody);
console.log('=== 邮件HTML内容 ===');
console.log(htmlBody);
console.log('===================');
// 尝试多种密码提取模式
// 密码可以包含任何可打印字符,直到遇到空格、换行或特定的结束词
const patterns = [
/Password[:\s]+(\S+?)(?:\s+Login|\s+$|\s+\n|$)/i,
/password[:\s]+(\S+?)(?:\s+Login|\s+$|\s+\n|$)/i,
/密码[:\s]+(\S+?)(?:\s+登录|\s+$|\s+\n|$)/,
/Your password is[:\s]+(\S+?)(?:\s+Login|\s+$|\s+\n|$)/i,
/临时密码[:\s]+(\S+?)(?:\s+登录|\s+$|\s+\n|$)/,
// 备用模式:匹配到下一个单词边界
/Password[:\s]+([^\s]+)/i,
/password[:\s]+([^\s]+)/i
];
let password = '';
// 首先尝试从文本内容提取
for (const pattern of patterns) {
const match = textBody.match(pattern);
if (match && match[1]) {
password = match[1].trim();
console.log(`✅ 从文本中提取到密码 (模式: ${pattern}): ${password}`);
break;
}
}
// 如果文本提取失败,尝试从 HTML 提取
if (!password && htmlBody) {
// 移除 HTML 标签后再尝试
const cleanHtml = htmlBody.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ');
console.log('清理后的HTML内容:', cleanHtml.substring(0, 300));
for (const pattern of patterns) {
const match = cleanHtml.match(pattern);
if (match && match[1]) {
password = match[1].trim();
console.log(`✅ 从HTML中提取到密码 (模式: ${pattern}): ${password}`);
break;
}
}
}
if (password) {
console.log(`🎉 成功提取密码: ${password}`);
imap.end();
resolve(password);
} else {
console.error('❌ 所有模式都未能提取密码');
console.error('请检查上面的邮件内容,手动确认密码格式');
imap.end();
reject(new Error('无法从邮件中提取密码'));
}
});
});
});
fetchBody.once('error', (err: Error) => {
imap.end();
reject(err);
});
}
});
});
fetchHeaders.once('error', (err: Error) => {
imap.end();
reject(err);
});
});
});
});
imap.once('error', (err: Error) => {
console.error('IMAP连接错误:', err);
reject(err);
});
imap.once('end', () => {
console.log('邮箱连接已关闭');
});
imap.connect();
});
};
// 步骤1: 管理员登录
await executeStep('访问首页', async () => {
await page.goto('https://pre.prodream.cn/en');
await expect(page.getByRole('button', { name: 'Log in' })).toBeVisible();
});
await executeStep('点击登录按钮', async () => {
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByRole('textbox', { name: 'Email Address' })).toBeVisible();
});
await executeStep('输入管理员登录信息', async () => {
await page.getByRole('textbox', { name: 'Email Address' }).click();
await page.getByRole('textbox', { name: 'Email Address' }).fill(adminInfo.email);
await page.getByRole('textbox', { name: 'Psssword' }).click();
await page.getByRole('textbox', { name: 'Psssword' }).fill(adminInfo.password);
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByRole('link').filter({ hasText: '学生工作台' })).toBeVisible();
});
// 步骤2: 创建新顾问
await executeStep('进入顾问管理页面', async () => {
await page.getByRole('link').filter({ hasText: '顾问管理' }).click();
await expect(page.getByRole('button', { name: 'New counselor' })).toBeVisible({ timeout: 10000 });
console.log('顾问管理页面已加载');
});
await executeStep('点击新建顾问', async () => {
await page.getByRole('button', { name: 'New counselor' }).click();
await expect(page.getByRole('textbox', { name: 'Name *' })).toBeVisible({ timeout: 5000 });
});
await executeStep('填写顾问信息', async () => {
await page.getByRole('textbox', { name: 'Name *' }).click();
await page.getByRole('textbox', { name: 'Name *' }).fill(counselorInfo.name);
await page.getByRole('textbox', { name: 'Email *' }).click();
await page.getByRole('textbox', { name: 'Email *' }).fill(counselorInfo.email);
await page.getByRole('textbox', { name: 'Branch *' }).click();
await page.getByRole('textbox', { name: 'Branch *' }).fill(counselorInfo.branch);
await page.getByRole('textbox', { name: 'Line *' }).click();
await page.getByRole('textbox', { name: 'Line *' }).fill(counselorInfo.line);
await page.getByRole('textbox', { name: 'Employee ID *' }).click();
await page.getByRole('textbox', { name: 'Employee ID *' }).fill(counselorInfo.employeeId);
console.log('顾问信息填写完成');
});
await executeStep('提交创建顾问', async () => {
await page.getByRole('button', { name: 'Add Counselor' }).click();
await page.waitForTimeout(2000);
// 确认修改
const confirmButton = page.getByRole('button', { name: 'Confirm Modification' });
if (await confirmButton.isVisible()) {
await confirmButton.click();
}
await page.waitForTimeout(2000);
console.log('顾问创建请求已提交');
});
await executeStep('验证顾问创建成功', async () => {
await page.getByRole('textbox', { name: 'Search by counselor name,' }).click();
await page.getByRole('textbox', { name: 'Search by counselor name,' }).fill(counselorInfo.name);
await page.waitForTimeout(1000);
// 验证顾问邮箱出现在列表中
await expect(page.locator('span').filter({ hasText: counselorInfo.email })).toBeVisible({ timeout: 10000 });
console.log('顾问创建成功并出现在列表中');
});
// 步骤3: 从邮箱获取密码
await executeStep('从QQ邮箱获取密码', async () => {
console.log('等待邮件发送30秒...');
await page.waitForTimeout(30000); // 等待邮件发送
try {
const password = await getPasswordFromEmail();
counselorInfo.password = password;
console.log(`成功获取密码: ${password}`);
} catch (error) {
console.error('获取邮件密码失败:', error);
throw new Error(`无法从邮箱获取密码: ${error instanceof Error ? error.message : String(error)}`);
}
});
// 步骤4: 管理员登出
await executeStep('管理员退出登录', async () => {
await page.locator('div').filter({ hasText: /^Admin$/ }).click();
await page.getByRole('menuitem', { name: '退出登录' }).click();
await expect(page.getByRole('button', { name: 'Log in' })).toBeVisible({ timeout: 10000 });
console.log('管理员已退出登录');
});
// 步骤5: 使用新顾问账号登录
await executeStep('使用新顾问账号登录', async () => {
await page.getByRole('textbox', { name: 'Email Address' }).click();
await page.getByRole('textbox', { name: 'Email Address' }).fill(counselorInfo.email);
await page.getByRole('textbox', { name: 'Psssword' }).click();
await page.getByRole('textbox', { name: 'Psssword' }).fill(counselorInfo.password);
await page.getByRole('button', { name: 'Log in' }).click();
// 验证登录成功
await expect(page.getByRole('img', { name: 'prodream' })).toBeVisible({ timeout: 15000 });
console.log('新顾问账号登录成功');
});
// 步骤6: 测试新顾问的各项功能
await executeStep('测试 DreamiExplore 功能', async () => {
await page.getByRole('link').filter({ hasText: 'DreamiExplore Everything' }).click();
await expect(page.getByText('我是Dreami擅长升学领域各类问题请随意提问咨询哦。')).toBeVisible({ timeout: 15000 });
await page.getByRole('textbox').click();
await page.getByRole('textbox').fill('hello');
await page.getByRole('button', { name: 'send' }).click();
await expect(page.getByRole('button', { name: '复制' })).toBeVisible({ timeout: 15000 });
console.log('DreamiExplore 功能正常');
});
await executeStep('测试学生工作台', async () => {
await page.getByRole('link').filter({ hasText: '学生工作台' }).click();
await expect(page.getByRole('button', { name: 'New Student' })).toBeVisible({ timeout: 10000 });
console.log('学生工作台可访问');
});
await executeStep('测试文书记录', async () => {
await page.getByRole('link').filter({ hasText: '文书记录' }).click();
await expect(page.getByRole('button', { name: 'New Essay' })).toBeVisible({ timeout: 10000 });
console.log('文书记录可访问');
});
await executeStep('测试规划记录', async () => {
await page.getByRole('link').filter({ hasText: '规划记录' }).click();
await expect(page.getByRole('button', { name: 'icon AI规划' })).toBeVisible({ timeout: 10000 });
console.log('规划记录可访问');
});
await executeStep('测试 Discover 页面', async () => {
await page.getByRole('link').filter({ hasText: 'Discover' }).click();
await page.waitForTimeout(2000);
console.log('Discover 页面可访问');
});
await executeStep('测试通知页面', async () => {
await page.getByRole('link').filter({ hasText: '通知' }).click();
await page.waitForTimeout(2000);
console.log('通知页面可访问');
});
// 最后输出错误汇总
console.log('\n\n========== 测试执行完成 ==========');
// 生成错误报告文件
const generateErrorReport = () => {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const reportPath = path.join(process.cwd(), `test-2-counselor-report-${timestamp}.txt`);
let report = '========== 顾问注册与登录测试报告 ==========\n\n';
report += `报告生成时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
report += '【测试账号信息】\n';
report += `管理员账号: ${adminInfo.email}\n`;
report += `管理员密码: ${adminInfo.password}\n`;
report += `新顾问姓名: ${counselorInfo.name}\n`;
report += `新顾问邮箱: ${counselorInfo.email}\n`;
report += `新顾问密码: ${counselorInfo.password || '(未获取到)'}\n`;
report += `测试环境: https://pre.prodream.cn/en\n\n`;
report += `${'='.repeat(60)}\n\n`;
if (errors.length === 0) {
report += '✅ 所有功能测试通过,未发现问题!\n';
} else {
report += `发现 ${errors.length} 个问题,详情如下:\n\n`;
errors.forEach((err, index) => {
report += `【问题 ${index + 1}\n`;
report += `问题功能: ${err.step}\n`;
report += `页面链接: ${err.pageUrl}\n`;
report += `错误详情: ${err.error}\n`;
report += `发生时间: ${new Date(err.timestamp).toLocaleString('zh-CN')}\n`;
report += '\n';
});
report += `${'='.repeat(60)}\n`;
report += `\n说明: 测试过程中遇到错误会自动跳过并继续执行后续步骤。\n`;
}
fs.writeFileSync(reportPath, report, 'utf-8');
return reportPath;
};
if (errors.length === 0) {
console.log('✅ 所有步骤执行成功!');
} else {
console.log(`\n⚠ 发现 ${errors.length} 个问题:\n`);
errors.forEach((err, index) => {
console.log(`【问题 ${index + 1}`);
console.log(` 问题功能: ${err.step}`);
console.log(` 页面链接: ${err.pageUrl}`);
console.log(` 错误详情: ${err.error}`);
console.log('');
});
const reportPath = generateErrorReport();
console.log(`📄 详细错误报告已保存到: ${reportPath}`);
console.log(`\n管理员账号: ${adminInfo.email}`);
console.log(`管理员密码: ${adminInfo.password}`);
console.log(`新顾问账号: ${counselorInfo.email}`);
console.log(`新顾问密码: ${counselorInfo.password}\n`);
}
});