```html Test Case Template - Login Feature

Test Case Management

Fresher Tester Template — v1.0

Dự án:
Tester:
Ngày test:
Pass Fail Blocked Not Run Nhấp vào ô để chỉnh sửa nội dung
Test Cases – Tính năng Đăng nhập
# TC ID Tên Test Case Precondition Các Bước Thực Hiện Kết Quả Mong Đợi Kết Quả Thực Tế Trạng Thái
Mẹo: Nhấn Tab để di chuyển giữa các ô. Template by QA Team

Ghi Chú / Notes

@x.c\n3. Nhập mật khẩu bất kỳ: Test123\n4. Nhấn nút "Đăng nhập"', expected: '- Không thực thi script\n- Hiển thị lỗi validate: "Email không hợp lệ"\n- Không gửi request đến server\n- Ứng dụng không bị crash', actual: '', status: 'notrun' } ]; let rowCounter = tcData.length; // ── Status config ───────────────────────────────────────────────── const statusConfig = { pass: { label: 'Pass', icon: 'fa-circle-check', cls: 'status-pass' }, fail: { label: 'Fail', icon: 'fa-circle-xmark', cls: 'status-fail' }, blocked: { label: 'Blocked', icon: 'fa-ban', cls: 'status-blocked' }, notrun: { label: 'Not Run', icon: 'fa-regular fa-circle', cls: 'status-notrun' }, }; // ── Render ──────────────────────────────────────────────────────── function render() { const tbody = document.getElementById('tcBody'); tbody.innerHTML = ''; tcData.forEach((tc, idx) => { const tr = document.createElement('tr'); tr.className = 'group transition-colors'; tr.dataset.idx = idx; tr.innerHTML = ` ${idx + 1} ${renderStatusSelect(tc.status, idx)} `; tbody.appendChild(tr); }); attachListeners(); updateStats(); } function renderStatusSelect(val, idx) { const cfg = statusConfig[val] || statusConfig.notrun; const opts = Object.entries(statusConfig).map(([k, v]) => `` ).join(''); return `
`; } // ── Event listeners ─────────────────────────────────────────────── function attachListeners() { document.querySelectorAll('#tcBody input[data-field], #tcBody textarea[data-field]').forEach(el => { el.addEventListener('input', e => { const { idx, field } = e.target.dataset; tcData[+idx][field] = e.target.value; if (field === 'status') updateStats(); }); }); } function onStatusChange(sel) { const idx = +sel.dataset.idx; const val = sel.value; tcData[idx].status = val; const cfg = statusConfig[val]; sel.className = `status-select ${cfg.cls} border rounded-lg px-2 py-1.5 text-xs font-semibold w-full focus:outline-none`; sel.previousElementSibling.querySelector('i').className = `fa-solid ${cfg.icon} text-xs`; updateStats(); } // ── Add / Delete ────────────────────────────────────────────────── function addRow() { rowCounter++; tcData.push({ id: `TC-LOGIN-00${rowCounter}`, name: '', pre: '', steps: '', expected: '', actual: '', status: 'notrun' }); render(); // Scroll & focus new row setTimeout(() => { const rows = document.querySelectorAll('#tcBody tr'); const last = rows[rows.length - 1]; if (last) { last.scrollIntoView({ behavior: 'smooth', block: 'center' }); const firstInput = last.querySelector('input, textarea'); if (firstInput) firstInput.focus(); } }, 50); } function deleteRow(idx) { if (tcData.length === 1) { alert('Phải có ít nhất 1 test case.'); return; } if (confirm(`Xóa "${tcData[idx].id || 'Test Case #' + (idx + 1)}"?`)) { tcData.splice(idx, 1); render(); } } // ── Stats ───────────────────────────────────────────────────────── function updateStats() { const counts = { pass: 0, fail: 0, blocked: 0, notrun: 0 }; tcData.forEach(tc => counts[tc.status]++); const total = tcData.length; document.getElementById('totalBadge').textContent = total + ' TCs'; document.getElementById('statsBar').innerHTML = ` ${counts.pass} ${counts.fail} ${counts.blocked} ${counts.notrun} `; } // ── Export CSV ──────────────────────────────────────────────────── function exportCSV() { const headers = ['TC ID','Tên Test Case','Precondition','Các Bước','Kết Quả Mong Đợi','Kết Quả Thực Tế','Trạng Thái']; const rows = tcData.map(tc => [ tc.id, tc.name, tc.pre, tc.steps, tc.expected, tc.actual, statusConfig[tc.status]?.label || tc.status ].map(v => `"${(v || '').replace(/"/g, '""')}"`)); const csv = [headers.join(','), ...rows.map(r => r.join(','))].join('\r\n'); const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'test_cases_login.csv'; a.click(); URL.revokeObjectURL(url); } // ── Utils ───────────────────────────────────────────────────────── function escHtml(str) { return (str || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } // ── Init ────────────────────────────────────────────────────────── document.getElementById('testDate').value = new Date().toISOString().split('T')[0]; render(); ```