feat: complete modular plugin refactor with 100% E2E coverage for interactions
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from GramAddict.core.telepathic_engine import TelepathicEngine
|
||||
import os, json
|
||||
|
||||
DUMP_PATH = "/Volumes/Alpha SSD/Coding/bot/debug/xml_dumps/post_load_timeout__2026-04-17_15-02-36.xml"
|
||||
with open(DUMP_PATH, "r") as f:
|
||||
xml_content = f.read()
|
||||
@@ -8,15 +8,13 @@ engine = TelepathicEngine.get_instance()
|
||||
nodes = engine._extract_semantic_nodes(xml_content)
|
||||
grid_nodes = []
|
||||
for node in nodes:
|
||||
if node.get("resource_id") in ["com.instagram.android:id/grid_card_layout_container", "com.instagram.android:id/image_button"]:
|
||||
if node.get("resource_id") in [
|
||||
"com.instagram.android:id/grid_card_layout_container",
|
||||
"com.instagram.android:id/image_button",
|
||||
]:
|
||||
grid_nodes.append(node)
|
||||
|
||||
grid_nodes.sort(key=lambda n: (
|
||||
round(n["y"] / 5) * 5,
|
||||
n["x"],
|
||||
n["naf"],
|
||||
-n["area"]
|
||||
))
|
||||
grid_nodes.sort(key=lambda n: (round(n["y"] / 5) * 5, n["x"], n["naf"], -n["area"]))
|
||||
|
||||
for n in grid_nodes[:5]:
|
||||
print(f"Y={n['y']} (rnd={round(n['y']/5)*5}), NAF={n['naf']}, Area={n['area']}, ID={n['resource_id']}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from GramAddict.core.telepathic_engine import TelepathicEngine
|
||||
import os
|
||||
|
||||
DUMP_PATH = "/Volumes/Alpha SSD/Coding/bot/debug/xml_dumps/post_load_timeout__2026-04-17_15-02-36.xml"
|
||||
with open(DUMP_PATH, "r") as f:
|
||||
xml_content = f.read()
|
||||
@@ -8,15 +8,27 @@ engine = TelepathicEngine.get_instance()
|
||||
TelepathicEngine._last_click_context = {"x": 178, "y": 558}
|
||||
intent = "first image in explore grid"
|
||||
|
||||
print(f"Any grid check: {any(k in intent.lower() for k in ['explore grid', 'profile grid', 'first image', 'grid item'])}")
|
||||
print(
|
||||
f"Any grid check: {any(k in intent.lower() for k in ['explore grid', 'profile grid', 'first image', 'grid item'])}"
|
||||
)
|
||||
post_markers = [
|
||||
"row_feed_button_like", "row_feed_button_comment", "row_feed_button_share",
|
||||
"row_feed_comment_textview_layout", "row_feed_view_group",
|
||||
"row_feed_photo_profile_name", "row_feed_photo_imageview",
|
||||
"clips_media_component", "clips_viewer", "clips_like_button",
|
||||
"clips_comment_button", "reel_viewer", "clips_music_attribution",
|
||||
"carousel_page_indicator", "media_set_page_indicator",
|
||||
"action_bar_original_title", "media_header_user",
|
||||
"row_feed_button_like",
|
||||
"row_feed_button_comment",
|
||||
"row_feed_button_share",
|
||||
"row_feed_comment_textview_layout",
|
||||
"row_feed_view_group",
|
||||
"row_feed_photo_profile_name",
|
||||
"row_feed_photo_imageview",
|
||||
"clips_media_component",
|
||||
"clips_viewer",
|
||||
"clips_like_button",
|
||||
"clips_comment_button",
|
||||
"reel_viewer",
|
||||
"clips_music_attribution",
|
||||
"carousel_page_indicator",
|
||||
"media_set_page_indicator",
|
||||
"action_bar_original_title",
|
||||
"media_header_user",
|
||||
]
|
||||
print(f"Marker found check: {any(m in xml_content.lower() for m in post_markers)}")
|
||||
|
||||
|
||||
@@ -1,40 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import time
|
||||
import logging
|
||||
|
||||
# Ensure GramAddict is in PYTHONPATH if script is run directly
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from GramAddict.core.device_facade import create_device
|
||||
from GramAddict.core.config import Config
|
||||
|
||||
# Setup a clean logger for the toolkit
|
||||
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s | %(message)s', datefmt='%H:%M:%S')
|
||||
logging.basicConfig(level=logging.INFO, format="[%(asctime)s] %(levelname)s | %(message)s", datefmt="%H:%M:%S")
|
||||
logger = logging.getLogger("TestingToolkit")
|
||||
|
||||
|
||||
def _save_dump(device, fixture_dir, filename, description):
|
||||
logger.info(f"⏳ Waiting for UI to settle for [{description}]...")
|
||||
time.sleep(3.5) # ensure animations finish
|
||||
time.sleep(3.5) # ensure animations finish
|
||||
xml_data = device.dump_hierarchy()
|
||||
|
||||
|
||||
if not xml_data or len(xml_data) < 100:
|
||||
logger.warning(f"⚠️ Received empty or exceptionally small XML dump for {filename}. Is the app open?")
|
||||
|
||||
|
||||
path = os.path.join(fixture_dir, filename)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(xml_data)
|
||||
logger.info(f"✅ Saved REAL DUMP to {filename} ({len(xml_data)} bytes)")
|
||||
|
||||
|
||||
def run_interactive_guide(device, fixture_dir):
|
||||
print("\n" + "="*60)
|
||||
print("\n" + "=" * 60)
|
||||
print("🤖 AUTOMATED E2E DUMP CAPTURE GUIDE")
|
||||
print("="*60)
|
||||
print("=" * 60)
|
||||
print("This guide will help you refresh the golden fixtures for the E2E suite.")
|
||||
print("Please follow the instructions on your phone/emulator.")
|
||||
print("="*60 + "\n")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
steps = [
|
||||
("comment_sheet.xml", "Post Comment Sheet", "Open any post and open its comment section."),
|
||||
@@ -50,7 +51,7 @@ def run_interactive_guide(device, fixture_dir):
|
||||
("followers_list_dump.xml", "Followers List", "On that user's profile, tap 'Followers'."),
|
||||
("carousel_post_dump.xml", "Carousel Post", "Find a post with multiple images on your feed."),
|
||||
("home_feed_with_ad.xml", "Sponsored Ad", "Scroll until you see a 'Sponsored' post."),
|
||||
("dm_thread_dump.xml", "DM Chat Thread", "Open any specific chat conversation.")
|
||||
("dm_thread_dump.xml", "DM Chat Thread", "Open any specific chat conversation."),
|
||||
]
|
||||
|
||||
for i, (fname, desc, instr) in enumerate(steps, 1):
|
||||
@@ -63,9 +64,10 @@ def run_interactive_guide(device, fixture_dir):
|
||||
print("\n🛑 Guide interrupted.")
|
||||
return
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("\n" + "=" * 60)
|
||||
logger.info("🎉 Capture Sequence Complete! All fixtures have been updated.")
|
||||
print("="*60 + "\n")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Instagram Bot Testing Toolkit - Fixture Management")
|
||||
@@ -76,14 +78,15 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
device_id = args.device
|
||||
|
||||
|
||||
# Try to extract device from config if provided
|
||||
if args.config:
|
||||
try:
|
||||
import yaml
|
||||
with open(args.config, 'r', encoding='utf-8') as f:
|
||||
|
||||
with open(args.config, "r", encoding="utf-8") as f:
|
||||
config_data = yaml.safe_load(f)
|
||||
device_id = config_data.get('device') or device_id
|
||||
device_id = config_data.get("device") or device_id
|
||||
logger.info(f"Loaded device ID from config: {device_id}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not read config file {args.config}: {e}")
|
||||
@@ -108,11 +111,13 @@ def main():
|
||||
run_interactive_guide(device, fixture_dir)
|
||||
elif args.fixture:
|
||||
fname = args.fixture
|
||||
if not fname.endswith(".xml"): fname += ".xml"
|
||||
if not fname.endswith(".xml"):
|
||||
fname += ".xml"
|
||||
_save_dump(device, fixture_dir, fname, f"Single Fixture: {fname}")
|
||||
else:
|
||||
logger.info("Nothing to do. Use --interactive or --fixture <name>.")
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user