```html
Test Case Template - Login Feature
Test Case Management
Fresher Tester Template — v1.0
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
@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();
```