from __future__ import annotations
import time
from typing import TYPE_CHECKING, Callable
import pytest
if TYPE_CHECKING:
from conftest import MockAXElement, PerformanceResult, TestApp
class TestFindByTitle:
@pytest.mark.requires_app
def test_find_by_title_simple(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("1")
assert element is not None
@pytest.mark.requires_app
def test_find_by_title_with_spaces(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("AC")
assert element is not None
def test_find_by_title_mock(self, mock_calculator_tree: MockAXElement) -> None:
tree = mock_calculator_tree
def find_by_title(node: MockAXElement, title: str) -> MockAXElement | None:
if node.title == title:
return node
for child in node.get_children():
result = find_by_title(child, title)
if result:
return result
return None
button = find_by_title(tree, "5")
assert button is not None
assert button.role == "AXButton"
assert button.title == "5"
@pytest.mark.requires_app
def test_find_by_title_returns_first_match(self, finder_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Finder")
try:
element = app.find("File")
assert element is not None
except RuntimeError:
pass
def test_find_by_title_case_sensitive(
self, mock_calculator_tree: MockAXElement
) -> None:
def find_by_title(node: MockAXElement, title: str) -> MockAXElement | None:
if node.title == title:
return node
for child in node.get_children():
result = find_by_title(child, title)
if result:
return result
return None
ac_button = find_by_title(mock_calculator_tree, "AC")
lowercase = find_by_title(mock_calculator_tree, "ac")
assert ac_button is not None
assert lowercase is None
class TestFindByRole:
@pytest.mark.requires_app
def test_find_by_role_button(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find_by_role("AXButton")
assert element is not None
assert element.role() == "AXButton"
@pytest.mark.requires_app
def test_find_by_role_with_title(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find_by_role("AXButton", title="5")
assert element is not None
assert element.title() == "5"
@pytest.mark.requires_app
def test_find_by_role_window(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find_by_role("AXWindow")
assert element is not None
assert element.role() == "AXWindow"
@pytest.mark.requires_app
def test_find_by_role_with_identifier(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
try:
element = app.find_by_role("AXButton", identifier="some_id")
assert element is not None
except RuntimeError:
pass
def test_find_by_role_invalid_role(self) -> None:
import axterminator as ax
app = ax.app(name="Finder")
with pytest.raises(RuntimeError):
app.find_by_role("AXInvalidRole12345")
@pytest.mark.requires_app
def test_find_by_role_statictext(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
try:
element = app.find_by_role("AXStaticText")
assert element is not None
except RuntimeError:
pass
class TestFindByIdentifier:
def test_find_by_identifier_mock(self, mock_calculator_tree: MockAXElement) -> None:
def find_by_id(node: MockAXElement, identifier: str) -> MockAXElement | None:
if node.identifier == identifier:
return node
for child in node.get_children():
result = find_by_id(child, identifier)
if result:
return result
return None
button = find_by_id(mock_calculator_tree, "calc_btn_5")
assert button is not None
assert button.title == "5"
@pytest.mark.requires_app
def test_find_by_identifier_using_find(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
try:
element = app.find("identifier:_NS:9") assert element is not None
except RuntimeError:
pass
def test_find_by_identifier_not_found(
self, mock_calculator_tree: MockAXElement
) -> None:
def find_by_id(node: MockAXElement, identifier: str) -> MockAXElement | None:
if node.identifier == identifier:
return node
for child in node.get_children():
result = find_by_id(child, identifier)
if result:
return result
return None
result = find_by_id(mock_calculator_tree, "nonexistent_identifier")
assert result is None
class TestFindWithTimeout:
@pytest.mark.requires_app
def test_find_with_timeout_found_immediately(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
start = time.perf_counter()
element = app.find("1", timeout_ms=5000)
elapsed = time.perf_counter() - start
assert element is not None
assert elapsed < 1.0
@pytest.mark.requires_app
@pytest.mark.slow
def test_find_with_timeout_waits(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
start = time.perf_counter()
with pytest.raises(RuntimeError, match="not found"):
app.find("NonExistentElement12345", timeout_ms=500)
elapsed = time.perf_counter() - start
assert 0.4 < elapsed < 1.5
@pytest.mark.requires_app
def test_wait_for_element_method(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.wait_for_element("1", timeout_ms=2000)
assert element is not None
@pytest.mark.requires_app
@pytest.mark.slow
def test_wait_for_element_timeout_raises(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
with pytest.raises(RuntimeError):
app.wait_for_element("NonExistent12345", timeout_ms=500)
@pytest.mark.requires_app
def test_timeout_zero_no_wait(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
start = time.perf_counter()
with pytest.raises(RuntimeError):
app.find("NonExistent", timeout_ms=0)
elapsed = time.perf_counter() - start
assert elapsed < 0.5
class TestElementNotFound:
@pytest.mark.requires_app
def test_element_not_found_error_message(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
query = "UniqueNonExistentElement12345"
with pytest.raises(RuntimeError) as exc_info:
app.find(query, timeout_ms=100)
error_msg = str(exc_info.value)
assert "not found" in error_msg.lower()
@pytest.mark.requires_app
def test_find_by_role_not_found(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
with pytest.raises(RuntimeError):
app.find_by_role("AXButton", title="NonExistentTitle12345")
def test_element_not_found_after_healing(self) -> None:
pass
class TestElementProperties:
@pytest.mark.requires_app
def test_element_role(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("1")
role = element.role()
assert role is not None
assert isinstance(role, str)
assert role == "AXButton"
@pytest.mark.requires_app
def test_element_title(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("5")
title = element.title()
assert title == "5"
@pytest.mark.requires_app
def test_element_value(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
try:
element = app.find_by_role("AXStaticText")
value = element.value()
assert value is not None or value == ""
except RuntimeError:
pass
@pytest.mark.requires_app
def test_element_description(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("1")
desc = element.description()
assert desc is None or isinstance(desc, str)
@pytest.mark.requires_app
def test_element_label(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("1")
label = element.label()
assert label is None or isinstance(label, str)
@pytest.mark.requires_app
def test_element_identifier(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("1")
identifier = element.identifier()
assert identifier is None or isinstance(identifier, str)
@pytest.mark.requires_app
def test_element_enabled(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("1")
enabled = element.enabled()
assert isinstance(enabled, bool)
assert enabled is True
@pytest.mark.requires_app
def test_element_focused(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("1")
focused = element.focused()
assert isinstance(focused, bool)
@pytest.mark.requires_app
def test_element_exists(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("1")
exists = element.exists()
assert exists is True
@pytest.mark.requires_app
def test_element_bounds(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("1")
bounds = element.bounds()
if bounds is not None:
assert len(bounds) == 4
x, y, width, height = bounds
assert isinstance(x, (int, float))
assert isinstance(y, (int, float))
assert isinstance(width, (int, float))
assert isinstance(height, (int, float))
class TestElementPropertyEdgeCases:
def test_properties_on_mock_element(
self, mock_ax_element: Callable[..., MockAXElement]
) -> None:
element = mock_ax_element(
role="AXButton",
title="Test",
value="Value",
identifier="test_id",
description="A test button",
label="Test Label",
enabled=True,
focused=False,
bounds=(100, 100, 50, 30),
)
assert element.role == "AXButton"
assert element.title == "Test"
assert element.value == "Value"
assert element.identifier == "test_id"
assert element.description == "A test button"
assert element.label == "Test Label"
assert element.enabled is True
assert element.focused is False
assert element.bounds == (100, 100, 50, 30)
def test_properties_with_none_values(
self, mock_ax_element: Callable[..., MockAXElement]
) -> None:
element = mock_ax_element(
role="AXButton",
title=None,
value=None,
identifier=None,
)
assert element.role == "AXButton"
assert element.title is None
assert element.value is None
assert element.identifier is None
def test_properties_with_empty_strings(
self, mock_ax_element: Callable[..., MockAXElement]
) -> None:
element = mock_ax_element(
role="AXButton",
title="",
value="",
)
assert element.title == ""
assert element.value == ""
def test_properties_with_unicode(
self, mock_ax_element: Callable[..., MockAXElement]
) -> None:
element = mock_ax_element(
role="AXButton",
title="Save Changes",
description="Saves the current document",
)
assert element.title == "Save Changes"
assert "Saves" in element.description
class TestQuerySyntax:
@pytest.mark.requires_app
def test_simple_title_query(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("7")
assert element is not None
@pytest.mark.requires_app
def test_role_prefix_query(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
try:
element = app.find("role:AXButton")
assert element is not None
except RuntimeError:
pass
@pytest.mark.requires_app
def test_combined_query(self, calculator_app: TestApp) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
try:
element = app.find("role:AXButton title:8")
assert element is not None
except RuntimeError:
pass
class TestElementPerformance:
@pytest.mark.slow
@pytest.mark.requires_app
def test_find_performance(
self,
calculator_app: TestApp,
perf_timer: Callable[..., PerformanceResult],
) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
result = perf_timer(
lambda: app.find("5", timeout_ms=100),
iterations=100,
name="find_element",
)
assert result.p95_ms < 10, f"Find too slow: {result.p95_ms}ms"
@pytest.mark.slow
@pytest.mark.requires_app
def test_property_access_performance(
self,
calculator_app: TestApp,
perf_timer: Callable[..., PerformanceResult],
) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
element = app.find("5")
result = perf_timer(
lambda: element.role(),
iterations=1000,
name="property_access",
)
assert result.p95_ms < 1, f"Property access too slow: {result.p95_ms}ms"
@pytest.mark.slow
@pytest.mark.requires_app
def test_multiple_finds_performance(
self,
calculator_app: TestApp,
perf_timer: Callable[..., PerformanceResult],
) -> None:
import axterminator as ax
app = ax.app(name="Calculator")
def find_multiple() -> None:
for i in range(10):
app.find(str(i % 10), timeout_ms=100)
result = perf_timer(
find_multiple,
iterations=10,
name="find_multiple",
)
assert result.avg_ms < 100, f"Multiple finds too slow: {result.avg_ms}ms"