|
27 | 27 | PredicateSpec, |
28 | 28 | RecoveryNavigationConfig, |
29 | 29 | SnapshotEscalationConfig, |
| 30 | + build_executor_prompt, |
30 | 31 | normalize_plan, |
31 | 32 | validate_plan_smoothness, |
32 | 33 | ) |
33 | 34 |
|
34 | 35 |
|
| 36 | +# --------------------------------------------------------------------------- |
| 37 | +# Test build_executor_prompt |
| 38 | +# --------------------------------------------------------------------------- |
| 39 | + |
| 40 | + |
| 41 | +class TestBuildExecutorPrompt: |
| 42 | + """Tests for the build_executor_prompt function.""" |
| 43 | + |
| 44 | + def test_basic_prompt_structure(self) -> None: |
| 45 | + sys_prompt, user_prompt = build_executor_prompt( |
| 46 | + goal="Click the submit button", |
| 47 | + intent=None, |
| 48 | + compact_context="123|button|Submit|100|1|0|-|0|", |
| 49 | + ) |
| 50 | + assert "CLICK(<id>)" in sys_prompt |
| 51 | + assert "TYPE(<id>" in sys_prompt |
| 52 | + assert "Goal: Click the submit button" in user_prompt |
| 53 | + assert "123|button|Submit" in user_prompt |
| 54 | + |
| 55 | + def test_includes_intent_when_provided(self) -> None: |
| 56 | + sys_prompt, user_prompt = build_executor_prompt( |
| 57 | + goal="Click on product", |
| 58 | + intent="Click the first product link", |
| 59 | + compact_context="456|link|Product|100|1|0|-|0|", |
| 60 | + ) |
| 61 | + assert "Intent: Click the first product link" in user_prompt |
| 62 | + |
| 63 | + def test_no_intent_line_when_none(self) -> None: |
| 64 | + sys_prompt, user_prompt = build_executor_prompt( |
| 65 | + goal="Click button", |
| 66 | + intent=None, |
| 67 | + compact_context="789|button|OK|100|1|0|-|0|", |
| 68 | + ) |
| 69 | + assert "Intent:" not in user_prompt |
| 70 | + |
| 71 | + def test_includes_input_text_when_provided(self) -> None: |
| 72 | + """Input text should be included for TYPE_AND_SUBMIT actions.""" |
| 73 | + sys_prompt, user_prompt = build_executor_prompt( |
| 74 | + goal="Search for Logitech mouse", |
| 75 | + intent=None, |
| 76 | + compact_context="167|searchbox|Search|100|1|0|-|0|", |
| 77 | + input_text="Logitech mouse", |
| 78 | + ) |
| 79 | + assert 'Text to type: "Logitech mouse"' in user_prompt |
| 80 | + |
| 81 | + def test_no_input_line_when_none(self) -> None: |
| 82 | + """No input text line when not provided.""" |
| 83 | + sys_prompt, user_prompt = build_executor_prompt( |
| 84 | + goal="Click button", |
| 85 | + intent=None, |
| 86 | + compact_context="123|button|Submit|100|1|0|-|0|", |
| 87 | + input_text=None, |
| 88 | + ) |
| 89 | + assert "Text to type:" not in user_prompt |
| 90 | + |
| 91 | + def test_includes_both_intent_and_input(self) -> None: |
| 92 | + """Both intent and input can be present.""" |
| 93 | + sys_prompt, user_prompt = build_executor_prompt( |
| 94 | + goal="Search for laptop", |
| 95 | + intent="search_box", |
| 96 | + compact_context="100|searchbox|Search|100|1|0|-|0|", |
| 97 | + input_text="laptop", |
| 98 | + ) |
| 99 | + assert "Intent: search_box" in user_prompt |
| 100 | + assert 'Text to type: "laptop"' in user_prompt |
| 101 | + |
| 102 | + |
35 | 103 | # --------------------------------------------------------------------------- |
36 | 104 | # Test normalize_plan |
37 | 105 | # --------------------------------------------------------------------------- |
@@ -1246,3 +1314,40 @@ def test_planner_executor_config_custom_auth_boundary(self) -> None: |
1246 | 1314 | ), |
1247 | 1315 | ) |
1248 | 1316 | assert config.auth_boundary.url_patterns == ("/custom-signin",) |
| 1317 | + |
| 1318 | + |
| 1319 | +# --------------------------------------------------------------------------- |
| 1320 | +# Test Modal Dismissal After Successful CLICK |
| 1321 | +# --------------------------------------------------------------------------- |
| 1322 | + |
| 1323 | + |
| 1324 | +class TestModalDismissalAfterSuccessfulClick: |
| 1325 | + """Tests for modal dismissal when verification passes after CLICK.""" |
| 1326 | + |
| 1327 | + def test_modal_dismissal_config_min_new_elements_default(self) -> None: |
| 1328 | + """Default min_new_elements should be 5 for DOM change detection.""" |
| 1329 | + config = ModalDismissalConfig() |
| 1330 | + assert config.min_new_elements == 5 |
| 1331 | + |
| 1332 | + def test_modal_enabled_by_default_in_planner_executor_config(self) -> None: |
| 1333 | + """Modal dismissal should be enabled by default.""" |
| 1334 | + config = PlannerExecutorConfig() |
| 1335 | + assert config.modal.enabled is True |
| 1336 | + assert config.modal.min_new_elements == 5 |
| 1337 | + |
| 1338 | + def test_modal_dismissal_patterns_include_no_thanks(self) -> None: |
| 1339 | + """'no thanks' should be in default patterns for drawer dismissal.""" |
| 1340 | + config = ModalDismissalConfig() |
| 1341 | + # This is the pattern that dismisses Amazon's product protection drawer |
| 1342 | + assert "no thanks" in config.dismiss_patterns |
| 1343 | + |
| 1344 | + def test_modal_config_has_required_fields_for_drawer_dismissal(self) -> None: |
| 1345 | + """Config should have all fields needed for drawer dismissal logic.""" |
| 1346 | + config = ModalDismissalConfig() |
| 1347 | + # These are all used in _attempt_modal_dismissal |
| 1348 | + assert hasattr(config, "enabled") |
| 1349 | + assert hasattr(config, "dismiss_patterns") |
| 1350 | + assert hasattr(config, "dismiss_icons") |
| 1351 | + assert hasattr(config, "role_filter") |
| 1352 | + assert hasattr(config, "max_attempts") |
| 1353 | + assert hasattr(config, "min_new_elements") |
0 commit comments