from playwright.sync_api import sync_playwright, expect, Page import time import random import re from pathlib import Path import sys import os import subprocess import signal # Add project root to path sys.path.insert(0, str(Path(__file__).parent.parent)) from e2e_tests.playwright_helpers import ( wait_for_gradio, load_character, send_message, click_tab, kill_port, # Import kill_port get_hp_from_sheet, # Import get_hp_from_sheet extract_creatures, # Import extract_creatures get_last_bot_message ) def test_adventure_simulation_playwright(): """ Automated Adventure Simulation - Random Travel & Combat Until Death (Playwright) """ print("=" * 80) print("šŸŽ® AUTOMATED ADVENTURE SIMULATION (Playwright)") print("=" * 80) # Ensure port 7860 is free kill_port(7860) # Start Gradio server print("\nšŸš€ Starting Gradio server...") gradio_process = subprocess.Popen( ['python3', 'web/app_gradio.py'], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) time.sleep(10) # Give server time to start with sync_playwright() as p: # Use headless for automation, set to False to watch browser = p.chromium.launch(headless=True) page = browser.new_page() page.set_viewport_size({"width": 1280, "height": 720}) # Set a reasonable viewport try: # Navigate to app print("\nšŸ“± Opening browser...") page.goto("http://localhost:7860") wait_for_gradio(page) # Click "Play Game" tab click_tab(page, "šŸŽ® Play Game") # Load character print("āš”ļø Loading Thorin Stormshield...") load_character(page, "Thorin Stormshield") print("āœ… Character loaded!\n") # Start adventure print("=" * 80) print("šŸ—ŗļø ADVENTURE BEGINS!") print("=" * 80) turn = 0 max_turns = 20 # Safety limit in_combat = False current_enemy = None combat_started_for_current_enemy = False # Track if we've called /start_combat # Stuck detection (from GRADIO_FREEZE_BUG_FIX.md) last_response_text = "" stuck_count = 0 while turn < max_turns: turn += 1 # Check HP hp = get_hp_from_sheet(page) print(f"\nšŸ’š Turn {turn} - HP: {hp if hp is not None else '???'}") if hp is not None and hp <= 0: print("\n" + "=" * 80) print("šŸ’€ GAME OVER - CHARACTER DIED!") print("=" * 80) break # Decide action if not in_combat: # Exploration mode - Ask GM what we see/encounter action_roll = random.random() if action_roll < 0.3: # Venture into wilderness/danger actions = [ "I venture deeper into the wilderness, looking for adventure", "I follow the path ahead, seeking what dangers or treasures await", "I explore the area thoroughly, searching for anything unusual", "I venture into the darker parts of this place", "I head toward the sounds of potential danger", "I search for signs of monsters or bandits in the area" ] action = random.choice(actions) print(f"\nšŸ” Turn {turn}: {action}") send_message(page, action) response = get_last_bot_message(page) print(f"GM: {response[:300]}...") # Check for creatures creatures = extract_creatures(response) if creatures: print(f"āš ļø CREATURE SPOTTED: {creatures[0]}!") current_enemy = creatures[0] in_combat = True elif action_roll < 0.6: # Ask GM about surroundings actions = [ "I look around carefully. What do I see?", "I listen for any sounds nearby. What do I hear?", "What dangers might be lurking here?", "I investigate the area. Is there anything interesting?", "Are there any creatures or people nearby?" ] action = random.choice(actions) print(f"\nšŸ‘€ Turn {turn}: {action}") send_message(page, action) response = get_last_bot_message(page) print(f"GM: {response[:300]}...") creatures = extract_creatures(response) if creatures: print(f"āš ļø CREATURE SPOTTED: {creatures[0]}!") current_enemy = creatures[0] in_combat = True else: # Move to a new area (use travel or natural language) destinations = ['Dark Forest', 'Mountain Road', 'Old Ruins', 'Temple District', 'Market Square', 'Forest Path', 'Town Gates', 'Town Square'] dest = random.choice(destinations) print(f"\n🚶 Turn {turn}: Traveling to {dest}...") send_message(page, f"I travel to {dest}") response = get_last_bot_message(page) print(f"GM: {response[:300]}...") # Check for creatures creatures = extract_creatures(response) if creatures: print(f"āš ļø CREATURE SPOTTED: {creatures[0]}!") current_enemy = creatures[0] in_combat = True else: # Combat mode - attack until someone dies print(f"\nāš”ļø Turn {turn}: COMBAT - Attacking {current_enemy}!") # Start combat only if we haven't started it for this enemy yet if not combat_started_for_current_enemy: send_message(page, f"/start_combat {current_enemy}") combat_started_for_current_enemy = True time.sleep(2) # Give a moment for combat to init response = get_last_bot_message(page) print(f"Combat started: {response[:200]}...") # Attack attack_variations = [ f"I attack the {current_enemy} with my battleaxe", f"I swing at the {current_enemy}", f"I strike the {current_enemy}", f"I attack {current_enemy}" ] attack = random.choice(attack_variations) send_message(page, attack) response = get_last_bot_message(page) print(f"Attack: {response[:250]}...") # Check if enemy died OR combat ended if ('dead' in response.lower() or 'dies' in response.lower() or 'falls' in response.lower() or 'combat has ended' in response.lower() or 'combat ended' in response.lower()): print(f"āœ… {current_enemy} defeated! Combat ended.") in_combat = False current_enemy = None combat_started_for_current_enemy = False # Reset flag for next combat # End combat if not already ended if 'combat has ended' not in response.lower(): send_message(page, "/end_combat") # Check our HP hp = get_hp_from_sheet(page) if hp is not None and hp <= 0: print(f"\nšŸ’€ Thorin has fallen in battle against {current_enemy}!") break # Pause between turns time.sleep(1) # Keep it running slower to better observe interactions if headless=False if turn >= max_turns: print("\nā±ļø Adventure ended - max turns reached") print("\n" + "=" * 80) print("šŸ ADVENTURE COMPLETE") print("=" * 80) print(f"\nTotal turns: {turn}") print(f"Final HP: {get_hp_from_sheet(page)}") # Keep browser open on error for inspection if "PYTEST_CURRENT_TEST" not in os.environ: # Only if run directly, not by pytest print("\nšŸ‘€ Browser will stay open for 10 seconds...") time.sleep(10) except Exception as e: print(f"\nāŒ Test Failed: {e}") page.screenshot(path="adventure_failure.png") raise e finally: browser.close() print("šŸ›‘ Stopping Gradio...") gradio_process.terminate() gradio_process.wait() if __name__ == "__main__": test_adventure_simulation_playwright()