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 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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 }}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I removed the if condition, the GUI test is now failing on linux. I have no clue what's going since the output seems to not be present for unknown reasons...

- 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"]
87 changes: 87 additions & 0 deletions tests/gui/runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lacking anything else to try...

Suggested change
.arg("browser-ui-test")
.arg("browser-ui-test")
.args(["--debug", "--jobs", "1"])

Copy link
Contributor

@notriddle notriddle Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GuillaumeGomez

Error: Failed to launch the browser process!
[8316:8316:1219/155437.724812:FATAL:zygote_host_impl_linux.cc(126)] No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/main/docs/linux/suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live dangerously and need an immediate workaround, you can try using --no-sandbox.

Okay, then. Looks like the solution is to turn off the sandbox (at least in CI, which is already isolated into containers).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to rustfmt this file. Tabs aren't used for indentation here.

.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.
let status = command.status().expect("failed to get command output");
assert!(status.success());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert!(status.success());
assert!(status.success(), "{status:?}",);

This should give us a little more information.

}
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