Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:04:44.438Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

a11y_atspi_test.py

206 lines · 6.6 KB · python
1"""AT-SPI integration test for the GPUI a11y example app.
2
3Walks the AT-SPI tree, finds the GPUI app, and exercises the counter
4(spin button with increment/decrement), reset button, and toggle switch
5— asserting accessible state after each interaction.
6"""
7
8import sys
9import time
10import pyatspi
11
12
13def find_app():
14    """Find the GPUI a11y example in the AT-SPI desktop."""
15    desktop = pyatspi.Registry.getDesktop(0)
16    for app in desktop:
17        if "gpui" in app.name.lower() or "a11y" in app.name.lower():
18            return app
19    names = [a.name for a in desktop]
20    raise AssertionError(f"GPUI app not found in AT-SPI desktop. Apps: {names}")
21
22
23def find_by_role_and_label(root, role, label_substring):
24    """Depth-first search for a node matching role and label substring."""
25    for child in root:
26        if child.getRole() == role and label_substring in (child.name or ""):
27            return child
28        result = find_by_role_and_label(child, role, label_substring)
29        if result is not None:
30            return result
31    return None
32
33
34def find_by_role(root, role):
35    """Depth-first search for all nodes matching role."""
36    results = []
37    for child in root:
38        if child.getRole() == role:
39            results.append(child)
40        results.extend(find_by_role(child, role))
41    return results
42
43
44def do_action_by_name(node, action_name):
45    """Perform a named action on a node."""
46    actions = node.queryAction()
47    for i in range(actions.nActions):
48        if actions.getName(i).lower() == action_name.lower():
49            actions.doAction(i)
50            time.sleep(0.5)
51            return
52    available = [actions.getName(i) for i in range(actions.nActions)]
53    raise AssertionError(
54        f"No '{action_name}' action on node: {node.name} "
55        f"(role={node.getRoleName()}). Available: {available}"
56    )
57
58
59def click(node):
60    """Perform the Click action on a node."""
61    do_action_by_name(node, "click")
62
63
64def get_toggled_state(node):
65    """Return whether the node is in a 'checked'/'pressed' state."""
66    state_set = node.getState()
67    return state_set.contains(pyatspi.STATE_CHECKED) or state_set.contains(pyatspi.STATE_PRESSED)
68
69
70def get_counter(app):
71    counter = find_by_role_and_label(app, pyatspi.ROLE_SPIN_BUTTON, "Counter:")
72    assert counter is not None, "Counter (spin button) not found"
73    return counter
74
75
76def get_reset_button(app):
77    button = find_by_role_and_label(app, pyatspi.ROLE_PUSH_BUTTON, "Reset counter")
78    assert button is not None, "Reset button not found"
79    return button
80
81
82def get_toggle_switch(app):
83    switches = find_by_role(app, pyatspi.ROLE_TOGGLE_BUTTON)
84    if not switches:
85        raise AssertionError(
86            f"No toggle switch found. Roles present: "
87            f"{[(c.getRoleName(), c.name) for c in find_by_role(app, None) if True]}"
88        )
89    switch = None
90    for s in switches:
91        if "feature" in (s.name or "").lower() or "enable" in (s.name or "").lower():
92            switch = s
93            break
94    if switch is None:
95        switch = switches[0]
96    return switch
97
98
99def assert_count(app, expected):
100    """Assert the counter's label contains the expected count."""
101    counter = get_counter(app)
102    expected_str = f"Counter: {expected}"
103    assert expected_str in counter.name, (
104        f"Expected label to contain '{expected_str}', got: '{counter.name}'"
105    )
106    print(f"  OK: count is {expected}")
107
108
109def get_numeric_value(node):
110    """Get the current numeric value from the AT-SPI Value interface."""
111    value = node.queryValue()
112    return value.currentValue
113
114
115def run_tests():
116    print("Finding GPUI app in AT-SPI tree...")
117    app = find_app()
118    print(f"Found app: {app.name}")
119
120    # --- Counter (spin button) ---
121    print("\n--- Counter spin button tests ---")
122
123    print("Checking initial count is 0...")
124    assert_count(app, 0)
125
126    # Verify the Value interface reports 0
127    counter = get_counter(app)
128    val = get_numeric_value(counter)
129    print(f"  Value interface reports: {val}")
130    assert val == 0.0, f"Expected numeric value 0.0, got {val}"
131    print("  OK: numeric value is 0")
132
133    # Test click (increments)
134    for i in range(1, 4):
135        print(f"Clicking counter (expecting {i})...")
136        counter = get_counter(app)
137        click(counter)
138        assert_count(app, i)
139
140    # Verify the Value interface tracks the count
141    counter = get_counter(app)
142    val = get_numeric_value(counter)
143    assert val == 3.0, f"Expected numeric value 3.0, got {val}"
144    print("  OK: numeric value is 3 after 3 clicks")
145
146    # List available actions for diagnostics
147    counter = get_counter(app)
148    actions = counter.queryAction()
149    available = [actions.getName(i) for i in range(actions.nActions)]
150    print(f"  Available actions on counter: {available}")
151
152    # Test reset button
153    print("Clicking reset...")
154    reset = get_reset_button(app)
155    click(reset)
156    assert_count(app, 0)
157
158    # --- Toggle switch ---
159    print("\n--- Toggle switch tests ---")
160
161    switch = get_toggle_switch(app)
162    print(f"Switch: role={switch.getRoleName()}, name={switch.name}")
163
164    toggled = get_toggled_state(switch)
165    print(f"Initial toggle state: {toggled}")
166    assert not toggled, f"Expected switch to be OFF initially, got {toggled}"
167    print("  OK: switch is OFF")
168
169    print("Toggling switch ON...")
170    click(switch)
171    switch = get_toggle_switch(app)
172    toggled = get_toggled_state(switch)
173    assert toggled, f"Expected switch to be ON after toggle, got {toggled}"
174    print("  OK: switch is ON")
175
176    print("Toggling switch OFF...")
177    click(switch)
178    switch = get_toggle_switch(app)
179    toggled = get_toggled_state(switch)
180    assert not toggled, f"Expected switch to be OFF after second toggle, got {toggled}"
181    print("  OK: switch is OFF")
182
183    # --- Window bounds / Component extents ---
184    print("\n--- Component extents tests ---")
185
186    counter = get_counter(app)
187    component = counter.queryComponent()
188    extents = component.getExtents(pyatspi.DESKTOP_COORDS)
189    print(f"  Counter extents (desktop coords): x={extents.x}, y={extents.y}, "
190          f"width={extents.width}, height={extents.height}")
191    assert extents.width > 0 and extents.height > 0, (
192        f"Expected non-zero extents from Component interface, got {extents}. "
193        f"This likely means a11y_update_window_bounds is not reporting bounds."
194    )
195    print("  OK: counter has non-zero extents")
196
197    print("\n=== ALL TESTS PASSED ===")
198
199
200if __name__ == "__main__":
201    try:
202        run_tests()
203    except Exception as e:
204        print(f"\nFAILED: {e}", file=sys.stderr)
205        sys.exit(1)
206
Served at tenant.openagents/omega Member data and write actions are omitted.