fix: shuffleArray bug + Playwright多轮对话测试 + 初学者考核脚本

- 修复 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>
This commit is contained in:
Developer
2026-06-08 22:34:04 +08:00
parent 0b2c6563ba
commit c57c3028e2
6 changed files with 640 additions and 5 deletions
+156
View File
@@ -0,0 +1,156 @@
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); });