Wallabidi.Browser (wallabidi v0.4.0-rc.8)

Copy Markdown View Source

The Browser module is the entrypoint for interacting with a real browser.

By default, action only work with elements that are visible to a real user.

Actions

Actions are used to interact with form elements. All actions work with the query interface:

<label for="first_name">
  First Name
</label>
<input id="user_first_name" type="text" name="first_name">
fill_in(page, Query.text_field("First Name"), with: "Grace")
fill_in(page, Query.text_field("first_name"), with: "Grace")
fill_in(page, Query.text_field("user_first_name"), with: "Grace")

These queries work with any of the available actions.

fill_in(page, Query.text_field("First Name"), with: "Chris")
clear(page, Query.text_field("user_email"))
click(page, Query.radio_button("Radio Button 1"))
click(page, Query.checkbox("Checkbox"))
click(page, Query.checkbox("Checkbox"))
click(page, Query.option("Option 1"))
click(page, Query.button("Some Button"))
attach_file(page, Query.file_field("Avatar"), path: "test/fixtures/avatar.jpg")

Actions return their parent element so that they can be chained together:

page
|> find(Query.css(".signup-form"), fn(form) ->
  form
  |> fill_in(Query.text_field("Name"), with: "Grace Hopper")
  |> fill_in(Query.text_field("Email"), with: "grace@hopper.com")
  |> click(Query.button("Submit"))
end)

Scoping

Finders provide scoping like so:

session
|> visit("/page.html")
|> find(Query.css(".users"))
|> find(Query.css(".user", count: 3))
|> List.first
|> find(Query.css(".user-name"))

If a callback is passed to find then the scoping will only apply to the callback and the parent will be passed to the next action in the chain:

page
|> find(Query.css(".todo-form"), fn(form) ->
  form
  |> fill_in(Query.text_field("What needs doing?"), with: "Write Wallabidi Documentation")
  |> click(Query.button("Save"))
end)
|> find(Query.css(".success-notification"), fn(notification) ->
  assert notification
  |> has_text?("Todo created successfully!")
end)

This allows you to create a test that is logically grouped together in a single pipeline. It also means that its easy to create re-usable helper functions without having to worry about chaining. You could re-write the above example like this:

def create_todo(page, todo) do
  find(Query.css(".todo-form"), & fill_in_and_save_todo(&1, todo))
end

def fill_in_and_save_todo(form, todo) do
  form
  |> fill_in(Query.text_field("What needs doing?"), with: todo)
  |> click(Query.button("Save"))
end

def todo_was_created?(page) do
  find Query.css(page, ".success-notification"), fn(notification) ->
    assert notification
    |> has_text?("Todo created successfully!")
  end
end

assert page
|> create_todo("Write Wallabidi Documentation")
|> todo_was_created?

Summary

Functions

Accepts one alert dialog, which must be triggered within the specified fun. Returns the message that was presented to the user. For example

Accepts one confirmation dialog, which must be triggered within the specified fun. Returns the message that was presented to the user. For example

Accepts one prompt, which must be triggered within the specified fun. The [with: value] option allows to simulate user input for the prompt. If no value is provided, the default value that was passed to window.prompt will be used instead. Returns the message that was presented to the user. For example

Finds all of the DOM elements that match the CSS selector. If no elements are found then an empty list is immediately returned. This is equivalent to calling find(session, css("element", count: nil, minimum: 0)).

Checks if query is present within parent and raises if not found.

Matches the Element's content with the provided text and raises if not found.

Attaches a file to a file input. Input elements are looked up by id, label text, or name.

Gets the value of the elements attribute.

Waits for the next LiveView DOM patch.

Clicks and holds the given mouse button at the current mouse coordinates.

Releases given previously held mouse button.

Clicks the mouse on the element returned by the query or at the current mouse cursor position.

Closes the current window.

Gets the current path of the session

Gets the current url of the session

Dismisses one confirmation dialog, which must be triggered within the specified fun. Returns the message that was presented to the user. For example

Dismisses one prompt, which must be triggered within the specified fun. Returns the message that was presented to the user. For example

Double-clicks left mouse button at the current mouse coordinates.

Executes JavaScript synchronously, taking as arguments the script to execute, an optional list of arguments available in the script via arguments, and an optional callback function with the result of script execution as a parameter.

Executes asynchronous JavaScript, taking as arguments the script to execute, an optional list of arguments available in the script via arguments, and an optional callback function with the result of script execution as a parameter.

Fills in an element identified by query with value.

Finds and returns one or more DOM element(s) on the page based on the given query.

Same as find/2, but takes a callback to enact side effects on the found element(s).

Changes the driver focus to the default (top level) frame.

Changes the driver focus to the frame found by query.

Changes the driver focus to the parent frame.

Focuses the window identified by the given handle.

Validates that the query returns a result. This can be used to define other types of matchers.

Searches for CSS on the page.

Searches for CSS that should not be on the page

Matches the parent's content with the provided text.

Matches the Element's value with the provided value.

Hovers over an element.

Maximizes the current window.

Moves mouse by an offset relative to current cursor position.

Sets the position of the current window.

Retrieves the source of the current page.

Gets the title for the current page

Checks if query is not present within parent and raises if it is found.

Sets the size of the current window.

Attempts to synchronize with the browser. This is most often used to execute queries repeatedly until it either exceeds the time limit or returns a success.

Checks if the element has been selected. Alias for checked?(element)

Sends a list of key strokes to active element. If strings are included then they are sent as individual keys. Special keys should be provided as a list of atoms, which are automatically converted into the corresponding key codes.

Sets the value of an element. The allowed type for the value depends on the type of the element. The value may be

Takes a screenshot of the current window. Screenshots are saved to a "screenshots" directory in the same directory the tests are run in.

Taps the element.

Gets the Element's text value.

Touches the screen at the given position.

Touches and holds the element on its top-left corner plus an optional offset.

Moves the touch pointer (finger, stylus etc.) on the screen to the point determined by the given coordinates.

Scroll on the screen from the given element by the given offset using touch events.

Stops touching the screen.

Checks if the element is visible on the page

Changes the current page to the provided route. Relative paths are appended to the provided base_url. Absolute paths do not use the base_url.

Gets the window handle of the current window.

Gets the window handles of all available windows.

Gets the position of the current window.

Gets the size of the current window.

Types

opts()

@type opts() :: Wallabidi.Query.opts()

parent()

@type parent() :: element() | session()

queryable()

@opaque queryable()

sync_result()

@type sync_result() :: {:ok, any()} | {:error, any()}

t()

@type t() :: any()

take_screenshot_opt()

@type take_screenshot_opt() :: {:name, String.t()} | {:log, boolean()}

Functions

accept_alert(session, fun)

Accepts one alert dialog, which must be triggered within the specified fun. Returns the message that was presented to the user. For example:

message = accept_alert session, fn(s) ->
  click(s, Query.link("Trigger alert"))
end

accept_confirm(session, fun)

Accepts one confirmation dialog, which must be triggered within the specified fun. Returns the message that was presented to the user. For example:

message = accept_confirm session, fn(s) ->
  click(s, Query.link("Trigger confirm"))
end

accept_prompt(session, fun)

Accepts one prompt, which must be triggered within the specified fun. The [with: value] option allows to simulate user input for the prompt. If no value is provided, the default value that was passed to window.prompt will be used instead. Returns the message that was presented to the user. For example:

message = accept_prompt session, fn(s) ->
  click(s, Query.link("Trigger prompt"))
end

Example providing user input:

message = accept_prompt session, [with: "User input"], fn(s) ->
  click(s, Query.link("Trigger prompt"))
end

accept_prompt(session, list, fun)

all(parent, query)

Finds all of the DOM elements that match the CSS selector. If no elements are found then an empty list is immediately returned. This is equivalent to calling find(session, css("element", count: nil, minimum: 0)).

assert_has(parent, query)

(macro)

Checks if query is present within parent and raises if not found.

Returns the given parent if the assertion is correct so that it is easily pipeable.

Examples

session
|> visit("/")
|> assert_has(Query.css(".login-button"))

assert_text(parent, text)

@spec assert_text(parent(), String.t()) :: parent()

assert_text(parent, query, text)

@spec assert_text(parent(), Wallabidi.Query.t(), String.t()) :: parent()

Matches the Element's content with the provided text and raises if not found.

Returns the given parent if the assertion is correct so that it is easily pipeable.

Examples

session
|> visit("/")
|> assert_text("Login")

Example providing query:

session
|> visit("/")
|> assert_text(Query.css(".login-button"), "Login")

attach_file(parent, query, list)

@spec attach_file(parent(), Wallabidi.Query.t(), [{:path, String.t()}]) :: parent()

Attaches a file to a file input. Input elements are looked up by id, label text, or name.

attr(parent, query, name)

@spec attr(parent(), Wallabidi.Query.t(), String.t()) :: String.t() | nil

Gets the value of the elements attribute.

await_patch(session, opts \\ [])

@spec await_patch(
  session(),
  keyword()
) :: session()

Waits for the next LiveView DOM patch.

Installed automatically via JavaScript — no app.js changes needed.

click/2 and fill_in/3 call this automatically. Use await_patch explicitly for patches triggered by something other than a direct interaction (e.g. PubSub broadcast, timer).

Options

  • :timeout — max wait in ms (default: 5_000)

Examples

# Wait for a PubSub-triggered update
Phoenix.PubSub.broadcast(MyApp.PubSub, "updates", :refresh)
session
|> await_patch()
|> assert_has(Query.css(".updated"))

button_down(parent, button \\ :left)

@spec button_down(parent(), atom()) :: parent()

Clicks and holds the given mouse button at the current mouse coordinates.

button_up(parent, button \\ :left)

@spec button_up(parent(), atom()) :: parent()

Releases given previously held mouse button.

clear(parent, query)

@spec clear(parent(), Wallabidi.Query.t()) :: parent()

clear(parent, query, opts)

@spec clear(parent(), Wallabidi.Query.t(), keyword()) :: parent()

click(parent, button)

@spec click(parent(), :left | :middle | :right) :: parent()
@spec click(parent(), Wallabidi.Query.t()) :: parent()

Clicks the mouse on the element returned by the query or at the current mouse cursor position.

Options

  • :await — controls the LiveView patch-await behaviour for clicks on phx-bound elements.
    • :auto (default) — wait for the resulting patch / page-ready signal before returning. This is what you want for ordinary tests.
    • :defer — fire the click, stash a pre-click pageId, and return immediately. Pair with Wallabidi.LiveView.await_patch/2 to consume the deferred wait. Use this when you need to assert on the optimistic-UI DOM between the click and the server reply. Outside a Wallabidi.LiveView.with_latency/3 block, the optimistic window is usually too short to observe reliably.

Non-LiveView clicks ignore :await — there's nothing to wait for.

click(parent, query, opts)

@spec click(parent(), Wallabidi.Query.t(), keyword()) :: parent()

close_window(session)

@spec close_window(session :: Wallabidi.Session.t()) :: Wallabidi.Session.t()

Closes the current window.

The window is either an instance of a browser tab or another operating system window.

Usage

feature "closing a window focuses the previously focused window", %{session: session} do
  original_handle =
    session
    |> visit("/home")
    |> window_handle()

  new_handle =
    session
    # click a link that takes you to a new tab
    |> click(Query.link("External Page"))
    |> close_window()
    |> window_handle()

  assert original_handle == new_handle
end

cookies(session)

current_path(session)

@spec current_path(parent()) :: String.t()

Gets the current path of the session

current_url(session)

@spec current_url(parent()) :: String.t()

Gets the current url of the session

dismiss_confirm(session, fun)

Dismisses one confirmation dialog, which must be triggered within the specified fun. Returns the message that was presented to the user. For example:

message = dismiss_confirm session, fn(s) ->
  click(s, Query.link("Trigger confirm"))
end

dismiss_prompt(session, fun)

Dismisses one prompt, which must be triggered within the specified fun. Returns the message that was presented to the user. For example:

message = dismiss_prompt session, fn(s) ->
  click(s, Query.link("Trigger prompt"))
end

double_click(parent)

@spec double_click(parent()) :: parent()

Double-clicks left mouse button at the current mouse coordinates.

execute_query(parent, query, opts \\ [])

execute_script(session, script)

@spec execute_script(parent(), String.t()) :: parent()

Executes JavaScript synchronously, taking as arguments the script to execute, an optional list of arguments available in the script via arguments, and an optional callback function with the result of script execution as a parameter.

execute_script(session, script, arguments)

@spec execute_script(parent(), String.t(), list()) :: parent()
@spec execute_script(parent(), String.t(), (binary() -> any())) :: parent()

execute_script(parent, script, arguments, callback)

@spec execute_script(parent(), String.t(), list(), (binary() -> any())) :: parent()

execute_script_async(session, script)

@spec execute_script_async(parent(), String.t()) :: parent()

Executes asynchronous JavaScript, taking as arguments the script to execute, an optional list of arguments available in the script via arguments, and an optional callback function with the result of script execution as a parameter.

execute_script_async(session, script, arguments)

@spec execute_script_async(parent(), String.t(), list()) :: parent()
@spec execute_script_async(parent(), String.t(), (binary() -> any())) :: parent()

execute_script_async(parent, script, arguments, callback)

@spec execute_script_async(parent(), String.t(), list(), (binary() -> any())) ::
  parent()

fill_in(parent, query, opts)

@spec fill_in(parent(), Wallabidi.Query.t(), [{:with, String.t()}]) :: parent()
@spec fill_in(parent(), Wallabidi.Query.t(), with: String.t(), await: atom()) ::
  parent()

Fills in an element identified by query with value.

All inputs previously present in the input field will be overridden.

Examples

page
|> fill_in(Query.text_field("name"), with: "Chris")
|> fill_in(Query.css("#password_field"), with: "secret42")

Note

Currently, Chrome only supports BMP Unicode characters via the WebDriver send_keys action. Emojis are SMP characters and will be ignored.

Using JavaScript is a known workaround for filling in fields with Emojis and other non-BMP characters.

find(parent, query)

Finds and returns one or more DOM element(s) on the page based on the given query.

The query is scoped by the first argument, which is either the %Session{} or an %Element{}.

Example

session
|> find(Query.css("#login-button"))
|> assert_text("Login")

buttons =
  session
  |> find(Query.css(".login-button", count: 2, text: "Login"))

Notes

  • Blocks until it finds the element(s) or the max time is reached.
  • By default only 1 element is expected to match the query. If more elements are present then a count can be specified. Use count: :any to allow any number of elements to be present.
  • By default only elements that would be visible to a real user on the page are returned.

find(parent, query, callback)

@spec find(parent(), Wallabidi.Query.t(), (Wallabidi.Element.t() -> any())) ::
  parent()

Same as find/2, but takes a callback to enact side effects on the found element(s).

Example

session
|> find(Query.css("#login-button"), fn button ->
  assert_text(button, "Login")
end)

session
|> find(Query.css(".login-button", count: 2, text: "Login"), fn buttons ->
  assert 2 == length(buttons)
end)

Notes

  • Returns the first argument to make the function pipe-able.

focus_default_frame(session)

@spec focus_default_frame(parent()) :: parent()

Changes the driver focus to the default (top level) frame.

focus_frame(session, query)

@spec focus_frame(parent(), Wallabidi.Query.t()) :: parent()

Changes the driver focus to the frame found by query.

focus_parent_frame(session)

@spec focus_parent_frame(parent()) :: parent()

Changes the driver focus to the parent frame.

focus_window(session, window_handle)

@spec focus_window(session :: Wallabidi.Session.t(), window_handle :: String.t()) ::
  parent()

Focuses the window identified by the given handle.

The window is either an instance of a browser tab or another operating system window.

Usage

feature "can switch between different tabs", %{session: session} do
  handle =
    session
    |> visit("/home")
    |> window_handle()

  path =
    session
    # click a link that takes you to a new tab
    |> click(Query.link("External Page"))
    |> assert_text("Some text")
    |> focus_window(handle)
    |> current_path()

  assert "/home" == path
end

has?(parent, query)

@spec has?(parent(), Wallabidi.Query.t()) :: boolean()

Validates that the query returns a result. This can be used to define other types of matchers.

has_css?(parent, css)

@spec has_css?(parent(), String.t()) :: boolean()

has_css?(parent, query, css)

@spec has_css?(parent(), Wallabidi.Query.t(), String.t()) :: boolean()

Searches for CSS on the page.

has_no_css?(parent, css)

@spec has_no_css?(parent(), String.t()) :: boolean()

has_no_css?(parent, query, css)

@spec has_no_css?(parent(), Wallabidi.Query.t(), String.t()) :: boolean()

Searches for CSS that should not be on the page

has_text?(session, text)

@spec has_text?(parent(), String.t()) :: boolean()

has_text?(parent, query, text)

@spec has_text?(parent(), Wallabidi.Query.t(), String.t()) :: boolean()

Matches the parent's content with the provided text.

Returns a boolean that indicates if the text was found.

Examples

session
|> visit("/")
|> has_text?("Login")

Example providing query:

session
|> visit("/")
|> has_text?(Query.css(".login-button"), "Login")

has_value?(element, value)

@spec has_value?(Wallabidi.Element.t(), any()) :: boolean()

has_value?(parent, query, value)

@spec has_value?(parent(), Wallabidi.Query.t(), any()) :: boolean()

Matches the Element's value with the provided value.

hover(parent, query)

@spec hover(parent(), Wallabidi.Query.t()) :: parent()

Hovers over an element.

maximize_window(session)

@spec maximize_window(session :: Wallabidi.Session.t()) :: Wallabidi.Session.t()

Maximizes the current window.

The window is either an instance of a browser tab or another operating system window.

For most browsers, this requires a graphical window manager to be running.

Usage

feature "maximizes the window to the full size of the display", %{session: session} do
  %{"width" => width, "height" => height} =
    session
    |> visit("/home")
    |> maximize_window()
    |> window_size()

  assert width == 1920
  assert height == 1080
end

move_mouse_by(parent, x_offset, y_offset)

@spec move_mouse_by(parent(), integer(), integer()) :: parent()

Moves mouse by an offset relative to current cursor position.

move_window(session, x, y)

@spec move_window(
  session :: Wallabidi.Session.t(),
  x :: pos_integer(),
  y :: pos_integer()
) ::
  Wallabidi.Session.t()

Sets the position of the current window.

The window is either an instance of a browser tab or another operating system window.

Usage

feature "gets the current display position of the window", %{session: session} do
  %{"x" => x, "y" => y} =
    session
    |> visit("/home")
    |> move_window(500, 500)
    |> window_position()

  assert x == 500
  assert y == 500
end

page_source(session)

@spec page_source(parent()) :: String.t()

Retrieves the source of the current page.

page_title(session)

@spec page_title(parent()) :: String.t()

Gets the title for the current page

refute_has(parent, query)

(macro)

Checks if query is not present within parent and raises if it is found.

Returns the given parent if the query is not found so that it is easily pipeable.

Examples

session
|> visit("/")
|> refute_has(Query.css(".secret-admin-content"))

resize_window(session, width, height)

@spec resize_window(
  session :: Wallabidi.Session.t(),
  width :: pos_integer(),
  height :: pos_integer()
) ::
  Wallabidi.Session.t()

Sets the size of the current window.

The window is either an instance of a browser tab or another operating system window.

Usage

feature "sets the size of the window to mobile dimensions", %{session: session} do
  %{"width" => width, "height" => height} =
    session
    |> visit("/home")
    |> resize_window(375, 667)
    |> window_size()

  assert width == 375
  assert height == 667
end

retry(f, start_time \\ current_time())

@spec retry((-> sync_result()), non_neg_integer()) :: sync_result()

Attempts to synchronize with the browser. This is most often used to execute queries repeatedly until it either exceeds the time limit or returns a success.

Note

It is possible that this function never halts. Whenever we experience a stale reference error we retry the query without checking to see if we've run over our time. In practice we should eventually be able to query the DOM in a stable state. However, if this error does continue to occur it will cause wallabidi to loop forever (or until the test is killed by exunit).

selected?(parent, query)

@spec selected?(parent(), Wallabidi.Query.t()) :: boolean()

Checks if the element has been selected. Alias for checked?(element)

send_keys(element, keys)

@spec send_keys(parent(), Wallabidi.Element.keys_to_send()) :: parent()

send_keys(parent, query, list)

Sends a list of key strokes to active element. If strings are included then they are sent as individual keys. Special keys should be provided as a list of atoms, which are automatically converted into the corresponding key codes.

For a list of available key codes see Wallabidi.Helpers.KeyCodes.

Example

iex> Wallabidi.Browser.send_keys(session, ["Example Text", :enter])
iex> Wallabidi.Browser.send_keys(session, [:enter])
iex> Wallabidi.Browser.send_keys(session, [:shift, :enter])

Note

Currently, Chrome only supports BMP Unicode characters via the WebDriver send_keys action. Emojis are SMP characters and will be ignored.

Using JavaScript is a known workaround for filling in fields with Emojis and other non-BMP characters.

set_cookie(session, key, value, attributes \\ [])

set_value(parent, query, value)

@spec set_value(parent(), Wallabidi.Query.t(), Wallabidi.Element.value()) :: parent()

Sets the value of an element. The allowed type for the value depends on the type of the element. The value may be:

  • a string of characters for a text element
  • :selected for a radio button, checkbox or select list option
  • :unselected for a checkbox

take_screenshot(screenshotable, opts \\ [])

@spec take_screenshot(parent(), [take_screenshot_opt()]) :: parent()

Takes a screenshot of the current window. Screenshots are saved to a "screenshots" directory in the same directory the tests are run in.

Pass [{:name, "some_name"}] to specify the file name. Defaults to a timestamp. Pass [{:log, true}] to log the location of the screenshot to stdout. Defaults to false.

tap(parent, query)

@spec tap(parent(), Wallabidi.Query.t()) :: session()

Taps the element.

text(session)

@spec text(parent()) :: String.t()

text(parent, query)

@spec text(parent(), Wallabidi.Query.t()) :: String.t()

Gets the Element's text value.

If the element is not visible, the return value will be "".

touch_down(parent, x, y)

@spec touch_down(parent(), integer(), integer()) :: session()

Touches the screen at the given position.

touch_down(parent, query, x_offset \\ 0, y_offset \\ 0)

@spec touch_down(parent(), Wallabidi.Query.t(), integer(), integer()) :: session()

Touches and holds the element on its top-left corner plus an optional offset.

touch_move(parent, x, y)

@spec touch_move(parent(), non_neg_integer(), non_neg_integer()) :: parent()

Moves the touch pointer (finger, stylus etc.) on the screen to the point determined by the given coordinates.

touch_scroll(parent, query, x, y)

@spec touch_scroll(parent(), Wallabidi.Query.t(), integer(), integer()) :: parent()

Scroll on the screen from the given element by the given offset using touch events.

touch_up(parent)

@spec touch_up(parent()) :: parent()

Stops touching the screen.

visible?(parent, query)

@spec visible?(parent(), Wallabidi.Query.t()) :: boolean()

Checks if the element is visible on the page

visit(session, path)

@spec visit(session(), String.t()) :: session()

Changes the current page to the provided route. Relative paths are appended to the provided base_url. Absolute paths do not use the base_url.

window_handle(session)

@spec window_handle(session :: Wallabidi.Session.t()) :: String.t()

Gets the window handle of the current window.

The window is either an instance of a browser tab or another operating system window. Getting the current window handle makes it easy to return to the window in case you need to switch between them.

Usage

feature "can open a new tab and switch back to the original tab", %{session: session} do
  handle =
    session
    |> visit("/home")
    |> window_handle()

  path =
    session
    # click a link that takes you to a new tab
    |> click(Query.link("External Page"))
    |> assert_text("Some text")
    |> focus_window(handle)
    |> current_path()

  assert "/home" == path
end

window_handles(session)

@spec window_handles(session :: Wallabidi.Session.t()) :: [String.t()]

Gets the window handles of all available windows.

The window is either an instance of a browser tab or another operating system window.

Usage

feature "can open new tabs for external links", %{session: session} do
  handles =
    session
    |> visit("/home")
    |> click(Query.link("External Page"))
    |> click(Query.link("Another External Page"))
    |> window_handles()

  assert 3 == length(path)
end

window_position(session)

@spec window_position(session :: Wallabidi.Session.t()) :: %{
  required(String.t()) => pos_integer(),
  required(String.t()) => pos_integer()
}

Gets the position of the current window.

The window is either an instance of a browser tab or another operating system window.

Usage

feature "gets the current display position of the window", %{session: session} do
  %{"x" => x, "y" => y} =
    session
    |> visit("/home")
    |> window_position()

  assert x == 200
  assert y == 200
end

window_size(session)

@spec window_size(session :: Wallabidi.Session.t()) :: %{
  required(String.t()) => pos_integer(),
  required(String.t()) => pos_integer()
}

Gets the size of the current window.

The window is either an instance of a browser tab or another operating system window.

This is useful for debugging responsive designs where the layout changes as the window size changes. The default window size is 1280x800.

Usage

feature "gets the size of the current window", %{session: session} do
  %{"width" => width, "height" => height} =
    session
    |> visit("/home")
    |> window_size()

  assert width == 1280
  assert height == 800
end