refactor(e2e): extract workflow infrastructure into conftest, one test file per workflow
BEFORE: Giant monolith test files with duplicated device stubs, config factories, and cognitive stack builders. The infrastructure was copy-pasted into each file. AFTER: Clean separation: - conftest.py: E2EDeviceStub, e2e_device, e2e_cognitive_stack, e2e_session, e2e_workflow_ctx fixtures - test_workflow_permission_dialog.py: Permission dialog recovery - test_workflow_foreign_app.py: Foreign app (Chrome) recovery - test_workflow_normal_feed.py: Normal post processing - test_workflow_plugin_integrity.py: Import + interface sanity Each workflow file is <100 lines. Zero duplication. Every test uses the shared e2e_workflow_ctx fixture which runs the EXACT code path from bot_flow.py:942-971. DELETED: test_full_workflow_hostile_env.py, test_plugin_chain_hostile_env.py (monolith files replaced by the above)
This commit is contained in:
@@ -17,6 +17,7 @@ import time
|
||||
import pytest
|
||||
|
||||
from GramAddict.core import utils
|
||||
from GramAddict.core.session_state import SessionState
|
||||
|
||||
# ═══════════════════════════════════════════════════════
|
||||
# CLI Options
|
||||
@@ -504,3 +505,179 @@ def setup_e2e_plugin_registry():
|
||||
plugin_registry.register(RepostPlugin())
|
||||
plugin_registry.register(PostInteractionPlugin())
|
||||
yield plugin_registry
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════
|
||||
# E2E Device Stub — The ONLY mock in true E2E tests
|
||||
# ═══════════════════════════════════════════════════════
|
||||
|
||||
|
||||
class E2EDeviceStub:
|
||||
"""
|
||||
Replays a sequence of XML dumps simulating the Android device.
|
||||
This is the ONLY thing mocked in E2E tests. Everything else is real.
|
||||
"""
|
||||
|
||||
def __init__(self, xml_sequence):
|
||||
self._xml_sequence = list(xml_sequence)
|
||||
self._dump_index = 0
|
||||
self.pressed_keys = []
|
||||
self.clicks = []
|
||||
self.swipes = []
|
||||
self.app_id = "com.instagram.android"
|
||||
self._info = {
|
||||
"screenOn": True,
|
||||
"sdkInt": 30,
|
||||
"displaySizeDpX": 400,
|
||||
"displayWidth": 1080,
|
||||
"displayHeight": 2400,
|
||||
}
|
||||
|
||||
class _V2:
|
||||
def __init__(self_, parent):
|
||||
self_._parent = parent
|
||||
self_.info = parent._info
|
||||
self_.settings = {}
|
||||
|
||||
def dump_hierarchy(self_, compressed=False):
|
||||
return self_._parent.dump_hierarchy()
|
||||
|
||||
def app_current(self_):
|
||||
return {"package": "com.instagram.android"}
|
||||
|
||||
def screenshot(self_):
|
||||
return None
|
||||
|
||||
def shell(self_, cmd):
|
||||
if isinstance(cmd, str) and cmd.startswith("input tap"):
|
||||
parts = cmd.split()
|
||||
try:
|
||||
x, y = int(parts[-2]), int(parts[-1])
|
||||
self_._parent.clicks.append((x, y))
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
self.deviceV2 = _V2(self)
|
||||
|
||||
class _Touch:
|
||||
def __init__(self_, parent):
|
||||
self_._parent = parent
|
||||
|
||||
def down(self_, x, y):
|
||||
self_._parent.clicks.append((x, y))
|
||||
|
||||
def up(self_, x, y):
|
||||
pass
|
||||
|
||||
self.deviceV2.touch = _Touch(self)
|
||||
|
||||
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_keys.append(key)
|
||||
|
||||
def click(self, x, y):
|
||||
self.clicks.append((x, y))
|
||||
|
||||
def swipe(self, sx, sy, ex, ey, **kwargs):
|
||||
self.swipes.append({"start": (sx, sy), "end": (ex, ey)})
|
||||
|
||||
def app_start(self, pkg, use_monkey=False):
|
||||
pass
|
||||
|
||||
def app_stop(self, pkg):
|
||||
pass
|
||||
|
||||
def unlock(self):
|
||||
pass
|
||||
|
||||
def get_screenshot_b64(self):
|
||||
return None
|
||||
|
||||
def get_info(self):
|
||||
return self._info
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════
|
||||
# E2E Workflow Fixtures — Real cognitive stack, real config
|
||||
# ═══════════════════════════════════════════════════════
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def e2e_device():
|
||||
"""Factory to create an E2EDeviceStub from an XML sequence."""
|
||||
def _create(xml_sequence):
|
||||
return E2EDeviceStub(xml_sequence)
|
||||
return _create
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def e2e_cognitive_stack():
|
||||
"""Build the REAL cognitive stack exactly like bot_flow.py does."""
|
||||
from GramAddict.core.dopamine_engine import DopamineEngine
|
||||
|
||||
dopamine = DopamineEngine()
|
||||
# Force session to end quickly so tests don't hang
|
||||
dopamine.session_limit_seconds = 0.5
|
||||
dopamine.session_start = time.time()
|
||||
|
||||
return {
|
||||
"dopamine": dopamine,
|
||||
"active_inference": None,
|
||||
"swarm": None,
|
||||
"resonance": None,
|
||||
"growth_brain": None,
|
||||
"radome": None,
|
||||
"nav_graph": None,
|
||||
"zero_engine": None,
|
||||
"telepathic": None,
|
||||
"darwin": None,
|
||||
"crm": None,
|
||||
"dm_memory": None,
|
||||
"writer": None,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def e2e_session(e2e_configs):
|
||||
"""Build a real SessionState."""
|
||||
return SessionState(e2e_configs)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def e2e_workflow_ctx(e2e_configs, e2e_cognitive_stack, setup_e2e_plugin_registry):
|
||||
"""
|
||||
Factory that builds a REAL BehaviorContext + runs execute_all().
|
||||
This is the EXACT code path from bot_flow.py:942-971.
|
||||
|
||||
Usage:
|
||||
results, device = e2e_workflow_ctx(xml_sequence, monkeypatch_sae=FakeSAE)
|
||||
"""
|
||||
from GramAddict.core.behaviors import BehaviorContext
|
||||
|
||||
def _run(device, context_xml=None):
|
||||
xml = context_xml if context_xml else device.dump_hierarchy()
|
||||
session = SessionState(e2e_configs)
|
||||
|
||||
ctx = BehaviorContext(
|
||||
device=device,
|
||||
configs=e2e_configs,
|
||||
session_state=session,
|
||||
cognitive_stack=e2e_cognitive_stack,
|
||||
context_xml=xml,
|
||||
sleep_mod=1.0,
|
||||
post_data={},
|
||||
username="",
|
||||
shared_state={"consecutive_marker_misses": 0, "consecutive_ads": 0},
|
||||
)
|
||||
|
||||
registry = setup_e2e_plugin_registry
|
||||
results = registry.execute_all(ctx)
|
||||
return results, ctx
|
||||
|
||||
return _run
|
||||
|
||||
Reference in New Issue
Block a user