Debugging Phoenix LiveView with open_browser/2
Tags: elixir
Published: 2023-04-04 12:00:00

In this blog post, you'll see how useful `open_browser/2` is when debugging LiveView tests. In addition, we'll give a brief introduction to testing LiveView.

Let's get started!

## Introducing LiveView Testing

Testing during the software development process is one way to build confidence and ensure your application will work as expected.

The ability to easily write meaningful tests is an important factor for any framework, regardless of programming language.

Developers can easily test the LiveView framework's component, life-cycle, and behavior by writing LiveView tests with pure Elixir, since it uses ExUnit (a built-in testing framework) for all its testing. We can be confident in writing LiveView tests that are fast, concurrent, and stable.

You can test the functionality of your live views' behavior through the help of the [`Phoenix.LiveViewTest`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#content) module, which offers convenient functions without the need to introduce JS testing frameworks. The test helpers assist us in writing meaningful tests for our LiveView modules with ease and speed.

## Sample Feature in LiveView

Let's write a sample feature to use as our testing example.

We shall add a form to our application that allows users to enter their email address and password when attempting to register. This feature will only focus on the rendered HTML and not include the ability to add users into the system (the backend part of taking user params and adding them into the data store).

Let's begin!

First, start by creating a new LiveView application:

```shell
mix phx.new sample_live --live
```

NB: You can use an existing LiveView application if you have one.

The `mix phx.new` command with the `--live` flag will create a new application with LiveView installed and configured.

Add a live path in the router.

```elixir
# lib/sample_live_web/router.ex

scope "/", SampleLiveWeb do

  live "/user_registration", RegistrationLive

end
```

Create `registration_live.ex` inside the `sample_live_web` folder.

```elixir
# lib/sample_live_web/registration_live.ex

defmodule SampleLiveWeb.RegistrationLive do

  use SampleLiveWeb, :live_view


  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  def render(assigns) do
    ~H"""

      <.form let={f} for={:changeset} id={"registration-form"} >

        <%= label f, :email %>
        <%= text_input f, :email, id: "email-input" %>
        <%= error_tag f, :email %>
        <%= label f, :password, id: "password-input" %>
        <%= password_input f, :password %>
        <%= error_tag f, :password %>
        <%= submit "Save" %>
      </.form>

    """
  end

end
```

In our LiveView application module, two callbacks have been defined: `mount/3` and `render/1`.

The `mount/3` callback expects three arguments — `params`, `session`, and `liveview socket` — and returns `{:ok, socket}`. When the LiveView page is rendered, the `mount/3` callback will be invoked twice: once to perform the initial page load and again to establish the live connection.

The `render/1` callback is responsible for displaying the `HTML` template/content — in this case, the added registration form. It receives the `socket assigns` and must return a template passed inside the `~H sigil`. Whenever there is a change in our LiveView, the `render/1` callback will be invoked.

In our template, we have defined a sample form where a user can enter their email and password.

Let's start the server and open the registration form in our browsers. You should see something similar to this:

![registration_page](https://blog.appsignal.com/images/blog/2023-03/registration-page.png)

## Display the Feature on the Browser while Testing

When working on an HTTP-based web application, we usually write integration tests to validate passing expected attributes and properties to parts of our application.

We'll write an integration test validating user interactions with our application. The test should verify that when a user visits the `/user_registration` page, they can see the registration form properties/attributes rendered. In addition, we'll explore how we can use [open_browser/2](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#open_browser/2) to debug our LiveView test.

First, let's write a test for our form implemented above that will verify the HTML rendered. In here, we shall use `open_browser/2` to verify the displayed form. The implemented registration form has an HTML `id` (`"registration-form"`) that should help us select the element in our test.

```elixir
# test/sample_live_web/registration_live_test.exs

defmodule SampleLiveWeb.RegistrationLiveTest do
  use SamplLiveWeb.ConnCase
  import Phoenix.LiveViewTest

  test "user can see registration form", %{conn: conn} do
    {:ok, view, html} = live(conn, "/user_registration")

    html =
      view
      |> element("#registration-form")
      |> open_browser()
      |> render()

    assert html =~ "Email</label>"
    assert html =~ "Password</label>"

  end

end
```

`import Phoenix.LiveViewTest` module gets access to the test helper functions.

In the sample feature, when a user is on the application and wants to visit the registration page, they will navigate to `/user_registration`. A similar thing happens here — we shall navigate the user to `/user_registration` using `live/2`, which performs a regular `get(conn, path)` and then upgrades the page to LiveView. It takes in the `conn` and the `path` then returns a three-element tuple with `:ok`, `liveview process`, and the `rendered HTML`.

`open_browser/2` expects a `view` or `element` as the first argument. In the test, I have opted to pass an element, and this can be done by invoking `element/3`, passing the `view` and the `query selector` to it. `element/3` returns an `element` which is then passed to `open_browser/2`. If `open_browser/2` finds the form element with the `query selector`(#registration-form) within the LiveView page, we expect the following:

1. The default browser to be opened displaying the HTML of the form element.
2. The form element to be returned.
   The `form element` returned can be passed to `render/1`, which takes in a `view_or_element` and returns an HTML string of the view that we can finally assert in the test.

Run the test:

```shell
mix test test/sampl_live_web/registration_live_test.exs
```

We should expect the default browser to open and our test to pass.

![registration page opened by open_browser](https://blog.appsignal.com/images/blog/2023-03/registration-page-open-browser.png)

Now, let's make the user enter their email and password by using `form/3`, which takes in the `view`, `query selector`, and `form data`, and then returns a form element. But this time we'll use a query selector that doesn't exist to demonstrate how `open_browser/2` can be useful in debugging LiveView tests.

```elixir
# test/sample_live_web/registration_live_test.exs

defmodule SampleLiveWeb.RegistrationLiveTest do
  use SamplLiveWeb.ConnCase
  import Phoenix.LiveViewTest

  test "user can see registration form", %{conn: conn} do
    {:ok, view, html} = live(conn, "/user_registration")

    view
    |> element("#registration-form")
    |> open_browser()

    view
    |> form("#wrong_registration-form", user: %{email: "hello@email.com", password: "hello123"})
    |> render()

  end
end
```

Let's run the test:

```shell
** (ArgumentError) expected selector "#wrong_registration-form" to return a single element, but got none within:

<main class="container"><p class="alert alert-info" role="alert" phx-click="lv:clear-flash" phx-value-key="info"></p><p class="alert alert-danger" role="alert" phx-click="lv:clear-flash" phx-value-key="error"></p><form action="#" method="post" id="registration-form" phx-submit="save"><input name="_csrf_token" type="hidden" value="CHwQRnBHAnF7JDQEXQk3AXx8QQ0hZxceYLY-91nA2PLS3bB13Q0HC8CQ"/><label for="registration-form_email">Email</label><input id="email-input" name="user[email]" type="text"/><label for="registration-form_password">Password</label><input id="password-input" name="user[password]" type="password"/><button type="submit">Save</button></form></main>
```

We notice that our test is failing with an `ArgumentError`. In this test, the `form/3` function returns a `form element` with the query selector `#wrong_registration-form`. This `form element` is then passed to `render/1`, which expects the `form element` with the query selector `#wrong_registration-form` passed to it as the first argument to return a form element (but it gets none within the LiveView page). The test has made it clear that we are using the wrong query selector.

Our form template is very short, so it's easy to identify our mistake. Even from the HTML string displayed on the terminal after running the test, it's easy to point out the query selector we should have used.

Imagine working with larger HTML templates, though. It will be cumbersome to go through the HTML string to try and identify the mistake. Let's examine how `open_browser/2` can come in handy in that case.

First, let's add `open_browser/2` after `form/3`:

```elixir
# test/sample_live_web/registration_live_test.exs

defmodule SampleLiveWeb.RegistrationLiveTest do
  use SamplLiveWeb.ConnCase
  import Phoenix.LiveViewTest

  test "user can see registration form", %{conn: conn} do
    {:ok, view, html} = live(conn, "/user_registration")

    view
    |> element("#registration-form")
    |> open_browser()

    view
    |> form("#wrong_registration-form", user: %{email: "hello@email.com", password: "hello123"})
    |> open_browser()
    |> render()

  end
end
```

This way, we expect the browser to open with the displayed registration form — but apparently, that won't be the case when we run our test. Just like the `render/1` function, `open_browser/2` expects the `form element` with the query selector `#wrong_registration-form` passed to it, but it doesn't exist.

The other option is to debug the LiveView page where the registration form is rendered. In the test, we shall call `open_browser/2` and pass the `view` returned after invoking `live/2` to it. Earlier on, I mentioned that `open_browser/2` could either take in a `view` or an `element`. The option to use an `element` is failing due to the wrong `query selector`. So using `view` can be a better option in our case to help us understand why the test is failing.

```elixir
# test/sample_live_web/registration_live_test.exs

defmodule SampleLiveWeb.RegistrationLiveTest do
  use SamplLiveWeb.ConnCase
  import Phoenix.LiveViewTest

  test "user can see registration form", %{conn: conn} do
    {:ok, view, html} = live(conn, "/user_registration")
    open_browser(view)
  end
end
```

The debug process will be as follows:

1. Run the test. This time, we should expect the default browser to be opened and our registration form displayed.
2. Use the element inspector in the browser to find the correct form selector.

![registration page browser inspect](https://blog.appsignal.com/images/blog/2023-03/browser-inspect.png)

From the image above, we can see that the form element under the inspect elements section highlighted in blue has a different `query selector` (the `id` attribute value) specified to the one passed in `form/3` in our test. Our test expected the `registration_form` query selector. If we now refactor our test and pass the required query selector, it will definitely pass.

We have seen that we can use `open_browser/2` as a debugging tool for our tests. With `open_browser/2`, we can easily verify that the correct HTML elements are rendered to users. If the correct `view_or_element` passes, the browser opens with the displayed HTML.

## Monitor Your Phoenix LiveView App in Production with AppSignal

If you also want to keep track of your live view performance in production, you can set up LiveView instrumentation in AppSignal. Once you've integrated AppSignal with Phoenix LiveView, you'll be able to monitor incoming HTTP requests.

[Check out our docs for Phoenix LiveView](https://docs.appsignal.com/elixir/integrations/phoenix.html) for more information.

We recommend using [our automatic LiveView instrumentation](https://blog.appsignal.com/2022/06/20/appsignal-for-phoenix-2-1-automatic-liveview-instrumentation.html) in the majority of cases.

## Wrapping Up

The introduction of [`open_browser/2`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#open_browser/2) has made debugging LiveView tests easy and efficient. In this post, we introduced how to use `open_browser/2` when testing to verify that the correct HTML is rendered within your live view.

Happy debugging!

**P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, [subscribe to our Elixir Alchemy newsletter and never miss a single post](/elixir-alchemy)!**