#!/usr/bin/env python3 import argparse import logging import os import sys import time # 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 # Setup a clean logger for the toolkit 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 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("šŸ¤– AUTOMATED E2E DUMP CAPTURE GUIDE") 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") steps = [ ("comment_sheet.xml", "Post Comment Sheet", "Open any post and open its comment section."), ("stories_feed_dump.xml", "Active Story Playback", "Tap any user's story ring on the HomeFeed."), ("dm_inbox_dump.xml", "DM Inbox / Threads List", "Go to your DM inbox list."), ("scraping_profile_dump.xml", "Own Profile Root", "Go to your OWN profile tab."), ("unfollow_list_dump.xml", "Following List", "On your profile, tap 'Following' to open the list."), ("search_feed_dump.xml", "Explore Search Focus", "Tap Explore, then tap into the Search bar."), ("reels_feed_dump.xml", "Reels Video Feed", "Go to the Reels tab and let a video play."), ("notifications_dump.xml", "Activity Feed", "Tap the Heart/Notifications icon."), ("explore_feed_dump.xml", "Explore Grid", "Tap the Explore tab (just the grid visible)."), ("user_profile_dump.xml", "Alien Profile Root", "Go to ANY OTHER user's profile."), ("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."), ] for i, (fname, desc, instr) in enumerate(steps, 1): try: print(f"\nšŸ‘‰ {i}/{len(steps)}. {desc.upper()}:") print(f" {instr}") input(" Press ENTER when ready to capture...") _save_dump(device, fixture_dir, fname, desc) except KeyboardInterrupt: print("\nšŸ›‘ Guide interrupted.") return print("\n" + "=" * 60) logger.info("šŸŽ‰ Capture Sequence Complete! All fixtures have been updated.") print("=" * 60 + "\n") def main(): parser = argparse.ArgumentParser(description="Instagram Bot Testing Toolkit - Fixture Management") parser.add_argument("--config", help="Path to your bot's .yml config (to auto-extract device ID)") parser.add_argument("--device", help="Manual ADB device ID (e.g., emulator-5554)") parser.add_argument("--fixture", help="Name of a single fixture to sync (e.g., explore_feed_dump.xml)") parser.add_argument("--interactive", action="store_true", help="Launch the full interactive capture guide") 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: config_data = yaml.safe_load(f) 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}") if not device_id: logger.error("No device ID provided! Use --device or --config to specify which device to connect to.") sys.exit(1) fixture_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "tests", "fixtures") os.makedirs(fixture_dir, exist_ok=True) print(f"Connecting to {device_id}...") try: device = create_device(device_id, "com.instagram.android") # Quick connectivity check device.get_info() except Exception as e: logger.error(f"Failed to connect to device: {e}") sys.exit(1) if args.interactive: run_interactive_guide(device, fixture_dir) elif args.fixture: fname = args.fixture 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 .") parser.print_help() if __name__ == "__main__": main()