Packages

Low-level WebDriver client for Elixir.

Current section

Files

Jump to
web_driver_client lib web_driver_client.ex
Raw

lib/web_driver_client.ex

defmodule WebDriverClient do
@moduledoc """
Webdriver API client.
"""
import WebDriverClient.CompatibilityMacros
import WebDriverClient.Guards
alias WebDriverClient.Config
alias WebDriverClient.ConnectionError
alias WebDriverClient.Cookie
alias WebDriverClient.Element
alias WebDriverClient.HTTPResponse
alias WebDriverClient.JSONWireProtocolClient
alias WebDriverClient.JSONWireProtocolClient.Commands, as: JWPCommands
alias WebDriverClient.KeyCodes
alias WebDriverClient.LogEntry
alias WebDriverClient.ProtocolMismatchError
alias WebDriverClient.ServerStatus
alias WebDriverClient.Session
alias WebDriverClient.Size
alias WebDriverClient.UnexpectedResponseError
alias WebDriverClient.W3CWireProtocolClient
alias WebDriverClient.W3CWireProtocolClient.Commands, as: W3CCommands
alias WebDriverClient.WebDriverError
@type protocol :: Config.protocol()
@type url :: String.t()
@type attribute_name :: String.t()
@type property_name :: String.t()
@type reason :: ProtocolMismatchError.t() | basic_reason
@type basic_reason ::
ConnectionError.t()
| UnexpectedResponseError.t()
| WebDriverError.t()
@doc """
Starts a new session
"""
doc_metadata subject: :sessions
@spec start_session(Config.t(), map()) :: {:ok, Session.t()} | {:error, reason}
def start_session(%Config{protocol: protocol} = config, payload) when is_map(payload) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.StartSession.send_request(config, payload) end,
w3c: fn -> W3CCommands.StartSession.send_request(config, payload) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.StartSession.parse_response(&1, config),
w3c: &W3CCommands.StartSession.parse_response(&1, config)
)
end
end
@doc """
Returns the list of sessions
"""
doc_metadata subject: :sessions
@spec fetch_sessions(Config.t()) :: {:ok, [Session.t()]} | {:error, reason}
def fetch_sessions(%Config{protocol: protocol} = config) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchSessions.send_request(config) end,
w3c: fn -> W3CCommands.FetchSessions.send_request(config) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FetchSessions.parse_response(&1, config),
w3c: &W3CCommands.FetchSessions.parse_response(&1, config)
)
end
end
@doc """
Ends a session
"""
doc_metadata subject: :sessions
@spec end_session(Session.t()) :: :ok | {:error, reason}
def end_session(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.EndSession.send_request(session) end,
w3c: fn -> W3CCommands.EndSession.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.EndSession.parse_response/1,
w3c: &W3CCommands.EndSession.parse_response/1
)
end
end
@doc """
Navigates the browser to the given url
"""
doc_metadata subject: :navigation
@spec navigate_to(Session.t(), url) :: :ok | {:error, reason}
def navigate_to(%Session{config: %Config{protocol: protocol}} = session, url)
when is_url(url) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.NavigateTo.send_request(session, url) end,
w3c: fn -> W3CCommands.NavigateTo.send_request(session, url) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.NavigateTo.parse_response/1,
w3c: &W3CCommands.NavigateTo.parse_response/1
)
end
end
@doc """
Returns the web browsers current url
"""
doc_metadata subject: :navigation
@spec fetch_current_url(Session.t()) :: {:ok, url} | {:error, reason}
def fetch_current_url(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchCurrentURL.send_request(session) end,
w3c: fn -> W3CCommands.FetchCurrentURL.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FetchCurrentURL.parse_response/1,
w3c: &W3CCommands.FetchCurrentURL.parse_response/1
)
end
end
@doc """
Returns page title of the top-most window.
This is the equivalent of calling `document.title`.
"""
doc_metadata subject: :navigation
@spec fetch_title(Session.t()) :: {:ok, String.t()} | {:error, reason}
def fetch_title(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchTitle.send_request(session) end,
w3c: fn -> W3CCommands.FetchTitle.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FetchTitle.parse_response/1,
w3c: &W3CCommands.FetchTitle.parse_response/1
)
end
end
@doc """
Returns page source of the top-most window.
"""
doc_metadata subject: :navigation
@spec fetch_page_source(Session.t()) :: {:ok, String.t()} | {:error, reason}
def fetch_page_source(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchPageSource.send_request(session) end,
w3c: fn -> W3CCommands.FetchPageSource.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FetchPageSource.parse_response/1,
w3c: &W3CCommands.FetchPageSource.parse_response/1
)
end
end
@doc """
Returns the size of the current window
"""
@spec fetch_window_size(Session.t()) :: {:ok, Size.t()} | {:error, reason}
def fetch_window_size(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchWindowSize.send_request(session) end,
w3c: fn -> W3CCommands.FetchWindowRect.send_request(session) end
) do
parse_with_fallbacks(
http_response,
protocol,
[
jwp: &JWPCommands.FetchWindowSize.parse_response/1,
w3c: &W3CCommands.FetchWindowRect.parse_response/1
],
fn
{:ok, size} -> {:ok, to_size(size)}
{:error, error} -> {:error, to_error(error)}
end
)
end
end
@type element_location_strategy :: :css_selector | :xpath
@type element_selector :: String.t()
@doc """
Finds the first element using the given search strategy
If no element is found, a `WebDriverClient.WebDriverError`
is returned
"""
doc_metadata subject: :elements
@spec find_element(Session.t(), element_location_strategy, element_selector) ::
{:ok, Element.t()} | {:error, reason}
def find_element(
%Session{config: %Config{protocol: protocol}} = session,
element_location_strategy,
element_selector
)
when is_element_location_strategy(element_location_strategy) and
is_element_selector(element_selector) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn ->
JWPCommands.FindElement.send_request(
session,
element_location_strategy,
element_selector
)
end,
w3c: fn ->
W3CCommands.FindElement.send_request(
session,
element_location_strategy,
element_selector
)
end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FindElement.parse_response/1,
w3c: &W3CCommands.FindElement.parse_response/1
)
end
end
@doc """
Finds the elements using the given search strategy
"""
doc_metadata subject: :elements
@spec find_elements(Session.t(), element_location_strategy, element_selector) ::
{:ok, [Element.t()]} | {:error, reason}
def find_elements(
%Session{config: %Config{protocol: protocol}} = session,
element_location_strategy,
element_selector
)
when is_element_location_strategy(element_location_strategy) and
is_element_selector(element_selector) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn ->
JWPCommands.FindElements.send_request(
session,
element_location_strategy,
element_selector
)
end,
w3c: fn ->
W3CCommands.FindElements.send_request(
session,
element_location_strategy,
element_selector
)
end
) do
parse_with_fallbacks(
http_response,
protocol,
jwp: &JWPCommands.FindElements.parse_response/1,
w3c: &W3CCommands.FindElements.parse_response/1
)
end
end
@doc """
Finds elements that are children of the given element
"""
doc_metadata subject: :elements
@spec find_elements_from_element(
Session.t(),
Element.t(),
element_location_strategy,
element_selector
) :: {:ok, [Element.t()] | {:error, reason}}
def find_elements_from_element(
%Session{config: %Config{protocol: protocol}} = session,
%Element{} = element,
element_location_strategy,
element_selector
)
when is_element_location_strategy(element_location_strategy) and
is_element_selector(element_selector) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn ->
JWPCommands.FindElementsFromElement.send_request(
session,
element,
element_location_strategy,
element_selector
)
end,
w3c: fn ->
W3CCommands.FindElementsFromElement.send_request(
session,
element,
element_location_strategy,
element_selector
)
end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FindElementsFromElement.parse_response/1,
w3c: &W3CCommands.FindElementsFromElement.parse_response/1
)
end
end
@doc """
Fetches the currently active (focused) element
"""
doc_metadata subject: :elements
@spec fetch_active_element(Session.t()) :: {:ok, Element.t()} | {:error, reason}
def fetch_active_element(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchActiveElement.send_request(session) end,
w3c: fn -> W3CCommands.FetchActiveElement.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FetchActiveElement.parse_response/1,
w3c: &W3CCommands.FetchActiveElement.parse_response/1
)
end
end
@type size_opt :: {:width, pos_integer} | {:height, pos_integer}
@doc """
Sets the size of the window
"""
@spec set_window_size(Session.t(), [size_opt]) :: :ok | {:error, reason}
def set_window_size(%Session{config: %Config{protocol: protocol}} = session, opts \\ []) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.SetWindowSize.send_request(session, opts) end,
w3c: fn -> W3CCommands.SetWindowRect.send_request(session, opts) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.SetWindowSize.parse_response/1,
w3c: &W3CCommands.SetWindowRect.parse_response/1
)
end
end
@type log_type :: String.t()
@doc """
Fetches the log types from the server
"""
doc_metadata subject: :logging
@spec fetch_log_types(Session.t()) :: {:ok, [log_type]} | {:error, reason()}
def fetch_log_types(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchLogTypes.send_request(session) end,
w3c: fn -> W3CCommands.FetchLogTypes.send_request(session) end
) do
parse_with_fallbacks(
http_response,
protocol,
jwp: &JWPCommands.FetchLogTypes.parse_response/1,
w3c: &W3CCommands.FetchLogTypes.parse_response/1
)
end
end
@doc """
Fetches log entries for the requested log type.
"""
doc_metadata subject: :logging
@spec fetch_logs(Session.t(), log_type) :: {:ok, [LogEntry.t()]} | {:error, reason()}
def fetch_logs(session, log_type)
def fetch_logs(%Session{config: %Config{protocol: protocol}} = session, log_type)
when is_binary(log_type) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchLogs.send_request(session, log_type) end,
w3c: fn -> W3CCommands.FetchLogs.send_request(session, log_type) end
) do
parse_with_fallbacks(
http_response,
protocol,
[
jwp: &JWPCommands.FetchLogs.parse_response/1,
w3c: &W3CCommands.FetchLogs.parse_response/1
],
fn
{:ok, log_entries} -> {:ok, Enum.map(log_entries, &to_log_entry/1)}
{:error, error} -> {:error, to_error(error)}
end
)
end
end
@doc """
Sends a request to the server to see if an element
is displayed
"""
doc_metadata subject: :elements
@spec fetch_element_displayed(Session.t(), Element.t()) :: {:ok, boolean} | {:error, reason}
def fetch_element_displayed(
%Session{config: %Config{protocol: protocol}} = session,
%Element{} = element
) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchElementDisplayed.send_request(session, element) end,
w3c: fn -> W3CCommands.FetchElementDisplayed.send_request(session, element) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FetchElementDisplayed.parse_response/1,
w3c: &W3CCommands.FetchElementDisplayed.parse_response/1
)
end
end
@doc """
Fetches the value of an element's attribute
"""
doc_metadata subject: :elements
@spec fetch_element_attribute(Session.t(), Element.t(), attribute_name) ::
{:ok, String.t()} | {:error, reason}
def fetch_element_attribute(
%Session{config: %Config{protocol: protocol}} = session,
%Element{} = element,
attribute_name
)
when is_attribute_name(attribute_name) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn ->
JWPCommands.FetchElementAttribute.send_request(session, element, attribute_name)
end,
w3c: fn ->
W3CCommands.FetchElementAttribute.send_request(session, element, attribute_name)
end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FetchElementAttribute.parse_response/1,
w3c: &W3CCommands.FetchElementAttribute.parse_response/1
)
end
end
@doc """
Fetches the value of an element's property
Only supported using `:w3c` protocol.
"""
doc_metadata subject: :elements
@spec fetch_element_property(Session.t(), Element.t(), property_name) ::
{:ok, String.t()} | {:error, reason}
def fetch_element_property(%Session{config: %Config{protocol: :jwp}}, %Element{}, property_name)
when is_property_name(property_name) do
{:error, WebDriverError.exception(reason: :unsupported_operation)}
end
def fetch_element_property(
%Session{config: %Config{protocol: protocol}} = session,
%Element{} = element,
property_name
)
when is_property_name(property_name) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
w3c: fn ->
W3CCommands.FetchElementProperty.send_request(session, element, property_name)
end
) do
parse_with_fallbacks(http_response, protocol,
w3c: &W3CCommands.FetchElementProperty.parse_response/1
)
end
end
@doc """
Fetches the visitble text of an element
"""
doc_metadata subject: :elements
@spec fetch_element_text(Session.t(), Element.t()) :: {:ok, String.t()} | {:error, reason}
def fetch_element_text(
%Session{config: %Config{protocol: protocol}} = session,
%Element{} = element
) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn ->
JWPCommands.FetchElementText.send_request(session, element)
end,
w3c: fn ->
W3CCommands.FetchElementText.send_request(session, element)
end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FetchElementText.parse_response/1,
w3c: &W3CCommands.FetchElementText.parse_response/1
)
end
end
@doc """
Clicks an element
"""
doc_metadata subject: :elements
@spec click_element(Session.t(), Element.t()) :: :ok | {:error, reason}
def click_element(
%Session{config: %Config{protocol: protocol}} = session,
%Element{} = element
) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn ->
JWPCommands.ClickElement.send_request(session, element)
end,
w3c: fn ->
W3CCommands.ClickElement.send_request(session, element)
end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.ClickElement.parse_response/1,
w3c: &W3CCommands.ClickElement.parse_response/1
)
end
end
@doc """
Clears the value of a text input or text area element
"""
doc_metadata subject: :elements
@spec clear_element(Session.t(), Element.t()) :: :ok | {:error, reason}
def clear_element(
%Session{config: %Config{protocol: protocol}} = session,
%Element{} = element
) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn ->
JWPCommands.ClearElement.send_request(session, element)
end,
w3c: fn ->
W3CCommands.ClearElement.send_request(session, element)
end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.ClearElement.parse_response/1,
w3c: &W3CCommands.ClearElement.parse_response/1
)
end
end
@type key_code :: unquote_splicing([KeyCodes.key_code_atoms_union()])
@type keystroke :: String.t() | key_code
@type keys :: keystroke | [keystroke]
@doc """
Sends a sequence of keystrokes to an element
"""
doc_metadata subject: :elements
@spec send_keys_to_element(Session.t(), Element.t(), keys) :: :ok | {:error, reason}
def send_keys_to_element(
%Session{config: %Config{protocol: protocol}} = session,
%Element{} = element,
keys
) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.SendKeysToElement.send_request(session, element, keys) end,
w3c: fn -> W3CCommands.SendKeysToElement.send_request(session, element, keys) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.SendKeysToElement.parse_response/1,
w3c: &W3CCommands.SendKeysToElement.parse_response/1
)
end
end
@doc """
Sends a sequence of keystrokes to the currently active element.
Only supported with `:jwp` protocol. Prefer `send_keys_to_element/3` for
improved compatibility.
"""
doc_metadata subject: :elements
@spec send_keys(Session.t(), keys) :: :ok | {:error, reason}
def send_keys(%Session{config: %Config{protocol: :w3c}}, _keys) do
{:error, WebDriverError.exception(reason: :unsupported_operation)}
end
def send_keys(%Session{config: %Config{protocol: protocol}} = session, keys) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.SendKeys.send_request(session, keys) end
) do
parse_with_fallbacks(http_response, protocol, jwp: &JWPCommands.SendKeys.parse_response/1)
end
end
@doc """
Fetches the text from the currently active alert.
"""
doc_metadata subject: :alerts
@spec fetch_alert_text(Session.t()) :: :ok | {:error, reason}
def fetch_alert_text(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchAlertText.send_request(session) end,
w3c: fn -> W3CCommands.FetchAlertText.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.FetchAlertText.parse_response/1,
w3c: &W3CCommands.FetchAlertText.parse_response/1
)
end
end
@doc """
Accepts the currently active alert dialog.
Usually this is the equivalent to clicking ok on a dialog.
"""
doc_metadata subject: :alerts
@spec accept_alert(Session.t()) :: :ok | {:error, reason}
def accept_alert(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.AcceptAlert.send_request(session) end,
w3c: fn -> W3CCommands.AcceptAlert.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.AcceptAlert.parse_response/1,
w3c: &W3CCommands.AcceptAlert.parse_response/1
)
end
end
@doc """
Dismisses the currently active alert dialog.
For `confirm()` and `prompt()` dialogs, this is equivalent to
clicking the 'Cancel' button. For `alert()` dialogs, this is
equivalent to clicking the 'OK' button.
"""
doc_metadata subject: :alerts
@spec dismiss_alert(Session.t()) :: :ok | {:error, reason}
def dismiss_alert(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.DismissAlert.send_request(session) end,
w3c: fn -> W3CCommands.DismissAlert.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.DismissAlert.parse_response/1,
w3c: &W3CCommands.DismissAlert.parse_response/1
)
end
end
@doc """
Sends keystrokes to the currently active Javascript `prompt` dialog.
"""
doc_metadata subject: :alerts
@spec send_alert_text(Session.t(), String.t()) :: :ok | {:error, reason}
def send_alert_text(%Session{config: %Config{protocol: protocol}} = session, text)
when is_binary(text) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.SendAlertText.send_request(session, text) end,
w3c: fn -> W3CCommands.SendAlertText.send_request(session, text) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.SendAlertText.parse_response/1,
w3c: &W3CCommands.SendAlertText.parse_response/1
)
end
end
@doc """
Takes a screenshot of the current page.
"""
@spec take_screenshot(Session.t()) :: {:ok, binary} | {:error, reason}
def take_screenshot(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.TakeScreenshot.send_request(session) end,
w3c: fn -> W3CCommands.TakeScreenshot.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.TakeScreenshot.parse_response/1,
w3c: &W3CCommands.TakeScreenshot.parse_response/1
)
end
end
@doc """
Fetches all cookies visible to the current web page.
"""
@spec fetch_cookies(Session.t()) :: {:ok, [Cookie.t()]} | {:error, reason}
def fetch_cookies(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchCookies.send_request(session) end,
w3c: fn -> W3CCommands.FetchCookies.send_request(session) end
) do
parse_with_fallbacks(
http_response,
protocol,
[
jwp: &JWPCommands.FetchCookies.parse_response/1,
w3c: &W3CCommands.FetchCookies.parse_response/1
],
fn
{:ok, cookies} -> {:ok, Enum.map(cookies, &to_cookie/1)}
{:error, error} -> {:error, to_error(error)}
end
)
end
end
@type set_cookie_opt :: {:domain, Cookie.domain()}
@doc """
Sets a cookie
"""
@spec set_cookie(Session.t(), Cookie.name(), Cookie.value(), [set_cookie_opt]) ::
:ok | {:error, reason}
def set_cookie(%Session{config: %Config{protocol: protocol}} = session, name, value, opts \\ [])
when is_cookie_name(name) and is_cookie_value(value) and is_list(opts) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.SetCookie.send_request(session, name, value, opts) end,
w3c: fn -> W3CCommands.SetCookie.send_request(session, name, value, opts) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.SetCookie.parse_response/1,
w3c: &W3CCommands.SetCookie.parse_response/1
)
end
end
@doc """
Deletes all cookies visible to the current page
"""
@spec delete_cookies(Session.t()) :: :ok | {:error, reason}
def delete_cookies(%Session{config: %Config{protocol: protocol}} = session) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.DeleteCookies.send_request(session) end,
w3c: fn -> W3CCommands.DeleteCookies.send_request(session) end
) do
parse_with_fallbacks(http_response, protocol,
jwp: &JWPCommands.DeleteCookies.parse_response/1,
w3c: &W3CCommands.DeleteCookies.parse_response/1
)
end
end
@doc """
Fetches server status
"""
@spec fetch_server_status(Config.t()) :: {:ok, ServerStatus.t()} | {:error, reason}
def fetch_server_status(%Config{protocol: protocol} = config) do
with {:ok, http_response} <-
send_request_for_protocol(protocol,
jwp: fn -> JWPCommands.FetchServerStatus.send_request(config) end,
w3c: fn -> W3CCommands.FetchServerStatus.send_request(config) end
) do
parse_with_fallbacks(
http_response,
protocol,
[
jwp: &JWPCommands.FetchServerStatus.parse_response/1,
w3c: &W3CCommands.FetchServerStatus.parse_response/1
],
fn
{:ok, server_status} -> {:ok, to_server_status(server_status)}
{:error, error} -> {:error, to_error(error)}
end
)
end
end
@spec to_log_entry(JSONWireProtocolClient.LogEntry.t()) :: LogEntry.t()
defp to_log_entry(%JSONWireProtocolClient.LogEntry{} = log_entry) do
log_entry
|> Map.from_struct()
|> (&struct!(LogEntry, &1)).()
end
@spec to_log_entry(W3CWireProtocolClient.LogEntry.t()) :: LogEntry.t()
defp to_log_entry(%W3CWireProtocolClient.LogEntry{} = log_entry) do
log_entry
|> Map.from_struct()
|> (&struct!(LogEntry, &1)).()
end
@spec to_cookie(W3CWireProtocolClient.Cookie.t() | JSONWireProtocolClient.Cookie.t()) ::
Cookie.t()
defp to_cookie(%JSONWireProtocolClient.Cookie{} = cookie) do
cookie
|> Map.from_struct()
|> (&struct!(Cookie, &1)).()
end
defp to_cookie(%W3CWireProtocolClient.Cookie{} = cookie) do
cookie
|> Map.from_struct()
|> (&struct!(Cookie, &1)).()
end
@spec to_server_status(
W3CWireProtocolClient.ServerStatus.t()
| JSONWireProtocolClient.ServerStatus.t()
) :: ServerStatus.t()
defp to_server_status(%JSONWireProtocolClient.ServerStatus{} = server_status) do
server_status
|> Map.from_struct()
|> (&struct!(ServerStatus, &1)).()
end
defp to_server_status(%W3CWireProtocolClient.ServerStatus{} = server_status) do
server_status
|> Map.from_struct()
|> (&struct!(ServerStatus, &1)).()
end
@spec to_size(W3CWireProtocolClient.Rect.t() | JSONWireProtocolClient.Size.t()) :: Size.t()
defp to_size(%JSONWireProtocolClient.Size{width: width, height: height}),
do: %Size{width: width, height: height}
defp to_size(%W3CWireProtocolClient.Rect{width: width, height: height}),
do: %Size{width: width, height: height}
defp to_error(%JSONWireProtocolClient.WebDriverError{reason: reason}) do
reason =
case reason do
:no_alert_open_error ->
:no_such_alert
reason ->
reason
end
WebDriverError.exception(reason: reason)
end
defp to_error(%W3CWireProtocolClient.WebDriverError{reason: reason}) do
WebDriverError.exception(reason: reason)
end
defp to_error(%W3CWireProtocolClient.UnexpectedResponseError{
reason: reason,
response_body: response_body
}) do
UnexpectedResponseError.exception(
reason: reason,
response_body: response_body,
protocol: :w3c
)
end
defp to_error(%JSONWireProtocolClient.UnexpectedResponseError{
reason: reason,
response_body: response_body
}) do
UnexpectedResponseError.exception(
reason: reason,
response_body: response_body,
protocol: :jwp
)
end
@spec send_request_for_protocol(protocol, [
{protocol, (-> {:ok, HTTPResponse.t()} | {:error, ConnectionError.t()})}
]) :: {:ok, HTTPResponse.t()} | {:error, ConnectionError.t()}
defp send_request_for_protocol(protocol, send_request_fns)
when is_list(send_request_fns) and is_atom(protocol) do
send_request_fns
|> Keyword.fetch!(protocol)
|> apply([])
end
defp parse_with_fallbacks(
http_response,
requested_protocol,
parse_fns,
normalize_fn \\ &default_normalize_response/1
)
when is_list(parse_fns) and is_atom(requested_protocol) and is_function(normalize_fn, 1) do
{parse_fn, additional_parse_fns} = Keyword.pop(parse_fns, requested_protocol)
initial_response =
http_response
|> parse_fn.()
|> normalize_fn.()
with {:error, %UnexpectedResponseError{}} <- initial_response do
Enum.reduce_while(additional_parse_fns, initial_response, fn {protocol, parse_fn}, acc ->
http_response
|> parse_fn.()
|> normalize_fn.()
|> case do
{:error, %UnexpectedResponseError{}} ->
{:cont, acc}
response ->
{:halt,
{:error,
ProtocolMismatchError.exception(
expected_protocol: requested_protocol,
actual_protocol: protocol,
response: response
)}}
end
end)
end
end
defp default_normalize_response(response)
defp default_normalize_response({:error, error}), do: {:error, to_error(error)}
defp default_normalize_response({:ok, result}), do: {:ok, result}
defp default_normalize_response(:ok), do: :ok
end