|  | 
|  | 1 | +//! The GUI test runner. | 
|  | 2 | +//! | 
|  | 3 | +//! This uses the browser-ui-test npm package to use a headless Chrome to | 
|  | 4 | +//! exercise the behavior of rendered books. See `CONTRIBUTING.md` for more | 
|  | 5 | +//! information. | 
|  | 6 | +
 | 
|  | 7 | +use serde_json::Value; | 
|  | 8 | +use std::env::current_dir; | 
|  | 9 | +use std::fs::read_to_string; | 
|  | 10 | +use std::process::Command; | 
|  | 11 | + | 
|  | 12 | +fn get_available_browser_ui_test_version_inner(global: bool) -> Option<String> { | 
|  | 13 | +    let mut command = Command::new("npm"); | 
|  | 14 | +    command.arg("list").arg("--parseable").arg("--long").arg("--depth=0"); | 
|  | 15 | +    if global { | 
|  | 16 | +        command.arg("--global"); | 
|  | 17 | +    } | 
|  | 18 | +    let stdout = command.output().expect("`npm` command not found").stdout; | 
|  | 19 | +    let lines = String::from_utf8_lossy(&stdout); | 
|  | 20 | +    lines | 
|  | 21 | +        .lines() | 
|  | 22 | +        .find_map(|l| l.split(':').nth(1)?.strip_prefix("browser-ui-test@")) | 
|  | 23 | +        .map(std::borrow::ToOwned::to_owned) | 
|  | 24 | +} | 
|  | 25 | + | 
|  | 26 | +fn get_available_browser_ui_test_version() -> Option<String> { | 
|  | 27 | +    get_available_browser_ui_test_version_inner(false).or_else(|| get_available_browser_ui_test_version_inner(true)) | 
|  | 28 | +} | 
|  | 29 | + | 
|  | 30 | +fn expected_browser_ui_test_version() -> String { | 
|  | 31 | +    let content = read_to_string("package.json").expect("failed to read `package.json`"); | 
|  | 32 | +    let v: Value = serde_json::from_str(&content).expect("failed to parse `package.json`"); | 
|  | 33 | +    let Some(dependencies) = v.get("dependencies") else { | 
|  | 34 | +        panic!("Missing `dependencies` key in `package.json`"); | 
|  | 35 | +    }; | 
|  | 36 | +    let Some(browser_ui_test) = dependencies.get("browser-ui-test") else { | 
|  | 37 | +        panic!("Missing `browser-ui-test` key in \"dependencies\" object in `package.json`"); | 
|  | 38 | +    }; | 
|  | 39 | +    let Value::String(version) = browser_ui_test else { | 
|  | 40 | +        panic!("`browser-ui-test` version is not a string"); | 
|  | 41 | +    }; | 
|  | 42 | +    version.trim().to_string() | 
|  | 43 | +} | 
|  | 44 | + | 
|  | 45 | +fn main() { | 
|  | 46 | +    let browser_ui_test_version = expected_browser_ui_test_version(); | 
|  | 47 | +    if let Some(version) = get_available_browser_ui_test_version() { | 
|  | 48 | +        if version != browser_ui_test_version { | 
|  | 49 | +            eprintln!( | 
|  | 50 | +                "⚠️ Installed version of browser-ui-test (`{version}`) is different than the \ | 
|  | 51 | +                 one used in the CI (`{browser_ui_test_version}`) You can install this version \ | 
|  | 52 | +                 using `npm update browser-ui-test` or by using `npm install browser-ui-test\ | 
|  | 53 | +                 @{browser_ui_test_version}`", | 
|  | 54 | +            ); | 
|  | 55 | +        } | 
|  | 56 | +    } else { | 
|  | 57 | +        let msg = format!( | 
|  | 58 | +            "`browser-ui-test` is not installed. You can install this package using `npm \ | 
|  | 59 | +             update browser-ui-test` or by using `npm install browser-ui-test\ | 
|  | 60 | +             @{browser_ui_test_version}`" | 
|  | 61 | +        ); | 
|  | 62 | + | 
|  | 63 | +        if std::env::var("FORCE_GUI").is_ok_and(|v| v == "1") { | 
|  | 64 | +            panic!("{msg}"); | 
|  | 65 | +        } else { | 
|  | 66 | +            println!( | 
|  | 67 | +                "Ignoring `gui` test (can be overloaded with `FORCE_GUI=1` environment \ | 
|  | 68 | +                 variable):" | 
|  | 69 | +            ); | 
|  | 70 | +            println!("{msg}"); | 
|  | 71 | +            return; | 
|  | 72 | +        } | 
|  | 73 | +    } | 
|  | 74 | + | 
|  | 75 | +    let current_dir = current_dir().expect("failed to retrieve current directory"); | 
|  | 76 | +    let html_file = current_dir.join("util/gh-pages/index.html"); | 
|  | 77 | +    assert!(html_file.is_file(), "Missing `{html_file:?}`, run `cargo collect-metadata` first"); | 
|  | 78 | +    let html_file = format!("file://{}", html_file.display()); | 
|  | 79 | + | 
|  | 80 | +    let mut command = Command::new("npx"); | 
|  | 81 | +    command | 
|  | 82 | +        .arg("browser-ui-test") | 
|  | 83 | +        .args(["--variable", "DOC_PATH", html_file.as_str()]) | 
|  | 84 | +        .args(["--display-format", "compact"]); | 
|  | 85 | + | 
|  | 86 | +    for arg in std::env::args().skip(1) { | 
|  | 87 | +        if arg.starts_with("--") { | 
|  | 88 | +            command.arg(arg); | 
|  | 89 | +        } else { | 
|  | 90 | +            command.args(["--filter", arg.as_str()]); | 
|  | 91 | +        } | 
|  | 92 | +    } | 
|  | 93 | + | 
|  | 94 | +    let test_dir = "tests/gui"; | 
|  | 95 | +    command.args(["--test-folder", test_dir]); | 
|  | 96 | + | 
|  | 97 | +    // Then we run the GUI tests on it. | 
|  | 98 | +    let status = command.status().expect("failed to get command output"); | 
|  | 99 | +    assert!(status.success(), "{status:?}"); | 
|  | 100 | +} | 
0 commit comments