Test infrastructure with conftest fixtures mocking run_shell_args/run_adb for device-free testing across all 8 mixins. Fixed: UI parser regex couldn't match hyphenated XML attributes (content-desc, resource-id). Notification parser captured trailing parenthesis in package names.
142 lines
5.1 KiB
Python
142 lines
5.1 KiB
Python
"""Tests for UI inspection mixin (dump, find, wait, tap_text)."""
|
|
|
|
from tests.conftest import fail, ok
|
|
|
|
SAMPLE_UI_XML = """<?xml version="1.0" encoding="UTF-8"?>
|
|
<hierarchy>
|
|
<node text="Settings" class="android.widget.TextView"
|
|
resource-id="com.android.settings:id/title"
|
|
bounds="[0,100][200,150]" clickable="true" focusable="true"
|
|
content-desc="" />
|
|
<node text="" class="android.widget.ImageView"
|
|
resource-id="com.android.settings:id/icon"
|
|
bounds="[0,50][48,98]" clickable="false" focusable="false"
|
|
content-desc="Settings icon" />
|
|
<node text="Wi-Fi" class="android.widget.TextView"
|
|
resource-id="com.android.settings:id/title"
|
|
bounds="[200,100][400,150]" clickable="true" focusable="false"
|
|
content-desc="" />
|
|
</hierarchy>
|
|
"""
|
|
|
|
|
|
class TestUiDump:
|
|
async def test_dump(self, server, ctx):
|
|
server.run_shell_args.side_effect = [
|
|
ok(), # uiautomator dump
|
|
ok(stdout=SAMPLE_UI_XML), # cat
|
|
ok(), # rm cleanup
|
|
]
|
|
result = await server.ui_dump(ctx)
|
|
assert result["success"] is True
|
|
assert result["element_count"] >= 2 # Settings + Wi-Fi at minimum
|
|
assert "xml" in result
|
|
|
|
async def test_dump_failure(self, server, ctx):
|
|
server.run_shell_args.return_value = fail("error")
|
|
result = await server.ui_dump(ctx)
|
|
assert result["success"] is False
|
|
|
|
|
|
class TestParseUiElements:
|
|
def test_parse_clickable(self, server):
|
|
elements = server._parse_ui_elements(SAMPLE_UI_XML)
|
|
texts = [e["text"] for e in elements]
|
|
assert "Settings" in texts
|
|
assert "Wi-Fi" in texts
|
|
|
|
def test_center_coordinates(self, server):
|
|
elements = server._parse_ui_elements(SAMPLE_UI_XML)
|
|
settings = [e for e in elements if e["text"] == "Settings"][0]
|
|
assert settings["center"] == {"x": 100, "y": 125}
|
|
|
|
def test_content_desc_included(self, server):
|
|
elements = server._parse_ui_elements(SAMPLE_UI_XML)
|
|
icon = [e for e in elements if e["content_desc"] == "Settings icon"]
|
|
assert len(icon) == 1
|
|
|
|
def test_empty_xml(self, server):
|
|
elements = server._parse_ui_elements("")
|
|
assert elements == []
|
|
|
|
|
|
class TestUiFindElement:
|
|
async def test_find_by_text(self, server):
|
|
server.run_shell_args.side_effect = [ok(), ok(stdout=SAMPLE_UI_XML), ok()]
|
|
result = await server.ui_find_element(text="Settings")
|
|
assert result["success"] is True
|
|
assert result["count"] == 1
|
|
assert result["matches"][0]["text"] == "Settings"
|
|
|
|
async def test_find_by_resource_id(self, server):
|
|
server.run_shell_args.side_effect = [ok(), ok(stdout=SAMPLE_UI_XML), ok()]
|
|
result = await server.ui_find_element(resource_id="title")
|
|
# Settings and Wi-Fi both have "title" in their resource-id
|
|
assert result["count"] >= 2
|
|
|
|
async def test_not_found(self, server):
|
|
server.run_shell_args.side_effect = [ok(), ok(stdout=SAMPLE_UI_XML), ok()]
|
|
result = await server.ui_find_element(text="Missing")
|
|
assert result["success"] is True
|
|
assert result["count"] == 0
|
|
|
|
|
|
class TestWaitForText:
|
|
async def test_found_immediately(self, server):
|
|
server.run_shell_args.side_effect = [ok(), ok(stdout=SAMPLE_UI_XML), ok()]
|
|
result = await server.wait_for_text("Settings", timeout_seconds=1)
|
|
assert result["success"] is True
|
|
assert result["found"] is True
|
|
assert result["attempts"] == 1
|
|
|
|
async def test_timeout(self, server):
|
|
server.run_shell_args.side_effect = [
|
|
ok(),
|
|
ok(stdout="<hierarchy></hierarchy>"),
|
|
ok(),
|
|
] * 10
|
|
result = await server.wait_for_text(
|
|
"Missing", timeout_seconds=0.1, poll_interval=0.05
|
|
)
|
|
assert result["success"] is False
|
|
assert result["found"] is False
|
|
|
|
|
|
class TestWaitForTextGone:
|
|
async def test_already_gone(self, server):
|
|
server.run_shell_args.side_effect = [
|
|
ok(),
|
|
ok(stdout="<hierarchy></hierarchy>"),
|
|
ok(),
|
|
]
|
|
result = await server.wait_for_text_gone("Missing", timeout_seconds=1)
|
|
assert result["success"] is True
|
|
assert result["gone"] is True
|
|
|
|
|
|
class TestTapText:
|
|
async def test_tap_found(self, server):
|
|
# find_element calls ui_dump which has 3 calls, then tap has 1
|
|
server.run_shell_args.side_effect = [
|
|
ok(),
|
|
ok(stdout=SAMPLE_UI_XML),
|
|
ok(), # ui_dump for find
|
|
ok(), # tap
|
|
]
|
|
result = await server.tap_text("Settings")
|
|
assert result["success"] is True
|
|
assert result["coordinates"] == {"x": 100, "y": 125}
|
|
|
|
async def test_not_found(self, server):
|
|
server.run_shell_args.side_effect = [
|
|
ok(),
|
|
ok(stdout=SAMPLE_UI_XML),
|
|
ok(), # first search by text
|
|
ok(),
|
|
ok(stdout=SAMPLE_UI_XML),
|
|
ok(), # fallback search by content_desc
|
|
]
|
|
result = await server.tap_text("NonExistent")
|
|
assert result["success"] is False
|
|
assert "No element found" in result["error"]
|