- Add --exec flag to execute multiple scripts before main program - Scripts are executed in order and share Python interpreter state - Implement full PyAutoGUI-compatible automation API in McRFPy_Automation - Add screenshot, mouse control, keyboard input capabilities - Fix Python initialization issues when multiple scripts are loaded - Update CommandLineParser to handle --exec with proper sys.argv management - Add comprehensive examples and documentation This enables automation testing by allowing test scripts to run alongside games using the same Python environment. The automation API provides event injection into the SFML render loop for UI testing. Closes #32 partially (Python interpreter emulation) References automation testing requirements
336 lines
No EOL
10 KiB
Python
336 lines
No EOL
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Examples of automation patterns using the proposed --exec flag
|
|
|
|
Usage:
|
|
./mcrogueface game.py --exec automation_basic.py
|
|
./mcrogueface game.py --exec automation_stress.py --exec monitor.py
|
|
"""
|
|
|
|
# ===== automation_basic.py =====
|
|
# Basic automation that runs alongside the game
|
|
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
import time
|
|
|
|
class GameAutomation:
|
|
"""Automated testing that runs periodically"""
|
|
|
|
def __init__(self):
|
|
self.test_count = 0
|
|
self.test_results = []
|
|
|
|
def run_test_suite(self):
|
|
"""Called by timer - runs one test per invocation"""
|
|
test_name = f"test_{self.test_count}"
|
|
|
|
try:
|
|
if self.test_count == 0:
|
|
# Test main menu
|
|
self.test_main_menu()
|
|
elif self.test_count == 1:
|
|
# Test inventory
|
|
self.test_inventory()
|
|
elif self.test_count == 2:
|
|
# Test combat
|
|
self.test_combat()
|
|
else:
|
|
# All tests complete
|
|
self.report_results()
|
|
return
|
|
|
|
self.test_results.append((test_name, "PASS"))
|
|
except Exception as e:
|
|
self.test_results.append((test_name, f"FAIL: {e}"))
|
|
|
|
self.test_count += 1
|
|
|
|
def test_main_menu(self):
|
|
"""Test main menu interactions"""
|
|
automation.screenshot("test_main_menu_before.png")
|
|
automation.click(400, 300) # New Game button
|
|
time.sleep(0.5)
|
|
automation.screenshot("test_main_menu_after.png")
|
|
|
|
def test_inventory(self):
|
|
"""Test inventory system"""
|
|
automation.hotkey("i") # Open inventory
|
|
time.sleep(0.5)
|
|
automation.screenshot("test_inventory_open.png")
|
|
|
|
# Drag item
|
|
automation.moveTo(100, 200)
|
|
automation.dragTo(200, 200, duration=0.5)
|
|
|
|
automation.hotkey("i") # Close inventory
|
|
|
|
def test_combat(self):
|
|
"""Test combat system"""
|
|
# Move character
|
|
automation.keyDown("w")
|
|
time.sleep(0.5)
|
|
automation.keyUp("w")
|
|
|
|
# Attack
|
|
automation.click(500, 400)
|
|
automation.screenshot("test_combat.png")
|
|
|
|
def report_results(self):
|
|
"""Generate test report"""
|
|
print("\n=== Automation Test Results ===")
|
|
for test, result in self.test_results:
|
|
print(f"{test}: {result}")
|
|
print(f"Total: {len(self.test_results)} tests")
|
|
|
|
# Stop the timer
|
|
mcrfpy.delTimer("automation_suite")
|
|
|
|
# Create automation instance and register timer
|
|
auto = GameAutomation()
|
|
mcrfpy.setTimer("automation_suite", auto.run_test_suite, 2000) # Run every 2 seconds
|
|
|
|
print("Game automation started - tests will run every 2 seconds")
|
|
|
|
|
|
# ===== automation_stress.py =====
|
|
# Stress testing with random inputs
|
|
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
import random
|
|
|
|
class StressTester:
|
|
"""Randomly interact with the game to find edge cases"""
|
|
|
|
def __init__(self):
|
|
self.action_count = 0
|
|
self.errors = []
|
|
|
|
def random_action(self):
|
|
"""Perform a random UI action"""
|
|
try:
|
|
action = random.choice([
|
|
self.random_click,
|
|
self.random_key,
|
|
self.random_drag,
|
|
self.random_hotkey
|
|
])
|
|
action()
|
|
self.action_count += 1
|
|
|
|
# Periodic screenshot
|
|
if self.action_count % 50 == 0:
|
|
automation.screenshot(f"stress_test_{self.action_count}.png")
|
|
print(f"Stress test: {self.action_count} actions performed")
|
|
|
|
except Exception as e:
|
|
self.errors.append((self.action_count, str(e)))
|
|
|
|
def random_click(self):
|
|
x = random.randint(0, 1024)
|
|
y = random.randint(0, 768)
|
|
button = random.choice(["left", "right"])
|
|
automation.click(x, y, button=button)
|
|
|
|
def random_key(self):
|
|
key = random.choice([
|
|
"a", "b", "c", "d", "w", "s",
|
|
"space", "enter", "escape",
|
|
"1", "2", "3", "4", "5"
|
|
])
|
|
automation.keyDown(key)
|
|
automation.keyUp(key)
|
|
|
|
def random_drag(self):
|
|
x1 = random.randint(0, 1024)
|
|
y1 = random.randint(0, 768)
|
|
x2 = random.randint(0, 1024)
|
|
y2 = random.randint(0, 768)
|
|
automation.moveTo(x1, y1)
|
|
automation.dragTo(x2, y2, duration=0.2)
|
|
|
|
def random_hotkey(self):
|
|
modifier = random.choice(["ctrl", "alt", "shift"])
|
|
key = random.choice(["a", "s", "d", "f"])
|
|
automation.hotkey(modifier, key)
|
|
|
|
# Create stress tester and run frequently
|
|
stress = StressTester()
|
|
mcrfpy.setTimer("stress_test", stress.random_action, 100) # Every 100ms
|
|
|
|
print("Stress testing started - random actions every 100ms")
|
|
|
|
|
|
# ===== monitor.py =====
|
|
# Performance and state monitoring
|
|
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
import json
|
|
import time
|
|
|
|
class PerformanceMonitor:
|
|
"""Monitor game performance and state"""
|
|
|
|
def __init__(self):
|
|
self.samples = []
|
|
self.start_time = time.time()
|
|
|
|
def collect_sample(self):
|
|
"""Collect performance data"""
|
|
sample = {
|
|
"timestamp": time.time() - self.start_time,
|
|
"fps": mcrfpy.getFPS() if hasattr(mcrfpy, 'getFPS') else 60,
|
|
"scene": mcrfpy.currentScene(),
|
|
"memory": self.estimate_memory_usage()
|
|
}
|
|
self.samples.append(sample)
|
|
|
|
# Log every 10 samples
|
|
if len(self.samples) % 10 == 0:
|
|
avg_fps = sum(s["fps"] for s in self.samples[-10:]) / 10
|
|
print(f"Average FPS (last 10 samples): {avg_fps:.1f}")
|
|
|
|
# Save data every 100 samples
|
|
if len(self.samples) % 100 == 0:
|
|
self.save_report()
|
|
|
|
def estimate_memory_usage(self):
|
|
"""Estimate memory usage based on scene complexity"""
|
|
# This is a placeholder - real implementation would use psutil
|
|
ui_count = len(mcrfpy.sceneUI(mcrfpy.currentScene()))
|
|
return ui_count * 1000 # Rough estimate in KB
|
|
|
|
def save_report(self):
|
|
"""Save performance report"""
|
|
with open("performance_report.json", "w") as f:
|
|
json.dump({
|
|
"samples": self.samples,
|
|
"summary": {
|
|
"total_samples": len(self.samples),
|
|
"duration": time.time() - self.start_time,
|
|
"avg_fps": sum(s["fps"] for s in self.samples) / len(self.samples)
|
|
}
|
|
}, f, indent=2)
|
|
print(f"Performance report saved ({len(self.samples)} samples)")
|
|
|
|
# Create monitor and start collecting
|
|
monitor = PerformanceMonitor()
|
|
mcrfpy.setTimer("performance_monitor", monitor.collect_sample, 1000) # Every second
|
|
|
|
print("Performance monitoring started - sampling every second")
|
|
|
|
|
|
# ===== automation_replay.py =====
|
|
# Record and replay user actions
|
|
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
import json
|
|
import time
|
|
|
|
class ActionRecorder:
|
|
"""Record user actions for replay"""
|
|
|
|
def __init__(self):
|
|
self.recording = False
|
|
self.actions = []
|
|
self.start_time = None
|
|
|
|
def start_recording(self):
|
|
"""Start recording user actions"""
|
|
self.recording = True
|
|
self.actions = []
|
|
self.start_time = time.time()
|
|
print("Recording started - perform actions to record")
|
|
|
|
# Register callbacks for all input types
|
|
mcrfpy.registerPyAction("record_click", self.record_click)
|
|
mcrfpy.registerPyAction("record_key", self.record_key)
|
|
|
|
# Map all mouse buttons
|
|
for button in range(3):
|
|
mcrfpy.registerInputAction(8192 + button, "record_click")
|
|
|
|
# Map common keys
|
|
for key in range(256):
|
|
mcrfpy.registerInputAction(4096 + key, "record_key")
|
|
|
|
def record_click(self, action_type):
|
|
"""Record mouse click"""
|
|
if not self.recording or action_type != "start":
|
|
return
|
|
|
|
pos = automation.position()
|
|
self.actions.append({
|
|
"type": "click",
|
|
"time": time.time() - self.start_time,
|
|
"x": pos[0],
|
|
"y": pos[1]
|
|
})
|
|
|
|
def record_key(self, action_type):
|
|
"""Record key press"""
|
|
if not self.recording or action_type != "start":
|
|
return
|
|
|
|
# This is simplified - real implementation would decode the key
|
|
self.actions.append({
|
|
"type": "key",
|
|
"time": time.time() - self.start_time,
|
|
"key": "unknown"
|
|
})
|
|
|
|
def stop_recording(self):
|
|
"""Stop recording and save"""
|
|
self.recording = False
|
|
with open("recorded_actions.json", "w") as f:
|
|
json.dump(self.actions, f, indent=2)
|
|
print(f"Recording stopped - {len(self.actions)} actions saved")
|
|
|
|
def replay_actions(self):
|
|
"""Replay recorded actions"""
|
|
print("Replaying recorded actions...")
|
|
|
|
with open("recorded_actions.json", "r") as f:
|
|
actions = json.load(f)
|
|
|
|
start_time = time.time()
|
|
action_index = 0
|
|
|
|
def replay_next():
|
|
nonlocal action_index
|
|
if action_index >= len(actions):
|
|
print("Replay complete")
|
|
mcrfpy.delTimer("replay")
|
|
return
|
|
|
|
action = actions[action_index]
|
|
current_time = time.time() - start_time
|
|
|
|
# Wait until it's time for this action
|
|
if current_time >= action["time"]:
|
|
if action["type"] == "click":
|
|
automation.click(action["x"], action["y"])
|
|
elif action["type"] == "key":
|
|
automation.keyDown(action["key"])
|
|
automation.keyUp(action["key"])
|
|
|
|
action_index += 1
|
|
|
|
mcrfpy.setTimer("replay", replay_next, 10) # Check every 10ms
|
|
|
|
# Example usage - would be controlled by UI
|
|
recorder = ActionRecorder()
|
|
|
|
# To start recording:
|
|
# recorder.start_recording()
|
|
|
|
# To stop and save:
|
|
# recorder.stop_recording()
|
|
|
|
# To replay:
|
|
# recorder.replay_actions()
|
|
|
|
print("Action recorder ready - call recorder.start_recording() to begin") |