Playwright driver for PhoenixTest.
This module implements the PhoenixTest driver protocol, running tests in a real browser
via Playwright. The conn in tests is not a Plug.Conn but a %PhoenixTest.Playwright{}
struct holding the Playwright session state (page, frame, browser context, etc.).
It also provides browser-specific functions beyond the standard PhoenixTest API,
such as screenshot/3, evaluate/2, type/3, press/3, and drag/3.
See the README for getting started, configuration, and troubleshooting.
Missing Playwright features
See the README.
Summary
Functions
Add cookies to the browser context, using Plug.Conn.put_resp_cookie/3
Add a Plug.Session cookie to the browser context.
Removes all cookies from the context.
Click an element that is not a link or button.
Otherwise, use click_link/4 and click_button/4.
Like PhoenixTest.click_button/3, but allows exact text match.
Like PhoenixTest.click_link/3, but allows exact text match.
Drag and drop a source element to a target element.
Evaluates a JavaScript expression in the page context.
Focuses the matching element and presses a combination of the keyboard keys.
Takes a screenshot of the current page and saves it to the given file path.
Label a step in the Playwright trace.
Focuses the matching element and simulates user typing.
Like PhoenixTest.visit/2, but with a custom timeout.
Handle browser dialogs (alert(), confirm(), prompt()) while executing the inner function.
Types
@type css_selector() :: String.t()
@type playwright_selector() :: String.t()
@type selector() :: playwright_selector() | css_selector()
@opaque t()
Functions
Add cookies to the browser context, using Plug.Conn.put_resp_cookie/3
Note that for signed cookies the signing salt is not configurable.
As such, this function is not appropriate for signed Plug.Session cookies.
For signed session cookies, use add_session_cookie/3
A cookie's value must be a binary unless the cookie is signed/encrypted
Cookie fields
| key | type | description |
|---|---|---|
:name | binary() | |
:value | binary() | |
:url | binary() | (optional) either url or domain / path are required |
:domain | binary() | (optional) either url or domain / path are required |
:path | binary() | (optional) either url or domain / path are required |
:max_age | float() | (optional) The cookie max age, in seconds. |
:http_only | boolean() | (optional) |
:secure | boolean() | (optional) |
:encrypt | boolean() | (optional) |
:sign | boolean() | (optional) |
:same_site | binary() | (optional) one of "Strict", "Lax", "None" |
Two of the cookie fields mean nothing to Playwright. These are:
:encrypt:sign
The :max_age cookie field means the same thing as documented in Plug.Conn.put_resp_cookie/4.
The :max_age value is used to infer the correct expires value that Playwright requires.
See https://playwright.dev/docs/api/class-browsercontext#browser-context-add-cookies
Add a Plug.Session cookie to the browser context.
This is useful for emulating a logged-in user.
Note that that the cookie :value must be a map, since we are using
Plug.Conn.put_session/3 to write each of value's key-value pairs
to the cookie.
The session_options are exactly the same as the opts used when
writing plug Plug.Session in your router/endpoint module.
Examples
|> add_session_cookie(
[value: %{user_token: Accounts.generate_user_session_token(user)}],
MyAppWeb.Endpoint.session_options()
)
@spec clear_cookies(t(), [{:timeout, non_neg_integer()}]) :: t()
Removes all cookies from the context.
Options
:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
See click/4.
Click an element that is not a link or button.
Otherwise, use click_link/4 and click_button/4.
Options
:exact(boolean/0) - Exact or substring text match. The default value isfalse.:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
Examples
|> click(Selector.menuitem("Edit"))
|> click("summary", "(expand)", exact: false)
Like PhoenixTest.click_button/3, but allows exact text match.
Options
:exact(boolean/0) - Exact or substring text match. The default value isfalse.:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
Like PhoenixTest.click_link/3, but allows exact text match.
Options
:exact(boolean/0) - Exact or substring text match. The default value isfalse.:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
@spec drag(t(), selector(), to: selector(), playwright: keyword(), timeout: non_neg_integer() ) :: t()
Drag and drop a source element to a target element.
Options
:to(selector/0) - Required. The target selector.:playwright(keyword/0) - Additional options passed to frame.dragAndDrop. The default value is[].:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
Examples
|> drag("#source", to: "#target")
|> drag(Selector.text("Draggable"), to: Selector.text("Target"))
Evaluates a JavaScript expression in the page context.
When a callback function is given, it receives the JavaScript result. This is useful for assertions or side effects without breaking the pipe chain.
Options
:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
Examples
conn
|> evaluate("window.scrollTo(0, document.body.scrollHeight)")
conn
|> evaluate("document.title", & assert &1 =~ "Dashboard")
@spec press(t(), selector(), String.t(), delay: non_neg_integer(), timeout: non_neg_integer() ) :: t()
Focuses the matching element and presses a combination of the keyboard keys.
Use type/4 if you don't need to press special keys.
Examples of supported keys:
F1 - F12, Digit0- Digit9, KeyA- KeyZ, Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight, ArrowUp
Modifiers are also supported:
Shift, Control, Alt, Meta, ShiftLeft, ControlOrMeta
Combinations are also supported:
Control+o, Control++, Control+Shift+T
Options
:delay(non_neg_integer/0) - Time to wait between keydown and keyup in milliseconds. The default value is0.:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
Examples
|> press("#id", "Control+Shift+T")
|> press(Selector.button("Submit"), "Enter")
@spec screenshot(t(), String.t(), full_page: boolean(), omit_background: boolean(), timeout: non_neg_integer() ) :: t()
Takes a screenshot of the current page and saves it to the given file path.
The file type will be inferred from the file extension on the path you provide.
The file is saved in :screenshot_dir, see PhoenixTest.Playwright.Config.
Options
:full_page(boolean/0) - The default value istrue.:omit_background(boolean/0) - Only applicable to .png images. The default value isfalse.:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
Examples
|> screenshot("my-screenshot.png")
|> screenshot("my-test/my-screenshot.jpg")
Label a step in the Playwright trace.
This is useful for marking custom helper functions or complex multi-step operations so they appear as distinct steps in the trace viewer for easier debugging. Steps can be nested. Their source location is noted in the trace.
Examples
def complete_checkout(conn, user_email) do
conn
|> sign_in_as(user_email)
|> step("Check out", fn conn ->
conn
|> step("Fill shipping information", fn conn ->
conn
|> fill_in("Address", with: "123 Main St")
|> fill_in("City", with: "Portland")
|> click_button("Continue")
end)
|> step("Fill payment information", fn conn ->
conn
|> fill_in("Card number", with: "4242424242424242")
|> fill_in("CVV", with: "123")
|> click_button("Place order")
end)
end)
end
defp sign_in_as(conn, user_email) do
conn
|> step("Sign in as #{user_email}", fn conn ->
conn
|> fill_in("Email", with: user_email)
|> fill_in("Password", with: "password123")
|> click_button("Sign In")
end)
end
@spec type(t(), selector(), String.t(), delay: non_neg_integer(), timeout: non_neg_integer() ) :: t()
Focuses the matching element and simulates user typing.
In most cases, you should use PhoenixTest.fill_in/4 instead.
Options
:delay(non_neg_integer/0) - Time to wait between key presses in milliseconds. The default value is0.:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
Examples
|> type("#id", "some text")
|> type(Selector.role("heading", "Untitled"), "New title")
See PhoenixTest.unwrap/2.
Invokes fun with various Playwright IDs.
These can be used to interact with the Playwright
BrowserContext,
Page and
Frame.
Examples
|> unwrap(fn %{page_id: page_id} -> PlaywrightEx.subscribe(page_id) end)
@spec visit(t(), String.t(), [{:timeout, non_neg_integer()}]) :: t()
Like PhoenixTest.visit/2, but with a custom timeout.
Options
:timeout(non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.
Handle browser dialogs (alert(), confirm(), prompt()) while executing the inner function.
Note: Add @tag accept_dialogs: false before tests that call this function.
Otherwise, all dialogs are accepted by default.
Callback return values
The callback may return one of these values:
:accept-> accepts confirmation dialog{:accept, prompt_text}-> accepts prompt dialog with text:dismiss-> dismisses dialog- Any other value will ignore the dialog
Examples
@tag accept_dialogs: false
test "conditionally handle dialog", %{conn: conn} do
conn
|> visit("/")
|> with_dialog(
fn
%{message: "Are you sure?"} -> :accept
%{message: "Enter the magic number"} -> {:accept, "42"}
%{message: "Self destruct?"} -> :dismiss
end,
fn conn ->
conn
|> click_button("Delete")
|> assert_has(".flash", text: "Deleted")
end
end)
end