# PageObjects pattern for e2e testing - And packages yet?

**URL:** <https://forum.elixirforum.com/t/pageobjects-pattern-for-e2e-testing-and-packages-yet/60313>\
**Category:** Questions\
**Tags:** wallaby, phoenix, testing\
**Created:** [December 12, 2023, 1:29pm UTC](https://forum.elixirforum.com/t/pageobjects-pattern-for-e2e-testing-and-packages-yet/60313 "2023-12-12T13:29:41Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![jeffdeville](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jeffdeville/32/2810_2.png) [@jeffdeville](https://forum.elixirforum.com/u/jeffdeville)\
**Post date:** [December 12, 2023, 1:29pm UTC](https://forum.elixirforum.com/t/pageobjects-pattern-for-e2e-testing-and-packages-yet/60313/1 "2023-12-12T13:29:41Z")

</div>

Is there a page objects library for e2e testing phoenix apps out there?

This concept is what I’m looking for:

> **[bliki: Page Object](https://martinfowler.com/bliki/PageObject.html)**
>
> A page object wraps an HTML page, or fragment, with an application-specific API, allowing you to manipulate page elements for testing without digging around in the HTML.

Back in my ruby days, I **loved** this project:

[https://github.com/site-prism/site\_prism](https://github.com/site-prism/site_prism)

I’m having trouble finding something equivalent. Anyone have any pointers?

---

<div class="post-metadata">

**Author:** ![al2o3cr](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/al2o3cr/32/3457_2.png) [@al2o3cr](https://forum.elixirforum.com/u/al2o3cr)\
**Post date:** [December 12, 2023, 5:24pm UTC](https://forum.elixirforum.com/t/pageobjects-pattern-for-e2e-testing-and-packages-yet/60313/2 "2023-12-12T17:24:07Z")

</div>

I haven’t used a library for this in Elixir, but it’s possible to get similar expressiveness with just functions using tools like [`Phoenix.LiveViewTest.element`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#element/3)

You’d write a helper function like:

```elixir
def shiny_magic_button(view, name_filter \\ nil) do
  element(view, "button.shiny.magic", name_filter)
end

```

Then you could write assertions like:

```elixir
assert view
       |> shiny_magic_button(~r{[wat]+})
       |> render_focus() =~ "Some message"

```

Alternatively, I’ve also written helpers that took arguments and build a large “selector”, since functions like `has_element?` expect that.

---

<div class="post-metadata">

**Author:** ![jeffdeville](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/jeffdeville/32/2810_2.png) [@jeffdeville](https://forum.elixirforum.com/u/jeffdeville)\
**Post date:** [December 12, 2023, 5:35pm UTC](https://forum.elixirforum.com/t/pageobjects-pattern-for-e2e-testing-and-packages-yet/60313/3 "2023-12-12T17:35:03Z")

</div>

That’s true, and is likely where I’ll start.

FWIW for anyone used to just wallaby how this could help.

Page objects don’t just encapsulate css in a new way, they provide an abstraction level that lets you define tests in terms of a html/css/network-ignorant manner. For example, here’s part of a test we have:

```elixir
session
    # visit and login as a belayalpaca investor
    |> visit("/login")
    |> assert_text("Login")
    |> fill_in(@email_field, with: email)
    |> click(@submit_button)
    # assert we got to the home page and then click a holding to buy an offering for
    |> wait_for_phx_connect()
    |> assert_text("Holdings")
    |> click(@first_holding)
    # assert we got to the policy offering page and select an offering
    |> wait_for_phx_connect()
    |> assert_text("Policy Offers")
    |> click(@first_offer)
    # assert we got to the checkout page and checkout
    |> assert_text("Purchase Insurance")
    |> click(@first_checkbox)
    |> click(@second_checkbox)
    |> click(@checkout_button)
    # assert we get redirected back to the home page after a successful purchase
    |> wait_for_page_redirect("/dashboard")
    |> wait_for_phx_connect()
    |> assert_text("Holdings")
    |> click(@active_policies_button)
    # assert we can see the newly purchased active policy
    |> wait_for_phx_connect()
    |> assert_text("Active Policies")
    |> assert_policy()

```

Not too bad, but it’s testing _how_ to navigate the site rather than what to do on the site. So compare this to what I would do with site\_prism:

```elixir
session
|> LoginPage.visit() # visit's and asserts the Login text
|> LoginPage.login(email, password) # fill_in email and submit
|> HoldingsPage.get_holding(0) # click first holding
|> OfferingsPage.buy_offer(0) # all the clicking, and await a success message
|> HoldingsPage.click_active_policies()
|> PoliciesPage.assert_policy()

```

Where these page objects can look something like:

```elixir
defmodule LoginPage do
  import Components.Menu
  # creates the visit func, assert_visible()
  use Page, url: "/login" 

  def login(session, email, password) do
    fill_in(session, @email_field, with: email)
    fill_in(session, @password_field, with: password)
     |> click(@submit_button)
     |> wait_for_phx_connect()
  end
end

```

html, css, multi-part steps, and (very important) network challenges for slow operations are now completely abstracted away from the test.

---

<div class="post-metadata">

**Author:** ![eahanson](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/eahanson/32/20895_2.png) [@eahanson](https://forum.elixirforum.com/u/eahanson)\
**Post date:** [December 15, 2023, 11:33am UTC](https://forum.elixirforum.com/t/pageobjects-pattern-for-e2e-testing-and-packages-yet/60313/4 "2023-12-15T11:33:30Z")

</div>

Yes, there’s Pages: [pages | Hex](https://hex.pm/packages/pages)

I’m the coauthor and have used it on multiple projects. It works with LiveView and controllers.

```elixir
defmodule Web.HomeLiveTest do
  use Test.ConnCase, async: true
  
  test "has login button", %{conn: conn} do
    conn
    |> Pages.new()
    |> Test.Pages.HomePage.assert_here()
    |> Test.Pages.HomePage.click_login_link()
    |> Test.Pages.LoginPage.assert_here()
  end
end

```

Here’s an example page, using [HtmlQuery](https://hexdocs.pm/html_query/HtmlQuery.html) for finding things in the HTML, and [Moar.Assertions](https://hexdocs.pm/moar/Moar.Assertions.html) for a pipe-able assertion function with some handy options.

```elixir
defmodule Test.Pages.HomePage do
  import Moar.Assertions
  alias HtmlQuery, as: Hq

  @spec assert_here(Pages.Driver.t()) :: Pages.Driver.t()
  def assert_here(%Pages.Driver.LiveView{} = page) do
    page
    |> Hq.find("[data-page]")
    |> Hq.attr("data-page")
    |> assert_eq("home", returning: page)
  end

  @spec click_login_link(Pages.Driver.t()) :: Pages.Driver.t()
  def click_login_link(page),
    do: page |> Pages.click("Log In", test_role: "login-link")

  @spec visit(Pages.Driver.t()) :: Pages.Driver.t()
  def visit(page),
    do: page |> Pages.visit("/")
end

```
