webtest-agent v1.0.0: AI网页测试智能体(LLM决策+agent-browser执行+断言+报告)
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
/* webtest-agent 前端逻辑 */
|
||||
const $ = s => document.querySelector(s);
|
||||
const API = '';
|
||||
|
||||
async function refreshHealth() {
|
||||
try {
|
||||
const r = await fetch(API + '/health');
|
||||
const d = await r.json();
|
||||
$('#health').textContent = `服务正常 | 模型: ${d.llm_model} | 运行中任务: ${d.concurrent}`;
|
||||
$('#health').classList.add('ok');
|
||||
} catch (e) {
|
||||
$('#health').textContent = '服务异常';
|
||||
$('#health').classList.remove('ok');
|
||||
}
|
||||
}
|
||||
|
||||
const STATUS_MAP = {
|
||||
queued: '排队中', running: '测试中', finished: '已完成', stopped: '已停止'
|
||||
};
|
||||
const RESULT_MAP = {
|
||||
pending: '待定', pass: '✅ 通过', fail: '❌ 失败', error: '⚠️ 错误', stopped: '⏹️ 停止'
|
||||
};
|
||||
|
||||
function esc(s) {
|
||||
return String(s ?? '').replace(/[&<>"']/g, c => (
|
||||
{'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
async function loadTasks() {
|
||||
try {
|
||||
const r = await fetch(API + '/api/tasks');
|
||||
const d = await r.json();
|
||||
const tb = $('#task-table tbody');
|
||||
if (!d.tasks.length) {
|
||||
tb.innerHTML = '<tr><td colspan="8" class="empty">暂无任务,先发起一个吧</td></tr>';
|
||||
return;
|
||||
}
|
||||
tb.innerHTML = d.tasks.map(t => {
|
||||
const ops = t.status === 'running' || t.status === 'queued'
|
||||
? `<button class="btn small" onclick="stopTask('${t.id}')">停止</button> `
|
||||
: '';
|
||||
const report = t.status === 'finished'
|
||||
? `<a class="report-link" href="#" onclick="openReport('${t.id}');return false;">查看报告</a>`
|
||||
: '—';
|
||||
return `<tr>
|
||||
<td>${esc(t.id)}</td>
|
||||
<td>${esc(t.url)}</td>
|
||||
<td><div class="goal-cell" title="${esc(t.goal)}">${esc(t.goal)}</div></td>
|
||||
<td><span class="status ${esc(t.status)}">${STATUS_MAP[t.status] || esc(t.status)}</span></td>
|
||||
<td><span class="result ${esc(t.result)}">${RESULT_MAP[t.result] || esc(t.result)}</span></td>
|
||||
<td>${t.steps || 0}</td>
|
||||
<td>${esc(t.created)}</td>
|
||||
<td>${ops}${report}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
} catch (e) {
|
||||
$('#task-table tbody').innerHTML = '<tr><td colspan="8" class="empty">加载失败</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
async function stopTask(tid) {
|
||||
if (!confirm('确定停止该任务?')) return;
|
||||
await fetch(API + `/api/tasks/${tid}/stop`, { method: 'POST' });
|
||||
setTimeout(loadTasks, 500);
|
||||
}
|
||||
|
||||
function openReport(tid) {
|
||||
$('#report-frame').src = API + `/api/tasks/${tid}/report`;
|
||||
$('#modal-title').textContent = `测试报告 - ${tid}`;
|
||||
$('#report-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
$('#modal-close').onclick = () => $('#report-modal').classList.add('hidden');
|
||||
$('#report-modal').onclick = e => { if (e.target === $('#report-modal')) $('#report-modal').classList.add('hidden'); };
|
||||
|
||||
$('#submit').onclick = async () => {
|
||||
const url = $('#url').value.trim();
|
||||
const goal = $('#goal').value.trim();
|
||||
if (!url || !goal) { alert('请填写网址和测试目标'); return; }
|
||||
const btn = $('#submit');
|
||||
btn.disabled = true; btn.textContent = '提交中...';
|
||||
try {
|
||||
const r = await fetch(API + '/api/tasks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url, goal,
|
||||
max_steps: parseInt($('#max_steps').value) || 30,
|
||||
timeout: parseInt($('#timeout').value) || 600
|
||||
})
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.error) { alert('提交失败: ' + d.error); }
|
||||
else {
|
||||
$('#goal').value = '';
|
||||
loadTasks();
|
||||
// 轮询直到该任务结束
|
||||
pollTask(d.task_id);
|
||||
}
|
||||
} catch (e) { alert('提交失败: ' + e); }
|
||||
finally { btn.disabled = false; btn.textContent = '🚀 开始测试'; }
|
||||
};
|
||||
|
||||
function pollTask(tid, count = 0) {
|
||||
if (count > 400) return;
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const r = await fetch(API + `/api/tasks/${tid}`);
|
||||
const t = await r.json();
|
||||
loadTasks();
|
||||
if (t.status === 'finished' || t.status === 'stopped') return;
|
||||
pollTask(tid, count + 1);
|
||||
} catch (e) { /* ignore */ }
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
$('#refresh').onclick = loadTasks;
|
||||
|
||||
refreshHealth();
|
||||
loadTasks();
|
||||
setInterval(refreshHealth, 30000);
|
||||
setInterval(loadTasks, 5000);
|
||||
Reference in New Issue
Block a user