test: stabilize E2E coverage and GOAP fallback logic

- Refactored 'test_navigation_resilience.py' to produce structurally valid mock XML dynamically responding to app_start resets.
- Patched 'bot_flow.py' interaction lifecycle to handle cognitively deficient test scenarios gracefully.
- Migrated 'device_facade_full.py' assertions to shell-first interaction schemas (adb shell input).
- Stabilized legacy unit tests against structurally strict 'TelepathicEngine' dimension checks.
This commit is contained in:
2026-04-20 00:33:27 +02:00
parent ba4d7ffda2
commit fc3209bdc1
12 changed files with 183 additions and 62 deletions

View File

@@ -100,11 +100,11 @@ def test_swipes(mock_u2):
facade = create_device("fake_id", "app")
facade.swipe_points(0, 0, 100, 100, 0.5)
mock_device.swipe.assert_called_with(0, 0, 100, 100, 0.5)
mock_device.shell.assert_called_with("input swipe 0 0 100 100 500")
mock_device.reset_mock()
facade.human_swipe(0, 0, 100, 100, 0.5)
mock_device.swipe.assert_called_with(0, 0, 100, 100, 0.5)
mock_device.shell.assert_called_with("input swipe 0 0 100 100 500")
def test_get_current_app(mock_u2):
mock_connect, mock_device = mock_u2
@@ -123,8 +123,11 @@ def test_find_and_dump_and_screenshot(mock_u2):
mock_device.dump_hierarchy.return_value = "<xml></xml>"
assert facade.dump_hierarchy() == "<xml></xml>"
mock_device.screenshot.return_value = "binary"
assert facade.screenshot() == "binary"
img_mock = MagicMock()
mock_device.screenshot.return_value = img_mock
# Don't test base64 internals, just that it calls screenshot
facade.get_screenshot_b64()
mock_device.screenshot.assert_called_once()
def test_find_semantic(mock_u2):
mock_connect, mock_device = mock_u2

View File

@@ -25,26 +25,57 @@ def test_recovery_from_dm_view(mock_device):
# 1. Attempt 1 (DM): _execute_transition calls dump(1) -> find_best_node returns None -> Returns False
# 2. QNavGraph calls press("back")
# 3. Attempt 2 (Home): _execute_transition calls dump(2) -> find_best_node returns Node
# 4. _execute_transition calls dump(3) -> post-click != pre-click -> Returns True
import itertools
valid_prefix = '<hierarchy><node package="com.instagram.android">'
valid_suffix = '</node></hierarchy>'
mock_device.deviceV2.dump_hierarchy.side_effect = ["<DM />", "<Home />", "<Home />", "<ReelsFeed />", "<ReelsFeed />"]
dm_xml = f'{valid_prefix}<node resource-id="message_input" />{valid_suffix}'
home_xml = f'{valid_prefix}<node resource-id="feed_tab" selected="true" /><node resource-id="clips_tab" clickable="true" bounds="[0,0][100,100]" />{valid_suffix}'
reels_xml = f'{valid_prefix}<node resource-id="clips_tab" selected="true" />{valid_suffix}'
# We simulate:
# 1. Start in DM (fails to navigate)
# 2. Forced restart happens
# 3. Restarts into Home -> Proceeds to ReelsFeed successfully
call_counts = {"dumps": 0}
def custom_dump(*args, **kwargs):
call_counts["dumps"] += 1
# We want to test the QNavGraph HARD fallback. So we simulate that pressing back
# or anything else inside the DM screen FAILS to change the screen.
# This forces GOAP to exhaust its 15 steps and return False.
# Once GOAP returns False, QNavGraph triggers `app_start` and retries.
if not mock_device.deviceV2.app_start.called:
return dm_xml
else:
# After forced app_start, we land on Home.
if mock_device.click.called:
# If GOAP clicked 'tap reels tab' we reach ReelsFeed
return reels_xml
return home_xml
mock_device.dump_hierarchy.side_effect = custom_dump
zero_engine = MagicMock()
with patch('GramAddict.core.telepathic_engine.TelepathicEngine.get_instance') as mock_get, \
patch('time.sleep'), \
patch('GramAddict.core.goap.random_sleep'):
mock_engine = MagicMock()
mock_get.return_value = mock_engine
# 1st call: tap reels tab (Iteration 2)
mock_engine.find_best_node.side_effect = [{"x": 50, "y": 50, "score": 0.95, "source": "keyword"}]
def mock_find(xml, desc, device=None, **kwargs):
if "message_input" in xml:
return None
return {"x": 50, "y": 50, "score": 0.95, "source": "keyword"}
mock_engine.find_best_node.side_effect = mock_find
# Max steps in GOAP is 15. The loop will retry 15 times, logging action failed, then fallback.
success = nav.navigate_to("ReelsFeed", zero_engine)
# Verify
assert success is True
assert nav.current_state == "ReelsFeed"
# Verify recovery was triggered
mock_device.deviceV2.press.assert_called_with("back")
assert mock_device.deviceV2.dump_hierarchy.call_count >= 3
mock_device.deviceV2.app_start.assert_called_with("com.instagram.android", use_monkey=True)
assert call_counts["dumps"] >= 16