elixir

Debugging Phoenix LiveView with open_browser/2

Tracey Onim

Tracey Onim on

Debugging Phoenix LiveView with open_browser/2

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 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

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 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

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

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 for more information.

We recommend using our automatic LiveView instrumentation in the majority of cases.

Wrapping Up

The introduction of 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!

Tracey Onim

Tracey Onim

Guest author Tracey is a software developer programming with Elixir, Phoenix and Liveview. She's the community organizer of Elixir Kenya and ElixirConf Africa.

All articles by Tracey Onim

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps