diff --git a/tests/e2e/test_engine_perception.py b/tests/e2e/test_engine_perception.py index 871014f..f599d90 100644 --- a/tests/e2e/test_engine_perception.py +++ b/tests/e2e/test_engine_perception.py @@ -293,3 +293,73 @@ class TestSAERealFixturePerception: @pytest.mark.skip(reason="Lying mock tests removed: Using StatefulMockDevice with string transitions is theater.") def test_perception_mock_theater_purged(): pass + + +# ───────────────────────────────────────────────────── +# STORY VIEW DETECTION TESTS (Phase 4) +# Exposes critical gap: Story screens were classified as UNKNOWN, +# causing GOAP to scroll blindly instead of pressing back. +# ───────────────────────────────────────────────────── + + +class TestStoryViewDetection: + """Story views MUST be structurally detected — no LLM fallback needed. + + Bug evidence from run 2026-04-27_23-46-57: + - Bot started on a Story screen (reel_viewer_media_layout, Like Story button) + - ScreenIdentity returned UNKNOWN + - GOAP chose 'scroll down' 4 times instead of 'press back' + - Bot was trapped in an infinite scroll loop on a story + """ + + def test_screen_identity_classifies_story_as_story_view(self): + """ScreenIdentity must detect reel_viewer_* markers as STORY_VIEW.""" + from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType + + si = ScreenIdentity(bot_username="marisaundmarc") + xml = _load_fixture("story_view_full.xml") + result = si.identify(xml) + + assert result["screen_type"] == ScreenType.STORY_VIEW, ( + f"Story view misclassified as {result['screen_type']}! " + f"Expected STORY_VIEW but ScreenIdentity returned {result['screen_type'].name}." + ) + + def test_sae_perceive_story_as_normal(self): + """SAE must classify Story views as NORMAL (it's Instagram, not an obstacle). + + The bot's reaction to a Story should be: press back → navigate away. + But first, SAE must NOT flag it as an obstacle. + """ + device = make_mock_device() + sae = SituationalAwarenessEngine(device) + xml = _load_fixture("story_view_full.xml") + result = sae.perceive(xml) + assert result == SituationType.NORMAL, f"Story view misclassified as {result}" + + def test_story_view_available_actions_include_press_back(self): + """On a story, 'press back' must be in available actions and 'scroll down' should NOT + be a meaningful action (stories don't scroll, they swipe).""" + from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType + + si = ScreenIdentity(bot_username="marisaundmarc") + xml = _load_fixture("story_view_full.xml") + result = si.identify(xml) + + assert "press back" in result["available_actions"], ( + "'press back' must be available on Story views!" + ) + + def test_story_view_has_no_navigation_tabs(self): + """Stories hide the navigation bar. The available actions must NOT + include tab navigation (tap home tab, tap explore tab, etc.).""" + from GramAddict.core.perception.screen_identity import ScreenIdentity + + si = ScreenIdentity(bot_username="marisaundmarc") + xml = _load_fixture("story_view_full.xml") + result = si.identify(xml) + + tab_actions = [a for a in result["available_actions"] if "tap" in a and "tab" in a] + assert len(tab_actions) == 0, ( + f"Story view should have NO tab navigation, but found: {tab_actions}" + ) diff --git a/tests/fixtures/story_view_full.xml b/tests/fixtures/story_view_full.xml new file mode 100644 index 0000000..d1d25a4 --- /dev/null +++ b/tests/fixtures/story_view_full.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file