import { chromium } from 'playwright'; const BASE = 'http://localhost:13001'; async function waitForSpinner(page) { await page.waitForFunction(() => !document.querySelector('.animate-spin'), { timeout: 60000 }).catch(() => {}); await new Promise(r => setTimeout(r, 1000)); } /** Extract the last assistant message (question text) */ async function getLastQuestion(page) { return await page.evaluate(() => { // Find all message bubbles: elements with px-5 py-4 classes const allBubbles = Array.from(document.querySelectorAll('.px-5.py-4')); // The last one that's from assistant (white bg, not indigo) is the question for (let i = allBubbles.length - 1; i >= 0; i--) { const el = allBubbles[i]; const text = el.textContent || ''; const style = el.getAttribute('class') || ''; // Skip user messages (indigo bg) and empty/footer text if (style.includes('bg-indigo')) continue; if (text.length < 20) continue; return text.substring(0, 600); } return ''; }); } async function run() { console.log('=== 🧑‍🎓 我来做题! ===\n'); const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); // 登录 console.log('[1] 登录...'); 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('**/'); console.log(' ✅ 登录成功'); // 进入考核 await page.goto(`${BASE}/assessment`, { waitUntil: 'networkidle' }); await page.waitForTimeout(2000); await page.locator('button:has-text("AI协作技巧")').first().click(); await page.waitForTimeout(500); await page.locator('button:has-text("开始专业评估")').first().click(); // 等出题 console.log('\n[2] 等待出题...'); for (let i = 0; i < 120; i++) { const text = await page.textContent('body').catch(() => ''); if (text.includes('问题 ') || text.includes('Question ')) break; await new Promise(r => setTimeout(r, 2000)); } await waitForSpinner(page); console.log(' ✅ 题目已加载\n'); // 答题 let qIdx = 1; const totalQs = 4; while (qIdx <= totalQs) { // 判断是选择题还是简答题 const state = await page.evaluate(() => { // 选择题选项按钮:CSS类 w-full text-left px-5 py-4 const optionBtns = Array.from(document.querySelectorAll('button.w-full.text-left.px-5.py-4')) .filter(b => !b.textContent?.includes('确认答案')); // textarea 表示简答题 const ta = document.querySelector('textarea'); const busy = document.querySelector('.animate-spin') !== null; // 确认答案按钮:找 button 文字包含"确认" const confirmBtns = Array.from(document.querySelectorAll('button')) .filter(b => (b.textContent || '').includes('确认')); return { optionCount: optionBtns.length, optionTexts: optionBtns.map(b => b.textContent?.trim() || ''), hasTextarea: ta !== null && ta.offsetParent !== null, hasConfirmBtn: confirmBtns.length > 0, busy, }; }); if (state.busy) { await new Promise(r => setTimeout(r, 2000)); continue; } // 读取题目 const question = await getLastQuestion(page); console.log(`\n═══ 第 ${qIdx}/${totalQs} 题 ═══`); console.log(`📖 ${question}\n`); if (state.optionCount > 0) { // ── 选择题 ── console.log('📋 选项:'); state.optionTexts.forEach((t, i) => { console.log(` ${String.fromCharCode(65 + i)}) ${t}`); }); console.log(''); // 凭常识选题 const btns = page.locator('button.w-full.text-left.px-5.py-4'); const count = await btns.count(); // 根据题目内容推理 const qLower = question.toLowerCase(); const texts = state.optionTexts.map(t => t.toLowerCase()); let chosen = 1; // default B if (qLower.includes('提示词') || qLower.includes('prompt') || qLower.includes('prompts')) { chosen = texts.findIndex(t => t.includes('清晰') || t.includes('具体') || t.includes('举例') || t.includes('角色')); } else if (qLower.includes('安全') || qLower.includes('敏感') || qLower.includes('泄露')) { chosen = texts.findIndex(t => t.includes('脱敏') || t.includes('敏感') || t.includes('安全')); } else if (qLower.includes('测试') || qLower.includes('测试用例')) { chosen = texts.findIndex(t => t.includes('测试') || t.includes('质量') || t.includes('验证')); } else if (qLower.includes('选型') || qLower.includes('成本') || qLower.includes('模型')) { chosen = texts.findIndex(t => t.includes('成本') || t.includes('任务') || t.includes('性价比')); } else if (qLower.includes('代码审查') || qLower.includes('review')) { chosen = texts.findIndex(t => t.includes('安全') || t.includes('质量') || t.includes('逻辑')); } else if (qLower.includes('幻觉') || qLower.includes('hallucination')) { chosen = texts.findIndex(t => t.includes('事实') || t.includes('验证') || t.includes('核对')); } if (chosen < 0) chosen = 1; await btns.nth(chosen).click(); await new Promise(r => setTimeout(r, 500)); console.log(` 👉 我选 ${String.fromCharCode(65 + chosen)}`); // 提交 if (state.hasConfirmBtn) { await page.locator('button:has-text("确认答案")').click(); console.log(' ✅ 提交'); } // 如果有下一题的过渡 qIdx++; } else if (state.hasTextarea) { // ── 简答题 ── console.log('✏️ 简答题,我来回答:\n'); // 根据题目内容生成回答 const qLower = question.toLowerCase(); let answer = ''; if (qLower.includes('测试') || qLower.includes('测试用例') || (qLower.includes('写代码') && qLower.includes('测试'))) { answer = '我觉得即使有AI写代码,测试还是必须写的。AI写的代码也可能有bug,需要人工验证。测试不只是找bug,还能帮我们理解代码逻辑。而且测试用例本身也是需求的一部分,能告诉我们代码应该怎么用。AI可以帮我们写测试代码,但不能完全代替人去思考测试场景。'; } else if (qLower.includes('提示词') || qLower.includes('prompt')) { answer = '写提示词要清晰具体,告诉AI它的角色是什么。可以给例子,让AI理解格式要求。复杂任务可以让AI一步一步思考。也要注意测试不同提示词的效果,找到最合适的。'; } else if (qLower.includes('安全') || qLower.includes('敏感') || qLower.includes('泄露')) { answer = '要注意不要把敏感信息发给AI,比如密码、API密钥、客户数据。如果要用真实数据,要先脱敏处理。也要检查AI生成的代码有没有安全漏洞。'; } else if (qLower.includes('代码审查') || qLower.includes('review') || qLower.includes('代码质量')) { answer = '代码评审要看代码的功能是否正确,有没有bug。还要看代码风格是否一致,性能好不好,有没有安全问题。AI可以帮我们做一部分检查,但最终还是需要人来做判断。'; } else if (qLower.includes('ai协作') || qLower.includes('ai合作') || qLower.includes('协作技巧')) { answer = '和AI协作要分工明确,AI做它擅长的(生成、总结、分析),人做判断和决策。要给AI清晰的任务描述,分步骤沟通。AI生成的内容要自己核实,不能完全相信。'; } else { answer = '我觉得首先要理解问题的本质,然后让AI帮忙分析。AI的输出要结合自己的经验和判断。不能完全依赖AI,要多验证AI给出的结果是否合理。安全意识和质量控制很重要。'; } // 输入回答 await page.locator('textarea').first().click(); await page.evaluate((text) => { const ta = document.querySelector('textarea'); if (!ta) return; const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set; setter?.call(ta, text); ta.dispatchEvent(new Event('input', { bubbles: true })); }, answer); await new Promise(r => setTimeout(r, 800)); console.log(` 💬 "${answer.substring(0, 50)}..."`); // 等 button 可用再点 await page.waitForFunction(() => { const btn = document.querySelector('button:has(svg.lucide-send)'); return btn && !btn.disabled && btn.offsetParent !== null; }, { timeout: 15000 }).catch(() => {}); await page.locator('button:has(svg.lucide-send)').last().click({ timeout: 8000 }).catch(() => { page.locator('button:has(svg.lucide-send)').last().click({ force: true, timeout: 5000 }).catch(() => {}); }); console.log(' ✅ 已提交\n'); // 等批改 await waitForSpinner(page); // 检查是否有追问 const stillTA = await page.evaluate(() => { const ta = document.querySelector('textarea'); return ta !== null && ta.offsetParent !== null; }); if (stillTA) { console.log(' 🔄 AI追问来了!再回答一轮'); // 读追问的题目 const followQ = await getLastQuestion(page); console.log(` 📖 追问: "${followQ.substring(0, 100)}..."`); const followAnswer = '还要看代码的可维护性和可读性,团队协作时需要统一的代码风格。也要考虑性能优化和异常处理。总之AI是工具,人是决策者,不能把责任推给AI。'; await page.locator('textarea').first().click(); await page.evaluate((text) => { const ta = document.querySelector('textarea'); if (!ta) return; const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set; setter?.call(ta, text); ta.dispatchEvent(new Event('input', { bubbles: true })); }, followAnswer); await new Promise(r => setTimeout(r, 800)); await page.waitForFunction(() => { const btn = document.querySelector('button:has(svg.lucide-send)'); return btn && !btn.disabled; }, { timeout: 10000 }).catch(() => {}); await page.locator('button:has(svg.lucide-send)').last().click({ timeout: 5000 }).catch(() => { page.locator('button:has(svg.lucide-send)').last().click({ force: true, timeout: 3000 }).catch(() => {}); }); console.log(' ✅ 追问已回答'); await waitForSpinner(page); } qIdx++; } else { console.log(' ⏳ 等待...'); await new Promise(r => setTimeout(r, 3000)); continue; } // 等过渡 await waitForSpinner(page); } // 结果 console.log('\n═══ 考核结果 ═══'); await new Promise(r => setTimeout(r, 5000)); await page.waitForFunction(() => !document.querySelector('.animate-spin'), { timeout: 60000 }).catch(() => {}); const result = await page.evaluate(() => { const body = document.body.textContent || ''; const scoreMatch = body.match(/(\d+\.?\d*)\/10/); const levelMatch = body.match(/等级.*?(\w+)/i) || body.match(/LEVEL:\s*(\w+)/i); const finalScoreMatch = body.match(/最终得分[::]\s*(\d+\.?\d*)/); const passMatch = body.includes('合格') || body.includes('VERIFIED'); const failMatch = body.includes('不合格') || body.includes('FAIL'); // 各题得分 const allScores = Array.from(body.matchAll(/(\d+)\/10/g)).map(m => m[1]); return { score: scoreMatch?.[1] || finalScoreMatch?.[1] || '?', level: levelMatch?.[1] || '?', passed: passMatch, failed: failMatch, allScores: allScores.join(', '), }; }); console.log(` 📊 各题得分: ${result.allScores || '无'}`); console.log(` 🏆 等级: ${result.level}`); console.log(` ${result.passed ? '🎉 合格!' : result.failed ? '😅 不合格...' : '...'}`); await page.screenshot({ path: 'assessment-result-beginner.png', fullPage: true }); console.log(' 📸 截图已保存'); await browser.close(); console.log('\n=== 完成 ==='); } run().catch(e => { console.error('\n❌', e.message); process.exit(1); });