Revert "refactor: clean up legacy code, rename hopper to entity_extractor, and add third-party notices"
This reverts commit 54fcef9887.
Please do not submit unrelated docs and codes in the same commit. I am reverting this commit so that the related files can be submitted in better-organized future commits.
This commit is contained in:
1 parent
54fcef9887
commit
1d82d59ec8
83 files changed
+3677
-2013
No files matched your search
@@ -11,11 +11,6 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Portions of this file are derived from mobile-use (https://github.com/minitap-ai/mobile-use)
|
||||
# Copyright 2025-2026 Minitap, Inc. Licensed under the Apache License 2.0.
|
||||
|
||||
"""Unit tests validating Artemis UI hierarchy parsing and geometry computation."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -34,160 +29,215 @@ def test_text_input_is_empty():
|
||||
assert text_input_is_empty(text=None, hint_text=None)
|
||||
assert text_input_is_empty(text="", hint_text=None)
|
||||
assert text_input_is_empty(text="", hint_text="")
|
||||
assert text_input_is_empty(text="search", hint_text="search")
|
||||
assert text_input_is_empty(text="text", hint_text="text")
|
||||
|
||||
assert not text_input_is_empty(text="search", hint_text=None)
|
||||
assert not text_input_is_empty(text="search", hint_text="")
|
||||
assert not text_input_is_empty(text="text", hint_text=None)
|
||||
assert not text_input_is_empty(text="text", hint_text="")
|
||||
|
||||
|
||||
def test_find_element_by_resource_id():
|
||||
tree = [
|
||||
ui_hierarchy = [
|
||||
{
|
||||
"resourceId": "com.google.android.settings:id/search_bar",
|
||||
"text": "Search Settings",
|
||||
"resourceId": "com.example:id/button1",
|
||||
"text": "Button 1",
|
||||
"children": [],
|
||||
},
|
||||
{
|
||||
"resourceId": "com.google.android.settings:id/content_frame",
|
||||
"resourceId": "com.example:id/container",
|
||||
"children": [
|
||||
{
|
||||
"resourceId": "com.google.android.settings:id/network_item",
|
||||
"text": "Network & Internet",
|
||||
"resourceId": "com.example:id/nested_button",
|
||||
"text": "Nested Button",
|
||||
"children": [],
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
target = find_element_by_resource_id(tree, "com.google.android.settings:id/search_bar")
|
||||
assert target is not None
|
||||
assert target["resourceId"] == "com.google.android.settings:id/search_bar"
|
||||
assert target["text"] == "Search Settings"
|
||||
result = find_element_by_resource_id(ui_hierarchy, "com.example:id/button1")
|
||||
assert result is not None
|
||||
assert result["resourceId"] == "com.example:id/button1"
|
||||
assert result["text"] == "Button 1"
|
||||
|
||||
nested = find_element_by_resource_id(tree, "com.google.android.settings:id/network_item")
|
||||
assert nested is not None
|
||||
assert nested["resourceId"] == "com.google.android.settings:id/network_item"
|
||||
result = find_element_by_resource_id(ui_hierarchy, "com.example:id/nested_button")
|
||||
assert result is not None
|
||||
assert result["resourceId"] == "com.example:id/nested_button"
|
||||
assert result["text"] == "Nested Button"
|
||||
|
||||
assert find_element_by_resource_id(tree, "com.google.android.settings:id/missing") is None
|
||||
assert find_element_by_resource_id([], "com.google.android.settings:id/search_bar") is None
|
||||
result = find_element_by_resource_id(ui_hierarchy, "com.example:id/nonexistent")
|
||||
assert result is None
|
||||
|
||||
result = find_element_by_resource_id([], "com.example:id/button1")
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_find_element_by_resource_id_with_index():
|
||||
duplicate_items = [
|
||||
{"resourceId": "com.artemis.ui:id/row_item", "text": "Item Alpha", "children": []},
|
||||
{"resourceId": "com.artemis.ui:id/row_item", "text": "Item Beta", "children": []},
|
||||
ui_hierarchy = [
|
||||
{
|
||||
"resourceId": "com.artemis.ui:id/section_group",
|
||||
"children": [
|
||||
{"resourceId": "com.artemis.ui:id/row_item", "text": "Item Gamma", "children": []}
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
first = find_element_by_resource_id(duplicate_items, "com.artemis.ui:id/row_item", index=0)
|
||||
assert first is not None
|
||||
assert first["text"] == "Item Alpha"
|
||||
|
||||
second = find_element_by_resource_id(duplicate_items, "com.artemis.ui:id/row_item", index=1)
|
||||
assert second is not None
|
||||
assert second["text"] == "Item Beta"
|
||||
|
||||
third = find_element_by_resource_id(duplicate_items, "com.artemis.ui:id/row_item", index=2)
|
||||
assert third is not None
|
||||
assert third["text"] == "Item Gamma"
|
||||
|
||||
assert find_element_by_resource_id(duplicate_items, "com.artemis.ui:id/row_item", index=3) is None
|
||||
|
||||
|
||||
def test_find_element_by_resource_id_rich_hierarchy():
|
||||
rich_nodes = [
|
||||
{
|
||||
"attributes": {"resource-id": "com.android.systemui:id/clock"},
|
||||
"resourceId": "com.example:id/button",
|
||||
"text": "Button 1",
|
||||
"children": [],
|
||||
},
|
||||
{
|
||||
"attributes": {"resource-id": "com.android.systemui:id/status_bar"},
|
||||
"resourceId": "com.example:id/button",
|
||||
"text": "Button 2",
|
||||
"children": [],
|
||||
},
|
||||
{
|
||||
"resourceId": "com.example:id/container",
|
||||
"children": [
|
||||
{
|
||||
"attributes": {"resource-id": "com.android.systemui:id/battery_meter"},
|
||||
"resourceId": "com.example:id/button",
|
||||
"text": "Button 3",
|
||||
"children": [],
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
match = find_element_by_resource_id(
|
||||
rich_nodes, "com.android.systemui:id/clock", is_rich_hierarchy=True
|
||||
)
|
||||
assert match is not None
|
||||
assert match["resource-id"] == "com.android.systemui:id/clock"
|
||||
result = find_element_by_resource_id(ui_hierarchy, "com.example:id/button", index=0)
|
||||
assert result is not None
|
||||
assert result["text"] == "Button 1"
|
||||
|
||||
nested_match = find_element_by_resource_id(
|
||||
rich_nodes, "com.android.systemui:id/battery_meter", is_rich_hierarchy=True
|
||||
)
|
||||
assert nested_match is not None
|
||||
assert nested_match["resource-id"] == "com.android.systemui:id/battery_meter"
|
||||
result = find_element_by_resource_id(ui_hierarchy, "com.example:id/button", index=1)
|
||||
assert result is not None
|
||||
assert result["text"] == "Button 2"
|
||||
|
||||
assert find_element_by_resource_id(
|
||||
rich_nodes, "com.android.systemui:id/unknown", is_rich_hierarchy=True
|
||||
) is None
|
||||
result = find_element_by_resource_id(ui_hierarchy, "com.example:id/button", index=2)
|
||||
assert result is not None
|
||||
assert result["text"] == "Button 3"
|
||||
|
||||
result = find_element_by_resource_id(ui_hierarchy, "com.example:id/button", index=3)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_find_element_by_resource_id_rich_hierarchy():
|
||||
rich_hierarchy = [
|
||||
{
|
||||
"attributes": {"resource-id": "com.example:id/button1"},
|
||||
"children": [],
|
||||
},
|
||||
{
|
||||
"attributes": {"resource-id": "com.example:id/container"},
|
||||
"children": [
|
||||
{
|
||||
"attributes": {"resource-id": "com.example:id/nested_button"},
|
||||
"children": [],
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
result = find_element_by_resource_id(
|
||||
rich_hierarchy, "com.example:id/button1", is_rich_hierarchy=True
|
||||
)
|
||||
assert result is not None
|
||||
assert result["resource-id"] == "com.example:id/button1"
|
||||
|
||||
result = find_element_by_resource_id(
|
||||
rich_hierarchy, "com.example:id/nested_button", is_rich_hierarchy=True
|
||||
)
|
||||
assert result is not None
|
||||
assert result["resource-id"] == "com.example:id/nested_button"
|
||||
|
||||
result = find_element_by_resource_id(
|
||||
rich_hierarchy, "com.example:id/nonexistent", is_rich_hierarchy=True
|
||||
)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_is_element_focused():
|
||||
assert is_element_focused({"focused": "true"})
|
||||
assert is_element_focused({"focused": True})
|
||||
assert not is_element_focused({"focused": "false"})
|
||||
assert not is_element_focused({"focused": False})
|
||||
assert not is_element_focused({"text": "sample"})
|
||||
assert not is_element_focused({"focused": None})
|
||||
focused_element = {"focused": "true"}
|
||||
assert is_element_focused(focused_element)
|
||||
|
||||
non_focused_element = {"focused": "false"}
|
||||
assert not is_element_focused(non_focused_element)
|
||||
|
||||
no_focused_element = {"text": "some text"}
|
||||
assert not is_element_focused(no_focused_element)
|
||||
|
||||
none_focused_element = {"focused": None}
|
||||
assert not is_element_focused(none_focused_element)
|
||||
|
||||
|
||||
def test_get_element_text():
|
||||
node = {"text": "Battery", "hintText": "Search settings"}
|
||||
assert get_element_text(node) == "Battery"
|
||||
assert get_element_text(node, hint_text=False) == "Battery"
|
||||
assert get_element_text(node, hint_text=True) == "Search settings"
|
||||
element = {"text": "Button Text", "hintText": "Hint Text"}
|
||||
assert get_element_text(element) == "Button Text"
|
||||
assert get_element_text(element, hint_text=False) == "Button Text"
|
||||
assert get_element_text(element, hint_text=True) == "Hint Text"
|
||||
|
||||
node_no_text = {"hintText": "Placeholder"}
|
||||
assert get_element_text(node_no_text) is None
|
||||
assert get_element_text(node_no_text, hint_text=True) == "Placeholder"
|
||||
element_no_text = {"hintText": "Hint Text"}
|
||||
assert get_element_text(element_no_text) is None
|
||||
assert get_element_text(element_no_text, hint_text=True) == "Hint Text"
|
||||
element_no_hint = {"text": "Button Text"}
|
||||
assert get_element_text(element_no_hint) == "Button Text"
|
||||
assert get_element_text(element_no_hint, hint_text=True) is None
|
||||
|
||||
assert get_element_text({}) is None
|
||||
empty_element = {}
|
||||
assert get_element_text(empty_element) is None
|
||||
assert get_element_text(empty_element, hint_text=True) is None
|
||||
|
||||
|
||||
def test_get_bounds_for_element():
|
||||
node_with_box = {"bounds": {"x": 24, "y": 48, "width": 400, "height": 80}}
|
||||
box = get_bounds_for_element(node_with_box)
|
||||
assert box is not None
|
||||
assert isinstance(box, ElementBounds)
|
||||
assert box.x == 24
|
||||
assert box.y == 48
|
||||
assert box.width == 400
|
||||
assert box.height == 80
|
||||
element_with_bounds = {"bounds": {"x": 10, "y": 20, "width": 100, "height": 50}}
|
||||
bounds = get_bounds_for_element(element_with_bounds)
|
||||
assert bounds is not None
|
||||
assert isinstance(bounds, ElementBounds)
|
||||
assert bounds.x == 10
|
||||
assert bounds.y == 20
|
||||
assert bounds.width == 100
|
||||
assert bounds.height == 50
|
||||
|
||||
assert get_bounds_for_element({"text": "Click"}) is None
|
||||
element_no_bounds = {"text": "Button"}
|
||||
bounds = get_bounds_for_element(element_no_bounds)
|
||||
assert bounds is None
|
||||
|
||||
# Suppress logger output for the invalid bounds test case
|
||||
with patch("artemis.utils.ui_hierarchy.logger.error"):
|
||||
bad_box = {"bounds": {"x": "corrupted", "y": 48, "width": 400, "height": 80}}
|
||||
assert get_bounds_for_element(bad_box) is None
|
||||
element_invalid_bounds = {
|
||||
"bounds": {
|
||||
"x": "invalid", # Should be int
|
||||
"y": 20,
|
||||
"width": 100,
|
||||
"height": 50,
|
||||
}
|
||||
}
|
||||
bounds = get_bounds_for_element(element_invalid_bounds)
|
||||
assert bounds is None
|
||||
|
||||
|
||||
def test_element_bounds_geometry():
|
||||
box = ElementBounds(x=100, y=200, width=500, height=100)
|
||||
def test_element_bounds():
|
||||
bounds = ElementBounds(x=10, y=20, width=100, height=50)
|
||||
|
||||
center = box.get_center()
|
||||
center = bounds.get_center()
|
||||
assert isinstance(center, Point)
|
||||
assert center.x == 350
|
||||
assert center.y == 250
|
||||
assert center.x == 60
|
||||
assert center.y == 45
|
||||
|
||||
mid = box.get_relative_point(0.5, 0.5)
|
||||
assert mid.x == 350
|
||||
assert mid.y == 250
|
||||
center_point = bounds.get_relative_point(0.5, 0.5)
|
||||
assert isinstance(center_point, Point)
|
||||
assert center_point.x == 60
|
||||
assert center_point.y == 45
|
||||
|
||||
top_left = box.get_relative_point(0.0, 0.0)
|
||||
assert top_left.x == 100
|
||||
assert top_left.y == 200
|
||||
top_left = bounds.get_relative_point(0.0, 0.0)
|
||||
assert top_left.x == 10
|
||||
assert top_left.y == 20
|
||||
|
||||
bottom_right = box.get_relative_point(1.0, 1.0)
|
||||
assert bottom_right.x == 600
|
||||
assert bottom_right.y == 300
|
||||
bottom_right = bounds.get_relative_point(1.0, 1.0)
|
||||
assert bottom_right.x == 110
|
||||
assert bottom_right.y == 70
|
||||
custom_point = bounds.get_relative_point(0.95, 0.95)
|
||||
assert custom_point.x == 105
|
||||
assert custom_point.y == 67
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_text_input_is_empty()
|
||||
test_find_element_by_resource_id()
|
||||
test_find_element_by_resource_id_with_index()
|
||||
test_find_element_by_resource_id_rich_hierarchy()
|
||||
test_is_element_focused()
|
||||
test_get_element_text()
|
||||
test_get_bounds_for_element()
|
||||
test_element_bounds()
|
||||
print("All tests passed")
|
||||
Reference in new issue
Block a user