Arsitektur AT-SPI pada Stack Linux Desktop
Assistive Technology Service Provider Interface (AT-SPI) adalah standar D-Bus di lingkungan GNOME dan Linux desktop yang mengekspos representasi semantik elemen GUI ke teknologi asistif seperti pembaca layar (Orca). Aplikasi GTK menerjemahkan widget grafis menjadi simpul-simpul dalam accessibility tree (accessible tree). Setiap simpul mengekspos properti standar: nama, deskripsi, status (misalnya focused, checked), antarmuka aksi (AccessibleAction), dan peran (AccessibleRole).
Pengujian regresi fungsional berbasis koordinat piksel atau OCR rentan rusak terhadap perubahan tema sistem dan resolusi layar. Melalui AT-SPI via D-Bus, pengujian dijalankan langsung terhadap struktur pohon objek aksesibilitas aplikasi. Ini memungkinkan verifikasi semantik elemen secara deterministik tanpa interaksi perangkat keras fisik.
Konfigurasi Lingkungan Headless untuk CI/CD
AT-SPI membutuhkan daemon sesi D-Bus dan server grafis aktif agar dapat menjalin komunikasi antar-proses (IPC). Di lingkungan automasi headless atau pipeline CI/CD, kebutuhan ini dipenuhi dengan menjalankan virtual framebuffer (Xvfb) dan menginisialisasi bus AT-SPI secara terisolasi.
sudo apt-get update && sudo apt-get install -y \
xvfb \
at-spi2-core \
dbus-x11 \
python3-gi \
gir1.2-atspi-2.0 \
python3-pytestSebelum meluncurkan aplikasi target dan skrip uji, variabel lingkungan sesi D-Bus dan AT-SPI harus aktif di terminal:
# Inisialisasi virtual display
export DISPLAY=:99
Xvfb :99 -screen 0 1024x768x24 &
# Aktifkan daemon AT-SPI jika belum berjalan otomatis
export GTK_A11Y=1
export NO_AT_BRIDGE=0
eval $(dbus-launch --sh-syntax)
/usr/libexec/at-spi-bus-launcher --launch-immediately &
/usr/libexec/at-spi2-registryd &Implementasi Test Harness Menggunakan Python
Pustaka introspeksi GObject (gi.repository.Atspi) mengekspos C API asli AT-SPI ke Python. Alternatif tingkat tinggi seperti pustaka dogtail membungkus panggilan ini, tetapi menggunakan Atspi langsung lewat PyGObject meminimalkan dependensi eksternal dan memberi kendali penuh atas navigasi pohon.
Berikut adalah modul test harness untuk mencari aplikasi dan memvalidasi properti semantik widget secara rekursif:
import time
import gi
gi.require_version("Atspi", "2.0")
from gi.repository import Atspi
class A11yHarness:
@staticmethod
def wait_for_app(app_name: str, timeout: float = 10.0):
start = time.time()
while time.time() - start < timeout:
desktop = Atspi.get_desktop(0)
for i in range(desktop.get_child_count()):
app = desktop.get_child_at_index(i)
if app and app.get_name() == app_name:
return app
time.sleep(0.5)
raise TimeoutError(f"Aplikasi '{app_name}' tidak ditemukan pada AT-SPI bus.")
@classmethod
def find_node(cls, root, role_name: str = None, name: str = None):
if not root:
return None
current_role = root.get_role_name()
current_name = root.get_name()
match_role = (role_name is None) or (current_role == role_name)
match_name = (name is None) or (current_name == name)
if match_role and match_name:
return root
for i in range(root.get_child_count()):
child = root.get_child_at_index(i)
found = cls.find_node(child, role_name=role_name, name=name)
if found:
return found
return None
Verifikasi Semantik Tree dan Role Widget dengan Pytest
Skrip uji berikut memverifikasi bahwa aplikasi target menyediakan role yang benar untuk kontrol formulir, label terkait tidak kosong, dan aksi klik dapat dipicu melalui antarmuka aksesibilitas.
import pytest
from gi.repository import Atspi
from harness import A11yHarness
@pytest.fixture(scope="session")
def target_app():
return A11yHarness.wait_for_app("gnome-calculator", timeout=5.0)
def test_button_semantic_properties(target_app):
# Cari tombol spesifik dalam hierarki
button = A11yHarness.find_node(
target_app, role_name="push button", name="Clear entry"
)
assert button is not None, "Tombol 'Clear entry' tidak ditemukan dalam accessibility tree"
# Verifikasi role
assert button.get_role() == Atspi.Role.PUSH_BUTTON
# Verifikasi status widget
states = button.get_state_set().get_states()
assert Atspi.StateType.VISIBLE in states
assert Atspi.StateType.SENSITIVE in states
assert Atspi.StateType.DEFUNCT not in states
def test_trigger_accessible_action(target_app):
button = A11yHarness.find_node(target_app, role_name="push button", name="1")
assert button is not None
# Mengakses antarmuka AccessibleAction
action_count = button.get_n_actions()
assert action_count > 0, "Elemen interaktif tidak memiliki antarmuka AccessibleAction"
action_name = button.get_action_name(0)
assert action_name in ["click", "press", "activate"]
# Eksekusi aksi tanpa pointer mouse
success = button.do_action(0)
assert success is True, "Gagal memicu aksi aksesibilitas pada target"
Mitigasi Flaky Test Akibat Async Event Bus
Komunikasi AT-SPI bersifat asinkron via D-Bus. State perubahan widget (seperti perpindahan fokus, validasi form, atau perubahan teks) tidak instan. Menggunakan sleep() statis adalah penyebab utama flaky tests di CI/CD.
Gunakan Polling Bersyarat
Gantikan jeda waktu tetap dengan polling deterministik berbasis predicate function:
def wait_until(predicate, timeout: float = 3.0, interval: float = 0.1):
start = time.time()
while time.time() - start < timeout:
result = predicate()
if result:
return result
time.sleep(interval)
raise TimeoutError("Kondisi aksesibilitas tidak terpenuhi sebelum timeout")
# Contoh penggunaan: Menunggu status focused diperbarui
def test_input_focus_state(target_app):
text_entry = A11yHarness.find_node(target_app, role_name="text")
assert text_entry is not None
# Minta fokus via AT-SPI Component interface
component = text_entry.get_component()
component.grab_focus()
# Hindari sleep(1), gunakan predicate polling
def is_focused():
states = text_entry.get_state_set().get_states()
return Atspi.StateType.FOCUSED in states
assert wait_until(is_focused, timeout=2.0) is True
Manfaatkan AT-SPI Event Listeners
Untuk verifikasi state reaktif yang kompleks, gunakan Atspi.EventListener guna mendengarkan signal langsung dari D-Bus:
received_events = []
def on_state_changed(event):
if event.type == "object:state-changed:focused":
received_events.append(event)
listener = Atspi.EventListener.new(on_state_changed)
listener.register("object:state-changed:focused")
# Operasi GUI pemicu...
Debugging dan Inspeksi Simpul A11y
Saat pengujian gagal menemukan simpul atau atribut salah dibaca, beberapa langkah debugging sistemik dapat digunakan:
- Accerciser: Gunakan tool interaktif
accerciserpada sesi lokal untuk melihat langsung hierarki pohon objek AT-SPI dan memvalidasi jalur XPath dari widget yang bersangkutan. - Monitor Bus D-Bus: Jalankan perintah CLI untuk memantau event aksesibilitas secara langsung:
dbus-monitor --session "interface='org.a11y.atspi.Event.Object'" - Logging Dump: Saat test gagal pada runner CI, buat fungsi traversal untuk mencetak pohon objek saat kegagalan terjadi guna memudahkan audit struktur widget aplikasi yang sedang diuji.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!