forked from hangshuo652/aurak
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>
188 lines
7.1 KiB
JavaScript
188 lines
7.1 KiB
JavaScript
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); });
|