Flaky test pada end-to-end (E2E) browser testing terjadi saat pengujian menghasilkan status lolos atau gagal yang tidak konsisten tanpa adanya perubahan kode aplikasi. Masalah ini paling sering muncul pada alur registrasi multi-tahap (multi-step registration) yang melibatkan dynamic DOM mutation, dependensi pihak ketiga seperti captcha dan OTP SMS, serta latensi jaringan asinkron.
Mengandalkan jeda waktu statis seperti time.sleep() adalah anti-pattern yang memperlambat pipeline CI dan memperparah ketidakstabilan pengujian. Untuk mencapai uji reliabilitas otomasi registrasi yang deterministik, teknisi QA dan software engineer perlu menerapkan isolasi dependensi eksternal, web-first assertions, serta strategi retry yang terukur.
Akar Masalah Flaky Test pada Alur Registrasi
Pengujian alur registrasi modern memiliki karakteristik yang rentan terhadap race condition:
- Dynamic DOM Mutations: Wizard registrasi multi-tahap sering kali merender ulang elemen form secara asinkron atau memanipulasi atribut
disabledsaat validasi field berjalan di background. - Ketergantungan Eksternal Tak Terkontrol: Layanan third-party seperti reCAPTCHA, Cloudflare Turnstile, atau SMS gateway (Twilio, AWS SNS) rentan terhadap rate-limiting dan variasi latensi eksternal.
- Stale Element References: Driver pengujian mencoba berinteraksi dengan elemen yang sedang di-detach atau digantikan oleh re-render framework frontend (React/Vue).
Isolasi Network Mocking untuk Captcha dan OTP
Menjalankan dependensi pihak ketiga langsung di pipeline CI menambah faktor kegagalan di luar kontrol aplikasi. Pendekatan yang benar adalah melakukan intercept pada layer browser network routing untuk meniru respons verifikasi eksternal.
Dengan network route mocking Playwright, verifikasi captcha atau trigger OTP dapat di-bypass secara deterministik tanpa mengubah source code backend aplikasi uji.
Implementasi Skrip Uji Deterministik dengan Python dan Playwright
Contoh skrip berikut mendemonstrasikan registrasi multi-tahap menggunakan Playwright Python. Skrip menangani isolasi network, explicit polling via locator assertion, serta mitigasi DOM mutation.
import re
from playwright.sync_api import Page, expect
def test_multi_step_registration_flow(page: Page):
# 1. Mock vendor captcha eksternal agar tidak memicu throttling
page.route(
re.compile(r"https://api\.external-captcha\.com/verify"),
lambda route: route.fulfill(
status=200,
content_type="application/json",
body='{"success": true, "token": "mocked-token-12345"}'
)
)
# 2. Mock verifikasi SMS gateway pihak ketiga
page.route(
re.compile(r"https://sms-provider\.com/v1/send-otp"),
lambda route: route.fulfill(
status=200,
content_type="application/json",
body='{"status": "sent", "tracking_id": "mock-track-999"}'
)
)
page.goto("https://app.example.com/register")
# Step 1: Data Kredensial Dasar
# Auto-waiting memastikan elemen visible dan interactive sebelum aksi
page.locator("input#email").fill("[email protected]")
page.locator("input#password").fill("SecureP@ssw0rd!123")
# Tunggu validasi client-side selesai dan tombol menjadi enabled
next_btn_step1 = page.locator("button#btn-step-1")
expect(next_btn_step1).to_be_enabled()
next_btn_step1.click()
# Step 2: Profil & Verifikasi Mock
# Tangani dynamic DOM: Tunggu field tahap 2 ter-mount sempurna
profile_step_container = page.locator("div#step-2-container")
expect(profile_step_container).to_be_visible()
page.locator("input#full-name").fill("Jane Doe")
page.locator("input#phone-number").fill("+6281234567890")
# Submit pemicu OTP
page.locator("button#trigger-otp").click()
# Step 3: Input OTP
otp_input = page.locator("input#otp-code")
expect(otp_input).to_be_visible()
# Gunakan static mock value yang diterima sistem uji
otp_input.fill("000000")
page.locator("button#btn-submit-registration").click()
# Verifikasi Final: Dashboard redirect
expect(page).to_have_url(re.compile(r".*/dashboard"))
welcome_banner = page.locator("h1#dashboard-welcome")
expect(welcome_banner).to_contain_text("Selamat Datang")
Retry Pattern Berbasis Exponential Backoff di Pipeline CI
Meskipun auto-waiting mengatasi race condition lokal, flakiness level infrastruktur (seperti node resource exhaustion atau latensi database container) dapat tetap terjadi di CI. Solusinya adalah retry pattern berbasis exponential backoff pada level runner pengujian, bukan mengulang perintah individual di dalam browser.
Konfigurasi pytest-rerunfailures
Daripada menulis loop retry manual yang dapat merusak browser session state, gunakan flag native pytest:
# Install dependensi
pip install pytest-rerunfailures
# Jalankan test dengan maksimal 3 retry dan delay bertingkat
pytest tests/e2e/test_registration.py \
--reruns 3 \
--reruns-delay 2
Custom Exponential Backoff Runner (Python)
Jika memerlukan retry logic custom untuk provisioning state atau reset session database sebelum eksekusi ulang:
import time
import logging
logger = logging.getLogger(__name__)
def run_e2e_with_backoff(test_fn, max_attempts=3, base_delay=2):
for attempt in range(1, max_attempts + 1):
try:
return test_fn()
except Exception as error:
if attempt == max_attempts:
logger.error(f"E2E failed permanently after {attempt} attempts.")
raise error
sleep_duration = base_delay * (2 ** (attempt - 1))
logger.warning(
f"Attempt {attempt} failed: {error}. "
f"Retrying in {sleep_duration}s..."
)
time.sleep(sleep_duration)
Best Practices Menjaga Stabilitas Test Suite
- Hindari Absolute XPaths: Gunakan locator yang tahan perubahan UI seperti
get_by_role,get_by_label, atau atribut spesifik test ID (data-testid). - Hindari
time.sleep(): Selalu gunakan explicit polling atau assertion library built-in yang mengobservasi DOM mutation secara reaktif. - Isolasi Test Data: Buat identitas pengguna unik per eksekusi (misal:
user-{uuid}@test.local) untuk menghindari kegagalan akibat duplicate unique key di database. - Amankan Traces & Artifacts: Konfigurasikan penyimpanan trace, video, dan screenshot hanya saat pengujian berstatus
failedatauretriedguna mempercepat debugging tanpa membebani storage CI.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!