diff --git a/GramAddict/core/behaviors/obstacle_guard.py b/GramAddict/core/behaviors/obstacle_guard.py
index da11b54..696b880 100644
--- a/GramAddict/core/behaviors/obstacle_guard.py
+++ b/GramAddict/core/behaviors/obstacle_guard.py
@@ -42,6 +42,21 @@ class ObstacleGuardPlugin(BehaviorPlugin):
misses = ctx.shared_state.get("consecutive_marker_misses", 0)
+ # ── System Dialog / Permission Modal (e.g. "Allow Instagram to record audio?") ──
+ if situation == SituationType.OBSTACLE_SYSTEM:
+ logger.warning("⚠️ [ObstacleGuard] System permission dialog detected. Dismissing with BACK...")
+ ctx.device.press("back")
+ sleep(1.5 * ctx.sleep_mod)
+ return BehaviorResult(executed=True, should_skip=True)
+
+ # ── Foreign App Takeover (e.g. browser opened, wrong app in foreground) ──
+ if situation == SituationType.OBSTACLE_FOREIGN_APP:
+ logger.warning("⚠️ [ObstacleGuard] Foreign app detected. Pressing BACK to recover...")
+ ctx.device.press("back")
+ sleep(1.5 * ctx.sleep_mod)
+ return BehaviorResult(executed=True, should_skip=True)
+
+ # ── Instagram Modal / Overlay (survey, "Not Now" prompt, creation flow) ──
if situation == SituationType.OBSTACLE_MODAL:
if misses >= 2:
logger.error("🛑 [ObstacleGuard] Failed to recover from OBSTACLE_MODAL after multiple attempts.")
diff --git a/GramAddict/core/behaviors/resonance_evaluator.py b/GramAddict/core/behaviors/resonance_evaluator.py
index f828348..c8b1dd0 100644
--- a/GramAddict/core/behaviors/resonance_evaluator.py
+++ b/GramAddict/core/behaviors/resonance_evaluator.py
@@ -72,7 +72,6 @@ class ResonanceEvaluatorPlugin(BehaviorPlugin):
if marker and marker.strip():
from GramAddict.core.utils import learn_ad_marker
learn_ad_marker(marker, ctx.context_xml)
- from GramAddict.core.utils import humanized_scroll
humanized_scroll(ctx.device)
return BehaviorResult(executed=True, should_skip=True)
diff --git a/tests/unit/test_obstacle_guard_system_dialog.py b/tests/unit/test_obstacle_guard_system_dialog.py
new file mode 100644
index 0000000..fb23e4d
--- /dev/null
+++ b/tests/unit/test_obstacle_guard_system_dialog.py
@@ -0,0 +1,144 @@
+"""
+🔴 RED: Prove that ObstacleGuard currently FAILS to handle system permission dialogs.
+
+The obstacle_guard only checks for OBSTACLE_MODAL but ignores OBSTACLE_SYSTEM
+and OBSTACLE_FOREIGN_APP. This test forces the bug to surface.
+
+Uses monkeypatch + real fixtures (no unittest.mock).
+"""
+import pytest
+from dataclasses import dataclass, field
+from typing import Any, Dict, Optional
+
+from GramAddict.core.behaviors import BehaviorContext, BehaviorResult
+from GramAddict.core.behaviors.obstacle_guard import ObstacleGuardPlugin
+from GramAddict.core.situational_awareness import SituationType
+
+
+PERMISSION_DIALOG_XML = """
+
+
+
+
+
+
+
+
+"""
+
+NORMAL_INSTAGRAM_XML = ''
+
+
+class FakeDevice:
+ """Minimal real-ish device stub for obstacle_guard tests."""
+
+ def __init__(self, xml_sequence=None):
+ self._xml_sequence = xml_sequence or [NORMAL_INSTAGRAM_XML]
+ self._dump_index = 0
+ self._pressed = []
+ self._clicks = []
+ self.app_id = "com.instagram.android"
+
+ class _V2:
+ info = {"screenOn": True}
+ self.deviceV2 = _V2()
+
+ def dump_hierarchy(self):
+ if self._dump_index < len(self._xml_sequence):
+ xml = self._xml_sequence[self._dump_index]
+ self._dump_index += 1
+ return xml
+ return self._xml_sequence[-1]
+
+ def press(self, key):
+ self._pressed.append(key)
+
+ def click(self, x, y):
+ self._clicks.append((x, y))
+
+
+class FakeConfig:
+ class args:
+ pass
+
+
+class FakeSessionState:
+ job_target = "test"
+
+
+def _make_ctx(xml: str, device: FakeDevice = None) -> BehaviorContext:
+ dev = device or FakeDevice()
+ return BehaviorContext(
+ device=dev,
+ configs=FakeConfig(),
+ session_state=FakeSessionState(),
+ cognitive_stack={},
+ shared_state={},
+ context_xml=xml,
+ )
+
+
+class TestObstacleGuardHandlesSystemDialogs:
+ """The guard MUST handle OBSTACLE_SYSTEM, not just OBSTACLE_MODAL."""
+
+ def test_system_permission_dialog_triggers_back_press(self, monkeypatch):
+ """ObstacleGuard must react to OBSTACLE_SYSTEM by pressing back."""
+ plugin = ObstacleGuardPlugin()
+ device = FakeDevice(xml_sequence=[NORMAL_INSTAGRAM_XML])
+ ctx = _make_ctx(PERMISSION_DIALOG_XML, device=device)
+
+ # Monkeypatch SAE to return OBSTACLE_SYSTEM for the permission dialog
+ class FakeSAE:
+ @classmethod
+ def get_instance(cls, device=None):
+ return cls()
+ def perceive(self, xml):
+ return SituationType.OBSTACLE_SYSTEM
+
+ monkeypatch.setattr(
+ "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine",
+ FakeSAE,
+ )
+
+ result = plugin.execute(ctx)
+
+ assert result.executed is True
+ assert result.should_skip is True
+ assert "back" in device._pressed
+
+ def test_foreign_app_triggers_back_press(self, monkeypatch):
+ """ObstacleGuard must react to OBSTACLE_FOREIGN_APP by pressing back."""
+ plugin = ObstacleGuardPlugin()
+ device = FakeDevice(xml_sequence=[NORMAL_INSTAGRAM_XML])
+ ctx = _make_ctx(PERMISSION_DIALOG_XML, device=device)
+
+ class FakeSAE:
+ @classmethod
+ def get_instance(cls, device=None):
+ return cls()
+ def perceive(self, xml):
+ return SituationType.OBSTACLE_FOREIGN_APP
+
+ monkeypatch.setattr(
+ "GramAddict.core.behaviors.obstacle_guard.SituationalAwarenessEngine",
+ FakeSAE,
+ )
+
+ result = plugin.execute(ctx)
+
+ assert result.executed is True
+ assert result.should_skip is True
+ assert "back" in device._pressed
+
+
+class TestResonanceEvaluatorImportIntegrity:
+ """The humanized_scroll import must resolve correctly at module level."""
+
+ def test_humanized_scroll_is_importable_from_resonance_evaluator(self):
+ """Verify the resonance_evaluator module loads without ImportError."""
+ # If the import on line 6 or 75 is broken, this will crash
+ from GramAddict.core.behaviors.resonance_evaluator import ResonanceEvaluatorPlugin
+ assert ResonanceEvaluatorPlugin is not None