Phoenix
Test smarter, not harder
The Evolution of a
Feature test
Phoenix
- wallaby / playwright ๐ง
- phoenix_test
- LiveViewTest
- ConnTest
Wallaby
- chromedriver
- selenium
- HTTP + JSON: WebDriver Wire Protocol
- chromedriver: CDP
๐ Ruby on Rails
require "application_system_test_case"
class RegistrationTest < ApplicationSystemTestCase
test "register" do
visit registration_path
fill_in "Email", with: "f@ftes.de"
fill_in "Password", with: "1234"
click_on "Register"
assert_text "Account created"
- rack_test
- selenium
- cuprite
https://www.allthingswild.co.uk/wild-bunch/capybara-2/
phoenix_test
Made with โค๏ธ by German Velasco
test "register", %{conn: conn} do
conn
|> visit("/register")
|> fill_in("Email", with: "f@ftes.de")
|> fill_in("Password", with: "1234")
|> submit()
|> assert_has("#flash-info", text: "User created")
- static
- live
- playwright ๐ง
https://www.germanvelasco.com/blog/introducing-phoenix-test (Jan 31, 2024)
| mix phx.gen.auth Accounts User users |
| |
| Do you want to create a LiveView based |
| authentication system? [Yn] |
| |
| n |
| |
| mix test |
| mix phx.gen.auth Accounts User users |
| |
| Do you want to create a LiveView based |
| authentication system? [Yn] |
| |
| n |
| |
| mix test |
| mix phx.gen.auth Accounts User users |
| |
| Do you want to create a LiveView based |
| authentication system? [Yn] |
| |
| n |
| |
| mix test |
| mix phx.gen.auth Accounts User users |
| |
| Do you want to create a LiveView based |
| authentication system? [Yn] |
| |
| n |
| |
| mix test |
| defmodule SmartWeb.UserRegistrationControllerTest do |
| test "creates account and logs the user in", %{conn: conn} do |
| email = unique_user_email() |
| |
| conn = |
| post(conn, ~p"/users/register", %{ |
| "user" => valid_user_attributes(email: email) |
| }) |
| |
| assert get_session(conn, :user_token) |
| assert redirected_to(conn) == ~p"/" |
| |
| |
| conn = get(conn, ~p"/") |
| response = html_response(conn, 200) |
| assert response =~ email |
| assert response =~ ~p"/users/settings" |
| assert response =~ ~p"/users/log_out" |
| defmodule SmartWeb.UserRegistrationControllerTest do |
| test "creates account and logs the user in", %{conn: conn} do |
| email = unique_user_email() |
| |
| conn = |
| post(conn, ~p"/users/register", %{ |
| "user" => valid_user_attributes(email: email) |
| }) |
| |
| assert get_session(conn, :user_token) |
| assert redirected_to(conn) == ~p"/" |
| |
| |
| conn = get(conn, ~p"/") |
| response = html_response(conn, 200) |
| assert response =~ email |
| assert response =~ ~p"/users/settings" |
| assert response =~ ~p"/users/log_out" |
| defmodule SmartWeb.UserRegistrationControllerTest do |
| test "creates account and logs the user in", %{conn: conn} do |
| email = unique_user_email() |
| |
| conn = |
| post(conn, ~p"/users/register", %{ |
| "user" => valid_user_attributes(email: email) |
| }) |
| |
| assert get_session(conn, :user_token) |
| assert redirected_to(conn) == ~p"/" |
| |
| |
| conn = get(conn, ~p"/") |
| response = html_response(conn, 200) |
| assert response =~ email |
| assert response =~ ~p"/users/settings" |
| assert response =~ ~p"/users/log_out" |
| defmodule SmartWeb.UserRegistrationControllerTest do |
| test "creates account and logs the user in", %{conn: conn} do |
| email = unique_user_email() |
| |
| conn = |
| post(conn, ~p"/users/register", %{ |
| "user" => valid_user_attributes(email: email) |
| }) |
| |
| assert get_session(conn, :user_token) |
| assert redirected_to(conn) == ~p"/" |
| |
| |
| conn = get(conn, ~p"/") |
| response = html_response(conn, 200) |
| assert response =~ email |
| assert response =~ ~p"/users/settings" |
| assert response =~ ~p"/users/log_out" |
| defmodule UserRegistrationTest do |
| use Smart.DataCase |
| use PhoenixTest.Case |
| |
| test "register, log out and in", %{conn: conn} do |
| conn |
| |> visit("/") |
| |
| |
| |> click_link("Register") |
| |> fill_in("Email", with: "f@ftes.de") |
| |> fill_in("Password", with: "password1234") |
| |> submit() |
| |> assert_has("#flash-info", text: "User created") |
| |> open_browser() |
| |
| |
| |
| defmodule UserRegistrationTest do |
| use Smart.DataCase |
| use PhoenixTest.Case |
| |
| test "register, log out and in", %{conn: conn} do |
| conn |
| |> visit("/") |
| |
| |
| |> click_link("Register") |
| |> fill_in("Email", with: "f@ftes.de") |
| |> fill_in("Password", with: "password1234") |
| |> submit() |
| |> assert_has("#flash-info", text: "User created") |
| |> open_browser() |
| |
| |
| |
| defmodule UserRegistrationTest do |
| use Smart.DataCase |
| use PhoenixTest.Case |
| |
| test "register, log out and in", %{conn: conn} do |
| conn |
| |> visit("/") |
| |
| |
| |> click_link("Register") |
| |> fill_in("Email", with: "f@ftes.de") |
| |> fill_in("Password", with: "password1234") |
| |> submit() |
| |> assert_has("#flash-info", text: "User created") |
| |> open_browser() |
| |
| |
| |
| defmodule UserRegistrationTest do |
| use Smart.DataCase |
| use PhoenixTest.Case |
| |
| test "register, log out and in", %{conn: conn} do |
| conn |
| |> visit("/") |
| |
| |
| |> click_link("Register") |
| |> fill_in("Email", with: "f@ftes.de") |
| |> fill_in("Password", with: "password1234") |
| |> submit() |
| |> assert_has("#flash-info", text: "User created") |
| |> open_browser() |
| |
| |
| |
| mix phx.gen.auth Accounts User users |
| |
| Do you want to create a LiveView based |
| authentication system? [Yn] |
| |
| y |
| |
| mix test test/features/user_registration_test.exs |
| mix phx.gen.auth Accounts User users |
| |
| Do you want to create a LiveView based |
| authentication system? [Yn] |
| |
| y |
| |
| mix test test/features/user_registration_test.exs |
| mix phx.gen.auth Accounts User users |
| |
| Do you want to create a LiveView based |
| authentication system? [Yn] |
| |
| y |
| |
| mix test test/features/user_registration_test.exs |
| mix phx.gen.auth Accounts User users |
| |
| Do you want to create a LiveView based |
| authentication system? [Yn] |
| |
| y |
| |
| mix test test/features/user_registration_test.exs |
| Could not find any elements with selector "#flash-info" |
| and text "User created" |
| |
| Found these elements matching the selector "#flash-info": |
| |
| <div id="flash-info"> |
| Success! |
| Account created successfully! |
| </div> |
| Could not find any elements with selector "#flash-info" |
| and text "User created" |
| |
| Found these elements matching the selector "#flash-info": |
| |
| <div id="flash-info"> |
| Success! |
| Account created successfully! |
| </div> |
| Could not find any elements with selector "#flash-info" |
| and text "User created" |
| |
| Found these elements matching the selector "#flash-info": |
| |
| <div id="flash-info"> |
| Success! |
| Account created successfully! |
| </div> |
| defmodule UserRegistrationTest do |
| use Smart.DataCase |
| use PhoenixTest.Case |
| |
| test "register, log out and in", %{conn: conn} do |
| conn |
| |> visit("/") |
| |
| |
| |> click_link("Register") |
| |> fill_in("Email", with: "f@ftes.de") |
| |> fill_in("Password", with: "password1234") |
| |> submit() |
| |
| |> assert_has("#flash-info", text: "Account created") |
| |
| |
| |
| defmodule UserRegistrationTest do |
| use Smart.DataCase |
| use PhoenixTest.Case |
| |
| test "register, log out and in", %{conn: conn} do |
| conn |
| |> visit("/") |
| |
| |
| |> click_link("Register") |
| |> fill_in("Email", with: "f@ftes.de") |
| |> fill_in("Password", with: "password1234") |
| |> submit() |
| |
| |> assert_has("#flash-info", text: "Account created") |
| |
| |
| |
| defmodule UserRegistrationTest do |
| use Smart.DataCase |
| use PhoenixTest.Case |
| |
| test "register, log out and in", %{conn: conn} do |
| conn |
| |> visit("/") |
| |
| |
| |> click_link("Register") |
| |> fill_in("Email", with: "f@ftes.de") |
| |> fill_in("Password", with: "password1234") |
| |> submit() |
| |
| |> assert_has("#flash-info", text: "Account created") |
| |
| |
| |
| conn |
| |> visit("/") |
| |> click_link("Register") |
| |
| |> fill_in("Password", with: "short") |
| |> assert_has(".text-rose-600", text: "at least 12") |
| conn |
| |> visit("/") |
| |> click_link("Register") |
| |
| |> fill_in("Password", with: "short") |
| |> assert_has(".text-rose-600", text: "at least 12") |
| <.simple_form |
| for={@form} |
| id="registration_form" |
| phx-submit="save" |
| phx-change={false # "validate"} |
| phx-trigger-action={@trigger_submit} |
| action={~p"/users/log_in?_action=registered"} |
| method="post" |
| > |
| <.input field={@form[:email]} type="email" label="Email" required /> |
| <.input phx-hook="Password" field={@form[:password]} type="password" label="Password" required /> |
| <p id="pwd-error" class="hidden mt-3 flex gap-3 text-sm leading-6 text-rose-600 phx-no-feedback:hidden"><span class="hero-exclamation-circle-mini mt-0.5 h-5 w-5 flex-none"></span> |
| should be at least 12 character(s) |
| </p> |
| <.simple_form |
| for={@form} |
| id="registration_form" |
| phx-submit="save" |
| phx-change={false # "validate"} |
| phx-trigger-action={@trigger_submit} |
| action={~p"/users/log_in?_action=registered"} |
| method="post" |
| > |
| <.input field={@form[:email]} type="email" label="Email" required /> |
| <.input phx-hook="Password" field={@form[:password]} type="password" label="Password" required /> |
| <p id="pwd-error" class="hidden mt-3 flex gap-3 text-sm leading-6 text-rose-600 phx-no-feedback:hidden"><span class="hero-exclamation-circle-mini mt-0.5 h-5 w-5 flex-none"></span> |
| should be at least 12 character(s) |
| </p> |
| <.simple_form |
| for={@form} |
| id="registration_form" |
| phx-submit="save" |
| phx-change={false # "validate"} |
| phx-trigger-action={@trigger_submit} |
| action={~p"/users/log_in?_action=registered"} |
| method="post" |
| > |
| <.input field={@form[:email]} type="email" label="Email" required /> |
| <.input phx-hook="Password" field={@form[:password]} type="password" label="Password" required /> |
| <p id="pwd-error" class="hidden mt-3 flex gap-3 text-sm leading-6 text-rose-600 phx-no-feedback:hidden"><span class="hero-exclamation-circle-mini mt-0.5 h-5 w-5 flex-none"></span> |
| should be at least 12 character(s) |
| </p> |
mix phx.server
| mix test test/features/user_registration_test.exs |
| 1 test, 0 failures |
| mix test test/features/user_registration_test.exs |
| 1 test, 0 failures |
| conn |
| |> visit("/") |
| |> click_link("Register") |
| |> fill_in("Email", with: "f@ftes.de") |
| |
| |> fill_in("Password", with: "short") |
| |
| |> assert_has(".text-rose-600:not(.hidden)", text: "at least 12") |
| mix test test/features/user_registration_test.exs |
| 1 test, 1 failure |
| defmodule UserRegistrationTest do |
| use Smart.DataCase |
| use PhoenixTest.Case, |
| playwright: :chromium, |
| headless: false, |
| slow_mo: 900 |
| |
| @tag :playwright |
| test "register, log out and in", %{conn: conn} do |
| conn |
| |> visit("/") |
| |> fill_in("Password", with: "short") |
| |> assert_has(".text-rose-600:not(.hidden)", text: "at least 12") |
| defmodule UserRegistrationTest do |
| use Smart.DataCase |
| use PhoenixTest.Case, |
| playwright: :chromium, |
| headless: false, |
| slow_mo: 900 |
| |
| @tag :playwright |
| test "register, log out and in", %{conn: conn} do |
| conn |
| |> visit("/") |
| |> fill_in("Password", with: "short") |
| |> assert_has(".text-rose-600:not(.hidden)", text: "at least 12") |
| mix test test/features/user_registration_test.exs |
| 1 test, 0 failures |
๐ Summary
- feature tests = phoenix_test
- Don't lose it - reuse it ๐ถ
- โ
static + live view (vanilla)
- ๐ง playwright (JS)
- ๐ง advanced assertions (locators, input values)
Fredrik Teschke
Freelance Dev
f@ftes.de
@ftes
co-founder of
BogenGaudi
Arrow Tag rental by ๐ฆ
teamengine.co.uk
Digital contracting & time-recording
for the Film & TV industry
๐ญ Other stuff ๐ฌ
- graphite
- mix_unused
- a11y_audit_elixir
- test_also_with