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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
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.1k
Building Adaptive Systems
keathley
44
3.1k
Contracts for building reliable systems
keathley
6
1.1k
Kafka, the hard parts
keathley
3
2k
Building Resilient Elixir Systems
keathley
7
2.5k
Consistent, Distributed Elixir
keathley
6
1.7k
Telling stories with data visualization
keathley
1
720
Easing into continuous deployment
keathley
2
450
Leveling up your git skills
keathley
0
860
Other Decks in Programming
See All in Programming
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
4.2k
Creating Composable Callables in Contemporary C++
rollbear
0
150
RTSPクライアントを自作してみた話
simotin13
0
610
net-httpのHTTP/2対応について
naruse
0
500
Make SRE Operations Easier with Azure SRE Agent
kkamegawa
0
6.8k
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
2.1k
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
180
Developing with AI Agents — Codex, Claude Code & Cowork Practical Guide
x5gtrn
PRO
0
1.3k
さぁV100、メモリをお食べ・・・
nilpe
0
140
セキュリティの専門家じゃなくてもできる。「セキュリティ意識」をアップデートして サプライチェーン攻撃への耐性を高めよう。
tk3fftk
5
880
C# and C++ Interoperability - cho-dotnetnew
harukasao
0
250
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
550
Featured
See All Featured
The SEO identity crisis: Don't let AI make you average
varn
0
490
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
850
Building an army of robots
kneath
306
46k
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
200
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
410
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.7k
The World Runs on Bad Software
bkeepers
PRO
72
12k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.5k
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]