Files
E2E-Test/学生工作台测试.spec.ts
2025-11-04 16:29:07 +08:00

462 lines
17 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';
// 辅助函数等待文件上传和AI识别完成
async function waitForFileRecognition(page: any, fileType: string) {
console.log(`等待 ${fileType} 文件识别...`);
// 第一步:等待文件上传完成(给更多时间)
await page.waitForTimeout(5000);
console.log(`${fileType} 文件已上传等待AI识别...`);
// 第二步:等待"正在智能文件识别"消失增加超时时间到5分钟
await page.waitForFunction(() => {
const bodyText = document.body.innerText;
return !bodyText.includes('正在智能文件识别') &&
!bodyText.includes('识别中') &&
!bodyText.includes('Processing');
}, { timeout: 300000 }).catch(() => {
console.log(`${fileType} - 未检测到"正在识别"状态,或已完成`);
});
// 第三步:检查是否有文件损坏错误
const errorMessage = await page.locator('text=文件已损坏导致解析失败').isVisible({ timeout: 5000 }).catch(() => false);
if (errorMessage) {
console.log(`⚠️ ${fileType} - 检测到文件损坏错误`);
throw new Error(`文件已损坏导致解析失败, 请使用新文件重试`);
}
// 第四步等待识别完成的提示增加超时时间到5分钟
console.log(`${fileType} - 等待识别完成提示...`);
const successText = await page.getByText('文档信息已识别自动填充,可查看右侧学生信息对照走查,以免信息有误')
.waitFor({ state: 'visible', timeout: 300000 })
.then(() => {
console.log(`${fileType} 文件识别成功!`);
return true;
})
.catch(async () => {
// 如果找不到完整文本,尝试查找部分文本(也增加超时时间)
console.log(`${fileType} - 未找到完整提示文本,尝试部分匹配...`);
const partialMatch = await page.locator('text=文档信息已识别').or(page.locator('text=已识别自动填充'))
.first()
.waitFor({ state: 'visible', timeout: 60000 })
.then(() => {
console.log(`⚠️ ${fileType} 文件可能识别成功(部分匹配)`);
return true;
})
.catch(async () => {
// 调试:截图并打印内容
console.error(`${fileType} 文件识别失败,正在截图...`);
await page.screenshot({ path: `debug-${fileType}-failed.png`, fullPage: true });
const text = await page.locator('body').textContent();
console.log(`${fileType} 页面内容:`, text?.substring(0, 800));
return false;
});
return partialMatch;
});
if (!successText) {
throw new Error(`${fileType} 文件识别失败,请查看截图 debug-${fileType}-failed.png`);
}
}
// 辅助函数:点击智能文件识别按钮(带重试逻辑)
async function clickUploadButton(page: any) {
// 关闭可能的弹窗
await page.keyboard.press('Escape').catch(() => {});
await page.waitForTimeout(1000);
// 尝试多种方式点击按钮
let clicked = false;
// 方法1: footer中的按钮
try {
const footerButton = page.locator('footer').getByRole('button', { name: 'star 智能文件识别' });
await footerButton.waitFor({ state: 'visible', timeout: 5000 });
await footerButton.click({ timeout: 5000 });
clicked = true;
console.log('成功点击footer中的智能文件识别按钮');
} catch (e1) {
console.log('footer按钮不可用尝试其他方式...');
}
// 方法2: 页面中任意位置的按钮
if (!clicked) {
try {
await page.getByRole('button', { name: 'star 智能文件识别' }).click({ timeout: 5000 });
clicked = true;
console.log('成功点击页面中的智能文件识别按钮');
} catch (e2) {
console.log('页面按钮也不可用,尝试文本点击...');
}
}
// 方法3: 通过文本点击
if (!clicked) {
try {
await page.locator('text=智能文件识别').click({ timeout: 5000 });
clicked = true;
console.log('通过文本点击成功');
} catch (e3) {
console.warn('所有点击方式都失败,可能不需要再次点击按钮');
}
}
await page.waitForTimeout(500);
}
// 辅助函数:上传文件并处理损坏文件重试逻辑
async function uploadFileWithRetry(
page: any,
fileType: string,
filePath: string,
clickSelector: string | (() => Promise<void>),
corruptedFiles: string[]
) {
let retryCount = 0;
const maxRetries = 2; // 最多尝试2次首次 + 1次重试
while (retryCount < maxRetries) {
try {
console.log(`\n[尝试 ${retryCount + 1}/${maxRetries}] 上传 ${fileType} 文件`);
// 触发文件选择器
const fileChooserPromise = page.waitForEvent('filechooser');
if (typeof clickSelector === 'string') {
await page.locator(clickSelector).click();
} else {
await clickSelector();
}
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(filePath);
// 等待文件识别完成
await waitForFileRecognition(page, fileType);
console.log(`${fileType} 文件上传成功(尝试 ${retryCount + 1} 次)`);
return true; // 成功,退出函数
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
if (errorMsg.includes('文件已损坏导致解析失败')) {
retryCount++;
if (retryCount < maxRetries) {
console.log(`⚠️ ${fileType} 文件损坏,关闭错误提示并准备重试(第 ${retryCount} 次重试)...`);
// 点击关闭按钮快速关闭错误提示
try {
await page.getByRole('button', { name: 'close' }).click({ timeout: 3000 });
console.log('已关闭错误提示弹窗');
} catch (e) {
console.log('未找到关闭按钮,继续重试');
}
await page.waitForTimeout(1000); // 等待1秒后重试
} else {
console.error(`${fileType} 文件重试 ${maxRetries} 次后仍然失败`);
// 关闭最后一次的错误提示
try {
await page.getByRole('button', { name: 'close' }).click({ timeout: 3000 });
} catch (e) {
// 忽略错误
}
corruptedFiles.push(fileType);
return false;
}
} else {
// 其他类型的错误,直接抛出
console.error(`${fileType} 文件上传失败: ${errorMsg}`);
throw error;
}
}
}
return false;
}
test('comprehensive student workflow with AI file upload', async ({ page }) => {
// 设置整个测试的超时时间为40分钟6个文件 * 5分钟 + 其他操作时间)
test.setTimeout(2400000);
// 错误收集数组
const errors: { step: string; error: string; timestamp: string; pageUrl: string }[] = [];
// 损坏文件收集数组
const corruptedFiles: string[] = [];
// 辅助函数:执行步骤并捕获错误
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}`);
}
};
// 步骤1: 登录系统
await executeStep('访问首页并登录', async () => {
await page.goto('https://pre.prodream.cn/en');
await page.getByRole('button', { name: 'Log in' }).click();
await page.getByRole('textbox', { name: 'Email Address' }).click();
await page.getByRole('textbox', { name: 'Email Address' }).fill('xdf.admin@applify.ai');
await page.getByRole('textbox', { name: 'Psssword' }).click();
await page.getByRole('textbox', { name: 'Psssword' }).fill('b9#0!;+{Tx4649op');
await page.getByRole('textbox', { name: 'Psssword' }).press('Enter');
await expect(page.getByRole('link').filter({ hasText: '学生工作台' })).toBeVisible();
});
await executeStep('进入学生工作台', async () => {
await page.getByRole('link').filter({ hasText: '学生工作台' }).click();
await expect(page.getByText('Student Name')).toBeVisible();
});
await executeStep('创建新学生', async () => {
await page.getByRole('button', { name: 'New Student' }).click();
await page.getByRole('textbox', { name: 'Name *', exact: true }).click();
await page.getByRole('textbox', { name: 'Name *', exact: true }).fill('黄子旭测试');
await page.locator('div').filter({ hasText: /^Please select branch$/ }).nth(2).click();
await page.locator('div').filter({ hasText: /^1$/ }).nth(1).click();
await page.getByText('Select counselor').click();
await page.locator('div').filter({ hasText: /^2-2@2\.com$/ }).nth(1).click();
await page.locator('svg').nth(5).click();
await page.getByRole('textbox', { name: 'Contract Category *' }).click();
await page.getByRole('textbox', { name: 'Contract Category *' }).fill('11');
await page.getByRole('textbox', { name: 'Contract Name *' }).click();
await page.getByRole('textbox', { name: 'Contract Name *' }).fill('11');
await page.getByRole('button', { name: 'Confirm' }).click();
});
await executeStep('进入学生档案', async () => {
await page.getByRole('button', { name: 'More' }).first().dblclick();
await page.getByText('学生档案').click();
});
// 步骤2: AI智能文件识别上传6个文件
await executeStep('打开智能文件识别', async () => {
await page.locator('footer').getByRole('button', { name: 'star 智能文件识别' }).click();
await page.waitForTimeout(2000);
});
// 上传 DOCX 文件
await executeStep('上传DOCX文件', async () => {
const success = await uploadFileWithRetry(
page,
'DOCX',
path.join(__dirname, '..', '建档测试文件', '测试建档DOCX.docx'),
'text=或点击选择文件 不超过10MB | 支持格式: DOCX、',
corruptedFiles
);
if (success) await clickUploadButton(page);
});
// 上传 PDF 文件
await executeStep('上传PDF文件', async () => {
const success = await uploadFileWithRetry(
page,
'PDF',
path.join(__dirname, '..', '建档测试文件', '测试建档PDF.pdf'),
'text=或点击选择文件 不超过10MB | 支持格式: DOCX、',
corruptedFiles
);
if (success) await clickUploadButton(page);
});
// 上传 JPEG 文件
await executeStep('上传JPEG文件', async () => {
const success = await uploadFileWithRetry(
page,
'JPEG',
path.join(__dirname, '..', '建档测试文件', '测试建档JPEG.jpg'),
async () => {
await page.locator('div').filter({ hasText: '拖拽文档到这里或点击选择文件 不超过10MB' }).nth(4).click();
},
corruptedFiles
);
if (success) await clickUploadButton(page);
});
// 上传 PNG 文件
await executeStep('上传PNG文件', async () => {
const success = await uploadFileWithRetry(
page,
'PNG',
path.join(__dirname, '..', '建档测试文件', '测试建档PNG.png'),
'text=或点击选择文件 不超过10MB | 支持格式: DOCX、',
corruptedFiles
);
if (success) await clickUploadButton(page);
});
// 上传 BMP 文件
await executeStep('上传BMP文件', async () => {
const success = await uploadFileWithRetry(
page,
'BMP',
path.join(__dirname, '..', '建档测试文件', '测试建档 BMP.bmp'),
'text=或点击选择文件 不超过10MB | 支持格式: DOCX、',
corruptedFiles
);
if (success) await clickUploadButton(page);
});
// 上传 XLSX 文件
await executeStep('上传XLSX文件', async () => {
const success = await uploadFileWithRetry(
page,
'XLSX',
path.join(__dirname, '..', '建档测试文件', '测试建档XLSX.xlsx'),
async () => {
await page.getByRole('heading', { name: '拖拽文档到这里' }).click();
},
corruptedFiles
);
// XLSX是最后一个文件上传后不需要再点击智能识别按钮
});
// 步骤3: 验证学生档案信息已填充
await executeStep('验证学生基本信息已填充', async () => {
// 等待页面稳定
await page.waitForTimeout(3000);
// 验证姓名字段有值
const nameField = page.getByRole('textbox', { name: 'Name *', exact: true });
const nameValue = await nameField.inputValue();
if (!nameValue || nameValue.trim() === '') {
throw new Error('学生姓名未填充');
}
console.log(`✅ 学生姓名已填充: ${nameValue}`);
});
await executeStep('验证学生详细信息已填充', async () => {
// 检查多个关键字段是否有数据
const fieldsToCheck = [
{ name: 'Email', selector: page.getByRole('textbox', { name: 'Email' }) },
{ name: 'Phone', selector: page.getByRole('textbox', { name: 'Phone' }) }
];
let filledCount = 0;
for (const field of fieldsToCheck) {
try {
const value = await field.selector.inputValue({ timeout: 5000 });
if (value && value.trim() !== '') {
console.log(`${field.name} 已填充: ${value}`);
filledCount++;
}
} catch (e) {
console.log(`⚠️ ${field.name} 字段未找到或未填充`);
}
}
if (filledCount === 0) {
console.log('⚠️ 警告:部分学生信息字段未能自动填充');
} else {
console.log(`✅ 成功填充 ${filledCount} 个学生信息字段`);
}
});
// 步骤4: 保存学生档案
await executeStep('保存学生档案', async () => {
const saveButton = page.getByRole('button', { name: 'Save' });
await saveButton.click({ timeout: 5000 });
await page.waitForTimeout(2000);
console.log('学生档案已保存');
});
// 最后输出错误汇总
console.log('\n\n========== 测试执行完成 ==========');
// 显示损坏文件信息
if (corruptedFiles.length > 0) {
console.log('\n⚠ 文件损坏警告以下文件重试2次后仍然失败\n');
corruptedFiles.forEach((fileType, index) => {
console.log(` ${index + 1}. ${fileType} 文件 - 文件已损坏导致解析失败`);
});
console.log('');
}
// 生成错误报告文件
const generateErrorReport = () => {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const reportPath = path.join(process.cwd(), `test-2-student-workflow-report-${timestamp}.txt`);
let report = '========== 学生工作流程与AI建档测试报告 ==========\n\n';
report += `报告生成时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
report += '【测试账号信息】\n';
report += `测试账号: xdf.admin@applify.ai\n`;
report += `学生姓名: 黄子旭测试\n`;
report += `测试环境: https://pre.prodream.cn/en\n\n`;
report += `${'='.repeat(60)}\n\n`;
// 添加损坏文件警告
if (corruptedFiles.length > 0) {
report += '【AI建档 - 文件损坏警告】\n';
report += `发现 ${corruptedFiles.length} 个文件损坏重试2次后仍失败:\n\n`;
corruptedFiles.forEach((fileType, index) => {
report += ` ${index + 1}. ${fileType} 文件 - 文件已损坏导致解析失败, 请使用新文件重试\n`;
});
report += '\n';
report += `${'='.repeat(60)}\n\n`;
}
if (errors.length === 0 && corruptedFiles.length === 0) {
report += '✅ 所有功能测试通过,未发现问题!\n';
} else {
if (errors.length > 0) {
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 && corruptedFiles.length === 0) {
console.log('✅ 所有步骤执行成功!');
} else {
if (errors.length > 0) {
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}`);
}
});