Apa itu Flaky Test?
Flaky test adalah test yang kadang lulus, kadang gagal — tanpa perubahan kode. Ini racun pelan-pelan: tim mulai mengabaikan kegagalan CI ("ah flaky, retry aja"), lalu bug asli lolos karena sudah terbiasa skip.
Penyebab Umum Flakiness
- Timing / async race — test asumsikan sesuatu selesai dalam waktu tertentu (
setTimeout(500)) - Shared state — data dari test sebelumnya bocor ke test berikutnya
- Network — panggilan HTTP asli ke server yang kadang lambat/error
- Order dependency — test B lulus hanya kalau test A jalan duluan
- Time-based logic — test pakai
new Date()asli, gagal saat melewati midnight atau DST - Random data — faker tanpa seed menghasilkan data berbeda tiap run
- Resource contention — port bentrok, file lock, browser instance yang belum close
Fix #1: Kontrol Waktu dengan Fake Timers
import { vi, it, expect } from "vitest";
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2025-01-15T10:00:00Z")); // deterministic
});
afterEach(() => vi.useRealTimers());
it("menampilkan 'hari ini'", () => {
expect(formatDate(new Date())).toBe("15 Januari 2025");
});
Jangan pernah setTimeout/sleep asli di test — ganti dengan vi.advanceTimersByTime().
Fix #2: Isolasi State
// ❌ Bocor antar test
const cart = new Cart();
it("menambah item", () => { cart.add(item); expect(cart.size).toBe(1); });
it("kosong awalnya", () => { expect(cart.size).toBe(0); }); // GAGAL!
// ✅ Reset di beforeEach
let cart;
beforeEach(() => { cart = new Cart(); });
Di Laravel: RefreshDatabase bungkus tiap test dalam transaksi yang di-rollback. Pastikan cache, queue, dan static variable juga di-reset.
Fix #3: Hilangkan Network dengan Mock
// ❌ Flaky — butuh server benaran
it("fetch users", async () => {
const users = await fetch("/api/users").then(r => r.json());
expect(users).toHaveLength(3);
});
// ✅ Pakai MSW / Http::fake()
server.use(
http.get("/api/users", () => HttpResponse.json([/* 3 users */]))
);
Fix #4: Deterministic Random
// ❌ Flaky
const id = Math.random();
// ✅ Seed faker di config test
fake()->seed(12345); // Laravel: FakerFactory::setDefaultSeed()
// atau suntik RNG sebagai dependency
Retry: Obat Sementara, Bukan Solusi
Playwright dan Vitest punya mekanisme retry:
// Vitest
it.retry(3)("operasi async", async () => { ... });
// Playwright — di config
export default defineConfig({
retries: process.env.CI ? 2 : 0,
});
Peringatan: retry menyembunyikan flakiness, tidak memperbaikinya. Pakai retry hanya saat akar masalahnya di luar kendalimu (jaringan CI), dan catat test yang sering retry untuk dibenahi.
Debug Flaky Test Playwright
- Trace Viewer —
--trace onmerekam setiap step (DOM, network, console). Buka dengannpx playwright show-trace trace.zip. --workers=1— jalankan serial untuk mengecek apakah flakiness karena paralelisme--repeat-each=10— jalankan test yang sama 10× untuk memastikan sudah stabil setelah fix- Video & screenshot on-failure — simpan bukti kegagalan di CI
Root-Cause > Retry
Saat test flaky, tahan godaan untuk langsung retry. Langkah diagnosis:
- Reproduksi lokal dengan
--repeat-each=20. Kalau hanya flaky di CI, cek perbedaan environment (timezone, resource). - Identifikasi penyebab — timing, state, network?
- Fix akar masalah. Retry hanya dipakai untuk kasus eksternal yang benar-benar tidak bisa kamu kontrol.
- Tambah
--repeat-each=50di CI nightly untuk mendeteksi regresi flakiness baru.