Files
E2E-Test/顾问管理测试.spec.ts
2025-11-04 16:29:07 +08:00

473 lines
19 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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