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

View File

@ -0,0 +1,462 @@
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}`);
}
});