Support multiple books in the GUI tests

This adds the ability to use multiple books for the GUI tests. This is
helpful since some tests need special configuration, and sharing the
same book can make it difficult or impossible to test different
configurations. It also makes it difficult to make changes to the
test_book since it can affect other tests.

This works by placing the books in the tests/gui/books directory. The
test runner will automatically build all the books. The gui tests can
then just access the DOC_PATH with the name of the book.

Books are now saved in a temp directory to make it easier to use the
DOC_PATH variable, instead of being tests/gui/books/book_name/book which
is a little awkward.

Following commits will restructure the existing book. This is just a
mechanical move.
This commit is contained in:
Eric Huss
2025-10-13 20:07:38 -07:00
parent 4e41c844c3
commit e37e5314f8
49 changed files with 111 additions and 61 deletions

2
.gitignore vendored
View File

@@ -8,7 +8,7 @@ guide/book
.vscode .vscode
tests/dummy_book/book/ tests/dummy_book/book/
test_book/book/ tests/gui/books/*/book/
tests/testsuite/*/*/book/ tests/testsuite/*/*/book/
# Ignore Jetbrains specific files. # Ignore Jetbrains specific files.

View File

@@ -185,9 +185,7 @@ If you want to disable the headless mode, use the `--disable-headless-test` opti
cargo test --test gui -- --disable-headless-test cargo test --test gui -- --disable-headless-test
``` ```
The GUI tests are in the directory `tests/gui` in text files with the `.goml` extension. These tests are run The GUI tests are in the directory `tests/gui` in text files with the `.goml` extension. The books that the tests use are located in the `tests/gui/books` directory. These tests are run using a `node.js` framework called `browser-ui-test`. You can find documentation for this language on its [repository](https://github.com/GuillaumeGomez/browser-UI-test/blob/master/goml-script.md).
using a `node.js` framework called `browser-ui-test`. You can find documentation for this language on its
[repository](https://github.com/GuillaumeGomez/browser-UI-test/blob/master/goml-script.md).
### Checking changes in `.js` files ### Checking changes in `.js` files

View File

@@ -1,7 +1,7 @@
// Tests for collapsed heading sidebar navigation. // Tests for collapsed heading sidebar navigation.
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/collapsed.html" go-to: |DOC_PATH| + "test_book/headings/collapsed.html"
assert-count: (".header-item", 12) assert-count: (".header-item", 12)
assert-count: (".current-header", 1) assert-count: (".current-header", 1)

View File

@@ -2,7 +2,7 @@
// bottom. // bottom.
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/current-to-bottom.html" go-to: |DOC_PATH| + "test_book/headings/current-to-bottom.html"
assert-count: (".current-header", 1) assert-count: (".current-header", 1)
assert-text: (".current-header", "First header") assert-text: (".current-header", "First header")

View File

@@ -1,6 +1,6 @@
// When there aren't any headings, there shouldn't be any header items in the sidebar. // When there aren't any headings, there shouldn't be any header items in the sidebar.
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/empty.html" go-to: |DOC_PATH| + "test_book/headings/empty.html"
assert-count: (".header-item", 0) assert-count: (".header-item", 0)
assert-count: (".current-header", 0) assert-count: (".current-header", 0)

View File

@@ -2,7 +2,7 @@
// you scroll down and make it visible on screen. // you scroll down and make it visible on screen.
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/large-intro.html" go-to: |DOC_PATH| + "test_book/headings/large-intro.html"
assert-count: (".header-item", 2) assert-count: (".header-item", 2)
assert-count: (".current-header", 0) assert-count: (".current-header", 0)

View File

@@ -1,7 +1,7 @@
// When a header has various markup, the sidebar should replicate it. // When a header has various markup, the sidebar should replicate it.
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/markup.html" go-to: |DOC_PATH| + "test_book/headings/markup.html"
assert-count: (".header-item", 5) assert-count: (".header-item", 5)
assert-count: (".current-header", 1) assert-count: (".current-header", 1)

View File

@@ -2,7 +2,7 @@
// should be "current". // should be "current".
set-window-size: (1400, 800) set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/normal-intro.html" go-to: |DOC_PATH| + "test_book/headings/normal-intro.html"
assert-count: (".header-item", 4) assert-count: (".header-item", 4)
assert-count: (".current-header", 1) assert-count: (".current-header", 1)
assert-text: (".current-header", "The first heading") assert-text: (".current-header", "The first heading")

View File

@@ -1,6 +1,6 @@
// This GUI test checks help popup. // This GUI test checks help popup.
go-to: |DOC_PATH| + "index.html" go-to: |DOC_PATH| + "test_book/index.html"
assert-css: ("#mdbook-help-container", {"display": "none"}) assert-css: ("#mdbook-help-container", {"display": "none"})
press-key: '?' press-key: '?'
wait-for-css: ("#mdbook-help-container", {"display": "flex"}) wait-for-css: ("#mdbook-help-container", {"display": "flex"})

View File

@@ -1,6 +1,6 @@
// This tests pressing the left and right arrows moving to previous and next page. // This tests pressing the left and right arrows moving to previous and next page.
go-to: |DOC_PATH| + "index.html" go-to: |DOC_PATH| + "test_book/index.html"
// default page is the first numbered page // default page is the first numbered page
assert-text: ("title", "Introduction - mdBook test book") assert-text: ("title", "Introduction - mdBook test book")

View File

@@ -1,41 +1,41 @@
go-to: |DOC_PATH| + "format/config.html" go-to: |DOC_PATH| + "test_book/format/config.html"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html"})
// Check that it preserves fragments when redirecting. // Check that it preserves fragments when redirecting.
go-to: |DOC_PATH| + "format/config.html#fragment" go-to: |DOC_PATH| + "test_book/format/config.html#fragment"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html#fragment"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html#fragment"})
// The fragment one here isn't necessary, but should still work. // The fragment one here isn't necessary, but should still work.
go-to: |DOC_PATH| + "pointless-fragment.html" go-to: |DOC_PATH| + "test_book/pointless-fragment.html"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html"})
go-to: |DOC_PATH| + "pointless-fragment.html#foo" go-to: |DOC_PATH| + "test_book/pointless-fragment.html#foo"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html#foo"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html#foo"})
// Page rename, and a fragment rename. // Page rename, and a fragment rename.
go-to: |DOC_PATH| + "rename-page-and-fragment.html" go-to: |DOC_PATH| + "test_book/rename-page-and-fragment.html"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html"})
go-to: |DOC_PATH| + "rename-page-and-fragment.html#orig" go-to: |DOC_PATH| + "test_book/rename-page-and-fragment.html#orig"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html#new"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html#new"})
// Page rename, and the fragment goes to a *different* page from the default. // Page rename, and the fragment goes to a *different* page from the default.
go-to: |DOC_PATH| + "rename-page-fragment-elsewhere.html" go-to: |DOC_PATH| + "test_book/rename-page-fragment-elsewhere.html"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html"})
go-to: |DOC_PATH| + "rename-page-fragment-elsewhere.html#orig" go-to: |DOC_PATH| + "test_book/rename-page-fragment-elsewhere.html#orig"
assert-window-property: ({"location": |DOC_PATH| + "suffix.html#new"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/suffix.html#new"})
// Rename fragment on an existing page. // Rename fragment on an existing page.
go-to: |DOC_PATH| + "prefix.html#orig" go-to: |DOC_PATH| + "test_book/prefix.html#orig"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html#new"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html#new"})
// Other fragments aren't affected. // Other fragments aren't affected.
go-to: |DOC_PATH| + "index.html" // Reset page since redirects are processed on load. go-to: |DOC_PATH| + "test_book/index.html" // Reset page since redirects are processed on load.
go-to: |DOC_PATH| + "prefix.html" go-to: |DOC_PATH| + "test_book/prefix.html"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html"})
go-to: |DOC_PATH| + "index.html" // Reset page since redirects are processed on load. go-to: |DOC_PATH| + "test_book/index.html" // Reset page since redirects are processed on load.
go-to: |DOC_PATH| + "prefix.html#dont-change" go-to: |DOC_PATH| + "test_book/prefix.html#dont-change"
assert-window-property: ({"location": |DOC_PATH| + "prefix.html#dont-change"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/prefix.html#dont-change"})
// Rename fragment on an existing page to another page. // Rename fragment on an existing page to another page.
go-to: |DOC_PATH| + "index.html" // Reset page since redirects are processed on load. go-to: |DOC_PATH| + "test_book/index.html" // Reset page since redirects are processed on load.
go-to: |DOC_PATH| + "prefix.html#orig-new-page" go-to: |DOC_PATH| + "test_book/prefix.html#orig-new-page"
assert-window-property: ({"location": |DOC_PATH| + "suffix.html#new"}) assert-window-property: ({"location": |DOC_PATH| + "test_book/suffix.html#new"})

View File

@@ -5,9 +5,9 @@
//! information. //! information.
use serde_json::Value; use serde_json::Value;
use std::env::current_dir; use std::fs::read_to_string;
use std::fs::{read_to_string, remove_dir_all}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::{Command, Output};
fn get_available_browser_ui_test_version_inner(global: bool) -> Option<String> { fn get_available_browser_ui_test_version_inner(global: bool) -> Option<String> {
let mut command = Command::new("npm"); let mut command = Command::new("npm");
@@ -69,23 +69,75 @@ fn main() {
} }
} }
let current_dir = current_dir().expect("failed to retrieve current directory"); let out_dir = Path::new(env!("CARGO_TARGET_TMPDIR")).join("gui");
let test_book = current_dir.join("test_book"); build_books(&out_dir);
run_browser_ui_test(&out_dir);
}
// Result doesn't matter. fn build_books(out_dir: &Path) {
let _ = remove_dir_all(test_book.join("book")); let exe = build_mdbook();
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let books_dir = root.join("tests/gui/books");
for entry in books_dir.read_dir().unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if !path.is_dir() {
continue;
}
println!("Building `{}`", path.display());
let mut cmd = Command::new(&exe);
let output = cmd
.arg("build")
.arg("--dest-dir")
.arg(out_dir.join(path.file_name().unwrap()))
.arg(&path)
.output()
.expect("mdbook should be built");
check_status(&cmd, &output);
}
}
fn build_mdbook() -> PathBuf {
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.arg("run").arg("build").arg(&test_book); let output = cmd
// Then we run the GUI tests on it. .arg("build")
assert!(cmd.status().is_ok_and(|status| status.success())); .output()
.expect("cargo should be installed");
check_status(&cmd, &output);
let target_dir = detect_target_dir();
target_dir.join("debug/mdbook")
}
let book_dir = format!("file://{}", current_dir.join("test_book/book/").display()); fn detect_target_dir() -> PathBuf {
let mut cmd = Command::new("cargo");
let output = cmd
.args(["metadata", "--format-version=1", "--no-deps"])
.output()
.expect("cargo should be installed");
check_status(&cmd, &output);
let v: serde_json::Value = serde_json::from_slice(&output.stdout).expect("invalid json");
PathBuf::from(v["target_directory"].as_str().unwrap())
}
fn check_status(cmd: &Command, output: &Output) {
if !output.status.success() {
eprintln!("error: `{cmd:?}` failed");
let stdout = std::str::from_utf8(&output.stdout).expect("stdout is not utf8");
let stderr = std::str::from_utf8(&output.stderr).expect("stderr is not utf8");
eprintln!("\n--- stdout\n{stdout}\n--- stderr\n{stderr}");
std::process::exit(1);
}
}
fn run_browser_ui_test(out_dir: &Path) {
let mut command = Command::new("npx"); let mut command = Command::new("npx");
let mut doc_path = format!("file://{}", out_dir.display());
if !doc_path.ends_with('/') {
doc_path.push('/');
}
command command
.arg("browser-ui-test") .arg("browser-ui-test")
.args(["--variable", "DOC_PATH", book_dir.as_str()]) .args(["--variable", "DOC_PATH", doc_path.as_str()])
.args(["--display-format", "compact"]); .args(["--display-format", "compact"]);
for arg in std::env::args().skip(1) { for arg in std::env::args().skip(1) {

View File

@@ -1,7 +1,7 @@
// This tests basic search behavior. // This tests basic search behavior.
fail-on-js-error: true fail-on-js-error: true
go-to: |DOC_PATH| + "index.html" go-to: |DOC_PATH| + "test_book/index.html"
define-function: ( define-function: (
"open-search", "open-search",
@@ -31,7 +31,7 @@ write: "strikethrough"
wait-for-text: ("#mdbook-searchresults-header", "2 search results for 'strikethrough':") wait-for-text: ("#mdbook-searchresults-header", "2 search results for 'strikethrough':")
// Now we test search shortcuts and more page changes. // Now we test search shortcuts and more page changes.
go-to: |DOC_PATH| + "index.html" go-to: |DOC_PATH| + "test_book/index.html"
// This check is to ensure that the search bar is inside the search wrapper. // This check is to ensure that the search bar is inside the search wrapper.
assert: "#mdbook-search-wrapper #mdbook-searchbar" assert: "#mdbook-search-wrapper #mdbook-searchbar"
@@ -66,7 +66,7 @@ assert-document-property: ({"URL": "?search=test"}, ENDS_WITH)
// Now we ensure that when we land on the page with a "search in progress", the search results are // Now we ensure that when we land on the page with a "search in progress", the search results are
// loaded and that the search input has focus. // loaded and that the search input has focus.
go-to: |DOC_PATH| + "index.html?search=test" go-to: |DOC_PATH| + "test_book/index.html?search=test"
wait-for-text: ("#mdbook-searchresults-header", "search results for 'test':", ENDS_WITH) wait-for-text: ("#mdbook-searchresults-header", "search results for 'test':", ENDS_WITH)
assert: "#mdbook-searchbar:focus" assert: "#mdbook-searchbar:focus"
assert: "#mdbook-searchresults" assert: "#mdbook-searchresults"

View File

@@ -1,17 +1,17 @@
// This GUI test checks the active page sidebar highlight. // This GUI test checks the active page sidebar highlight.
go-to: |DOC_PATH| + "index.html" go-to: |DOC_PATH| + "test_book/index.html"
assert-text: ("mdbook-sidebar-scrollbox a.active", "Prefix Chapter") assert-text: ("mdbook-sidebar-scrollbox a.active", "Prefix Chapter")
go-to: |DOC_PATH| + "individual/index.html" go-to: |DOC_PATH| + "test_book/individual/index.html"
assert-text: ("mdbook-sidebar-scrollbox a.active", "3. Markdown Individual tags") assert-text: ("mdbook-sidebar-scrollbox a.active", "3. Markdown Individual tags")
go-to: |DOC_PATH| + "index.html?highlight=test" go-to: |DOC_PATH| + "test_book/index.html?highlight=test"
assert-text: ("mdbook-sidebar-scrollbox a.active", "Prefix Chapter") assert-text: ("mdbook-sidebar-scrollbox a.active", "Prefix Chapter")
go-to: |DOC_PATH| + "individual/index.html?highlight=test" go-to: |DOC_PATH| + "test_book/individual/index.html?highlight=test"
assert-text: ("mdbook-sidebar-scrollbox a.active", "3. Markdown Individual tags") assert-text: ("mdbook-sidebar-scrollbox a.active", "3. Markdown Individual tags")

View File

@@ -4,7 +4,7 @@
// We disable javascript // We disable javascript
javascript: false javascript: false
go-to: |DOC_PATH| + "index.html" go-to: |DOC_PATH| + "test_book/index.html"
store-value: (height, 1028) store-value: (height, 1028)
set-window-size: (1028, |height|) set-window-size: (1028, |height|)

View File

@@ -1,7 +1,7 @@
// This GUI test checks sidebar hide/show and also its behaviour on smaller // This GUI test checks sidebar hide/show and also its behaviour on smaller
// width. // width.
go-to: |DOC_PATH| + "index.html" go-to: |DOC_PATH| + "test_book/index.html"
set-window-size: (1100, 600) set-window-size: (1100, 600)
// Need to reload for the new size to be taken account by the JS. // Need to reload for the new size to be taken account by the JS.
reload: reload:

View File

@@ -2,7 +2,7 @@
debug: true debug: true
go-to: |DOC_PATH| + "index.html" go-to: |DOC_PATH| + "test_book/index.html"
// TODO: Dark mode is automatic, how to check that here? // TODO: Dark mode is automatic, how to check that here?
assert-css: ("#mdbook-theme-list", {"display": "none"}) assert-css: ("#mdbook-theme-list", {"display": "none"})