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