hkblockchaintechnology commited on
Commit
4df6f60
·
verified ·
1 Parent(s): 61aac99

Publish tests/ from marimo

Browse files
Files changed (1) hide show
  1. tests/test_accessibility.py +124 -5
tests/test_accessibility.py CHANGED
@@ -10,10 +10,12 @@ import os
10
  import pytest
11
  from PIL import Image
12
 
 
 
13
  from tiny_computer_agent import (
14
  INTERACTIVE_ROLES, TEXT_ROLES, AccessibleNode, AtspiDetector, AtspiReader,
15
- AutoDetector, Element, IndexedScreen, Observation, detector_available,
16
- make_detector,
17
  )
18
  from tiny_computer_agent.accessibility import accessibility_available
19
  from tiny_computer_agent.perception import DetectorUnavailable
@@ -131,6 +133,28 @@ def test_screen_coordinates_are_not_double_mapped_by_a_downscaled_capture():
131
  assert (save.x, save.y) == (390, 201), "the accessibility tree was scaled twice"
132
 
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  def test_a_frame_relative_detector_is_still_mapped():
135
  """The opt-out is per-detector: a grid in a downscaled frame must be mapped."""
136
  from tiny_computer_agent import GridDetector
@@ -151,9 +175,7 @@ def test_a_reader_that_cannot_reach_the_bus_raises_rather_than_lying():
151
  AtspiDetector(reader=Broken()).detect()
152
 
153
 
154
- def test_ui_automation_is_reported_as_not_implemented():
155
- ok, detail = accessibility_available("uia")
156
- assert ok is False and "not implemented" in detail
157
  assert accessibility_available("telepathy")[0] is False
158
 
159
 
@@ -226,6 +248,103 @@ def test_the_new_detector_names_are_reachable():
226
  assert TEXT_ROLES and "label" in TEXT_ROLES
227
 
228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  # -- the one test that drives real AT-SPI ------------------------------------
230
 
231
  OPT_IN = os.environ.get("TINY_COMPUTER_AGENT_LIVE_TEST") == "1"
 
10
  import pytest
11
  from PIL import Image
12
 
13
+ import platform
14
+
15
  from tiny_computer_agent import (
16
  INTERACTIVE_ROLES, TEXT_ROLES, AccessibleNode, AtspiDetector, AtspiReader,
17
+ AutoDetector, Element, GridDetector, IndexedScreen, Observation, UiaDetector,
18
+ UiaReader, detector_available, make_detector,
19
  )
20
  from tiny_computer_agent.accessibility import accessibility_available
21
  from tiny_computer_agent.perception import DetectorUnavailable
 
133
  assert (save.x, save.y) == (390, 201), "the accessibility tree was scaled twice"
134
 
135
 
136
+ def test_the_chain_reports_the_space_of_the_branch_it_used():
137
+ """The bug this pins down: `best` wraps the tree detector, so a *static* flag
138
+ said "frame" and every accessibility click was re-mapped through the downscale
139
+ - shifting it by the capture's scale factor, silently."""
140
+ tree = AtspiDetector(reader=FakeReader([node("button", "Save", 390, 201, 100, 36)]))
141
+
142
+ class Empty:
143
+ name = "empty"
144
+
145
+ def detect(self, image=None):
146
+ return ()
147
+
148
+ chained = AutoDetector(accessibility=tree, grid=GridDetector(rows=1, cols=2))
149
+ observation = IndexedScreen(ScaledSource(), chained).capture()
150
+ assert observation.elements[0].x == 390, "the chained tree index was double-mapped"
151
+
152
+ chained.accessibility = Empty() # now the grid answers
153
+ observation = IndexedScreen(ScaledSource(), chained).capture()
154
+ assert observation.elements[0].x == 0 # frame space: mapped
155
+ assert chained.coordinates_of_last_detect() == "frame"
156
+
157
+
158
  def test_a_frame_relative_detector_is_still_mapped():
159
  """The opt-out is per-detector: a grid in a downscaled frame must be mapped."""
160
  from tiny_computer_agent import GridDetector
 
175
  AtspiDetector(reader=Broken()).detect()
176
 
177
 
178
+ def test_an_unknown_accessibility_backend_is_refused():
 
 
179
  assert accessibility_available("telepathy")[0] is False
180
 
181
 
 
248
  assert TEXT_ROLES and "label" in TEXT_ROLES
249
 
250
 
251
+ # -- the Windows backend: its mapping, exercised without Windows ---------------
252
+
253
+ class FakeBox:
254
+ def __init__(self, left, top, right, bottom):
255
+ self.left, self.top, self.right, self.bottom = left, top, right, bottom
256
+
257
+
258
+ class FakeControl:
259
+ """The slice of a UI Automation control element this reader actually uses."""
260
+
261
+ def __init__(self, name, control_type, box=None, children=()):
262
+ self.Name = name
263
+ self.ControlTypeName = control_type
264
+ self.BoundingRectangle = box
265
+ self._children = list(children)
266
+
267
+ def GetChildren(self):
268
+ return self._children
269
+
270
+
271
+ def uia_tree():
272
+ """The same window the GTK app opens, as UI Automation would report it."""
273
+ return FakeControl("Accessible desktop", "WindowControl", FakeBox(160, 160, 620, 380), [
274
+ FakeControl("", "PaneControl", FakeBox(174, 174, 606, 366), [
275
+ FakeControl("Files", "ButtonControl", FakeBox(174, 201, 274, 237)),
276
+ FakeControl("Terminal", "ButtonControl", FakeBox(282, 201, 382, 237)),
277
+ FakeControl("Save", "ButtonControl", FakeBox(390, 201, 490, 237)),
278
+ FakeControl("Cancel", "ButtonControl", FakeBox(498, 201, 598, 237)),
279
+ FakeControl("Choose an action", "TextControl", FakeBox(174, 245, 598, 264)),
280
+ ]),
281
+ ])
282
+
283
+
284
+ def uia_elements():
285
+ return UiaDetector(reader=UiaReader(provider=uia_tree)).detect()
286
+
287
+
288
+ def test_uia_maps_control_types_to_the_roles_the_harness_speaks():
289
+ assert [(e.label, e.kind) for e in uia_elements()] == [
290
+ ("Files", "button"), ("Terminal", "button"), ("Save", "button"), ("Cancel", "button")]
291
+ save = next(e for e in uia_elements() if e.label == "Save")
292
+ assert (save.x, save.y, save.width, save.height) == (390, 201, 100, 36)
293
+ assert save.center == (440, 219)
294
+
295
+
296
+ def test_uia_window_furniture_is_not_offered_as_a_choice():
297
+ """A window and a pane have names too, and neither is a button."""
298
+ roles = {e.kind for e in uia_elements()}
299
+ assert roles == {"button"}
300
+
301
+
302
+ def test_uia_keeps_an_unnamed_container_but_walks_into_it():
303
+ """The pane has no name, so it is not an element - its children still are."""
304
+ assert all(e.label for e in uia_elements())
305
+ assert len(uia_elements()) == 4
306
+
307
+
308
+ def test_uia_respects_the_depth_and_node_caps():
309
+ deep = UiaReader(provider=uia_tree, max_depth=1)
310
+ assert [e.label for e in UiaDetector(reader=deep).detect()] == []
311
+ # the window itself is the first node, so the cap of two leaves one button
312
+ capped = UiaReader(provider=uia_tree, max_nodes=2)
313
+ assert [e.label for e in UiaDetector(reader=capped).detect()] == ["Files"]
314
+
315
+
316
+ def test_uia_detector_text_reads_the_tree_too():
317
+ assert "Choose an action" in UiaDetector(reader=UiaReader(provider=uia_tree)).text()
318
+
319
+
320
+ def test_the_windows_backend_is_reported_honestly_on_this_machine():
321
+ ok, detail = accessibility_available("uia")
322
+ if platform.system() == "Windows":
323
+ return # on Windows this is a real, working backend
324
+ assert ok is False and "Windows-only" in detail
325
+
326
+
327
+ def test_the_uia_reader_refuses_rather_than_returning_an_empty_index():
328
+ if platform.system() == "Windows":
329
+ pytest.skip("this machine really is Windows")
330
+ with pytest.raises(DetectorUnavailable, match="Windows-only"):
331
+ UiaReader().read()
332
+
333
+
334
+ def test_the_detector_names_cover_both_backends():
335
+ assert type(make_detector("atspi")).__name__ == "AtspiDetector"
336
+ ok, detail = detector_available("uia")
337
+ assert isinstance(ok, bool) and detail
338
+
339
+
340
+ def test_a_provider_that_throws_is_reported_as_unavailable():
341
+ def broken():
342
+ raise RuntimeError("COM is not initialised")
343
+
344
+ with pytest.raises(DetectorUnavailable, match="could not reach UI Automation"):
345
+ UiaReader(provider=broken).read()
346
+
347
+
348
  # -- the one test that drives real AT-SPI ------------------------------------
349
 
350
  OPT_IN = os.environ.get("TINY_COMPUTER_AGENT_LIVE_TEST") == "1"