Save 37% off PRO during our Black Friday Sale! »

Concurrent Feature tests with Wallaby

Concurrent Feature tests with Wallaby

06f8b41980eb4c577fa40c41d5030c19?s=128

Chris Keathley

September 05, 2016
Tweet

Transcript

  1. Concurrent Feature Tests with Wallaby Chris Keathley / @ChrisKeathley /

    keathley@carbonfive.com
  2. Feature tests: Driving tests from the UI

  3. Name email Password Sign Up

  4. Name email Password Sign Up Fill in

  5. Name email Password Sign Up Fill in Click

  6. Thanks for signing up! Did this show up?

  7. Feature Tests

  8. Feature Tests Represent real users

  9. Feature Tests Represent real users Provide Confidence in the System

  10. Feature Tests Represent real users Provide Confidence in the System

    Dark Side
  11. Feature Tests Represent real users Provide Confidence in the System

  12. Feature Tests Represent real users Provide Confidence in the System

    (Sometimes)
  13. Feature Tests Represent real users Provide Confidence in the System

    (Sometimes) (When they aren’t randomly failing)
  14. Happens

  15. Feature Tests ARE Slow

  16. Wallaby

  17. Wallaby Manages multiple browsers Concurrent Assumes async Interfaces

  18. Wallaby TL;DR

  19. Name email Password Sign Up

  20. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do end end
  21. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session end end
  22. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") end end
  23. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") end end
  24. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") end end
  25. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") end end
  26. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text end end
  27. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end
  28. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end
  29. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end
  30. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end
  31. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end Sessions Queries actions
  32. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end Sessions Queries actions
  33. Wallaby Phoenix EcTo Test Sessions

  34. Wallaby Phoenix EcTo Test Sessions Browser

  35. Wallaby Phoenix EcTo Test Sessions Browser Browser

  36. Wallaby Phoenix EcTo Test Sessions Browser

  37. setup tags do {:ok, session} = Wallaby.start_session([]) on_exit, fn ->

    Wallaby.end_session(session) end {:ok, session: session} end
  38. Browser Browser Phoenix Ecto

  39. Browser Browser Phoenix Ecto

  40. Browser Browser Phoenix Ecto

  41. Browser Phoenix Ecto

  42. Browser Phoenix Ecto Ownership Metadata

  43. Browser Phoenix Ecto Ownership Metadata Transaction Ownership

  44. setup tags do {:ok, session} = Wallaby.start_session([]) on_exit, fn ->

    Wallaby.end_session(session) end {:ok, session: session} end
  45. setup tags do :ok = Ecto.Adapters.SQL.Sandbox.checkout(YourApp.Repo) metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for( YourApp.Repo,

    self()) {:ok, session} = Wallaby.start_session(metadata: metadata) on_exit, fn -> Wallaby.end_session(session) end {:ok, session: session} end
  46. setup tags do :ok = Ecto.Adapters.SQL.Sandbox.checkout(YourApp.Repo) metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for( YourApp.Repo,

    self()) {:ok, session} = Wallaby.start_session(metadata: metadata) on_exit, fn -> Wallaby.end_session(session) end {:ok, session: session} end
  47. setup tags do :ok = Ecto.Adapters.SQL.Sandbox.checkout(YourApp.Repo) metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for( YourApp.Repo,

    self()) {:ok, session} = Wallaby.start_session(metadata: metadata) on_exit, fn -> Wallaby.end_session(session) end {:ok, session: session} end
  48. setup tags do :ok = Ecto.Adapters.SQL.Sandbox.checkout(YourApp.Repo) metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for( YourApp.Repo,

    self()) {:ok, session} = Wallaby.start_session(metadata: metadata) on_exit, fn -> Wallaby.end_session(session) end {:ok, session: session} end
  49. defmodule YourApp.Endpoint do use Phoenix.Endpoint, otp_app: :your_app if Application.get_env(:your_app, :sql_sandbox)

    do plug Phoenix.Ecto.SQL.Sandbox end plug YourApp.Router end
  50. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end Sessions Queries actions
  51. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end Sessions Queries actions
  52. Thanks for signing up!

  53. Thanks for signing up! <div class="alert"> <span class=“msg"> Thanks for

    signing up! </span> </div>
  54. Thanks for signing up! <div class="alert"> <span class=“msg"> Thanks for

    signing up! </span> </div> session |> find(“.alert")
  55. Thanks for signing up! <div class="alert"> <span class=“msg"> Thanks for

    signing up! </span> </div> session |> find(“.alert")
  56. <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span

    class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div> HTML
  57. <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span

    class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div> HTML Test - User Names session
  58. <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span

    class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div> HTML Test - User Names session |> find(".user")
  59. <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span

    class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div> HTML Test - User Names session |> find(".user") Ambiguous
  60. <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span

    class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div> HTML Test - User Names session |> find(".user", count: 2)
  61. <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span

    class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div> HTML Test - User Names session |> find(".user", count: 2) |> Enum.map(& find(&1, ".name") )
  62. <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span

    class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div> HTML Test - User Names session |> find(".user", count: 2) |> Enum.map(& find(&1, ".name") ) |> Enum.map(& text(&1) ) # => ["Grace Hopper", "Alan Turing"]
  63. session HTML Test - Grace’s Email <div class="users"> <div class="user">

    <span class=“name"> Grace Hopper </span> <span class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div>
  64. session |> find(".user", text: "Grace Hopper") HTML Test - Grace’s

    Email <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div>
  65. session |> find(".user", text: "Grace Hopper") |> find(".email") HTML Test

    - Grace’s Email <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div>
  66. session |> find(".user", text: "Grace Hopper") |> find(".email") |> text

    # => "grace@hopper.com" HTML Test - Grace’s Email <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span class=“email"> grace@hopper.com </span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email"> alan@turing.com </span> </div> </div>
  67. find(".user") ? Browser

  68. find(".user") JS Browser

  69. find(".user") JS Browser

  70. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end Sessions Queries actions
  71. defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can

    register", %{session: session} do session |> visit("/users/new") |> find(".registration_form") |> fill_in("Full Name", with: "Grace Hopper") |> fill_in("Email", with: "grace@hoppper.com") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end Sessions Queries actions
  72. Name Sign Up

  73. <form> <label for=“user_name"> Name </label> <input type="text" name=“user_name"> <button type=“submit">

    Register </button> </form> HTML
  74. HTML <form> <label for=“user_name"> Name </label> <input type="text" name=“user_name"> <button

    type=“submit"> Register </button> </form>
  75. <form> <label for=“user_name"> Name </label> <input type="text" name=“user_name"> <button type=“submit">

    Register </button> </form> HTML Test session
  76. <form> <label for=“user_name"> Name </label> <input type="text" name=“user_name"> <button type=“submit">

    Register </button> </form> HTML Test session |> fill_in("Name", with: "Grace Hopper")
  77. <form> <label for=“user_name"> Name </label> <input type="text" name=“user_name"> <button type=“submit">

    Register </button> </form> HTML Test session |> fill_in("Name", with: "Grace Hopper") |> click_on("Register")
  78. HTML Test session |> fill_in(“user_name", with: "Grace Hopper") |> click_on("Register")

    <form> <label for=“user_name"> Name </label> <input type="text" name=“user_name"> <button type=“submit"> Register </button> </form>
  79. HTML Test session |> fill_in(“user_name", with: "Grace Hopper") |> click_on("Register")

    <form> <label for="user_name"> Name </label> <input type="text" name=“user_name"> <label> <input type="checkbox" value="true" name="save_login"> Save login </label> <button type="submit"> Register </button> </form>
  80. HTML Test session |> fill_in(“user_name", with: "Grace Hopper”) |> check("Save

    login") |> click_on("Register") <form> <label for="user_name"> Name </label> <input type="text" name=“user_name"> <label> <input type="checkbox" value="true" name="save_login"> Save login </label> <button type="submit"> Register </button> </form>
  81. fill_in(session, "First Name", with: "Chris") choose(session, "Radio Button 1") check(session,

    "Checkbox") uncheck(session, "Checkbox") select(session, "My Awesome Select", option: "Option 1") click_on(session, "Some Button") attach_file(session, "File", path: "path/to/file") Other Actions
  82. Wallaby Sessions Queries Interactions

  83. Still More work to do!

  84. https://github.com/keathley/wallaby https://speakerdeck.com/keathley Getting Started

  85. None
  86. None
  87. Lets Build awesome stuff Together!

  88. THANKS Chris Keathley / @ChrisKeathley / keathley@carbonfive.com