Files
aurak/test-multiround.mjs
T
Developer c57c3028e2 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>
2026-06-08 22:34:04 +08:00

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); });