Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base for GUI tests #2476

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ on:
pull_request:
merge_group:

env:
BROWSER_UI_TEST_VERSION: '0.18.2'

jobs:
test:
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -47,6 +50,17 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust
run: bash ci/install-rust.sh ${{ matrix.rust }} ${{ matrix.target }}
- name: Install npm
if: matrix.os != 'windows-latest'
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install browser-ui-test
if: matrix.os != 'windows-latest'
run: npm install browser-ui-test@"${BROWSER_UI_TEST_VERSION}"
- name: Build and run tests (+ GUI)
if: matrix.os != 'windows-latest'
run: cargo test --locked --target ${{ matrix.target }} --test gui
- name: Build and run tests
run: cargo test --locked --target ${{ matrix.target }}
- name: Test no default
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ test_book/book/
# Ignore Vim temporary and swap files.
*.sw?
*~

# GUI tests
node_modules
package-lock.json
package.json
19 changes: 17 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,23 @@ We generally strive to keep mdBook compatible with a relatively recent browser o
That is, supporting Chrome, Safari, Firefox, Edge on Windows, macOS, Linux, iOS, and Android.
If possible, do your best to avoid breaking older browser releases.

Any change to the HTML or styling is encouraged to manually check on as many browsers and platforms that you can.
Unfortunately at this time we don't have any automated UI or browser testing, so your assistance in testing is appreciated.
GUI tests are checked with the GUI testsuite. To run it, you need to install `npm` first. Then run:

```
cargo test --test gui
```

The first time, it'll fail and ask you to install the `browser-ui-test` package. Install it then re-run the tests.

If you want to disable the headless mode, use the `DISABLE_HEADLESS_TEST=1` environment variable:

```
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
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).

## Updating highlight.js

Expand Down
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,10 @@ name = "remove-emphasis"
path = "examples/remove-emphasis/test.rs"
crate-type = ["lib"]
test = true

[[test]]
harness = false
test = false
name = "gui"
path = "tests/gui/runner.rs"
crate-type = ["bin"]
86 changes: 86 additions & 0 deletions tests/gui/runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::env::current_dir;
use std::fs::{read_to_string, remove_dir_all};
use std::process::Command;

fn get_available_browser_ui_test_version_inner(global: bool) -> Option<String> {
let mut command = Command::new("npm");
command
.arg("list")
.arg("--parseable")
.arg("--long")
.arg("--depth=0");
if global {
command.arg("--global");
}
let stdout = command.output().expect("`npm` command not found").stdout;
let lines = String::from_utf8_lossy(&stdout);
lines
.lines()
.find_map(|l| l.split(':').nth(1)?.strip_prefix("browser-ui-test@"))
.map(std::borrow::ToOwned::to_owned)
}

fn get_available_browser_ui_test_version() -> Option<String> {
get_available_browser_ui_test_version_inner(false)
.or_else(|| get_available_browser_ui_test_version_inner(true))
}

fn expected_browser_ui_test_version() -> String {
let content = read_to_string(".github/workflows/main.yml")
.expect("failed to read `.github/workflows/main.yml`");
for line in content.lines() {
let line = line.trim();
if let Some(version) = line.strip_prefix("BROWSER_UI_TEST_VERSION:") {
return version.trim().replace('\'', "");
}
}
panic!("failed to retrieved `browser-ui-test` version");
}

fn main() {
let browser_ui_test_version = expected_browser_ui_test_version();
match get_available_browser_ui_test_version() {
Some(version) => {
if version != browser_ui_test_version {
eprintln!(
"⚠️ Installed version of browser-ui-test (`{version}`) is different than the \
one used in the CI (`{browser_ui_test_version}`) You can install this version \
using `npm update browser-ui-test` or by using `npm install browser-ui-test\
@{browser_ui_test_version}`",
);
}
}
None => {
panic!(
"`browser-ui-test` is not installed. You can install this package using `npm \
update browser-ui-test` or by using `npm install browser-ui-test\
@{browser_ui_test_version}`",
);
}
}

let current_dir = current_dir().expect("failed to retrieve current directory");
let test_book = current_dir.join("test_book");

// Result doesn't matter.
let _ = remove_dir_all(test_book.join("book"));

let mut cmd = Command::new("cargo");
cmd.arg("run").arg("build").arg(&test_book);
// Then we run the GUI tests on it.
assert!(cmd.status().is_ok_and(|status| status.success()));

let book_dir = format!("file://{}", current_dir.join("test_book/book/").display());

let mut command = Command::new("npx");
command
.arg("browser-ui-test")
.args(["--variable", "DOC_PATH", book_dir.as_str()])
.args(["--test-folder", "tests/gui"]);
if std::env::args().any(|arg| arg == "--disable-headless-test") {
command.arg("--no-headless");
}

// Then we run the GUI tests on it.
assert!(command.status().is_ok_and(|status| status.success()));
}
59 changes: 59 additions & 0 deletions tests/gui/sidebar.goml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// This GUI test checks sidebar hide/show and also its behaviour on smaller
// width.

// We disable the requests checks because `searchindex.json` will always fail
// locally.
fail-on-request-error: false
go-to: |DOC_PATH| + "index.html"
set-window-size: (1100, 600)
// Need to reload for the new size to be taken account by the JS.
reload:

store-value: (content_indent, 308)

define-function: (
"hide-sidebar",
[],
block {
// The content should be "moved" to the right because of the sidebar.
assert-css: ("#sidebar", {"transform": "none"})
assert-position: ("#page-wrapper", {"x": |content_indent|})

// We now hide the sidebar.
click: "#sidebar-toggle"
wait-for: "body.sidebar-hidden"
// `transform` is 0.3s so we need to wait a bit (0.5s) to ensure the animation is done.
wait-for: 5000
assert-css-false: ("#sidebar", {"transform": "none"})
// The page content should now be on the left.
assert-position: ("#page-wrapper", {"x": 0})
},
)

define-function: (
"show-sidebar",
[],
block {
// The page content should be on the left and the sidebar "moved out".
assert-css: ("#sidebar", {"transform": "matrix(1, 0, 0, 1, -308, 0)"})
assert-position: ("#page-wrapper", {"x": 0})

// We expand the sidebar.
click: "#sidebar-toggle"
wait-for: "body.sidebar-visible"
// `transform` is 0.3s so we need to wait a bit (0.5s) to ensure the animation is done.
wait-for: 5000
assert-css-false: ("#sidebar", {"transform": "matrix(1, 0, 0, 1, -308, 0)"})
// The page content should be moved to the right.
assert-position: ("#page-wrapper", {"x": |content_indent|})
},
)

call-function: ("hide-sidebar", {})
call-function: ("show-sidebar", {})

// We now test on smaller width to ensure that the sidebar is collapsed by default.
set-window-size: (900, 600)
reload:
call-function: ("show-sidebar", {})
call-function: ("hide-sidebar", {})
Loading