View Source PhoenixTest (PhoenixTest v0.2.11)

PhoenixTest provides a unified way of writing feature tests -- regardless of whether you're testing LiveView pages or static pages.

It also handles navigation between LiveView and static pages seamlessly. So, you don't have to worry about what type of page you're visiting. Just write the tests from the user's perspective.

Thus, you can test a flow going from static to LiveView pages and back without having to worry about the underlying implementation.

This is a sample flow:

  test "admin can create a user", %{conn: conn} do
    conn
    |> visit("/")
    |> click_link("Users")
    |> fill_form("#user-form", name: "Aragorn", email: "aragorn@dunedan.com")
    |> click_button("Create")
    |> assert_has(".user", "Aragorn")
  end

Note that PhoenixTest does not handle JavaScript. If you're looking for something that supports JavaScript, take a look at Wallaby.

Summary

Functions

Assert helper to ensure an element with given CSS selector is present.

Assert helper to ensure an element with given CSS selector and options.

Assert helper to verify current request path. Takes an optional query_params map.

Same as assert_path/2 but takes an optional query_params map.

Perfoms action defined by button (and based on attributes present).

Performs action defined by button with CSS selector.

Clicks a link with given text and performs the action.

Clicks a link with given CSS selector and text and performs the action. selector to target the link.

Fills form data, validating that input fields are present.

Open the default browser to display current HTML of session.

Opposite of assert_has/2 helper. Verifies that element with given CSS selector is not present.

Opposite of assert_has/3 helper. Verifies that element with given CSS selector and text is not present.

Verifies current request path is NOT the one provided. Takes an optional query_params map for more specificity.

Same as refute_path/2 but takes an optional query_params for more specific refutation.

Submits form in the same way one would do by pressing <Enter>.

Entrypoint to create a session.

Functions

Link to this function

assert_has(session, selector)

View Source

Assert helper to ensure an element with given CSS selector is present.

It'll raise an error if no elements are found, but it will not raise if more than one matching element is found.

If you want to specify the content of the element, use assert_has/3.

Examples

# assert there's an h1
assert_has(session, "h1")

# assert there's an element with ID "user"
assert_has(session, "#user")
Link to this function

assert_has(session, selector, opts)

View Source

Assert helper to ensure an element with given CSS selector and options.

It'll raise an error if no elements are found, but it will not raise if more than one matching element is found.

Options

  • text: the text filter to look for.

  • exact: by default assert_has/3 will perform a substring match (e.g. a =~ b). That makes it easier to assert text within HTML elements that also contain other HTML elements. But sometimes we want to assert the exact text is present. For that, use exact: true. (defaults to false)

  • count: the number of items you expect to match CSS selector (and text if provided)

  • at: the element to be asserted against

Examples

# assert there's an element with ID "user" and text "Aragorn"
assert_has(session, "#user", text: "Aragorn")
  # ^ succeeds if text found is "Aragorn" or "Aragorn, Son of Arathorn"

# assert there's an element with ID "user" and text "Aragorn"
assert_has(session, "#user", text: "Aragorn", exact: true)
  # ^ succeeds only if text found is "Aragorn". Fails if finds "Aragorn, Son of Arathorn"

# assert there are two elements with class "posts"
assert_has(session, ".posts", count: 2)

# assert there are two elements with class "posts" and text "Hello"
assert_has(session, ".posts", text: "Hello", count: 2)

# assert the second element in the list of ".posts" has text "Hello"
assert_has(session, ".posts", at: 2, text: "Hello")
Link to this function

assert_path(session, path)

View Source

Assert helper to verify current request path. Takes an optional query_params map.

Examples

# assert we're at /users
conn
|> visit("/users")
|> assert_path("/users")

# assert we're at /users?name=frodo
conn
|> visit("/users")
|> assert_path("/users", query_params: %{name: "frodo"})
Link to this function

assert_path(session, path, opts)

View Source

Same as assert_path/2 but takes an optional query_params map.

Link to this function

click_button(session, text)

View Source

Perfoms action defined by button (and based on attributes present).

This can be used in a number of ways.

Button with phx-click

If the button has a phx-click on it, it'll send the event to the LiveView.

Example

<button phx-click="save">Save</button>
session
|> click_button("Save") # <- will send "save" event to LiveView

Button relying on Phoenix.HTML.js

If the button acts as a form via Phoenix.HTML's data-method, data-to, and data-csrf, this will emulate Phoenix.HTML.js and submit the form via data attributes.

But note that this doesn't guarantee the JavaScript that handles form submissions via data attributes is loaded. The test emulates the behavior but you must make sure the JavaScript is loaded.

For more on that, see https://hexdocs.pm/phoenix_html/Phoenix.HTML.html#module-javascript-library

Example

<button data-method="delete" data-to="/users/2" data-csrf="token">Delete</button>
session
|> click_button("Delete") # <- will submit form like Phoenix.HTML.js does

Combined with fill_form/3

This function can be preceded by fill_form to fill out a form and subsequently submit the form.

Note that fill_form/3 + click_button/2 works for both static and live pages.

Example

session
|> fill_form("#user-form", name: "Aragorn")
|> click_button("Create")

Single-button form

If click_button/2 is used alone (without phx-click, data-* attrs, or fill_form/3), it is assumed it is a form with a single button (e.g. "Delete").

Example

<form method="post" action="/users/2">
  <input type="hidden" name="_method" value="delete" />
  <button>Delete</button>
</form>
session
|> click_button("Delete") # <- Triggers full form delete
Link to this function

click_button(session, selector, text)

View Source

Performs action defined by button with CSS selector.

See click_button/2 for more details.

Link to this function

click_link(session, text)

View Source

Clicks a link with given text and performs the action.

Here's how it handles different types of a tags:

  • With href: follows it to the next page
  • With phx-click: it'll send the event to the appropriate LiveView
  • With live redirect: it'll follow the live navigation to the next LiveView
  • With live patch: it'll patch the current LiveView

Examples

<.link href="/page/2">Page 2</.link>
<.link phx-click="next-page">Next Page</.link>
<.link navigate="next-liveview">Next LiveView</.link>
<.link patch="page/details">Page Details</.link>
session
|> click_link("Page 2") # <- follows to next page

session
|> click_link("Next Page") # <- sends "next-page" event to LiveView

session
|> click_link("Next LiveView") # <- follows to next LiveView

session
|> click_link("Page Details") # <- applies live patch

Submitting forms

Phoenix allows for submitting forms on links via Phoenix.HTML's data-method, data-to, and data-csrf.

We can use click_link to emulate Phoenix.HTML.js and submit the form via data attributes.

But note that this doesn't guarantee the JavaScript that handles form submissions via data attributes is loaded. The test emulates the behavior but you must make sure the JavaScript is loaded.

For more on that, see https://hexdocs.pm/phoenix_html/Phoenix.HTML.html#module-javascript-library

Example

<a href="/users/2" data-method="delete" data-to="/users/2" data-csrf="token">
  Delete
</a>
session
|> click_link("Delete") # <- will submit form like Phoenix.HTML.js does
Link to this function

click_link(session, selector, text)

View Source

Clicks a link with given CSS selector and text and performs the action. selector to target the link.

See click_link/2 for more details.

Link to this function

fill_form(session, selector, data)

View Source

Fills form data, validating that input fields are present.

This can be used by both static and live pages.

If the form is a LiveView form, and if the form has a phx-change attribute defined, fill_form/3 will trigger the phx-change event.

This can be followed by a click_button/3 to submit the form.

Examples

session
|> fill_form("#user-form", name: "Aragorn")
|> click_button("Create")

If your form has nested data -- for example, with an input such as <input name="user[email]"> -- you can pass a nested map as the last argument:

session
|> fill_form("#user-form", user: %{email: "aragorn@dunedain.com"})
|> click_button("Create")

Open the default browser to display current HTML of session.

Examples

session
|> visit("/")
|> open_browser()
|> submit_form("#user-form", name: "Aragorn")
Link to this function

refute_has(session, selector)

View Source

Opposite of assert_has/2 helper. Verifies that element with given CSS selector is not present.

It'll raise an error if any elements that match selector are found.

If you want to specify the content of the element, use refute_has/3.

Example

# refute there's an h1
refute_has(session, "h1")

# refute there's an element with ID "user"
refute_has(session, "#user")
Link to this function

refute_has(session, selector, opts)

View Source

Opposite of assert_has/3 helper. Verifies that element with given CSS selector and text is not present.

It'll raise an error if any elements that match selector and options.

Options

  • text: the text filter to look for.

  • exact: by default refute_has/3 will perform a substring match (e.g. a =~ b). That makes it easier to refute text within HTML elements that also contain other HTML elements. But sometimes we want to refute the exact text is absent. For that, use exact: true.

  • count: the number of items you're expecting should not match the CSS selector (and text if provided)

  • at: the element to be refuted against

Examples

# refute there's an element with ID "user" and text "Aragorn"
refute_has(session, "#user", text: "Aragorn")

# refute there's an element with ID "user" and exact text "Aragorn"
refute_has(session, "#user", text: "Aragorn", exact: true)

# refute there are two elements with class "posts" (less or more will not raise)
refute_has(session, ".posts", count: 2)

# refute there are two elements with class "posts" and text "Hello"
refute_has(session, ".posts", text: "Hello", count: 2)

# refute the second element with class "posts" has text "Hello"
refute_has(session, ".posts", at: 2, text: "Hello")
Link to this function

refute_path(session, path)

View Source

Verifies current request path is NOT the one provided. Takes an optional query_params map for more specificity.

Examples

# refute we're at /posts
conn
|> visit("/users")
|> refute_path("/posts")

# refute we're at /users?name=frodo
conn
|> visit("/users?name=aragorn")
|> refute_path("/users", query_params: %{name: "frodo"})
Link to this function

refute_path(session, path, opts)

View Source

Same as refute_path/2 but takes an optional query_params for more specific refutation.

Link to this function

submit_form(session, selector, data)

View Source

Submits form in the same way one would do by pressing <Enter>.

Note that this does not validate presence of the submit button.

In the case of LiveView forms, it'll submit the form with LiveView's phx-submit event.

If it's a static form, this is equivalent to filling the form and submitting it with the form's method and to the form's action.

Note: if your form has a submit button, it's recommended you test with fill_form/3 + click_button/2 instead.

Examples

session
|> submit_form("#user-form", name: "Aragorn")

If your form has nested data -- for example, with an input such as <input name="user[email]"> -- you can pass a nested map as the last argument:

session
|> submit_form("#user-form", user: %{email: "aragorn@dunedain.com"})

Entrypoint to create a session.

visit/2 takes a Plug.Conn struct and the path to visit.

It returns a session which the rest of the PhoenixTest functions can use.

Note that visit/2 is smart enough to know if the page you're visiting is a LiveView or a static view. You don't need to worry about which type of page you're visiting.