init
This commit is contained in:
0
tests/e2e/__init__.py
Normal file
0
tests/e2e/__init__.py
Normal file
126
tests/e2e/conftest.py
Normal file
126
tests/e2e/conftest.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import sys
|
||||
import os
|
||||
import pytest
|
||||
import time
|
||||
from unittest.mock import MagicMock
|
||||
from GramAddict.core import utils
|
||||
|
||||
# Force Qdrant mocking globally across ALL E2E tests so we never
|
||||
# block on connection refused trying to hit localhost:6344
|
||||
mock_qdrant = MagicMock()
|
||||
|
||||
# Setup correct return types for dimension check warnings in qdrant_memory
|
||||
mock_collection = MagicMock()
|
||||
mock_collection.config.params.vectors.size = 768
|
||||
mock_qdrant.get_collection.return_value = mock_collection
|
||||
|
||||
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.
|
||||
Will gracefully fail with a comprehensive assertion if the file is missing
|
||||
(per 'ECHTE DUMPS fehlen' reporting requirement).
|
||||
"""
|
||||
def _inject_dump(device_mock, xml_filename):
|
||||
fix_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "fixtures")
|
||||
xml_path = os.path.join(fix_dir, xml_filename)
|
||||
|
||||
if not os.path.exists(xml_path):
|
||||
pytest.fail(f"MISSING REAL DUMP: required XML fixture '{xml_filename}' for full E2E workflow testing could not be found at {xml_path}. FAKE_NOTHING policy implies dropping this test execution until it is captured.", pytrace=False)
|
||||
|
||||
with open(xml_path, "r") as f:
|
||||
real_xml = f.read()
|
||||
|
||||
device_mock.deviceV2.dump_hierarchy.return_value = real_xml
|
||||
return real_xml
|
||||
|
||||
return _inject_dump
|
||||
|
||||
@pytest.fixture
|
||||
def dynamic_e2e_dump_injector(monkeypatch):
|
||||
"""
|
||||
State-Machine Injector: Replaces dump_hierarchy dynamically when transitions occur.
|
||||
Validates that the Telepathic Engine's pathfinding truly worked.
|
||||
"""
|
||||
def _inject(device_mock, state_map, initial_xml):
|
||||
fix_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "fixtures")
|
||||
|
||||
def load_xml(filename):
|
||||
path = os.path.join(fix_dir, filename)
|
||||
if not os.path.exists(path):
|
||||
pytest.fail(f"MISSING REAL DUMP: {filename} not found.")
|
||||
with open(path, "r") as f:
|
||||
return f.read()
|
||||
|
||||
device_mock.deviceV2.dump_hierarchy.return_value = load_xml(initial_xml)
|
||||
|
||||
from GramAddict.core.q_nav_graph import QNavGraph
|
||||
original_execute = QNavGraph._execute_transition
|
||||
|
||||
def _mock_execute_transition(nav_self, action, zero_engine):
|
||||
if action == 'tap_post_username':
|
||||
return True
|
||||
|
||||
# Evaluate using the real internal LLM/Keyword logic against the current mock XML!
|
||||
success = original_execute(nav_self, action, zero_engine)
|
||||
if success is True and action in state_map:
|
||||
# The node was clicked successfully! Swap the XML to the target state.
|
||||
device_mock.deviceV2.dump_hierarchy.return_value = load_xml(state_map[action])
|
||||
return success
|
||||
|
||||
monkeypatch.setattr(QNavGraph, "_execute_transition", _mock_execute_transition)
|
||||
|
||||
return _inject
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_all_delays(monkeypatch):
|
||||
"""
|
||||
Strips out all humanized hardware delays specifically for the E2E test suite.
|
||||
Ensures loops evaluate instantly using the injected dumps.
|
||||
"""
|
||||
monkeypatch.setattr(time, "sleep", lambda x: None)
|
||||
monkeypatch.setattr(utils, "random_sleep", lambda *args, **kwargs: None)
|
||||
monkeypatch.setattr(utils, "sleep", lambda x: None)
|
||||
|
||||
# Standardize DarwinEngine across tests to prevent mockup math errors on session end
|
||||
try:
|
||||
from GramAddict.core.darwin_engine import DarwinEngine
|
||||
monkeypatch.setattr(DarwinEngine, "evaluate_session_end", lambda *args, **kwargs: None)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@pytest.fixture
|
||||
def e2e_configs():
|
||||
import argparse
|
||||
configs = MagicMock()
|
||||
configs.args = argparse.Namespace(
|
||||
username="testuser",
|
||||
device="emulator-5554",
|
||||
app_id="com.instagram.android",
|
||||
debug=True,
|
||||
feed=None,
|
||||
carousel_percentage=0,
|
||||
carousel_count="1",
|
||||
explore=None,
|
||||
reels=None,
|
||||
stories=None,
|
||||
interact_percentage=0,
|
||||
likes_percentage=0,
|
||||
follow_percentage=0,
|
||||
comment_percentage=0,
|
||||
working_hours=[0.0, 24.0],
|
||||
time_delta_session=0,
|
||||
speed_multiplier=1.0,
|
||||
disable_filters=False,
|
||||
interaction_users_amount="1",
|
||||
scrape_profiles=False,
|
||||
disable_ai_messaging=True,
|
||||
total_unfollows_limit=0,
|
||||
ai_telepathic_url="http://localhost",
|
||||
ai_telepathic_model="llama3",
|
||||
ai_condenser_url="http://localhost",
|
||||
ai_condenser_model="llama3"
|
||||
)
|
||||
return configs
|
||||
50
tests/e2e/test_e2e_carousel_sequence.py
Normal file
50
tests/e2e/test_e2e_carousel_sequence.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
@patch("GramAddict.core.bot_flow._humanized_horizontal_swipe")
|
||||
def test_full_e2e_carousel_handling(
|
||||
mock_swipe, mock_dopamine, mock_sess, mock_create_device, mock_rsleep, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector, e2e_configs
|
||||
):
|
||||
"""
|
||||
Tests that the core feed loop successfully identifies native Carousel identifiers
|
||||
in the XML and initiates organic swiping inputs.
|
||||
"""
|
||||
device = MagicMock()
|
||||
device.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}
|
||||
mock_create_device.return_value = device
|
||||
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, False, True]
|
||||
mock_d_inst.wants_to_change_feed.return_value = False
|
||||
mock_d_inst.wants_to_doomscroll.return_value = False
|
||||
mock_d_inst.boredom = 0.0
|
||||
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit for Carousel")]
|
||||
|
||||
e2e_configs.args.feed = "1-2"
|
||||
e2e_configs.args.carousel_percentage = 100
|
||||
e2e_configs.args.carousel_count = "3-3"
|
||||
|
||||
# Load the captured UI dump containing native carousel_page_indicator
|
||||
dynamic_e2e_dump_injector(device, {}, "carousel_post_dump.xml")
|
||||
|
||||
try:
|
||||
with patch("GramAddict.core.bot_flow.Config", return_value=e2e_configs):
|
||||
with patch("GramAddict.core.bot_flow.QNavGraph.navigate_to", return_value=True):
|
||||
with patch("secrets.choice", return_value="HomeFeed"):
|
||||
with patch("random.random", return_value=0.0):
|
||||
start_bot()
|
||||
except Exception as e:
|
||||
assert str(e) == "Clean Exit for Carousel"
|
||||
|
||||
# Verify that the bot accurately parsed the JSON/XML, detected the Carousel,
|
||||
# and initiated exactly 3 horizontal right-to-left swipes as requested by args.
|
||||
assert mock_swipe.call_count == 3
|
||||
46
tests/e2e/test_e2e_dm_sequence.py
Normal file
46
tests/e2e/test_e2e_dm_sequence.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
def test_full_e2e_dm_sequence(
|
||||
mock_dopamine, mock_sess, mock_create_device, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector
|
||||
):
|
||||
device = MagicMock()
|
||||
mock_create_device.return_value = device
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, True]
|
||||
mock_d_inst.boredom = 0.0
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit for DM")]
|
||||
|
||||
class ConfigArgs:
|
||||
username = "testuser"
|
||||
device = "emulator-5554"
|
||||
app_id = "com.instagram.android"
|
||||
debug = True
|
||||
disable_ai_messaging = False
|
||||
feed = None
|
||||
reels = None
|
||||
explore = None
|
||||
stories = None
|
||||
total_unfollows_limit = 0
|
||||
|
||||
configs = MagicMock()
|
||||
configs.args = ConfigArgs()
|
||||
|
||||
dynamic_e2e_dump_injector(device, {'tap_message_icon': 'dm_inbox_dump.xml'}, "home_feed_with_ad.xml")
|
||||
|
||||
# Let the core system hit its real execution loop with actual XMLs instead of circumventing it
|
||||
try:
|
||||
with patch("secrets.choice", return_value="MessageInbox"):
|
||||
start_bot(configs=configs)
|
||||
except Exception as e:
|
||||
assert str(e) == "Clean Exit for DM"
|
||||
|
||||
mock_open.assert_called()
|
||||
44
tests/e2e/test_e2e_dojo_integration.py
Normal file
44
tests/e2e/test_e2e_dojo_integration.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DojoEngine")
|
||||
def test_dojo_lifecycle_integration(
|
||||
mock_dojo, mock_sess, mock_create_device, mock_rsleep, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector
|
||||
):
|
||||
device = MagicMock()
|
||||
mock_create_device.return_value = device
|
||||
|
||||
mock_dojo_inst = mock_dojo.get_instance.return_value
|
||||
mock_dojo_inst.is_running = True
|
||||
|
||||
mock_sess.inside_working_hours.side_effect = [Exception("Lifecycle Exit")]
|
||||
|
||||
class ConfigArgs:
|
||||
username = "testuser"
|
||||
device = "emulator-5554"
|
||||
app_id = "com.instagram.android"
|
||||
debug = True
|
||||
feed = "1"
|
||||
working_hours = "00:00-23:59"
|
||||
time_delta_session = "0"
|
||||
|
||||
configs = MagicMock()
|
||||
configs.args = ConfigArgs()
|
||||
|
||||
dynamic_e2e_dump_injector(device, {'tap_profile_tab': 'scraping_profile_dump.xml'}, "home_feed_with_ad.xml")
|
||||
|
||||
try:
|
||||
start_bot(configs=configs)
|
||||
except Exception as e:
|
||||
assert "Lifecycle Exit" in str(e)
|
||||
|
||||
mock_dojo.get_instance.assert_called()
|
||||
mock_dojo_inst.start.assert_called()
|
||||
mock_dojo_inst.stop.assert_called()
|
||||
49
tests/e2e/test_e2e_explore_feed.py
Normal file
49
tests/e2e/test_e2e_explore_feed.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
def test_full_e2e_explore_feed_sequence(
|
||||
mock_dopamine, mock_sess, mock_create_device, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector
|
||||
):
|
||||
device = MagicMock()
|
||||
mock_create_device.return_value = device
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, True]
|
||||
mock_d_inst.boredom = 0.0
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit for Explore")]
|
||||
|
||||
class ConfigArgs:
|
||||
username = "testuser"
|
||||
device = "emulator-5554"
|
||||
app_id = "com.instagram.android"
|
||||
debug = True
|
||||
explore = "5-8"
|
||||
feed = None
|
||||
reels = None
|
||||
stories = None
|
||||
interact_percentage = 0
|
||||
likes_percentage = 0
|
||||
follow_percentage = 0
|
||||
comment_percentage = 0
|
||||
|
||||
configs = MagicMock()
|
||||
configs.args = ConfigArgs()
|
||||
|
||||
# The actual dump we need for this workflow (available in fixtures/fixtures)
|
||||
# The fixture will automatically hit pytest.fail if the dump vanishes.
|
||||
dynamic_e2e_dump_injector(device, {'tap_explore_tab': 'explore_feed_dump.xml'}, "home_feed_with_ad.xml")
|
||||
|
||||
try:
|
||||
with patch("secrets.choice", return_value="ExploreFeed"):
|
||||
start_bot(configs=configs)
|
||||
except Exception as e:
|
||||
assert str(e) == "Clean Exit for Explore"
|
||||
|
||||
mock_open.assert_called()
|
||||
54
tests/e2e/test_e2e_home_feed.py
Normal file
54
tests/e2e/test_e2e_home_feed.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
def test_full_e2e_home_feed_sequence(
|
||||
mock_dopamine, mock_sess, mock_create_device, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector
|
||||
):
|
||||
"""
|
||||
Test a full E2E sequence for Home Feed using actual real XML dumps.
|
||||
"""
|
||||
device = MagicMock()
|
||||
mock_create_device.return_value = device
|
||||
|
||||
# Setup mock dopamine & session
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, True]
|
||||
mock_d_inst.boredom = 0.0
|
||||
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit for Home")]
|
||||
|
||||
class ConfigArgs:
|
||||
username = "testuser"
|
||||
device = "emulator-5554"
|
||||
app_id = "com.instagram.android"
|
||||
debug = True
|
||||
feed = "5-8"
|
||||
explore = None
|
||||
reels = None
|
||||
stories = None
|
||||
interact_percentage = 0
|
||||
likes_percentage = 0
|
||||
follow_percentage = 0
|
||||
comment_percentage = 0
|
||||
|
||||
configs = MagicMock()
|
||||
configs.args = ConfigArgs()
|
||||
|
||||
dynamic_e2e_dump_injector(device, {}, "home_feed_with_ad.xml")
|
||||
|
||||
try:
|
||||
# We must also mock secrets.choice to ensure HomeFeed is picked
|
||||
with patch("secrets.choice", return_value="HomeFeed"):
|
||||
start_bot(configs=configs)
|
||||
except Exception as e:
|
||||
assert str(e) == "Clean Exit for Home"
|
||||
|
||||
mock_open.assert_called()
|
||||
47
tests/e2e/test_e2e_reels_feed.py
Normal file
47
tests/e2e/test_e2e_reels_feed.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
def test_full_e2e_reels_feed_sequence(
|
||||
mock_dopamine, mock_sess, mock_create_device, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector
|
||||
):
|
||||
device = MagicMock()
|
||||
mock_create_device.return_value = device
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, True]
|
||||
mock_d_inst.boredom = 0.0
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit for Reels")]
|
||||
|
||||
class ConfigArgs:
|
||||
username = "testuser"
|
||||
device = "emulator-5554"
|
||||
app_id = "com.instagram.android"
|
||||
debug = True
|
||||
reels = "10"
|
||||
feed = None
|
||||
explore = None
|
||||
stories = None
|
||||
interact_percentage = 0
|
||||
likes_percentage = 0
|
||||
follow_percentage = 0
|
||||
comment_percentage = 0
|
||||
|
||||
configs = MagicMock()
|
||||
configs.args = ConfigArgs()
|
||||
|
||||
dynamic_e2e_dump_injector(device, {'tap_reels_tab': 'reels_feed_dump.xml'}, "home_feed_with_ad.xml")
|
||||
|
||||
try:
|
||||
with patch("secrets.choice", return_value="ReelsFeed"):
|
||||
start_bot(configs=configs)
|
||||
except Exception as e:
|
||||
assert str(e) == "Clean Exit for Reels"
|
||||
|
||||
mock_open.assert_called()
|
||||
47
tests/e2e/test_e2e_scraping_sequence.py
Normal file
47
tests/e2e/test_e2e_scraping_sequence.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch, PropertyMock
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
@patch("GramAddict.core.bot_flow.ResonanceEngine")
|
||||
@patch("GramAddict.core.bot_flow._interact_with_profile")
|
||||
def test_full_e2e_scraping_sequence(
|
||||
mock_interact, mock_resonance, mock_dopamine, mock_sess, mock_create_device, mock_rsleep, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector, e2e_configs
|
||||
):
|
||||
device = MagicMock()
|
||||
device.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}
|
||||
mock_create_device.return_value = device
|
||||
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.wants_to_change_feed.return_value = False
|
||||
mock_d_inst.wants_to_doomscroll.return_value = False
|
||||
type(mock_d_inst).boredom = PropertyMock(return_value=0.0)
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, False, False, False, False, True]
|
||||
|
||||
mock_res_inst = mock_resonance.return_value
|
||||
mock_res_inst.calculate_resonance.return_value = 100.0
|
||||
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit Scrape")]
|
||||
|
||||
e2e_configs.args.scrape_profiles = True
|
||||
e2e_configs.args.interact_percentage = 100
|
||||
e2e_configs.args.feed = "1"
|
||||
|
||||
dynamic_e2e_dump_injector(device, {'tap_profile_tab': 'scraping_profile_dump.xml'}, "carousel_post_dump.xml")
|
||||
|
||||
with patch("GramAddict.core.bot_flow.Config", return_value=e2e_configs):
|
||||
with patch("GramAddict.core.bot_flow.QNavGraph.navigate_to", return_value=True):
|
||||
with patch("GramAddict.core.telepathic_engine.TelepathicEngine.find_best_node", return_value={"bounds": "[0,0][100,100]"}):
|
||||
with patch("secrets.choice", return_value="HomeFeed"):
|
||||
try:
|
||||
start_bot()
|
||||
except Exception as e:
|
||||
if "Clean Exit Scrape" not in str(e):
|
||||
raise e
|
||||
mock_interact.assert_called()
|
||||
52
tests/e2e/test_e2e_search_sequence.py
Normal file
52
tests/e2e/test_e2e_search_sequence.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
def test_full_e2e_search_sequence(
|
||||
mock_dopamine, mock_sess, mock_create_device, mock_rsleep, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector
|
||||
):
|
||||
device = MagicMock()
|
||||
mock_create_device.return_value = device
|
||||
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, True]
|
||||
mock_d_inst.boredom = 0.0
|
||||
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit for Search")]
|
||||
|
||||
class ConfigArgs:
|
||||
username = "testuser"
|
||||
device = "emulator-5554"
|
||||
app_id = "com.instagram.android"
|
||||
debug = True
|
||||
search = "coding"
|
||||
feed = None
|
||||
reels = None
|
||||
explore = None
|
||||
stories = None
|
||||
working_hours = "00:00-23:59"
|
||||
time_delta_session = "0"
|
||||
interact_percentage = 0
|
||||
likes_percentage = 0
|
||||
follow_percentage = 0
|
||||
comment_percentage = 0
|
||||
|
||||
configs = MagicMock()
|
||||
configs.args = ConfigArgs()
|
||||
|
||||
dynamic_e2e_dump_injector(device, {'tap_explore_tab': 'explore_feed_dump.xml'}, "home_feed_with_ad.xml")
|
||||
|
||||
try:
|
||||
with patch("secrets.choice", return_value="SearchFeed"):
|
||||
start_bot(configs=configs)
|
||||
except Exception as e:
|
||||
assert "Clean Exit" in str(e)
|
||||
|
||||
mock_open.assert_called()
|
||||
63
tests/e2e/test_e2e_session_limits.py
Normal file
63
tests/e2e/test_e2e_session_limits.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
@patch("GramAddict.core.bot_flow.GrowthBrain")
|
||||
def test_full_start_bot_e2e_working_hours_limits(
|
||||
mock_brain, mock_dopamine, mock_sess, mock_create_device, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector
|
||||
):
|
||||
"""
|
||||
Test start_bot full loop with working hours limits.
|
||||
Verifies that the bot correctly sleeps when outside working hours
|
||||
and exits the loop when session limits are reached.
|
||||
"""
|
||||
device = MagicMock()
|
||||
mock_create_device.return_value = device
|
||||
|
||||
# Setup mock dopamine
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, True]
|
||||
mock_d_inst.boredom = 0.0
|
||||
|
||||
class ConfigArgs:
|
||||
username = "testuser"
|
||||
device = "emulator-5554"
|
||||
app_id = "com.instagram.android"
|
||||
debug = True
|
||||
feed = "5-8"
|
||||
explore = None
|
||||
reels = None
|
||||
stories = None
|
||||
total_unfollows_limit = 0
|
||||
working_hours = ["10.00-11.00", "15.00-16.00"]
|
||||
time_delta_session = 10
|
||||
interact_percentage = 0
|
||||
likes_percentage = 0
|
||||
follow_percentage = 0
|
||||
comment_percentage = 0
|
||||
|
||||
configs = MagicMock()
|
||||
configs.args = ConfigArgs()
|
||||
|
||||
# On iteration 1: valid working hours
|
||||
# On iteration 2: Exception to jump out of loop
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit limits test")]
|
||||
|
||||
dynamic_e2e_dump_injector(device, {}, "home_feed_with_ad.xml")
|
||||
|
||||
try:
|
||||
start_bot(configs=configs)
|
||||
except Exception as e:
|
||||
assert str(e) == "Clean Exit limits test"
|
||||
|
||||
# Verify key interactions
|
||||
mock_sess.inside_working_hours.assert_called()
|
||||
mock_open.assert_called()
|
||||
mock_sleep.assert_called()
|
||||
47
tests/e2e/test_e2e_stories_feed.py
Normal file
47
tests/e2e/test_e2e_stories_feed.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
def test_full_e2e_stories_feed_sequence(
|
||||
mock_dopamine, mock_sess, mock_create_device, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector
|
||||
):
|
||||
device = MagicMock()
|
||||
mock_create_device.return_value = device
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, True]
|
||||
mock_d_inst.boredom = 0.0
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit for Stories")]
|
||||
|
||||
class ConfigArgs:
|
||||
username = "testuser"
|
||||
device = "emulator-5554"
|
||||
app_id = "com.instagram.android"
|
||||
debug = True
|
||||
stories = "5-8"
|
||||
feed = None
|
||||
reels = None
|
||||
explore = None
|
||||
interact_percentage = 0
|
||||
likes_percentage = 0
|
||||
follow_percentage = 0
|
||||
comment_percentage = 0
|
||||
|
||||
configs = MagicMock()
|
||||
configs.args = ConfigArgs()
|
||||
|
||||
dynamic_e2e_dump_injector(device, {'tap_home_tab': 'stories_feed_dump.xml'}, "home_feed_with_ad.xml")
|
||||
|
||||
try:
|
||||
with patch("secrets.choice", return_value="StoriesFeed"):
|
||||
start_bot(configs=configs)
|
||||
except Exception as e:
|
||||
assert str(e) == "Clean Exit for Stories"
|
||||
|
||||
mock_open.assert_called()
|
||||
48
tests/e2e/test_e2e_unfollow_sequence.py
Normal file
48
tests/e2e/test_e2e_unfollow_sequence.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from GramAddict.core.bot_flow import start_bot
|
||||
|
||||
@patch("GramAddict.core.bot_flow.open_instagram", return_value=True)
|
||||
@patch("GramAddict.core.bot_flow.close_instagram")
|
||||
@patch("GramAddict.core.bot_flow.sleep")
|
||||
@patch("GramAddict.core.bot_flow.random_sleep")
|
||||
@patch("GramAddict.core.bot_flow.create_device")
|
||||
@patch("GramAddict.core.bot_flow.SessionState")
|
||||
@patch("GramAddict.core.bot_flow.DopamineEngine")
|
||||
def test_full_e2e_unfollow_sequence(
|
||||
mock_dopamine, mock_sess, mock_create_device, mock_sleep, mock_close, mock_open, dynamic_e2e_dump_injector
|
||||
):
|
||||
device = MagicMock()
|
||||
mock_create_device.return_value = device
|
||||
mock_d_inst = mock_dopamine.return_value
|
||||
mock_d_inst.is_app_session_over.side_effect = [False, True]
|
||||
mock_d_inst.boredom = 0.0
|
||||
mock_sess.inside_working_hours.side_effect = [(True, 0), Exception("Clean Exit for Unfollow")]
|
||||
|
||||
class ConfigArgs:
|
||||
username = "testuser"
|
||||
device = "emulator-5554"
|
||||
app_id = "com.instagram.android"
|
||||
debug = True
|
||||
total_unfollows_limit = 10
|
||||
feed = None
|
||||
reels = None
|
||||
explore = None
|
||||
stories = None
|
||||
interact_percentage = 0
|
||||
likes_percentage = 0
|
||||
follow_percentage = 0
|
||||
comment_percentage = 0
|
||||
|
||||
configs = MagicMock()
|
||||
configs.args = ConfigArgs()
|
||||
|
||||
dynamic_e2e_dump_injector(device, {'tap_profile_tab': 'scraping_profile_dump.xml', 'tap_following_list': 'unfollow_list_dump.xml'}, "home_feed_with_ad.xml")
|
||||
|
||||
try:
|
||||
with patch("secrets.choice", return_value="FollowingList"):
|
||||
start_bot(configs=configs)
|
||||
except Exception as e:
|
||||
assert str(e) == "Clean Exit for Unfollow"
|
||||
|
||||
mock_open.assert_called()
|
||||
Reference in New Issue
Block a user