Flaky test merupakan masalah utama pada pipeline End-to-End (E2E) testing di React Native. Ketidakstabilan ini umumnya berakar dari desinkronisasi antara thread JavaScript, thread UI natif, dan antrean background task pada arsitektur framework.
Limitasi Sinkronisasi Grey-Box pada React Native
Framework pengujian grey-box seperti Detox bekerja dengan menyematkan kode pemantau langsung ke dalam runtime aplikasi. Pendekatan ini mengandalkan pelacakan idling resources pada native loop (Android Choreographer/Looper dan iOS RunLoop) serta pemantauan komunikasi React Native Bridge.
Pendekatan grey-box kerap mengalami kegagalan pada kondisi berikut:
- Adopsi New Architecture (JSI & Fabric): Komunikasi sinkron melalui JavaScript Interface (JSI) melewati bridge tradisional. Mekanisme internal idling detector sering gagal mendeteksi apakah eksekusi C++ microtask telah tuntas, menghasilkan timeout prematur.
- Animasi Asinkron & Gestur: Library seperti
react-native-reanimatedmemproses animasi di UI thread terpisah secara kontinu. Kondisi ini membuat idling detector mendeteksi aplikasi dalam status 'sibuk' terus-menerus sehingga pengujian tertahan tanpa batas (hang). - Aktivitas Polling & Background Timer: Polling data via WebSockets atau interval
setTimeoutpanjang memicu false positive, memaksa developer mengonfigurasi toleransi sinkronisasi secara manual dan rentan kesalahan.
Maestro memitigasi isu ini dengan mengadopsi model black-box testing modern yang digerakkan dari level OS (menggunakan UI Automator di Android dan XCTest Accessibility di iOS). Maestro memvalidasi kondisi visual hierarki UI secara deterministik tanpa terikat langsung pada kondisi thread engine JavaScript.
Standardisasi Selektor Berbasis accessibilityLabel
Agar selektor elemen tahan terhadap perubahan tata letak dan variasi platform, hindari identifikasi berbasis teks hardcoded. Gunakan properti testID pada komponen React Native. React Native memetakan properti ini secara langsung ke atribut native accessibility.
import React, { useState } from 'react';
import { View, TextInput, TouchableOpacity, Text, StyleSheet } from 'react-native';
export const LoginForm = ({ onLoginSuccess }: { onLoginSuccess: () => void }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
return (
<View style={styles.container}>
<TextInput
testID="input-email"
accessibilityLabel="input-email"
value={email}
onChangeText={setEmail}
placeholder="Masukkan Email"
style={styles.input}
/>
<TextInput
testID="input-password"
accessibilityLabel="input-password"
value={password}
onChangeText={setPassword}
secureTextEntry
placeholder="Masukkan Password"
style={styles.input}
/>
<TouchableOpacity
testID="button-submit-login"
accessibilityLabel="button-submit-login"
onPress={onLoginSuccess}
style={styles.button}
>
<Text>Masuk</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: { padding: 16 },
input: { height: 48, borderWidth: 1, marginBottom: 12, paddingHorizontal: 8 },
button: { height: 48, backgroundColor: '#007AFF', justifyContent: 'center', alignItems: 'center' },
});Catatan: Pada Android,
testIDdikonversi menjadiresource-idataucontent-description. Pada iOS, nilai ini dipetakan keaccessibilityIdentifier. MengisitestIDsekaligusaccessibilityLabelmemastikan konsistensi ekstraksi hierarki di kedua platform.
Implementasi Flow YAML Maestro: Autentikasi ke Dashboard
Maestro menggunakan format deklaratif YAML. Hindari penggunaan perintah sleepMs dengan durasi statis karena memicu flakiness pada lingkungan CI dengan spek CPU rendah. Gunakan penanganan kondisi eksplisit seperti assertVisible dengan parameter batas waktu atau extendedWaitUntil.
Simpan skenario berikut pada file .maestro/auth_flow.yaml:
appId: com.myapp.id
---
# 1. Reset state aplikasi
- clearState
- launchApp
# 2. Input kredensial via selektor ID
- tapOn:
id: "input-email"
- inputText: "engineer@domain.com"
- tapOn:
id: "input-password"
- inputText: "Rahasia123!"
# 3. Dismiss soft keyboard jika menutupi tombol
- hideKeyboard
# 4. Trigger aksi autentikasi
- tapOn:
id: "button-submit-login"
# 5. Penanganan asinkron respons jaringan dan render UI
- extendedWaitUntil:
visible:
id: "dashboard-balance-card"
timeout: 15000
# 6. Validasi integritas komponen UI Dashboard pasca login
- assertVisible:
id: "dashboard-balance-card"
- assertVisible:
text: "Saldo Aktif"
- assertVisible:
id: "button-quick-transfer"Integrasi Eksekusi Headless di GitHub Actions
Eksekusi E2E pada pipeline CI memerlukan runner yang mendukung virtualisasi hardware (KVM) untuk menjalankan emulator Android secara efisien.
Contoh pipeline headless GitHub Actions berikut mengonfigurasi emulator, memasang Maestro CLI, dan menjalankan pengetesan tanpa GUI display:
name: E2E Regression Suite
on:
pull_request:
branches: [main]
jobs:
android-e2e:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
- name: Install Dependencies
run: yarn install --frozen-lockfile
- name: Build Android Release APK (for Testing)
run: |
cd android
./gradlew assembleRelease
- name: Enable KVM Hardware Acceleration
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run Android Emulator & Maestro Flow
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
target: google_apis
arch: x86_64
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -no-audio -no-boot-anim
disable-animations: true
script: |
# Install Maestro CLI
curl -FsSL "https://get.maestro.mobile.dev" | bash
export PATH="$PATH:$HOME/.maestro/bin"
# Install binary yang telah di-build ke emulator
adb install android/app/build/outputs/apk/release/app-release.apk
# Eksekusi flow pengujian
maestro test .maestro/auth_flow.yamlKomparasi: Maestro vs Detox vs Native Framework
Evaluasi trade-off arsitektural sebelum menentukan standar otomatisasi regresi:
- Reliabilitas (Flakiness Resistance): Maestro unggul dalam stabilitas end-to-end karena tidak bergantung pada deteksi internal thread React Native. Framework native (Espresso/XCUITest) sangat stabil untuk komponen murni natif, tetapi memerlukan wrapper rumit untuk menangani siklus hidup React Native. Detox sering mengalami flakiness jika aplikasi banyak memakai operasi asinkron bridge kustom.
- Kecepatan Eksekusi: Detox dan Native framework mengeksekusi aksi lebih cepat dalam satuan milidetik per event karena injeksi interaksi berbasis proses lokal. Maestro memiliki sedikit overhead latensi OS command (100–300ms per interaksi), namun total durasi pengetesan E2E CI sering kali lebih stabil karena minimnya kegagalan akibat timeout sinkronisasi.
- Kompleksitas Konfigurasi & Tooling: Detox memerlukan konfigurasi bridge native yang ketat, setup build target khusus, dan penyesuaian dependensi native file (Podfile/Gradle). Maestro tidak membutuhkan konfigurasi kode di layer aplikasi; cukup install CLI dan targetkan
appIdpada file APK/IPA yang sudah ada. - Kemampuan Mocking Internal: Jika flow pengujian memerlukan manipulasi state JavaScript internal atau mocking fungsi modul spesifik secara langsung saat runtime, Detox menawarkan kontrol lebih dalam. Maestro memandang aplikasi secara murni dari kacamata pengguna akhir (mocking harus ditangani di layer jaringan atau via environment switch aplikasi).
Gunakan Maestro ketika tim membutuhkan verifikasi flow kritis lintas platform yang deterministik, implementasi cepat tanpa mengotori source code aplikasi, dan pipeline CI/CD yang stabil.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!