This version of the documentation is outdated, and features documented here may work differently now. You can see the latest stable version of the docs here.

Fantoccini Basics

Now that you know a bit more about how Perseus tests work, it's time to go through how to write them!

Remember, you're controlling an actual browser, so you basically have everything available to you that a user can do (mostly). You can even take screenshots! All this is achieved with Fantoccini, which you can learn more about here. For now though, here's a quick tutorial on the basics, using this example:

use fantoccini::{Client, Locator};
use perseus::wait_for_checkpoint;

#[perseus::test]
async fn main(c: &mut Client) -> Result<(), fantoccini::error::CmdError> {
    c.goto("http://localhost:8080").await?;
    wait_for_checkpoint!("begin", 0, c);
    let url = c.current_url().await?;
    assert!(url.as_ref().starts_with("http://localhost:8080"));

    // The greeting was passed through using build state
    wait_for_checkpoint!("initial_state_present", 0, c);
    wait_for_checkpoint!("page_visible", 0, c);
    let greeting = c.find(Locator::Css("p")).await?.text().await?;
    assert_eq!(greeting, "Hello World!");
    // For some reason, retrieving the inner HTML or text of a `<title>` doens't work
    let title = c.find(Locator::Css("title")).await?.html(false).await?;
    assert!(title.contains("Index Page"));

    // Go to `/about`
    c.find(Locator::Id("about-link")).await?.click().await?;
    let url = c.current_url().await?;
    assert!(url.as_ref().starts_with("http://localhost:8080/about"));
    wait_for_checkpoint!("initial_state_not_present", 0, c);
    wait_for_checkpoint!("page_visible", 1, c);
    // Make sure the hardcoded text there exists
    let text = c.find(Locator::Css("p")).await?.text().await?;
    assert_eq!(text, "About.");
    let title = c.find(Locator::Css("title")).await?.html(false).await?;
    assert!(title.contains("About Page"));
    // Make sure we get initial state if we refresh
    c.refresh().await?;
    wait_for_checkpoint!("initial_state_present", 0, c);

    Ok(())
}

Going to a Page

You can trivially go to a page of your app by running c.goto("..."). The above example ensures that the URL is valid, but you shouldn't have to do this unless you're testing a page that automatically redirects the user. Also, if you're using i18n, don't worry about testing automatic locale redirection, we've already done that for you!

Once you've arrived at a page, you should wait for the router_entry (this example uses begin because it tests internal parts of Perseus) checkpoint, which will be reached when Perseus has decided what to do with your app. If you're testing particular page logic, you should wait instead for page_visible, which will be reached when the user could see content on your page, and then for page_interactive, which will be reached when the page is completely ready. Remember though, you only need to wait for the checkpoints that you actually use (e.g. you don't need to wait for page_visible and page_interactive if you're not doing anything in between).

Finding an Element

You can find an element easily by using Fantoccini's Locator enum. This has two options, Id or Css. The former will find an element by its HTML id, and the latter will use a CSS selector (here's a list of them). In the above example, we've used Locator::Css("p") to get all paragraph elements, and then we've plugged that into c.find() to get the first one. Then, we can get its innerText with .text() and assert that is what we want it to be.

Caveats

As you may have noticed above, asserting on the contents of a <title> is extremely unintuitive, as it requires using .html(false) (meaning include the element tag itself) and asserting against that. For some reason, neither .html(true) nor .text() return anything. There's a tracking issue for this here.

Miscellaneous

For full documentation of how Fantoccini works, see its API documentation here.