c57c3028e2
- 修复 shuffleArray 返回新数组但调用处用 const 未接收返回值(3处) - 新增 test-multiround.mjs Playwright 多轮对话测试(简答+追问全流程) - 新增 do-assessment.mjs / check-result.mjs 考核体验脚本 - CLAUDE.md 增加 AI 工作流指令规则 - package.json 添加 playwright 依赖 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
157 lines
6.4 KiB
JavaScript
157 lines
6.4 KiB
JavaScript
import { chromium } from 'playwright';
|
||
|
||
const BASE = 'http://localhost:13001';
|
||
|
||
async function run() {
|
||
const browser = await chromium.launch({ headless: true });
|
||
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
||
|
||
// 登录
|
||
await page.goto(`${BASE}/login`, { waitUntil: 'networkidle' });
|
||
await page.waitForTimeout(1000);
|
||
await page.locator('input[type="text"]').first().fill('admin');
|
||
await page.locator('input[type="password"]').first().fill('admin123');
|
||
await page.locator('button[type="submit"]').click();
|
||
await page.waitForURL('**/');
|
||
|
||
// 进入考核页(首页会显示历史记录侧栏)
|
||
await page.goto(`${BASE}/assessment`, { waitUntil: 'networkidle' });
|
||
await page.waitForTimeout(3000);
|
||
|
||
// 截图1:首页(含历史记录侧栏)
|
||
await page.screenshot({ path: 'assessment-overview.png', fullPage: true });
|
||
console.log('📸 1/5 首页截图(含历史侧栏)保存');
|
||
|
||
// 看看历史记录里有什么
|
||
const historyInfo = await page.evaluate(() => {
|
||
const items = Array.from(document.querySelectorAll('.w-80 div.space-y-3 > div'));
|
||
return items.map(el => ({
|
||
text: (el.textContent || '').replace(/\s+/g, ' ').trim(),
|
||
}));
|
||
});
|
||
console.log('\n📋 历史记录:');
|
||
historyInfo.forEach((h, i) => console.log(` [${i+1}] ${h.text}`));
|
||
|
||
if (historyInfo.length === 0) {
|
||
console.log(' 没有历史记录,可能是空状态');
|
||
await browser.close();
|
||
return;
|
||
}
|
||
|
||
// 点击第一条历史记录查看详情(选分数最高的那条)
|
||
// 找分数最高的:解析数字
|
||
let bestIdx = 0;
|
||
let bestScore = -1;
|
||
historyInfo.forEach((h, i) => {
|
||
const m = h.text.match(/([\d.]+)\/10/);
|
||
if (m) {
|
||
const s = parseFloat(m[1]);
|
||
if (s > bestScore) { bestScore = s; bestIdx = i; }
|
||
}
|
||
});
|
||
console.log(`\n🔍 选择分数最高的记录 #${bestIdx+1} (${bestScore}/10)`);
|
||
|
||
// 历史记录在右侧边栏,每条记录最右边有个查看按钮(FileText图标)
|
||
const histButtons = page.locator('.w-80 div.space-y-3 > div button');
|
||
const btnCount = await histButtons.count();
|
||
console.log(` 右侧历史栏共有 ${btnCount} 个按钮`);
|
||
|
||
// 每条记录有2个按钮(删除+查看),查看按钮在最后
|
||
// 第N条记录的查看按钮索引 = (N * 2 + 1) (从0开始)
|
||
const viewBtnIdx = bestIdx * 2 + 1;
|
||
if (viewBtnIdx < btnCount) {
|
||
await histButtons.nth(viewBtnIdx).click();
|
||
await new Promise(r => setTimeout(r, 3000));
|
||
|
||
// 截图2:历史考核详情页
|
||
await page.screenshot({ path: 'assessment-history-detail.png', fullPage: true });
|
||
console.log('📸 2/5 考核详情页截图');
|
||
|
||
// 看看详情页有什么内容
|
||
const detailInfo = await page.evaluate(() => {
|
||
const body = document.body.textContent || '';
|
||
const scoreMatch = body.match(/([\d.]+)\/10/g);
|
||
const levelMatch = body.match(/(?:LEVEL|等级)[::]\s*(\w+)/i);
|
||
const reportSection = body.includes('综合报告') || body.includes('comprehensive');
|
||
const detailSection = body.includes('每题详情') || body.includes('details');
|
||
const hasPassed = body.includes('合格') || body.includes('VERIFIED');
|
||
|
||
// 按钮文字
|
||
const btns = Array.from(document.querySelectorAll('button'))
|
||
.map(b => (b.textContent || '').trim())
|
||
.filter(Boolean);
|
||
|
||
return { scores: scoreMatch, level: levelMatch?.[1], reportSection, detailSection, hasPassed, btns };
|
||
});
|
||
|
||
console.log(`\n📊 得分列表: ${detailInfo.scores?.join(', ') || '无'}`);
|
||
console.log(`🏆 等级: ${detailInfo.level || '未显示'}`);
|
||
console.log(`✅ 合格: ${detailInfo.hasPassed ? '是' : '否'}`);
|
||
console.log(`📋 每题详情: ${detailInfo.detailSection ? '✅ 有' : '❌ 无'}`);
|
||
console.log(`📝 综合报告: ${detailInfo.reportSection ? '✅ 有' : '❌ 无'}`);
|
||
console.log(`\n🔘 按钮列表:`);
|
||
detailInfo.btns.forEach(b => console.log(` - ${b}`));
|
||
|
||
// 找"查看证书"按钮
|
||
const certBtnText = detailInfo.btns.find(b =>
|
||
b.includes('证书') || b.includes('Certificate') || b.includes('certificate')
|
||
);
|
||
console.log(`\n🔖 证书按钮: ${certBtnText || '没找到'}`);
|
||
|
||
// 如果有证书按钮,点击它
|
||
if (certBtnText) {
|
||
const certBtn = page.locator('button', { hasText: /证书|Certificate|certificate/ });
|
||
if (await certBtn.isVisible().catch(() => false)) {
|
||
await certBtn.click();
|
||
await new Promise(r => setTimeout(r, 2000));
|
||
|
||
// 截图3:证书弹窗
|
||
await page.screenshot({ path: 'assessment-certificate-modal.png', fullPage: true });
|
||
console.log('📸 3/5 证书弹窗截图');
|
||
|
||
// 读取证书弹窗内容
|
||
const certData = await page.evaluate(() => {
|
||
// 找 portal(弹窗在 document.body 最下层)
|
||
const modal = document.querySelector('.fixed.inset-0.z-\\[1000\\]');
|
||
if (!modal) return { found: false };
|
||
|
||
const text = modal.textContent || '';
|
||
const level = text.match(/(\w+)/)?.[1] || '';
|
||
const totalScore = text.match(/([\d.]+)\/10/)?.[1] || '';
|
||
const dimScores = Array.from(text.matchAll(/(\w+)\s*([\d.]+)\/10/g))
|
||
.map(m => `${m[1]}: ${m[2]}/10`);
|
||
const questionCount = text.match(/题目列表[\s\S]*?#(\d+)/)?.[1] || '';
|
||
|
||
return {
|
||
found: true,
|
||
text: text.substring(0, 500),
|
||
level, totalScore, dimScores, questionCount,
|
||
};
|
||
});
|
||
|
||
if (certData.found) {
|
||
console.log(`\n📜 证书内容:`);
|
||
console.log(` 等级: ${certData.level}`);
|
||
console.log(` 总分: ${certData.totalScore}/10`);
|
||
console.log(` 维度得分: ${certData.dimScores.join(', ') || '无'}`);
|
||
console.log(` 题目数: ${certData.questionCount || '未知'}`);
|
||
}
|
||
|
||
// 关掉弹窗
|
||
await page.keyboard.press('Escape');
|
||
await new Promise(r => setTimeout(r, 500));
|
||
}
|
||
}
|
||
|
||
// 看 PDF 和 Excel 导出
|
||
const hasPdf = detailInfo.btns.some(b => b.includes('PDF'));
|
||
const hasExcel = detailInfo.btns.some(b => b.includes('Excel') || b.includes('excel'));
|
||
console.log(`\n📄 PDF下载: ${hasPdf ? '✅ 有' : '❌ 无'}`);
|
||
console.log(`📊 Excel导出: ${hasExcel ? '✅ 有' : '❌ 无'}`);
|
||
}
|
||
|
||
await browser.close();
|
||
console.log('\n=== 完成 ===');
|
||
}
|
||
run().catch(e => { console.error('❌', e.message); process.exit(1); });
|