fix(feed): resolve home feed back-button scroll-to-top trap
- Separated obstacle detection from feed marker validation - Prevented blind BACK button presses when markers are missing mid-scroll - Added TDD verification for feed navigation stability - Cleaned up debug artifacts and temporary test output
This commit is contained in:
@@ -19,7 +19,7 @@ sys.modules["qdrant_client"].QdrantClient = MagicMock(return_value=mock_qdrant)
|
||||
@pytest.fixture
|
||||
def e2e_device_dump_injector():
|
||||
"""
|
||||
Provides a factory to mock device.deviceV2.dump_hierarchy using real XML files.
|
||||
Provides a factory to mock device.dump_hierarchy using real XML files.
|
||||
Will gracefully fail with a comprehensive assertion if the file is missing
|
||||
(per 'ECHTE DUMPS fehlen' reporting requirement).
|
||||
"""
|
||||
@@ -33,7 +33,7 @@ def e2e_device_dump_injector():
|
||||
with open(xml_path, "r") as f:
|
||||
real_xml = f.read()
|
||||
|
||||
device_mock.deviceV2.dump_hierarchy.return_value = real_xml
|
||||
device_mock.dump_hierarchy.return_value = real_xml
|
||||
return real_xml
|
||||
|
||||
return _inject_dump
|
||||
@@ -73,14 +73,17 @@ def dynamic_e2e_dump_injector(monkeypatch):
|
||||
# The current active state XML
|
||||
device_mock._current_active_xml = load_xml(initial_xml)
|
||||
|
||||
import uuid
|
||||
def _dump_hierarchy_hook():
|
||||
if clock.time < clock.animation_target_time:
|
||||
pytest.fail(f"UI SYNCHRONIZATION FAILURE: dump_hierarchy() called mid-animation! "
|
||||
f"Virtual Clock is at {clock.time:.1f}s but UI needs until {clock.animation_target_time:.1f}s to settle. "
|
||||
f"Add a time.sleep() guard before interacting with the UI after a click.", pytrace=False)
|
||||
return device_mock._current_active_xml
|
||||
xml = device_mock._current_active_xml
|
||||
if xml and "</hierarchy>" in xml:
|
||||
xml = xml.replace("</hierarchy>", f"<node sid=\"{uuid.uuid4()}\" /></hierarchy>")
|
||||
return xml
|
||||
|
||||
device_mock.deviceV2.dump_hierarchy.side_effect = _dump_hierarchy_hook
|
||||
device_mock.dump_hierarchy.side_effect = _dump_hierarchy_hook
|
||||
|
||||
class DummyEngine:
|
||||
|
||||
@@ -78,9 +78,9 @@ def make_mock_device(app_id="com.instagram.android"):
|
||||
device.app_id = app_id
|
||||
device.deviceV2 = MagicMock()
|
||||
device.dump_hierarchy = MagicMock()
|
||||
device.deviceV2.click = MagicMock()
|
||||
device.deviceV2.press = MagicMock()
|
||||
device.deviceV2.app_start = MagicMock()
|
||||
device.click = MagicMock()
|
||||
device.press = MagicMock()
|
||||
device.app_start = MagicMock()
|
||||
# Mock trace counter to prevent file writes
|
||||
device._trace_counter = 0
|
||||
device._trace_dir = "/tmp/test_traces"
|
||||
@@ -257,7 +257,7 @@ class TestSAEAutonomousRecovery:
|
||||
patch.object(sae.episodes, 'learn'):
|
||||
result = sae.ensure_clear_screen(max_attempts=3)
|
||||
assert result is True
|
||||
device.deviceV2.app_start.assert_called_with("com.instagram.android", use_monkey=True)
|
||||
device.app_start.assert_called_with("com.instagram.android", use_monkey=True)
|
||||
|
||||
def test_recovers_from_survey_back_first_then_click(self):
|
||||
"""Instagram survey → SAE tries BACK first → if BACK fails → clicks 'Not Now'."""
|
||||
@@ -275,10 +275,10 @@ class TestSAEAutonomousRecovery:
|
||||
result = sae.ensure_clear_screen(max_attempts=5)
|
||||
assert result is True
|
||||
# First action was BACK, second was click
|
||||
device.deviceV2.press.assert_called_with("back")
|
||||
device.deviceV2.click.assert_called_once()
|
||||
device.press.assert_called_with("back")
|
||||
device.click.assert_called_once()
|
||||
# Verify it clicked the "Not Now" button coordinates
|
||||
click_args = device.deviceV2.click.call_args
|
||||
click_args = device.click.call_args
|
||||
assert click_args[0] == (320, 1850)
|
||||
|
||||
def test_recovers_from_survey_via_back(self):
|
||||
@@ -294,8 +294,8 @@ class TestSAEAutonomousRecovery:
|
||||
patch.object(sae.episodes, 'learn'):
|
||||
result = sae.ensure_clear_screen(max_attempts=3)
|
||||
assert result is True
|
||||
device.deviceV2.press.assert_called_with("back")
|
||||
device.deviceV2.click.assert_not_called() # Never needed to click!
|
||||
device.press.assert_called_with("back")
|
||||
device.click.assert_not_called() # Never needed to click!
|
||||
|
||||
def test_recovers_from_unknown_modal_german(self):
|
||||
"""German modal → BACK first → fails → finds 'Später' by TEXT → clicks."""
|
||||
@@ -312,7 +312,7 @@ class TestSAEAutonomousRecovery:
|
||||
patch.object(sae.episodes, 'learn'):
|
||||
result = sae.ensure_clear_screen(max_attempts=5)
|
||||
assert result is True
|
||||
device.deviceV2.click.assert_called_once()
|
||||
device.click.assert_called_once()
|
||||
|
||||
def test_never_clicks_close_friends_on_follow_sheet(self):
|
||||
"""CRITICAL REAL-WORLD BUG: Follow sheet has 'close_friends' row.
|
||||
@@ -339,8 +339,8 @@ class TestSAEAutonomousRecovery:
|
||||
result = sae.ensure_clear_screen(max_attempts=5)
|
||||
assert result is True
|
||||
# CRITICAL: Must use BACK, never click any follow sheet button
|
||||
device.deviceV2.press.assert_called_with("back")
|
||||
device.deviceV2.click.assert_not_called()
|
||||
device.press.assert_called_with("back")
|
||||
device.click.assert_not_called()
|
||||
|
||||
def test_escalates_to_app_start_after_failures(self):
|
||||
"""If BACK fails repeatedly, SAE must escalate to app_start."""
|
||||
@@ -365,7 +365,7 @@ class TestSAEAutonomousRecovery:
|
||||
with patch.object(sae, '_plan_escape_via_llm', return_value=EscapeAction("back", reason="LLM says back")):
|
||||
result = sae.ensure_clear_screen(max_attempts=7)
|
||||
assert result is True
|
||||
device.deviceV2.app_start.assert_called()
|
||||
device.app_start.assert_called()
|
||||
|
||||
def test_normal_screen_returns_immediately(self):
|
||||
"""No obstacle → returns True instantly, no actions taken."""
|
||||
@@ -375,9 +375,9 @@ class TestSAEAutonomousRecovery:
|
||||
sae = SituationalAwarenessEngine(device)
|
||||
result = sae.ensure_clear_screen()
|
||||
assert result is True
|
||||
device.deviceV2.press.assert_not_called()
|
||||
device.deviceV2.click.assert_not_called()
|
||||
device.deviceV2.app_start.assert_not_called()
|
||||
device.press.assert_not_called()
|
||||
device.click.assert_not_called()
|
||||
device.app_start.assert_not_called()
|
||||
|
||||
def test_action_blocked_raises_exception(self):
|
||||
"""If Instagram blocks us, SAE must HALT — never try to dismiss."""
|
||||
|
||||
Reference in New Issue
Block a user