PhoenixTest.Playwright (PhoenixTestPlaywright v0.15.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

Visual regression assertion: compares a screenshot of the current page against a stored baseline.

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

assert_download(conn, filename_or_fun)

assert_screenshot(conn, name, opts \\ [])

@spec assert_screenshot(t(), String.t(),
  full_page: boolean(),
  omit_background: boolean(),
  animations: term(),
  caret: term(),
  clip: map(),
  selector: binary(),
  max_diff_pixels: non_neg_integer(),
  max_diff_pixel_ratio: float(),
  threshold: float(),
  scale: term(),
  mask: [binary()],
  mask_color: binary(),
  snapshot_dir: binary(),
  timeout: non_neg_integer()
) :: t()

Visual regression assertion: compares a screenshot of the current page against a stored baseline.

name is a relative path within :snapshot_dir (see PhoenixTest.Playwright.Config), including the file extension — for example "home.png" or "auth/login.png". Subdirectories are created automatically.

On first run (no baseline exists), the screenshot is saved as the new baseline and the test passes. On subsequent runs, the live screenshot is compared pixel-by-pixel against the baseline. On mismatch, a diff image is saved to <snapshot_dir>/__diff__/<name> and the test fails.

To update a baseline, delete the snapshot file and re-run the test.

Options

  • :full_page (boolean/0) - When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Defaults to false.

  • :omit_background (boolean/0) - Hides default white background and allows capturing screenshots with transparency. Not applicable to jpeg images. Defaults to false.

  • :animations - When set to "disabled", stops CSS animations, CSS transitions and Web Animations. Animations get different treatment depending on their duration: finite animations are fast-forwarded to completion, so they'll fire transitionend event. Infinite animations are canceled to initial state, and then played over after the screenshot. Defaults to "disabled".

  • :caret - When set to "hide", screenshot will hide text caret. When set to "initial", text caret behavior will not be changed. Defaults to "hide".

  • :clip (map/0) - An object which specifies clipping of the resulting image. %{x: float, y: float, width: float, height: float}.

  • :selector (String.t/0) - Scope the screenshot to a CSS or Playwright selector instead of the full page.

  • :max_diff_pixels (non_neg_integer/0) - An acceptable amount of pixels that could be different. Unset by default.

  • :max_diff_pixel_ratio (float/0) - An acceptable ratio of pixels that are different to the total amount of pixels, between 0 and 1. Unset by default.

  • :threshold (float/0) - An acceptable perceived color difference in the YIQ color space between the same pixel in compared images, between zero (strict) and one (lax). Defaults to 0.2.

  • :scale - When set to "css", screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will keep screenshots small. Using "device" option will produce a single pixel per each device pixel. Defaults to "css".

  • :mask (list of String.t/0) - Specify selectors that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box #FF00FF (customized by mask_color) that completely covers its bounding box.

  • :mask_color (String.t/0) - Specify the color of the overlay box for masked elements, in CSS color format. Default color is pink #FF00FF.

  • :snapshot_dir (String.t/0) - Override the global :snapshot_dir config for this call.

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

Examples

|> assert_screenshot("home.png")
|> assert_screenshot("auth/login.png", full_page: false)
|> assert_screenshot("hero.png", selector: ".hero-section")

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.

  • :is_function (boolean/0) - Whether the expression is a function. The default value is false.

  • :arg (term/0) - Optional argument to pass to the function. The default value is nil.

Examples

conn
|> evaluate("window.scrollTo(0, document.body.scrollHeight)")

conn
|> evaluate("selectors => selectors.forEach(s => document.querySelector(s).remove())", is_function: true, arg: ["h1"])

conn
|> evaluate("document.title", & assert &1 =~ "Dashboard")

evaluate(conn, expression, fun)

@spec evaluate(
  t(),
  String.t(),
  [timeout: non_neg_integer(), is_function: boolean(), arg: term()]
  | (any() -> any())
) :: t()

evaluate(conn, expression, opts, fun)

@spec evaluate(
  t(),
  String.t(),
  [timeout: non_neg_integer(), is_function: boolean(), arg: term()],
  (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(),
  referer: binary(),
  wait_until: term()
) :: 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.

  • :referer (String.t/0) - Referer header value. If provided, takes preference over the referer header set via the extra_http_headers option to PlaywrightEx.Browser.new_context/2.

  • :wait_until - When to consider the operation succeeded. One of:

    • "load" (default) — fires when the load event is fired.
    • "domcontentloaded" — fires when the DOMContentLoaded event is fired.
    • "networkidle"discouraged — fires when there are no network connections for at least 500 ms. Rely on web assertions instead for testing readiness.
    • "commit" — fires when the network response is received and the document has started loading. The default value is "load".

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