Is there a page objects library for e2e testing phoenix apps out there?
This concept is what I’m looking for:
Back in my ruby days, I loved this project:
I’m having trouble finding something equivalent. Anyone have any pointers?
Is there a page objects library for e2e testing phoenix apps out there?
This concept is what I’m looking for:
Back in my ruby days, I loved this project:
I’m having trouble finding something equivalent. Anyone have any pointers?
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
You’d write a helper function like:
def shiny_magic_button(view, name_filter \\ nil) do
element(view, "button.shiny.magic", name_filter)
end
Then you could write assertions like:
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.
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:
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:
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:
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.
Yes, there’s Pages: pages | Hex
I’m the coauthor and have used it on multiple projects. It works with LiveView and controllers.
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 for finding things in the HTML, and Moar.Assertions for a pipe-able assertion function with some handy options.
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