import { chromium } from 'playwright'; const BASE = 'http://localhost:13001'; const SA_REPLIES = ['需要审查代码质量和安全性', '还要检查逻辑正确性和性能问题']; /** Fill a textarea via native setter + input event (reliable for React controlled inputs) */ async function fillTextarea(page, text) { await page.evaluate((t) => { const ta = document.querySelector('textarea'); if (!ta) return; const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set; setter?.call(ta, t); ta.dispatchEvent(new Event('input', { bubbles: true })); }, text); await new Promise(r => setTimeout(r, 300)); } /** Click the send button (wait for it to be enabled, then regular or force click) */ async function clickSendButton(page) { try { await page.waitForFunction(() => { const btn = document.querySelector('button:has(svg.lucide-send)'); return btn && !btn.disabled && btn.offsetParent !== null; }, { timeout: 15000 }); await page.locator('button:has(svg.lucide-send)').last().click({ timeout: 5000 }); } catch { // Regular click failed (stale/detached element); wait briefly and force await new Promise(r => setTimeout(r, 1000)); await page.locator('button:has(svg.lucide-send)').last().click({ force: true, timeout: 5000 }).catch(() => {}); } } async function run() { console.log('=== AuraK 考核多轮对话测试 ===\n'); const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); // Login 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(' ✅ 登录成功'); // Assessment page await page.goto(`${BASE}/assessment`, { waitUntil: 'networkidle' }); await page.waitForTimeout(2000); // Select template await page.locator('button:has-text("AI协作技巧")').first().click(); await page.waitForTimeout(500); await page.locator('button:has-text("开始专业评估")').first().click(); // Wait for first question console.log('[2] 等待出题...'); for (let i = 0; i < 90; i++) { const text = await page.textContent('body').catch(() => ''); if (text.includes('问题 ') || text.includes('Question ')) break; await new Promise(r => setTimeout(r, 2000)); } console.log(' ✅ 第 1 题已出现'); // Wait spinner to finish await page.waitForFunction(() => !document.querySelector('.animate-spin'), { timeout: 60000 }).catch(() => {}); await new Promise(r => setTimeout(r, 2000)); // Answer questions let qIdx = 1; let saCount = 0, followUpCount = 0; const totalQs = 4; while (qIdx <= totalQs) { const state = await page.evaluate(() => { const choiceBtns = Array.from(document.querySelectorAll('button')) .filter(b => /^[A-D]/.test(b.textContent || '') && (b.textContent || '').length > 5) .filter(b => !(b.textContent || '').startsWith('AuraK')) .filter(b => !(b.textContent || '').startsWith('Admin')); const ta = document.querySelector('textarea'); return { choiceCount: choiceBtns.length, hasTextarea: ta !== null && ta.offsetParent !== null, firstChoice: choiceBtns[0]?.textContent || '', busy: document.querySelector('.animate-spin') !== null, }; }); if (state.busy) { await new Promise(r => setTimeout(r, 2000)); continue; } if (state.choiceCount > 0) { // ── CHOICE ── console.log(`\n 🟦 第 ${qIdx}/${totalQs} 题 (选择) "${state.firstChoice.substring(0, 30)}..."`); const btns = page.locator('button.w-full.text-left.px-5.py-4'); const count = await btns.count(); if (count > 0) { await btns.first().click(); await new Promise(r => setTimeout(r, 500)); } const confirm = page.locator('button:has-text("确认答案")'); if (await confirm.isVisible().catch(() => false)) { await confirm.click(); console.log(` ✅ 已提交`); } qIdx++; } else if (state.hasTextarea) { // ── SHORT ANSWER ── saCount++; const replyIdx = Math.min(saCount - 1, SA_REPLIES.length - 1); console.log(`\n 🟩 第 ${qIdx}/${totalQs} 题 (简答 #${saCount})`); await page.locator('textarea').first().click(); await fillTextarea(page, SA_REPLIES[replyIdx]); console.log(` 📝 已输入: "${SA_REPLIES[replyIdx].substring(0, 20)}..."`); await clickSendButton(page); console.log(` ✅ 已提交`); // Wait for grading await new Promise(r => setTimeout(r, 3000)); await page.waitForFunction(() => !document.querySelector('.animate-spin'), { timeout: 60000 }).catch(() => {}); await new Promise(r => setTimeout(r, 2000)); // Check for follow-up: textarea visible again at same question position const stillTA = await page.evaluate(() => { const ta = document.querySelector('textarea'); return ta !== null && ta.offsetParent !== null; }); if (stillTA && followUpCount < SA_REPLIES.length - 1) { followUpCount++; const fReply = SA_REPLIES[Math.min(followUpCount, SA_REPLIES.length - 1)]; console.log(` 🔄 AI 追问 #${followUpCount} 已触发!`); await page.locator('textarea').first().click(); await fillTextarea(page, fReply); console.log(` 📝 追问已输入: "${fReply.substring(0, 20)}..."`); await clickSendButton(page); console.log(` ✅ 追问已提交`); await page.waitForFunction(() => !document.querySelector('.animate-spin'), { timeout: 60000 }).catch(() => {}); await new Promise(r => setTimeout(r, 2000)); } qIdx++; } else { // Wait for anything to appear console.log(` ⏳ 等待第 ${qIdx} 题...`); await new Promise(r => setTimeout(r, 3000)); continue; } // Wait for next question await page.waitForFunction(() => !document.querySelector('.animate-spin'), { timeout: 30000 }).catch(() => {}); await new Promise(r => setTimeout(r, 2000)); } // Results await new Promise(r => setTimeout(r, 4000)); const body = await page.textContent('body'); const scores = body.match(/\d+\/10/g); console.log(`\n 📊 结果:`); console.log(` 选择题: ${totalQs - saCount}`); console.log(` 简答题: ${saCount}`); console.log(` AI追问: ${followUpCount}`); console.log(` 分数: ${scores ? scores.join(', ') : '无'}`); if (followUpCount > 0) { console.log(`\n 🎉 多轮对话正常工作!`); } else if (saCount > 0) { console.log(`\n ✅ 简答题正常回答,未触发追问(回答已完整)。`); } else { console.log(`\n ⚠️ 未遇到简答题,需要确认 shuffle 是否生效。`); } await browser.close(); console.log('\n=== 完成 ==='); } run().catch(e => { console.error('\n❌', e.message); process.exit(1); });