Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions packages/flow/openadapt_flow/compiler/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1204,10 +1204,15 @@ def ident(o: dict) -> tuple:
and isinstance(name, str)
and name.strip()
)
# An id or testid check also binds the element's role and name when it had
# them, so an id that moved to another element can't pass: the generated-id
# patterns are only a hint, and no list keeps up with every framework.
pair: dict[str, Any] = {"focus_role": role, "focus_name": name} if named else {}
# Every check needs a role and a non-empty name. An id or testid check
# binds them too, so an id that moved to another element can't pass: the
# generated-id patterns are only a hint, and no list keeps up with every
# framework. An element with no name (a bare tabindex div, a host whose
# closed shadow root holds the real focus) gets no check, and the step
# stays vacuous.
if not named:
return None
pair: dict[str, Any] = {"focus_role": role, "focus_name": name}
if isinstance(testid, str) and testid.strip() and after.get("testid_count") == 1:
return Postcondition(
kind=PostconditionKind.FOCUS_ON,
Expand Down
18 changes: 14 additions & 4 deletions packages/flow/openadapt_flow/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -640,15 +640,25 @@ def _serialize_compatible(self, handler: Any) -> dict[str, Any]:
def focus_identity_is_specific(pc: "Postcondition") -> bool:
"""Whether a FOCUS_ON postcondition names one element, not a kind of element.

A ``testid`` or an ``id`` with a value, or a role with a non-empty
accessible name. A tag alone, or a role with no name, names no element:
such a postcondition asserts nothing and never passes.
A role with a non-empty accessible name, alone or with a ``testid`` or an
``id``. A tag, a role with no name, or an id or testid without a role and
name names no element: such a postcondition asserts nothing and never
passes.
"""

if pc.kind is not PostconditionKind.FOCUS_ON:
return False
if pc.focus_by in ("testid", "id"):
return bool(pc.focus_value and pc.focus_value.strip())
# An id or testid names one element only together with its role and
# name: an id alone can move to another element between renders.
return bool(
pc.focus_value
and pc.focus_value.strip()
and pc.focus_role
and pc.focus_role.strip()
and pc.focus_name
and pc.focus_name.strip()
)
if pc.focus_by == "role_name":
return bool(
pc.focus_role
Expand Down
45 changes: 45 additions & 0 deletions packages/flow/tests/test_focus_on_postcondition.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,48 @@ def test_an_unregistered_component_keeps_its_check_and_a_registered_one_does_not
assert backend.focus_identity() is None
finally:
close()


# -- follow-up: no check for an element without a name ------------------------------------


def test_an_id_or_testid_without_a_role_and_name_is_not_a_check():
for after in (
_focus(tag="div", role=None, name=None, id="pane-3", id_count=1),
_focus(tag="div", role="generic", name=None, testid="pane", testid_count=1),
):
assert _focus_postcondition(BEFORE, after) is None
bare = Postcondition(
kind=PostconditionKind.FOCUS_ON, focus_by="id", focus_value="pane-3"
)
assert not focus_identity_is_specific(bare)
assert is_vacuous(_key_step([bare])) is True


def test_a_plain_host_with_a_closed_shadow_root_gets_no_check():
backend, close = _launch(
"<div id=host tabindex=-1></div><script>"
"window._root = document.getElementById('host').attachShadow({mode: 'closed'});"
"window._root.innerHTML = '<button>One</button><button>Two</button>';</script>"
)
try:
backend.page.evaluate("window._root.querySelectorAll('button')[1].focus()")
seen = backend.focus_identity()
# The page sees the host, which has an id but no role and name.
assert seen is not None and seen["id"] == "host"
assert _focus_postcondition(BEFORE, seen) is None
finally:
close()


def test_an_unnamed_div_whose_id_shifts_gets_no_check():
backend, close = _launch(
"<div id=pane-3 tabindex=0>3</div><div id=pane-4 tabindex=0>4</div>"
)
try:
backend.page.locator("#pane-3").focus()
seen = backend.focus_identity()
assert seen is not None and seen["id"] == "pane-3"
assert _focus_postcondition(BEFORE, seen) is None
finally:
close()
Loading