155 lines
6.0 KiB
HTML
155 lines
6.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Tester - Test Plan Editor</title>
|
|
<style>
|
|
body { font-family: system-ui, -apple-system, sans-serif; background: #0b1120; color: #e2e8f0; margin: 0; padding: 20px; }
|
|
.container { max-width: 1400px; margin: 0 auto; }
|
|
h1 { color: #f59e0b; }
|
|
.plan-header { background: #1e2937; padding: 16px; border-radius: 8px; margin-bottom: 20px; }
|
|
.test-card { background: #1e2937; border: 1px solid #334155; border-radius: 8px; padding: 16px; margin-bottom: 16px; }
|
|
.test-header { font-weight: 600; color: #f59e0b; margin-bottom: 8px; }
|
|
textarea, select, input { background: #0f172a; border: 1px solid #334155; color: #e2e8f0; padding: 8px; border-radius: 4px; width: 100%; box-sizing: border-box; }
|
|
.field { margin-bottom: 10px; }
|
|
.label { font-size: 12px; color: #94a3b8; margin-bottom: 4px; display: block; }
|
|
.status-pass { color: #4ade80; }
|
|
.status-fail { color: #f87171; }
|
|
.auto-save { font-size: 12px; color: #64748b; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="plan-header">
|
|
<h1 id="planName"></h1>
|
|
<div id="planMeta" style="font-size:13px; color:#94a3b8;"></div>
|
|
<div class="auto-save" id="saveStatus"></div>
|
|
</div>
|
|
|
|
<div id="testsContainer"></div>
|
|
</div>
|
|
|
|
<script>
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const planId = urlParams.get('plan');
|
|
|
|
let plan = null;
|
|
let saveTimeout = null;
|
|
|
|
async function loadPlan() {
|
|
if (!planId) {
|
|
document.body.innerHTML = '<h1>No plan specified. Use ?plan=ID</h1>';
|
|
return;
|
|
}
|
|
|
|
const res = await fetch(`/api/test-plans/${planId}`);
|
|
if (!res.ok) {
|
|
document.body.innerHTML = '<h1>Plan not found</h1>';
|
|
return;
|
|
}
|
|
|
|
plan = await res.json();
|
|
document.getElementById('planName').textContent = plan.name;
|
|
document.getElementById('planMeta').textContent = `Created: ${new Date(plan.createdAt).toLocaleString()} • ${plan.tests.length} tests`;
|
|
|
|
renderTests();
|
|
}
|
|
|
|
function renderTests() {
|
|
const container = document.getElementById('testsContainer');
|
|
container.innerHTML = '';
|
|
|
|
plan.tests.forEach((test, index) => {
|
|
const div = document.createElement('div');
|
|
div.className = 'test-card';
|
|
div.innerHTML = `
|
|
<div class="test-header">${test.id}</div>
|
|
<div><strong>${test.description}</strong></div>
|
|
<div style="margin: 8px 0; font-size:12px; color:#94a3b8;">Group: ${test.group}</div>
|
|
|
|
<div class="field">
|
|
<span class="label">Steps</span>
|
|
<div style="font-size:12px; white-space: pre-line; background:#0f172a; padding:8px; border-radius:4px;">
|
|
${(test.steps || []).map(s => '• ' + s).join('\n')}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<span class="label">Expected Result</span>
|
|
<textarea data-field="expectedResult" data-index="${index}">${test.expectedResult || ''}</textarea>
|
|
</div>
|
|
|
|
<div style="display:flex; gap:12px;">
|
|
<div class="field" style="flex:1;">
|
|
<span class="label">Severity</span>
|
|
<select data-field="severity" data-index="${index}">
|
|
<option ${test.severity === 'Low' ? 'selected' : ''}>Low</option>
|
|
<option ${test.severity === 'Medium' ? 'selected' : ''}>Medium</option>
|
|
<option ${test.severity === 'High' ? 'selected' : ''}>High</option>
|
|
<option ${test.severity === 'Critical' ? 'selected' : ''}>Critical</option>
|
|
</select>
|
|
</div>
|
|
<div class="field" style="flex:1;">
|
|
<span class="label">Status</span>
|
|
<select data-field="testerStatus" data-index="${index}">
|
|
<option ${test.testerStatus === 'N/A' ? 'selected' : ''}>N/A</option>
|
|
<option ${test.testerStatus === 'Pass' ? 'selected' : ''}>Pass</option>
|
|
<option ${test.testerStatus === 'Fail' ? 'selected' : ''}>Fail</option>
|
|
<option ${test.testerStatus === 'Blocked' ? 'selected' : ''}>Blocked</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<span class="label">Actual Result / Comments</span>
|
|
<textarea data-field="actualResult" data-index="${index}" style="min-height:60px;">${test.actualResult || ''}</textarea>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<span class="label">Additional Comments</span>
|
|
<textarea data-field="comments" data-index="${index}">${test.comments || ''}</textarea>
|
|
</div>
|
|
`;
|
|
|
|
// Attach auto-save listeners
|
|
div.querySelectorAll('textarea, select').forEach(el => {
|
|
el.addEventListener('input', () => scheduleSave(index, el.dataset.field, el.value));
|
|
el.addEventListener('change', () => scheduleSave(index, el.dataset.field, el.value));
|
|
});
|
|
|
|
container.appendChild(div);
|
|
});
|
|
}
|
|
|
|
function scheduleSave(testIndex, field, value) {
|
|
plan.tests[testIndex][field] = value;
|
|
|
|
document.getElementById('saveStatus').textContent = 'Saving...';
|
|
|
|
clearTimeout(saveTimeout);
|
|
saveTimeout = setTimeout(async () => {
|
|
try {
|
|
const res = await fetch(`/api/test-plans/${planId}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(plan)
|
|
});
|
|
|
|
if (res.ok) {
|
|
document.getElementById('saveStatus').textContent = `Auto-saved at ${new Date().toLocaleTimeString()}`;
|
|
setTimeout(() => {
|
|
const el = document.getElementById('saveStatus');
|
|
if (el) el.textContent = '';
|
|
}, 2000);
|
|
}
|
|
} catch (e) {
|
|
document.getElementById('saveStatus').textContent = 'Save failed';
|
|
}
|
|
}, 800);
|
|
}
|
|
|
|
loadPlan();
|
|
</script>
|
|
</body>
|
|
</html> |