Spaces:
Paused
Paused
| """ | |
| Tests for config/timeouts.py - Timeout boundary guard tests. | |
| These tests prevent future regressions where excessive timeouts could cause | |
| slow startup or unresponsive behavior. The slow startup bug (68s → 10s fix) | |
| was caused by 30s × 5 selectors = 150s worst-case in find_first_visible_locator. | |
| Guard Test Philosophy: | |
| - Each timeout constant has a maximum acceptable value | |
| - If a future change increases a timeout beyond the threshold, tests fail | |
| - This forces developers to consciously acknowledge the performance impact | |
| """ | |
| from config.selector_utils import ( | |
| INPUT_WRAPPER_SELECTORS, | |
| ) | |
| from config.timeouts import ( | |
| CLICK_TIMEOUT_MS, | |
| POLLING_INTERVAL, | |
| POLLING_INTERVAL_STREAM, | |
| SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS, | |
| SELECTOR_VISIBILITY_TIMEOUT_MS, | |
| STARTUP_SELECTOR_VISIBILITY_TIMEOUT_MS, | |
| WAIT_FOR_ELEMENT_TIMEOUT_MS, | |
| ) | |
| class TestTimeoutBounds: | |
| """Guard tests ensuring timeout values stay within acceptable bounds. | |
| These tests prevent future regressions where timeouts are increased | |
| without consideration for their cumulative impact on startup time. | |
| """ | |
| def test_selector_existence_check_is_fast(self): | |
| """Existence check must be very fast (≤1000ms). | |
| This is the Phase 1 quick DOM check. It runs for EACH selector, | |
| so if this is too high, startup time multiplies quickly. | |
| Example: 5 selectors × 1000ms = 5s just for existence checks | |
| """ | |
| max_acceptable_ms = 1000 | |
| assert SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS <= max_acceptable_ms, ( | |
| f"SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS ({SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS}ms) " | |
| f"exceeds maximum acceptable value ({max_acceptable_ms}ms). " | |
| "This timeout runs for each selector, so increasing it significantly " | |
| "impacts startup time." | |
| ) | |
| def test_selector_visibility_timeout_is_reasonable(self): | |
| """Visibility timeout must be reasonable (≤10000ms). | |
| This is the Phase 2 visibility wait. It only runs on selectors | |
| that passed the existence check, but should still be bounded. | |
| """ | |
| max_acceptable_ms = 10000 | |
| assert SELECTOR_VISIBILITY_TIMEOUT_MS <= max_acceptable_ms, ( | |
| f"SELECTOR_VISIBILITY_TIMEOUT_MS ({SELECTOR_VISIBILITY_TIMEOUT_MS}ms) " | |
| f"exceeds maximum acceptable value ({max_acceptable_ms}ms)." | |
| ) | |
| def test_startup_selector_visibility_bounded(self): | |
| """Startup visibility timeout must be bounded (≤45000ms). | |
| This is used for critical startup path selectors where we allow | |
| longer waits because the page may still be loading. | |
| """ | |
| max_acceptable_ms = 45000 | |
| assert STARTUP_SELECTOR_VISIBILITY_TIMEOUT_MS <= max_acceptable_ms, ( | |
| f"STARTUP_SELECTOR_VISIBILITY_TIMEOUT_MS ({STARTUP_SELECTOR_VISIBILITY_TIMEOUT_MS}ms) " | |
| f"exceeds maximum acceptable value ({max_acceptable_ms}ms)." | |
| ) | |
| def test_click_timeout_is_fast(self): | |
| """Click timeout must be fast for responsive UX (≤5000ms).""" | |
| max_acceptable_ms = 5000 | |
| assert CLICK_TIMEOUT_MS <= max_acceptable_ms, ( | |
| f"CLICK_TIMEOUT_MS ({CLICK_TIMEOUT_MS}ms) exceeds maximum ({max_acceptable_ms}ms)." | |
| ) | |
| def test_polling_intervals_are_efficient(self): | |
| """Polling intervals must be efficient (≤500ms).""" | |
| max_acceptable_ms = 500 | |
| assert POLLING_INTERVAL <= max_acceptable_ms, ( | |
| f"POLLING_INTERVAL ({POLLING_INTERVAL}ms) exceeds maximum ({max_acceptable_ms}ms)." | |
| ) | |
| assert POLLING_INTERVAL_STREAM <= max_acceptable_ms, ( | |
| f"POLLING_INTERVAL_STREAM ({POLLING_INTERVAL_STREAM}ms) exceeds maximum ({max_acceptable_ms}ms)." | |
| ) | |
| def test_wait_for_element_timeout_bounded(self): | |
| """Wait for element timeout must be bounded (≤15000ms).""" | |
| max_acceptable_ms = 15000 | |
| assert WAIT_FOR_ELEMENT_TIMEOUT_MS <= max_acceptable_ms, ( | |
| f"WAIT_FOR_ELEMENT_TIMEOUT_MS ({WAIT_FOR_ELEMENT_TIMEOUT_MS}ms) " | |
| f"exceeds maximum ({max_acceptable_ms}ms)." | |
| ) | |
| class TestStartupTimeEstimate: | |
| """Tests that estimate and bound worst-case startup time. | |
| These tests calculate the theoretical worst-case startup time based on | |
| timeout constants and fail if it exceeds acceptable thresholds. | |
| """ | |
| def test_input_wrapper_selector_worst_case_bounded(self): | |
| """Worst-case time for INPUT_WRAPPER_SELECTORS must be bounded. | |
| The fix for the 68s startup bug uses a two-phase approach: | |
| - Phase 1: Quick existence check (SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS × N selectors) | |
| - Phase 2: Visibility wait (only on existing selectors, typically 1) | |
| - Phase 3 (fallback): Single selector visibility wait | |
| Worst case: All existence checks timeout + 1 visibility wait | |
| """ | |
| num_selectors = len(INPUT_WRAPPER_SELECTORS) | |
| # Worst case: all selectors fail existence check + fallback visibility wait | |
| worst_case_ms = ( | |
| num_selectors * SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS | |
| + SELECTOR_VISIBILITY_TIMEOUT_MS # fallback uses default timeout | |
| ) | |
| max_acceptable_ms = 15000 # 15 seconds | |
| assert worst_case_ms <= max_acceptable_ms, ( | |
| f"Worst-case selector search time ({worst_case_ms}ms) exceeds " | |
| f"maximum acceptable ({max_acceptable_ms}ms). " | |
| f"Current: {num_selectors} selectors × {SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS}ms " | |
| f"+ {SELECTOR_VISIBILITY_TIMEOUT_MS}ms fallback = {worst_case_ms}ms. " | |
| "Consider reducing SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS or the number of selectors." | |
| ) | |
| def test_startup_critical_path_time_bounded(self): | |
| """Critical startup path with STARTUP_SELECTOR_VISIBILITY_TIMEOUT_MS. | |
| When called with the higher startup timeout (30s), worst case should | |
| still be acceptable. | |
| """ | |
| num_selectors = len(INPUT_WRAPPER_SELECTORS) | |
| # With startup timeout: quick checks + one visibility wait with startup timeout | |
| worst_case_ms = ( | |
| num_selectors * SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS | |
| + STARTUP_SELECTOR_VISIBILITY_TIMEOUT_MS | |
| ) | |
| max_acceptable_ms = 45000 # 45 seconds (generous for startup) | |
| assert worst_case_ms <= max_acceptable_ms, ( | |
| f"Startup critical path worst-case ({worst_case_ms}ms) exceeds " | |
| f"maximum acceptable ({max_acceptable_ms}ms)." | |
| ) | |
| def test_selector_count_is_manageable(self): | |
| """Number of selectors should not grow unbounded. | |
| Each additional selector adds to startup time. This test ensures | |
| we keep the selector lists lean. | |
| """ | |
| max_selectors = 10 | |
| assert len(INPUT_WRAPPER_SELECTORS) <= max_selectors, ( | |
| f"INPUT_WRAPPER_SELECTORS has {len(INPUT_WRAPPER_SELECTORS)} items, " | |
| f"exceeding maximum ({max_selectors}). Each selector adds " | |
| f"{SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS}ms to worst-case startup. " | |
| "Consider removing old/unused selectors." | |
| ) | |
| class TestTimeoutDocumentation: | |
| """Tests ensuring timeout constants are well-documented. | |
| These tests verify that timeout files have proper documentation | |
| so future maintainers understand the performance implications. | |
| """ | |
| def test_timeouts_module_has_selector_section(self): | |
| """config/timeouts.py should have a selector timeout section.""" | |
| # This test ensures the timeout constants exist and are positive | |
| assert SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS > 0 | |
| assert SELECTOR_VISIBILITY_TIMEOUT_MS > 0 | |
| assert STARTUP_SELECTOR_VISIBILITY_TIMEOUT_MS > 0 | |
| def test_existence_check_faster_than_visibility(self): | |
| """Existence check should be faster than visibility timeout. | |
| The whole point of the two-phase approach is that existence checks | |
| are quick and visibility waits are only for existing elements. | |
| """ | |
| assert SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS < SELECTOR_VISIBILITY_TIMEOUT_MS, ( | |
| f"SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS ({SELECTOR_EXISTENCE_CHECK_TIMEOUT_MS}ms) " | |
| f"should be less than SELECTOR_VISIBILITY_TIMEOUT_MS ({SELECTOR_VISIBILITY_TIMEOUT_MS}ms). " | |
| "The two-phase approach requires fast existence checks." | |
| ) | |