From 97ace99536dcc6eee3ed29fc55ab68042dbe159e Mon Sep 17 00:00:00 2001 From: Adrian Stritzinger Date: Thu, 5 Jun 2025 14:27:41 +0200 Subject: [PATCH] feat: add switch element class locator --- src/askui/locators/locators.py | 11 ++++------- tests/e2e/agent/test_locate.py | 21 +++++++++++++++------ tests/fixtures/images/switch.png | Bin 0 -> 24326 bytes 3 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 tests/fixtures/images/switch.png diff --git a/src/askui/locators/locators.py b/src/askui/locators/locators.py index 7e3f3870..652c08b0 100644 --- a/src/askui/locators/locators.py +++ b/src/askui/locators/locators.py @@ -13,8 +13,8 @@ """The type of match to use. - `"similar"` uses a similarity threshold to determine if the text is a match. -- `"exact"` requires the text to be exactly the same (this is not the same as `"similar"` - with a `similarity_threshold` of `100` as a `similarity_threshold` of `100` can still +- `"exact"` requires the text to be exactly the same (this is not the same as `"similar"` + with a `similarity_threshold` of `100` as a `similarity_threshold` of `100` can still allow for small differences in very long texts). - `"contains"` requires the text to contain (exactly) the specified text. - `"regex"` uses a regular expression to match the text. @@ -70,7 +70,7 @@ class Element(Locator): """Locator for finding ui elements by their class. Args: - class_name (Literal["text", "textfield"] | None, optional): The class of the ui element, e.g., `'text'` or `'textfield'`. Defaults to `None`. + class_name (Literal["switch","text", "textfield"] | None, optional): The class of the ui element, e.g., `'text'` or `'textfield'`. Defaults to `None`. Examples: ```python @@ -87,10 +87,7 @@ class Element(Locator): @validate_call def __init__( self, - class_name: Annotated[ - Literal["text", "textfield"] | None, - Field(), - ] = None, + class_name: Literal["switch", "text", "textfield"] | None = None, ) -> None: super().__init__() self._class_name = class_name diff --git a/tests/e2e/agent/test_locate.py b/tests/e2e/agent/test_locate.py index 317451f4..04bf14b0 100644 --- a/tests/e2e/agent/test_locate.py +++ b/tests/e2e/agent/test_locate.py @@ -7,12 +7,7 @@ from askui.agent import VisionAgent from askui.exceptions import ElementNotFoundError -from askui.locators import ( - AiElement, - Element, - Prompt, - Text, -) +from askui.locators import AiElement, Element, Prompt, Text from askui.locators.locators import Image from askui.models import ModelName @@ -39,6 +34,20 @@ def test_locate_with_string_locator( assert 450 <= x <= 570 assert 190 <= y <= 260 + def test_locate_with_switch_class_locator( + self, + vision_agent: VisionAgent, + path_fixtures: pathlib.Path, + model: str, + ) -> None: + """Test locating elements using a class locator.""" + locator = Element("switch") + x, y = vision_agent.locate( + locator, path_fixtures / "images" / "switch.png", model=model + ) + assert 340 <= x <= 360 + assert 420 <= y <= 460 + def test_locate_with_textfield_class_locator( self, vision_agent: VisionAgent, diff --git a/tests/fixtures/images/switch.png b/tests/fixtures/images/switch.png new file mode 100644 index 0000000000000000000000000000000000000000..aa92424cd2f8e55239c5fb6eabe048eae47d36a9 GIT binary patch literal 24326 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 WZhgE5{;<8k)t{{|m72Le{J#M3|8%PW literal 0 HcmV?d00001