Playwright driver for PhoenixTest. Run feature tests in a real browser using the standard PhoenixTest API.
Use this when you need to test JavaScript behavior, browser-specific quirks, or anything requiring a real browser.
- Prefer the standard PhoenixTest API (
visit,click_link,click_button,fill_in,assert_has, etc.) - Only use Playwright-specific functions when the standard API doesn't cover your use case
- The
connin tests is NOT aPlug.Conn— it's a Playwright session. We use the nameconnso tests can easily switch between PhoenixTest drivers.
This assumes PhoenixTest and PhoenixTest.Playwright have been set up (dependencies, config, Ecto sandbox, test_helper.exs).
defmodule MyApp.Features.SomeTest do
# Ecto sandbox is managed automatically — do not set it up manually
use PhoenixTest.Playwright.Case, async: true
test "example", %{conn: conn} do
conn
|> visit(~p"/")
|> click_link("Sign in")
|> fill_in("Email", with: "user@example.com")
|> click_button("Submit")
|> assert_has(".success", text: "Welcome")
end
endThis library adds browser-specific functions (e.g. screenshot/2, evaluate/2, type/3, press/3, drag/3). See the docs for the full list.
For anything the library doesn't cover, use unwrap/2 to access PlaywrightEx modules (Frame, Selector, Page, BrowserContext) directly, or evaluate/2 for simple JavaScript (see Missing Playwright features):
# Subscribe to page-level events (e.g. downloads)
conn
|> unwrap(fn %{page_id: page_id} -> PlaywrightEx.subscribe(page_id) end)@tag trace: :open— record and open interactive trace viewer@tag screenshot: true— auto-capture screenshot on failureopen_browser/1— open current page in system browser
For username/password login, just visit the login page and fill in the credentials:
conn
|> visit(~p"/users/log_in")
|> fill_in("Email", with: "user@example.com")
|> fill_in("Password", with: "password123")
|> click_button("Sign in")For magic link / passwordless login, see the Emails section in the docs.
|> visit(~p"/")
|> assert_has("body .phx-connected")
# now continue, Playwright has waited for LiveView to connectFor LiveComponents, add data-connected={connected?(@socket)} and assert on that attribute instead.
See the docs.
This library runs in the test environment, so docs are not available via Tidewave or similar dev tools. Fetch and cache the hexdocs yourself: