browser-tests/tests/front_login.rs
//! ★ **The front page's "Log in" signs in with one press** (owner 2026-10-28, holger.rs): the
//! button beside "Sign up account" used to go to `/login`, a second page with a second "Log in".
//! Now the press starts holger's passkey sign-in at once, on the front page, and lands on
//! `/overview`, as Gunnar's front does. `/login` stays for a direct link (and for enrolment).
//! A ceremony the device refuses is said on the front page in the page's own words, never the
//! browser's, and the page stays where it is.
use holger_ui_browser_tests::browser::{Browser, NEVER, SIGNED_IN_OR_REGISTERED, SIGN_OUT_TAB};
use holger_ui_browser_tests::rig::{Rig, CONSOLE};
use monetize_site_buy::testkit::STRIPE;
/// The front page's "Log in", enabled and in the owner's word.
const LOG_IN_READY: &str = "(() => { const b = document.getElementById('enter'); return !!b && !b.disabled && b.offsetParent !== null && b.textContent.trim() === 'Log in'; })()";
/// What the front page says, once it says an error (`#msg` with the class `err`).
const FRONT_ERROR: &str = "(() => { const m = document.getElementById('msg'); return m && m.classList.contains('err') ? m.textContent : ''; })()";
/// What a person reads when no passkey answered (the console's `noPasskeyHere`, first sentence).
const NO_PASSKEY: &str = "No passkey for this site on this device. Use the phone, computer or password manager where you made your passkey.";
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn one_press_on_the_front_pages_log_in_signs_in_and_lands_on_the_overview() {
let rig = Rig::start().await;
let browser = Browser::launch().await.expect("chromium starts");
let tab = browser.tab().await.unwrap();
// ── the owner's passkey, enrolled on /login with the break-glass token ──
tab.open(&rig.at("/login")).await.unwrap();
tab.until("the bootstrap door", "!document.getElementById('reg').hidden").await;
tab.fill("#token", CONSOLE).await;
tab.js("document.getElementById('role').value = 'admin'").await.unwrap();
tab.js("document.getElementById('reg').open = true").await.unwrap();
tab.fill("#user", "owner").await;
tab.click("#register").await;
tab.until("registered", SIGNED_IN_OR_REGISTERED).await;
// A registration that signs in moves on to the console: let it land, then sign out on the
// front page, where the person comes back.
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
tab.open(&rig.at("/")).await.unwrap();
tab.js(SIGN_OUT_TAB).await.unwrap();
assert!(!tab.signed_in().await, "the tab still holds a session");
// ── one press on the front page ──
tab.watch_ceremonies().await.unwrap();
tab.open(&rig.at("/")).await.unwrap();
tab.until("the front page's Log in", LOG_IN_READY).await;
let before = browser.requests().len();
tab.press("#enter").await;
tab.until_url("one press on Log in lands on the overview", &rig.at("/overview")).await;
assert!(tab.signed_in().await, "the front page's press signed the person in");
let ceremonies = tab.ceremonies().await;
let gets: Vec<_> = ceremonies.iter().filter(|c| c["kind"] == "get").collect();
assert_eq!(gets.len(), 1, "one sign-in ceremony: {ceremonies:?}");
for c in &ceremonies {
assert_eq!(c["gesture"], true, "a ceremony started without the person's press: {c}");
assert_eq!(c["outcome"], "ok", "{c}");
}
// No second page: the press never opened /login.
let login = rig.at("/login");
let after: Vec<String> = browser.requests()[before..].to_vec();
assert!(
!after.iter().any(|u| u == &login || u.starts_with(&format!("{login}?")) || u.starts_with(&format!("{login}#"))),
"the front page's Log in went to /login: {after:?}"
);
browser.assert_nothing_went_to(NEVER, &rig.base, STRIPE);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_cancelled_sign_in_on_the_front_page_is_said_there_by_name() {
let rig = Rig::start().await;
let browser = Browser::launch().await.expect("chromium starts");
// A device that holds no passkey for this site: the ceremony is refused (NotAllowedError).
let tab = browser.tab().await.unwrap();
tab.watch_ceremonies().await.unwrap();
tab.open(&rig.at("/")).await.unwrap();
tab.until("the front page's Log in", LOG_IN_READY).await;
tab.press("#enter").await;
let said = tab.until("the front page says why", FRONT_ERROR).await;
let said = said.as_str().unwrap_or_default().to_owned();
assert!(said.contains(NO_PASSKEY), "the page's own words: {said:?}");
for browser_words in ["NotAllowedError", "not allowed", "The operation either timed out"] {
assert!(!said.contains(browser_words), "the browser's own words on the page: {said:?}");
}
let ceremonies = tab.ceremonies().await;
assert_eq!(ceremonies.iter().filter(|c| c["kind"] == "get").count(), 1, "the press started the sign-in: {ceremonies:?}");
let url = tab.url().await;
assert!(url == rig.at("/") || url == rig.base, "the page stays the front page: {url}");
assert!(!tab.signed_in().await, "a refused ceremony signed nobody in");
tab.until("Log in again", LOG_IN_READY).await;
}