PhoenixTest.Playwright (PhoenixTestPlaywright v0.13.0)

Copy Markdown View Source

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

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.

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

css_selector()

@type css_selector() :: String.t()

playwright_selector()

@type playwright_selector() :: String.t()

selector()

@type selector() :: playwright_selector() | css_selector()

t()

@opaque t()

Functions

add_cookies(conn, cookies)

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

keytypedescription
:namebinary()
:valuebinary()
:urlbinary()(optional) either url or domain / path are required
:domainbinary()(optional) either url or domain / path are required
:pathbinary()(optional) either url or domain / path are required
:max_agefloat()(optional) The cookie max age, in seconds.
:http_onlyboolean()(optional)
:secureboolean()(optional)
:encryptboolean()(optional)
:signboolean()(optional)
:same_sitebinary()(optional) one of "Strict", "Lax", "None"

Two of the cookie fields mean nothing to Playwright. These are:

  1. :encrypt
  2. :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_session_cookie(conn, cookie, session_options)

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()
)

clear_cookies(conn, opts \\ [])

@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.

click(conn, selector)

@spec click(t(), selector()) :: t()

See click/4.

click(conn, selector, text, opts \\ [])

@spec click(t(), selector(), String.t(), exact: boolean(), timeout: non_neg_integer()) ::
  t()

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 is false.

  • :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)

click_button(conn, selector \\ nil, text, opts \\ [])

Like PhoenixTest.click_button/3, but allows exact text match.

Options

  • :exact (boolean/0) - Exact or substring text match. The default value is false.

  • :timeout (non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.

click_link(conn, selector \\ nil, text, opts \\ [])

Like PhoenixTest.click_link/3, but allows exact text match.

Options

  • :exact (boolean/0) - Exact or substring text match. The default value is false.

  • :timeout (non_neg_integer/0) - Maximum wait time in milliseconds. Defaults to the configured timeout.

drag(conn, source_selector, opts)

@spec drag(t(), selector(),
  to: selector(),
  playwright: keyword(),
  timeout: non_neg_integer()
) :: t()

Drag and drop a source element to a target element.

Options

Examples

|> drag("#source", to: "#target")
|> drag(Selector.text("Draggable"), to: Selector.text("Target"))

evaluate(conn, expression)

@spec evaluate(t(), String.t()) :: t()

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")

evaluate(conn, expression, fun)

@spec evaluate(t(), String.t(), [{:timeout, non_neg_integer()}] | (any() -> any())) ::
  t()

evaluate(conn, expression, opts, fun)

@spec evaluate(t(), String.t(), [{:timeout, non_neg_integer()}], (any() -> any())) ::
  t()

press(conn, selector, key, opts \\ [])

@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 is 0.

  • :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")

screenshot(conn, file_path, opts \\ [])

@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 is true.

  • :omit_background (boolean/0) - Only applicable to .png images. The default value is false.

  • :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")

step(conn, title, fun)

(macro)

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

type(conn, selector, text, opts \\ [])

@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 is 0.

  • :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")

unwrap(conn, fun)

@spec unwrap(t(), (%{context_id: any(), page_id: any(), frame_id: any()} -> any())) ::
  t()

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)

visit(conn, path, opts)

@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.

with_dialog(session, callback, fun)

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