import unittest from matomo_bootstrap.installers.web import _click_next_with_wait, _count_locator class _FlakyLocator: def __init__(self, outcomes): self._outcomes = list(outcomes) self.calls = 0 def count(self) -> int: self.calls += 1 outcome = self._outcomes.pop(0) if isinstance(outcome, Exception): raise outcome return int(outcome) class _StaticLocator: def __init__(self, page, selector: str): self._page = page self._selector = selector def count(self) -> int: if self._selector == "#login-0": return 1 if self._page.login_visible else 0 if self._selector == "#siteName-0": return 0 return 0 @property def first(self): return self def is_visible(self) -> bool: return self.count() > 0 class _RoleLocator: def __init__(self, count_value: int): self._count_value = count_value def count(self) -> int: return self._count_value @property def first(self): return self def is_visible(self) -> bool: return self._count_value > 0 class _NoNextButLoginAppearsPage: def __init__(self): self.url = "http://matomo/index.php?action=setupSuperUser&module=Installation" self.login_visible = False self._wait_calls = 0 def locator(self, selector: str): return _StaticLocator(self, selector) def get_by_role(self, role: str, name: str): return _RoleLocator(0) def get_by_text(self, *_args, **_kwargs): return _RoleLocator(0) def title(self) -> str: return "setupSuperUser" def wait_for_load_state(self, *_args, **_kwargs): return None def wait_for_timeout(self, *_args, **_kwargs): self._wait_calls += 1 if self._wait_calls >= 1: self.login_visible = True class TestWebInstallerLocatorCountIntegration(unittest.TestCase): def test_retries_transient_navigation_error(self) -> None: locator = _FlakyLocator( [ RuntimeError( "Locator.count: Execution context was destroyed, most likely because of a navigation" ), RuntimeError( "Locator.count: Execution context was destroyed, most likely because of a navigation" ), 1, ] ) result = _count_locator(locator, timeout_s=0.5, retry_interval_s=0.0) self.assertEqual(result, 1) self.assertEqual(locator.calls, 3) def test_raises_non_transient_error_without_retry(self) -> None: locator = _FlakyLocator([RuntimeError("Locator is not attached to DOM")]) with self.assertRaises(RuntimeError): _count_locator(locator, timeout_s=0.5, retry_interval_s=0.0) self.assertEqual(locator.calls, 1) def test_click_next_wait_treats_login_form_as_progress(self) -> None: page = _NoNextButLoginAppearsPage() step = _click_next_with_wait(page, timeout_s=1) self.assertEqual(step, "Installation:setupSuperUser") self.assertTrue(page.login_visible) if __name__ == "__main__": unittest.main()