test: add E2E coverage for GOAP trap recovery and restart state reset
This commit is contained in:
75
tests/e2e/test_goap_trap_recovery.py
Normal file
75
tests/e2e/test_goap_trap_recovery.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
E2E: GOAP Trap Recovery
|
||||
========================
|
||||
Validates that the GOAP engine can properly escape "completely trapped" states
|
||||
by forcing an app restart, and that the internal failure states are correctly
|
||||
purged so it doesn't immediately trap itself again on the next iteration.
|
||||
"""
|
||||
|
||||
from GramAddict.core.goap import GoalExecutor
|
||||
from GramAddict.core.screen_topology import ScreenTopology
|
||||
from tests.e2e.conftest import load_fixture_xml
|
||||
|
||||
def test_goap_recovers_from_trapped_state_with_restart(e2e_device, monkeypatch):
|
||||
"""
|
||||
Simulates a broken UI where an action repeatedly fails.
|
||||
Verifies that GOAP triggers 'force start instagram', and successfully
|
||||
clears its tracking arrays (action_failures, etc.) to start fresh,
|
||||
avoiding an infinite restart loop.
|
||||
"""
|
||||
xml = load_fixture_xml("home_feed_real.xml")
|
||||
|
||||
# We create a device that ALWAYS returns the same home_feed XML,
|
||||
# no matter what action is taken. This forces the UI to never change,
|
||||
# which causes actions to fail repeatedly.
|
||||
device = e2e_device([xml] * 20)
|
||||
|
||||
goap = GoalExecutor.get_instance(device, bot_username="testuser")
|
||||
goap.action_failures.clear()
|
||||
|
||||
# We intercept app_start to track how many times it was forced to restart
|
||||
restart_count = 0
|
||||
def mock_app_start(*args, **kwargs):
|
||||
nonlocal restart_count
|
||||
restart_count += 1
|
||||
|
||||
monkeypatch.setattr(device, "app_start", mock_app_start)
|
||||
|
||||
# We want to ask it to reach REELS_FEED.
|
||||
# Since the UI never changes, 'tap reels tab' will fail.
|
||||
# It will fail twice, get masked, GOAP gets trapped, triggers restart.
|
||||
# We set max_steps to a small number so it doesn't loop forever in the test.
|
||||
# We just want to see that AFTER the restart, it tries 'tap reels tab' again,
|
||||
# meaning it cleared the state.
|
||||
|
||||
# Let's mock _execute_action slightly just to spy on it without changing behavior
|
||||
original_execute = goap._execute_action
|
||||
executed_actions = []
|
||||
|
||||
def spy_execute(action, goal=None):
|
||||
executed_actions.append(action)
|
||||
return original_execute(action, goal)
|
||||
|
||||
monkeypatch.setattr(goap, "_execute_action", spy_execute)
|
||||
|
||||
goap.achieve("open reels", max_steps=6)
|
||||
|
||||
# It should have tried 'tap reels tab' twice, failed both times,
|
||||
# then triggered 'force start instagram'.
|
||||
# Because of the fix, after the restart, it should have cleared failures
|
||||
# and tried 'tap reels tab' AGAIN.
|
||||
|
||||
assert restart_count > 0, "GOAP never attempted to force restart Instagram when trapped!"
|
||||
|
||||
# Count how many times it tried the action
|
||||
reels_attempts = executed_actions.count("tap reels tab")
|
||||
|
||||
assert reels_attempts > 2, (
|
||||
f"GOAP got trapped, restarted, but never tried the action again! "
|
||||
f"It only tried {reels_attempts} times, meaning the memory leak is still there. "
|
||||
f"Actions executed: {executed_actions}"
|
||||
)
|
||||
|
||||
# If the bug was present, it would only try 'tap reels tab' 2 times, mask it forever,
|
||||
# and then spam 'force start instagram' for the remaining steps.
|
||||
# With the fix, it tries 2 times, restarts, tries 2 times, restarts, etc.
|
||||
Reference in New Issue
Block a user