Harden setupSuperUser installer readiness and submit fallbacks
This commit is contained in:
@@ -34,6 +34,9 @@ INSTALLER_TABLES_CREATION_TIMEOUT_S = int(
|
|||||||
INSTALLER_TABLES_ERASE_TIMEOUT_S = int(
|
INSTALLER_TABLES_ERASE_TIMEOUT_S = int(
|
||||||
os.environ.get("MATOMO_INSTALLER_TABLES_ERASE_TIMEOUT_S", "120")
|
os.environ.get("MATOMO_INSTALLER_TABLES_ERASE_TIMEOUT_S", "120")
|
||||||
)
|
)
|
||||||
|
INSTALLER_SUPERUSER_RELOAD_INTERVAL_S = int(
|
||||||
|
os.environ.get("MATOMO_INSTALLER_SUPERUSER_RELOAD_INTERVAL_S", "30")
|
||||||
|
)
|
||||||
INSTALLER_DEBUG_DIR = os.environ.get(
|
INSTALLER_DEBUG_DIR = os.environ.get(
|
||||||
"MATOMO_INSTALLER_DEBUG_DIR", "/tmp/matomo-bootstrap"
|
"MATOMO_INSTALLER_DEBUG_DIR", "/tmp/matomo-bootstrap"
|
||||||
).rstrip("/")
|
).rstrip("/")
|
||||||
@@ -74,6 +77,11 @@ SUPERUSER_LOGIN_SELECTORS = (
|
|||||||
"input[name='login']",
|
"input[name='login']",
|
||||||
"form#generalsetupform input[name='login']",
|
"form#generalsetupform input[name='login']",
|
||||||
)
|
)
|
||||||
|
SUPERUSER_FORM_SELECTORS = (
|
||||||
|
"form#generalsetupform",
|
||||||
|
"form[action*='setupSuperUser']",
|
||||||
|
"form[action*='action=setupSuperUser']",
|
||||||
|
)
|
||||||
SUPERUSER_PASSWORD_SELECTORS = (
|
SUPERUSER_PASSWORD_SELECTORS = (
|
||||||
"#password-0",
|
"#password-0",
|
||||||
"#password",
|
"#password",
|
||||||
@@ -376,6 +384,19 @@ def _has_superuser_login_field(page, *, timeout_s: float = 0.2) -> bool:
|
|||||||
return loc is not None
|
return loc is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _has_superuser_form_container(page, *, timeout_s: float = 0.2) -> bool:
|
||||||
|
loc, _ = _first_present_css_locator(
|
||||||
|
page, SUPERUSER_FORM_SELECTORS, timeout_s=timeout_s
|
||||||
|
)
|
||||||
|
return loc is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _superuser_form_ready(page, *, timeout_s: float = 0.2) -> bool:
|
||||||
|
return _has_superuser_login_field(
|
||||||
|
page, timeout_s=timeout_s
|
||||||
|
) or _has_superuser_form_container(page, timeout_s=timeout_s)
|
||||||
|
|
||||||
|
|
||||||
def _has_first_website_name_field(page, *, timeout_s: float = 0.2) -> bool:
|
def _has_first_website_name_field(page, *, timeout_s: float = 0.2) -> bool:
|
||||||
loc, _ = _first_present_css_locator(
|
loc, _ = _first_present_css_locator(
|
||||||
page, FIRST_WEBSITE_NAME_SELECTORS, timeout_s=timeout_s
|
page, FIRST_WEBSITE_NAME_SELECTORS, timeout_s=timeout_s
|
||||||
@@ -392,20 +413,38 @@ def _wait_for_superuser_login_field(
|
|||||||
page, *, timeout_s: float, poll_interval_ms: int = 300
|
page, *, timeout_s: float, poll_interval_ms: int = 300
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if timeout_s <= 0:
|
if timeout_s <= 0:
|
||||||
return _has_superuser_login_field(page, timeout_s=0.2)
|
return _superuser_form_ready(page, timeout_s=0.2)
|
||||||
|
|
||||||
deadline = time.time() + timeout_s
|
deadline = time.time() + timeout_s
|
||||||
last_wait_log_at = 0.0
|
last_wait_log_at = 0.0
|
||||||
|
last_reload_at = time.time()
|
||||||
|
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
_wait_dom_settled(page)
|
_wait_dom_settled(page)
|
||||||
if _has_superuser_login_field(page, timeout_s=0.2):
|
if _superuser_form_ready(page, timeout_s=0.2):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
if (
|
||||||
|
INSTALLER_SUPERUSER_RELOAD_INTERVAL_S > 0
|
||||||
|
and now - last_reload_at >= INSTALLER_SUPERUSER_RELOAD_INTERVAL_S
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
page.reload(wait_until="domcontentloaded")
|
||||||
|
_wait_dom_settled(page)
|
||||||
|
_log(
|
||||||
|
"[install] Reloaded setupSuperUser page while waiting "
|
||||||
|
"for superuser form."
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
_log(f"[install] setupSuperUser reload attempt failed: {exc}")
|
||||||
|
last_reload_at = now
|
||||||
|
if _superuser_form_ready(page, timeout_s=0.2):
|
||||||
|
return True
|
||||||
|
|
||||||
if now - last_wait_log_at >= 5:
|
if now - last_wait_log_at >= 5:
|
||||||
_log(
|
_log(
|
||||||
"[install] setupSuperUser reached but login form is not visible yet; "
|
"[install] setupSuperUser reached but superuser form is not visible yet; "
|
||||||
f"waiting (url={page.url}, step={_get_step_hint(page.url)})"
|
f"waiting (url={page.url}, step={_get_step_hint(page.url)})"
|
||||||
)
|
)
|
||||||
_page_warnings(page)
|
_page_warnings(page)
|
||||||
@@ -413,7 +452,7 @@ def _wait_for_superuser_login_field(
|
|||||||
|
|
||||||
page.wait_for_timeout(poll_interval_ms)
|
page.wait_for_timeout(poll_interval_ms)
|
||||||
|
|
||||||
return _has_superuser_login_field(page, timeout_s=0.2)
|
return _superuser_form_ready(page, timeout_s=0.2)
|
||||||
|
|
||||||
|
|
||||||
def _fill_required_input(page, selectors, value: str, *, label: str) -> None:
|
def _fill_required_input(page, selectors, value: str, *, label: str) -> None:
|
||||||
@@ -445,6 +484,7 @@ def _fill_optional_input(page, selectors, value: str) -> bool:
|
|||||||
def _installer_interactive(page) -> bool:
|
def _installer_interactive(page) -> bool:
|
||||||
checks = [
|
checks = [
|
||||||
_has_superuser_login_field(page),
|
_has_superuser_login_field(page),
|
||||||
|
_has_superuser_form_container(page),
|
||||||
_has_first_website_name_field(page),
|
_has_first_website_name_field(page),
|
||||||
_has_continue_to_matomo_action(page),
|
_has_continue_to_matomo_action(page),
|
||||||
]
|
]
|
||||||
@@ -452,6 +492,95 @@ def _installer_interactive(page) -> bool:
|
|||||||
return any(checks) or loc is not None
|
return any(checks) or loc is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _submit_superuser_form_via_dom(
|
||||||
|
page, *, user: str, password: str, email: str
|
||||||
|
) -> bool:
|
||||||
|
try:
|
||||||
|
return bool(
|
||||||
|
page.evaluate(
|
||||||
|
"""
|
||||||
|
([user, password, email]) => {
|
||||||
|
const form =
|
||||||
|
document.querySelector("form#generalsetupform")
|
||||||
|
|| document.querySelector("form[action*='setupSuperUser']")
|
||||||
|
|| document.querySelector("form[action*='action=setupSuperUser']");
|
||||||
|
if (!form) return false;
|
||||||
|
|
||||||
|
const pick = (selectors) => {
|
||||||
|
for (const selector of selectors) {
|
||||||
|
const candidate = form.querySelector(selector);
|
||||||
|
if (candidate) return candidate;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginInput = pick([
|
||||||
|
"input[name='login']",
|
||||||
|
"input#login",
|
||||||
|
"input[id^='login-']",
|
||||||
|
"input[name*='login']",
|
||||||
|
"input[name*='user']",
|
||||||
|
"input[type='text']",
|
||||||
|
]);
|
||||||
|
const passwordInput = pick([
|
||||||
|
"input[name='password']",
|
||||||
|
"input#password",
|
||||||
|
"input[id^='password-']",
|
||||||
|
"input[type='password']:not([name='password_bis'])",
|
||||||
|
"input[type='password']",
|
||||||
|
]);
|
||||||
|
const repeatPasswordInput = pick([
|
||||||
|
"input[name='password_bis']",
|
||||||
|
"input#password_bis",
|
||||||
|
"input[id^='password_bis-']",
|
||||||
|
"input[name*='repeat']",
|
||||||
|
]);
|
||||||
|
const emailInput = pick([
|
||||||
|
"input[name='email']",
|
||||||
|
"input#email",
|
||||||
|
"input[id^='email-']",
|
||||||
|
"input[type='email']",
|
||||||
|
"input[name*='mail']",
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!loginInput || !passwordInput || !emailInput) return false;
|
||||||
|
|
||||||
|
const setValue = (element, value) => {
|
||||||
|
element.value = value;
|
||||||
|
element.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
element.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
};
|
||||||
|
|
||||||
|
setValue(loginInput, user);
|
||||||
|
setValue(passwordInput, password);
|
||||||
|
if (repeatPasswordInput) {
|
||||||
|
setValue(repeatPasswordInput, password);
|
||||||
|
}
|
||||||
|
setValue(emailInput, email);
|
||||||
|
|
||||||
|
const submit = form.querySelector(
|
||||||
|
"button[type='submit'],input[type='submit']"
|
||||||
|
);
|
||||||
|
if (submit) {
|
||||||
|
submit.click();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof form.requestSubmit === "function") {
|
||||||
|
form.requestSubmit();
|
||||||
|
} else {
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[user, password, email],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _wait_for_installer_interactive(page, *, timeout_s: int) -> None:
|
def _wait_for_installer_interactive(page, *, timeout_s: int) -> None:
|
||||||
_log(f"[install] Waiting for interactive installer UI (timeout={timeout_s}s)...")
|
_log(f"[install] Waiting for interactive installer UI (timeout={timeout_s}s)...")
|
||||||
deadline = time.time() + timeout_s
|
deadline = time.time() + timeout_s
|
||||||
@@ -509,6 +638,12 @@ def _click_next_with_wait(page, *, timeout_s: int) -> str:
|
|||||||
f"staying on step {current_step} (url {current_url})"
|
f"staying on step {current_step} (url {current_url})"
|
||||||
)
|
)
|
||||||
return current_step
|
return current_step
|
||||||
|
if _has_superuser_form_container(page, timeout_s=0.2):
|
||||||
|
_log(
|
||||||
|
"[install] Superuser form container became available without explicit click; "
|
||||||
|
f"staying on step {current_step} (url {current_url})"
|
||||||
|
)
|
||||||
|
return current_step
|
||||||
if _has_first_website_name_field(page, timeout_s=0.2):
|
if _has_first_website_name_field(page, timeout_s=0.2):
|
||||||
_log(
|
_log(
|
||||||
"[install] First website form became available without explicit click; "
|
"[install] First website form became available without explicit click; "
|
||||||
@@ -763,11 +898,11 @@ class WebInstaller(Installer):
|
|||||||
|
|
||||||
progress_deadline = time.time() + INSTALLER_STEP_DEADLINE_S
|
progress_deadline = time.time() + INSTALLER_STEP_DEADLINE_S
|
||||||
|
|
||||||
while not _has_superuser_login_field(page):
|
while not _superuser_form_ready(page):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if now >= progress_deadline:
|
if now >= progress_deadline:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Installer did not reach superuser step "
|
"Installer did not reach superuser form "
|
||||||
f"within {INSTALLER_STEP_DEADLINE_S}s "
|
f"within {INSTALLER_STEP_DEADLINE_S}s "
|
||||||
f"(url={page.url}, step={_get_step_hint(page.url)})."
|
f"(url={page.url}, step={_get_step_hint(page.url)})."
|
||||||
)
|
)
|
||||||
@@ -792,73 +927,40 @@ class WebInstaller(Installer):
|
|||||||
_click_next_with_wait(page, timeout_s=step_timeout)
|
_click_next_with_wait(page, timeout_s=step_timeout)
|
||||||
_page_warnings(page)
|
_page_warnings(page)
|
||||||
|
|
||||||
_fill_required_input(
|
submitted_superuser = _submit_superuser_form_via_dom(
|
||||||
page,
|
page,
|
||||||
SUPERUSER_LOGIN_SELECTORS,
|
user=config.admin_user,
|
||||||
config.admin_user,
|
password=config.admin_password,
|
||||||
label="superuser login",
|
email=config.admin_email,
|
||||||
)
|
)
|
||||||
_fill_required_input(
|
|
||||||
page,
|
|
||||||
SUPERUSER_PASSWORD_SELECTORS,
|
|
||||||
config.admin_password,
|
|
||||||
label="superuser password",
|
|
||||||
)
|
|
||||||
_fill_optional_input(
|
|
||||||
page, SUPERUSER_PASSWORD_REPEAT_SELECTORS, config.admin_password
|
|
||||||
)
|
|
||||||
_fill_required_input(
|
|
||||||
page,
|
|
||||||
SUPERUSER_EMAIL_SELECTORS,
|
|
||||||
config.admin_email,
|
|
||||||
label="superuser email",
|
|
||||||
)
|
|
||||||
_page_warnings(page)
|
|
||||||
|
|
||||||
submitted_superuser = False
|
|
||||||
try:
|
|
||||||
submitted_superuser = bool(
|
|
||||||
page.evaluate(
|
|
||||||
"""
|
|
||||||
([user, password, email]) => {
|
|
||||||
const form = document.querySelector("form#generalsetupform");
|
|
||||||
if (!form) return false;
|
|
||||||
|
|
||||||
const loginInput = form.querySelector("input[name='login']");
|
|
||||||
const passwordInput = form.querySelector("input[name='password']");
|
|
||||||
const repeatPasswordInput = form.querySelector("input[name='password_bis']");
|
|
||||||
const emailInput = form.querySelector("input[name='email']");
|
|
||||||
if (!loginInput || !passwordInput || !emailInput) return false;
|
|
||||||
|
|
||||||
loginInput.value = user;
|
|
||||||
passwordInput.value = password;
|
|
||||||
if (repeatPasswordInput) {
|
|
||||||
repeatPasswordInput.value = password;
|
|
||||||
}
|
|
||||||
emailInput.value = email;
|
|
||||||
|
|
||||||
if (typeof form.requestSubmit === "function") {
|
|
||||||
form.requestSubmit();
|
|
||||||
} else {
|
|
||||||
form.submit();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
[
|
|
||||||
config.admin_user,
|
|
||||||
config.admin_password,
|
|
||||||
config.admin_email,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
submitted_superuser = False
|
|
||||||
|
|
||||||
if submitted_superuser:
|
if submitted_superuser:
|
||||||
_wait_dom_settled(page)
|
_wait_dom_settled(page)
|
||||||
_log("[install] Submitted superuser form via form.requestSubmit().")
|
_log("[install] Submitted superuser form via form.requestSubmit().")
|
||||||
else:
|
else:
|
||||||
|
_fill_required_input(
|
||||||
|
page,
|
||||||
|
SUPERUSER_LOGIN_SELECTORS,
|
||||||
|
config.admin_user,
|
||||||
|
label="superuser login",
|
||||||
|
)
|
||||||
|
_fill_required_input(
|
||||||
|
page,
|
||||||
|
SUPERUSER_PASSWORD_SELECTORS,
|
||||||
|
config.admin_password,
|
||||||
|
label="superuser password",
|
||||||
|
)
|
||||||
|
_fill_optional_input(
|
||||||
|
page, SUPERUSER_PASSWORD_REPEAT_SELECTORS, config.admin_password
|
||||||
|
)
|
||||||
|
_fill_required_input(
|
||||||
|
page,
|
||||||
|
SUPERUSER_EMAIL_SELECTORS,
|
||||||
|
config.admin_email,
|
||||||
|
label="superuser email",
|
||||||
|
)
|
||||||
|
_page_warnings(page)
|
||||||
|
|
||||||
submit_loc, submit_label = _first_present_css_locator(
|
submit_loc, submit_label = _first_present_css_locator(
|
||||||
page, SUPERUSER_SUBMIT_SELECTORS, timeout_s=0.5
|
page, SUPERUSER_SUBMIT_SELECTORS, timeout_s=0.5
|
||||||
)
|
)
|
||||||
@@ -875,10 +977,10 @@ class WebInstaller(Installer):
|
|||||||
superuser_progress_deadline = time.time() + INSTALLER_STEP_TIMEOUT_S
|
superuser_progress_deadline = time.time() + INSTALLER_STEP_TIMEOUT_S
|
||||||
while time.time() < superuser_progress_deadline:
|
while time.time() < superuser_progress_deadline:
|
||||||
_wait_dom_settled(page)
|
_wait_dom_settled(page)
|
||||||
if not _has_superuser_login_field(page):
|
if not _superuser_form_ready(page):
|
||||||
break
|
break
|
||||||
page.wait_for_timeout(300)
|
page.wait_for_timeout(300)
|
||||||
if _has_superuser_login_field(page):
|
if _superuser_form_ready(page):
|
||||||
_page_warnings(page)
|
_page_warnings(page)
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Superuser form submit did not progress to first website setup "
|
"Superuser form submit did not progress to first website setup "
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ class _StaticLocator:
|
|||||||
def count(self) -> int:
|
def count(self) -> int:
|
||||||
if self._selector == "#login-0":
|
if self._selector == "#login-0":
|
||||||
return 1 if self._page.login_visible else 0
|
return 1 if self._page.login_visible else 0
|
||||||
|
if self._selector == "form#generalsetupform":
|
||||||
|
return 1 if getattr(self._page, "form_visible", False) else 0
|
||||||
if self._selector == "#siteName-0":
|
if self._selector == "#siteName-0":
|
||||||
return 0
|
return 0
|
||||||
return 0
|
return 0
|
||||||
@@ -79,6 +81,7 @@ class _NoNextButLoginAppearsPage:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.url = "http://matomo/index.php?action=setupSuperUser&module=Installation"
|
self.url = "http://matomo/index.php?action=setupSuperUser&module=Installation"
|
||||||
self.login_visible = False
|
self.login_visible = False
|
||||||
|
self.form_visible = False
|
||||||
self._wait_calls = 0
|
self._wait_calls = 0
|
||||||
|
|
||||||
def locator(self, selector: str):
|
def locator(self, selector: str):
|
||||||
@@ -129,10 +132,39 @@ class _NoNextButNamedLoginAppearsPage:
|
|||||||
self.login_visible = True
|
self.login_visible = True
|
||||||
|
|
||||||
|
|
||||||
|
class _NoNextButSuperuserFormContainerAppearsPage:
|
||||||
|
def __init__(self):
|
||||||
|
self.url = "http://matomo/index.php?action=setupSuperUser&module=Installation"
|
||||||
|
self.login_visible = False
|
||||||
|
self.form_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.form_visible = True
|
||||||
|
|
||||||
|
|
||||||
class _DelayedSuperuserLoginPage:
|
class _DelayedSuperuserLoginPage:
|
||||||
def __init__(self, *, reveal_after_wait_calls: int | None):
|
def __init__(self, *, reveal_after_wait_calls: int | None):
|
||||||
self.url = "http://matomo/index.php?action=setupSuperUser&module=Installation"
|
self.url = "http://matomo/index.php?action=setupSuperUser&module=Installation"
|
||||||
self.login_visible = False
|
self.login_visible = False
|
||||||
|
self.form_visible = False
|
||||||
self._wait_calls = 0
|
self._wait_calls = 0
|
||||||
self._reveal_after_wait_calls = reveal_after_wait_calls
|
self._reveal_after_wait_calls = reveal_after_wait_calls
|
||||||
|
|
||||||
@@ -160,6 +192,38 @@ class _DelayedSuperuserLoginPage:
|
|||||||
self.login_visible = True
|
self.login_visible = True
|
||||||
|
|
||||||
|
|
||||||
|
class _DelayedSuperuserFormContainerPage:
|
||||||
|
def __init__(self, *, reveal_after_wait_calls: int | None):
|
||||||
|
self.url = "http://matomo/index.php?action=setupSuperUser&module=Installation"
|
||||||
|
self.login_visible = False
|
||||||
|
self.form_visible = False
|
||||||
|
self._wait_calls = 0
|
||||||
|
self._reveal_after_wait_calls = reveal_after_wait_calls
|
||||||
|
|
||||||
|
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._reveal_after_wait_calls is not None
|
||||||
|
and self._wait_calls >= self._reveal_after_wait_calls
|
||||||
|
):
|
||||||
|
self.form_visible = True
|
||||||
|
|
||||||
|
|
||||||
class TestWebInstallerLocatorCountIntegration(unittest.TestCase):
|
class TestWebInstallerLocatorCountIntegration(unittest.TestCase):
|
||||||
def test_retries_transient_navigation_error(self) -> None:
|
def test_retries_transient_navigation_error(self) -> None:
|
||||||
locator = _FlakyLocator(
|
locator = _FlakyLocator(
|
||||||
@@ -203,6 +267,14 @@ class TestWebInstallerLocatorCountIntegration(unittest.TestCase):
|
|||||||
self.assertEqual(step, "Installation:setupSuperUser")
|
self.assertEqual(step, "Installation:setupSuperUser")
|
||||||
self.assertTrue(page.login_visible)
|
self.assertTrue(page.login_visible)
|
||||||
|
|
||||||
|
def test_click_next_wait_treats_superuser_form_container_as_progress(self) -> None:
|
||||||
|
page = _NoNextButSuperuserFormContainerAppearsPage()
|
||||||
|
|
||||||
|
step = _click_next_with_wait(page, timeout_s=1)
|
||||||
|
|
||||||
|
self.assertEqual(step, "Installation:setupSuperUser")
|
||||||
|
self.assertTrue(page.form_visible)
|
||||||
|
|
||||||
def test_wait_for_superuser_login_field_allows_delayed_form(self) -> None:
|
def test_wait_for_superuser_login_field_allows_delayed_form(self) -> None:
|
||||||
page = _DelayedSuperuserLoginPage(reveal_after_wait_calls=4)
|
page = _DelayedSuperuserLoginPage(reveal_after_wait_calls=4)
|
||||||
|
|
||||||
@@ -215,6 +287,18 @@ class TestWebInstallerLocatorCountIntegration(unittest.TestCase):
|
|||||||
self.assertTrue(visible)
|
self.assertTrue(visible)
|
||||||
self.assertTrue(page.login_visible)
|
self.assertTrue(page.login_visible)
|
||||||
|
|
||||||
|
def test_wait_for_superuser_login_field_allows_delayed_form_container(self) -> None:
|
||||||
|
page = _DelayedSuperuserFormContainerPage(reveal_after_wait_calls=4)
|
||||||
|
|
||||||
|
visible = _wait_for_superuser_login_field(
|
||||||
|
page,
|
||||||
|
timeout_s=1.0,
|
||||||
|
poll_interval_ms=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(visible)
|
||||||
|
self.assertTrue(page.form_visible)
|
||||||
|
|
||||||
def test_wait_for_superuser_login_field_times_out_when_absent(self) -> None:
|
def test_wait_for_superuser_login_field_times_out_when_absent(self) -> None:
|
||||||
page = _DelayedSuperuserLoginPage(reveal_after_wait_calls=None)
|
page = _DelayedSuperuserLoginPage(reveal_after_wait_calls=None)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user