Menjaring baris E2E yang bocor dengan test.afterAll
Jaring pengaman test.afterAll membersihkan baris E2E yang bocor saat test timeout: identitas unik per worker, sapuan terbatas, dan delete idempoten.
Ringkasan
Di KotaPortal, grid announcements kebanjiran baris sisa karena test E2E-nya timeout jadi DELETE nggak pernah jalan. Fixnya nambahin hook afterAll di spec informasi-crud: login sekali, terus hapus baris berjudul persis judul run atau yang lebih tua dari awal run, plus delete dua langkah idempoten. Hasilnya baris run lama bersih tanpa ngganggu run lain.
Grid admin/announcements di KotaPortal masih menampilkan satu baris informasi setelah suite E2E selesai, padahal speknya berstatus lulus. Pemeriksaan menunjukkan test tersebut melewati batas waktu, sehingga langkah DELETE di dalam badan test tidak pernah berjalan dan barisnya bocor. Playwright memang memisahkan jalur hook dari badan test: afterAll dieksekusi satu kali per worker setelah semua test dalam file [1].
Perbaikannya terpasang di frontend/e2e/admin/informasi-crud.spec.ts lewat commit 262fa07 (+71/-4, 16 Oktober 2026): satu hook test.afterAll sebagai jaring pengaman. Komentar di spek menyebutnya jaring pengaman karena hook ini tetap berjalan walau test gagal atau timeout, sementara langkah DELETE di dalam badan test tidak. Tiga aturan yang dibuktikan commit ini: identitas baris unik per worker, penyapuan yang dibatasi baris milik sendiri plus baris yang lebih tua dari awal run, dan delete dua langkah yang idempoten.
Prasyarat. Playwright Test, satu spec CRUD admin yang membuat baris lewat API, dan kredensial login yang diimpor dari file helper. Nilainya tidak pernah ditulis di dalam spek, jadi contoh di bawah memakai placeholder. Endpoint yang dipakai dua: daftar announcements lengkap dengan mode trash, dan hapus permanen per id. Dokumentasi Playwright yang dirujuk diakses pada 17 Oktober 2026.
Buat identitas baris unik per worker
- Ganti pembentukan judul test. Timestamp milidetik saja tidak cukup: dua worker paralel bisa memuat modul spek pada milidetik yang sama, judulnya identik, dan test satu worker menghapus baris milik worker lain. Playwright sendiri mendokumentasikan
workerIndexuntuk membuat data unik per worker [6].
const TITLE_PREFIX = "E2E Informasi ";
const RUN_STARTED_AT = Date.now(); // batas awal run, dihitung saat modul dimuat
const random5 = Math.random().toString(36).slice(2, 7);
const TITLE = `${TITLE_PREFIX}${Date.now()}-${random5}`;
- Pakai
TITLEsebagai judul baris yang dibuat test, danTITLE_PREFIXsebagai kata kunci penyaringan untuk seluruh spek.
RUN_STARTED_AT harus tetap di top-level modul. Nilai inilah yang membedakan baris run yang sedang berjalan dari baris sisa run sebelumnya.
Sisipkan afterAll sebagai jaring pengaman
Alasan jalur hook lebih aman tercatat di dokumentasi: saat test gagal, Playwright membuang seluruh worker beserta browsernya lalu memulai worker baru [2], dan afterAll tetap berjalan, termasuk diulang di worker pengganti [1]. Hook ini juga punya timeout terpisah yang default-nya sama dengan test timeout, yaitu 30 detik [3].
Versi pertama menyapu semua baris ber-prefix. Komentar commit mencatat regresinya: baris test yang masih berjalan ikut terhapus. Test dalam satu file berjalan berurutan di satu worker, sementara mode paralel melarang test berbagi state atau variabel global [4]. Cakupan yang aman karena itu hanya dua kriteria berikut.
- Daftarkan
test.afterAlldi akhir file spek, lalu buat satu konteks request lewatrequest.newContext()untuk login API sekali. - Ambil baris non-trash dan trash dengan kata kunci dari prefix, dipangkas dulu spasinya.
- Hapus hanya baris berjudul persis
TITLE, atau baris ber-prefix yang kolom waktunya jatuh sebelumRUN_STARTED_AT. Delete dua langkah: ke trash, lalu permanen; status 404 ditoleransi agar idempotent bila test sudah menghapusnya sendiri.
test.afterAll(async () => {
const api = await request.newContext({ baseURL: `${API}/` });
try {
const loginRes = await api.post("admin/auth/login", {
data: { email: "<ADMIN_EMAIL>", password: "<ADMIN_PASSWORD>" },
});
if (!loginRes.ok()) {
console.warn(`cleanup: login API gagal (${loginRes.status()})`);
return;
}
const { token } = await loginRes.json();
const headers = { Authorization: *** ${token}` };
const ids = new Set();
for (const trashed of [false, true]) {
const res = await api.get("admin/announcements", {
headers,
params: { q: TITLE_PREFIX.trim(), limit: 100, ...(trashed ? { trashed: 1 } : {}) },
});
if (!res.ok()) continue;
const { data = [] } = await res.json();
for (const row of data) {
if (!row.title?.startsWith(TITLE_PREFIX)) continue;
const own = row.title === TITLE;
const stale = row.created_at && Date.parse(row.created_at) < RUN_STARTED_AT;
if (own || stale) ids.add(row.id);
}
}
for (const id of ids) {
await api.delete(`admin/announcements/${id}`, { headers });
const del = await api.delete(`admin/announcements/${id}/permanent`, { headers });
// 404 ditoleransi: baris mungkin sudah dihapus oleh test yang lulus
if (!del.ok() && del.status() !== 404) {
console.warn(`cleanup: gagal menghapus baris ${id}`);
}
}
} finally {
await api.dispose();
}
});
Verifikasi dengan menjalankan spec dua kali berurutan
- Jalankan spek dengan dua worker:
npx playwright test frontend/e2e/admin/informasi-crud.spec.ts --workers=2. Output yang diharapkan: seluruh test lulus. - Jalankan perintah yang sama tanpa jeda untuk memulai run kedua.
- Selama run kedua berjalan, saring grid announcements dengan kata kunci prefix-nya. Output yang diharapkan: hanya baris buatan run kedua yang tampil, baris run pertama sudah tersapu, dan baris milik worker lain tidak terpengaruh.
Pola ini berlaku untuk setiap spek yang menulis ke data bersama. Best practices Playwright menuntut setiap test terisolasi dan datanya terkendali [5]. afterAll adalah lapisan pengendalian terakhir yang tetap jalan ketika test di dalamnya gagal, dan menjadi standar untuk spek CRUD lain di KotaPortal.
Sumber: