Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

xtask/src/browser.rs

//! **`cargo xtask browser`** (OX-WIRED 2026-10-28): monetize-site-buy's buy pages in a real
//! browser — the `browser_*` tests, which say NOT RUN without `CHROME` — in the browser image
//! (`browser/Containerfile`: the toolchain image plus Debian's Chromium, gunnar-ui's recipe),
//! built once per content of that file and tagged by its hash. [`NEEDS`]`=1` makes a missing
//! Chromium red in every one of them. Inside a container (the CI runner's, or
//! `NORNIR_CI_HOST=1`) the tests run right here and `$CHROME` must name a Chromium.

use std::path::Path;
use std::process::{Command, ExitCode};

/// The browser tests of monetize-site-buy (`crates/monetize-site-buy/tests/<name>.rs`).
pub const TESTS: &[&str] = &["browser_paid_page", "browser_paid_return", "browser_plans_order", "browser_welcome_settle"];

/// Set (to `1`): a browser test with no `CHROME` FAILS by name instead of saying NOT RUN.
pub const NEEDS: &str = "MONETIZE_NEEDS_BROWSER";

/// The browser image's recipe, from the repository's root.
pub const RECIPE: &str = "browser/Containerfile";

/// The cargo arguments that run [`TESTS`].
pub fn args() -> Vec<String> {
    let mut a: Vec<String> = ["test", "--locked", "-p", "monetize-site-buy", "--features", "testkit"].iter().map(|s| s.to_string()).collect();
    for t in TESTS {
        a.push("--test".into());
        a.push((*t).into());
    }
    a.extend(["--".into(), "--test-threads".into(), "1".into()]);
    a
}

/// The browser image's tag for a `Containerfile`'s bytes: 64-bit FNV-1a, as 16 hex digits
/// (gunnar-ui's `recipe_tag`: stable across Rust versions, and xtask takes no hash crate).
pub fn recipe_tag(recipe: &[u8]) -> String {
    let mut h: u64 = 0xcbf2_9ce4_8422_2325;
    for b in recipe {
        h ^= u64::from(*b);
        h = h.wrapping_mul(0x0000_0100_0000_01b3);
    }
    format!("{h:016x}")
}

/// **`cargo xtask browser`**: [`TESTS`] with [`NEEDS`]`=1`, here when the toolchain is here,
/// else in the browser image (built from [`RECIPE`] when its tag is not present yet).
pub fn run(root: &Path) -> ExitCode {
    let args = args();
    let env = vec![(NEEDS.to_string(), "1".to_string())];
    let go = |mut c: Command, what: &str| -> ExitCode {
        match c.status() {
            Ok(s) if s.success() => ExitCode::SUCCESS,
            Ok(s) => {
                eprintln!("xtask browser: {what} exited {s}");
                ExitCode::FAILURE
            }
            Err(e) => {
                eprintln!("xtask browser: could not start {what}: {e}");
                ExitCode::FAILURE
            }
        }
    };
    if nornir_xtask_support::ci::toolchain_is_here() {
        let mut c = Command::new("cargo");
        c.args(&args).envs(env.iter().map(|(k, v)| (k, v))).current_dir(root);
        return go(c, "the browser tests");
    }
    let mut image = nornir_xtask_support::ci::Container::from_env(root);
    let file = root.join(RECIPE);
    let recipe = match std::fs::read(&file) {
        Ok(r) => r,
        Err(e) => {
            eprintln!("xtask browser: read {}: {e}", file.display());
            return ExitCode::FAILURE;
        }
    };
    let tag = format!("localhost/monetize-browser:{}", recipe_tag(&recipe));
    let present = Command::new(&image.engine).args(["image", "exists", &tag]).status().is_ok_and(|s| s.success());
    if !present {
        let mut c = Command::new(&image.engine);
        c.args(["build", "-t", &tag, "--build-arg", &format!("BASE={}", image.image), "-f"]).arg(&file).arg(root.join("browser"));
        let built = go(c, "the browser image's build");
        if built != ExitCode::SUCCESS {
            return built;
        }
    }
    image.image = tag;
    if let Err(e) = std::fs::create_dir_all(&image.target) {
        eprintln!("xtask browser: create {}: {e}", image.target.display());
        return ExitCode::FAILURE;
    }
    let argv = image.argv(None, &env, "cargo", &args);
    let mut c = Command::new(&argv[0]);
    c.args(&argv[1..]);
    go(c, "the browser tests in their image")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_verb_runs_every_browser_test_one_at_a_time() {
        let a = args();
        for t in TESTS {
            assert!(a.windows(2).any(|w| w[0] == "--test" && w[1] == *t), "{t}: {a:?}");
        }
        assert!(a.starts_with(&["test".into(), "--locked".into(), "-p".into(), "monetize-site-buy".into(), "--features".into(), "testkit".into()]), "{a:?}");
        assert_eq!(recipe_tag(b"x"), recipe_tag(b"x"));
        assert_ne!(recipe_tag(b"x"), recipe_tag(b"y"), "a changed recipe is a new image");
    }
}