Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Concurrent Feature tests with Wallaby
Search
Chris Keathley
September 05, 2016
Programming
1.1k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Concurrent Feature tests with Wallaby
Chris Keathley
September 05, 2016
More Decks by Chris Keathley
See All by Chris Keathley
Solid code isn't flexible
keathley
5
1.2k
Building Adaptive Systems
keathley
44
3.2k
Contracts for building reliable systems
keathley
6
1.1k
Kafka, the hard parts
keathley
3
2.1k
Building Resilient Elixir Systems
keathley
7
2.5k
Consistent, Distributed Elixir
keathley
6
1.7k
Telling stories with data visualization
keathley
1
740
Easing into continuous deployment
keathley
2
470
Leveling up your git skills
keathley
0
880
Other Decks in Programming
See All in Programming
夏だ!祭りだ!祭りとはドメインモデリングでは?
ryugen04
0
400
Cloudflare is Agents
chimame
0
180
S3 を使うアプリケーションをローカル完結で動かすことに全力を注いでみた / Running S3 Apps Offline
contour_gara
0
620
まだ間に合う!今年の夏こそSchemeのマクロ展開器を完全理解!
omasanori
0
500
Webエンジニアなのにブラウザの仕組みがわからないので、Pythonで自作してみた
tatsuki12
4
1.1k
【QA Test Talk Vol.8】AI-DLC による Whole Team Approach の加速
pkshadeck
PRO
0
240
仕様書を書く前にハーネスを作る - Agent Native開発は「探索を速く、判定を固く」
gotalab555
4
1.8k
Flow は今どうなっているか
mizdra
PRO
0
720
FastAPI の並行処理モデルを完全に理解する
hoto17296
8
3k
Claude Code全社展開のためにやったことn選~プラグイン302個・コミッター271人を支えるために~
kenchan
5
1.8k
PyConJP2026_wat_Python × Signal Processing: How to Draw Pictures with Sound Using Spectrogram Art
wat
0
420
使いながら育てる Claude Code — 開発フローの1コマンド化 × 繰り返し指摘の自動仕組み化
shiki_kakaku
1
2k
Featured
See All Featured
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
430
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.4k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.3k
Tell your own story through comics
letsgokoyo
1
1k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.8k
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
900
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
490
How to Think Like a Performance Engineer
csswizardry
28
2.7k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
390
Transcript
Concurrent Feature Tests with Wallaby Chris Keathley / @ChrisKeathley /
[email protected]
Feature tests: Driving tests from the UI
Name email Password Sign Up
Name email Password Sign Up Fill in
Name email Password Sign Up Fill in Click
Thanks for signing up! Did this show up?
Feature Tests
Feature Tests Represent real users
Feature Tests Represent real users Provide Confidence in the System
Feature Tests Represent real users Provide Confidence in the System
Dark Side
Feature Tests Represent real users Provide Confidence in the System
Feature Tests Represent real users Provide Confidence in the System
(Sometimes)
Feature Tests Represent real users Provide Confidence in the System
(Sometimes) (When they aren’t randomly failing)
Happens
Feature Tests ARE Slow
Wallaby
Wallaby Manages multiple browsers Concurrent Assumes async Interfaces
Wallaby TL;DR
Name email Password Sign Up
defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can
register", %{session: session} do end end
defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can
register", %{session: session} do session end end
defmodule YourApp.UserRegistrationTest do use YourApp.AcceptanceCase, async: true test "users can
register", %{session: session} do session |> visit("/users/new") end end
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
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: "
[email protected]
") |> fill_in("Password", with: "password") end end
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: "
[email protected]
") |> fill_in("Password", with: "password") |> click("Register") end end
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: "
[email protected]
") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text end end
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: "
[email protected]
") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end
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: "
[email protected]
") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end
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: "
[email protected]
") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end
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: "
[email protected]
") |> fill_in("Password", with: "password") |> click("Register") msg_text = session |> find(".flash-message") |> text assert msg_text == "Welcome Grace Hopper" end end
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: "
[email protected]
") |> 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
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: "
[email protected]
") |> 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
Wallaby Phoenix EcTo Test Sessions
Wallaby Phoenix EcTo Test Sessions Browser
Wallaby Phoenix EcTo Test Sessions Browser Browser
Wallaby Phoenix EcTo Test Sessions Browser
setup tags do {:ok, session} = Wallaby.start_session([]) on_exit, fn ->
Wallaby.end_session(session) end {:ok, session: session} end
Browser Browser Phoenix Ecto
Browser Browser Phoenix Ecto
Browser Browser Phoenix Ecto
Browser Phoenix Ecto
Browser Phoenix Ecto Ownership Metadata
Browser Phoenix Ecto Ownership Metadata Transaction Ownership
setup tags do {:ok, session} = Wallaby.start_session([]) on_exit, fn ->
Wallaby.end_session(session) end {:ok, session: session} end
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
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
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
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
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
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: "
[email protected]
") |> 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
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: "
[email protected]
") |> 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
Thanks for signing up!
Thanks for signing up! <div class="alert"> <span class=“msg"> Thanks for
signing up! </span> </div>
Thanks for signing up! <div class="alert"> <span class=“msg"> Thanks for
signing up! </span> </div> session |> find(“.alert")
Thanks for signing up! <div class="alert"> <span class=“msg"> Thanks for
signing up! </span> </div> session |> find(“.alert")
<div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span
class=“email">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div> HTML
<div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span
class=“email">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div> HTML Test - User Names session
<div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span
class=“email">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div> HTML Test - User Names session |> find(".user")
<div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span
class=“email">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div> HTML Test - User Names session |> find(".user") Ambiguous
<div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span
class=“email">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div> HTML Test - User Names session |> find(".user", count: 2)
<div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span
class=“email">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div> HTML Test - User Names session |> find(".user", count: 2) |> Enum.map(& find(&1, ".name") )
<div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span
class=“email">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</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"]
session HTML Test - Grace’s Email <div class="users"> <div class="user">
<span class=“name"> Grace Hopper </span> <span class=“email">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div>
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">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div>
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">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div>
session |> find(".user", text: "Grace Hopper") |> find(".email") |> text
# => "
[email protected]
" HTML Test - Grace’s Email <div class="users"> <div class="user"> <span class=“name"> Grace Hopper </span> <span class=“email">
[email protected]
</span> </div> <div class="user"> <span class=“name"> Alan Turing </span> <span class=“email">
[email protected]
</span> </div> </div>
find(".user") ? Browser
find(".user") JS Browser
find(".user") JS Browser
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: "
[email protected]
") |> 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
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: "
[email protected]
") |> 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
Name Sign Up
<form> <label for=“user_name"> Name </label> <input type="text" name=“user_name"> <button type=“submit">
Register </button> </form> HTML
HTML <form> <label for=“user_name"> Name </label> <input type="text" name=“user_name"> <button
type=“submit"> Register </button> </form>
<form> <label for=“user_name"> Name </label> <input type="text" name=“user_name"> <button type=“submit">
Register </button> </form> HTML Test session
<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")
<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")
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>
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>
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>
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
Wallaby Sessions Queries Interactions
Still More work to do!
https://github.com/keathley/wallaby https://speakerdeck.com/keathley Getting Started
None
None
Lets Build awesome stuff Together!
THANKS Chris Keathley / @ChrisKeathley /
[email protected]