Packages
wallaby
0.26.0
0.31.0
0.30.12
0.30.11
0.30.10
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.1
0.29.0
0.28.1
0.28.0
0.27.0
0.26.2
0.26.1
0.26.0
0.25.1
0.25.0
0.24.1
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.0
0.16.1
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.1
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.1
0.3.0
0.2.0
0.1.0
0.0.1
Concurrent feature tests for elixir
Current section
Files
Jump to
Current section
Files
lib/wallaby/chrome.ex
defmodule Wallaby.Chrome do
@moduledoc """
The Chrome driver uses [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) to power Google Chrome and Chromium.
## Usage
Start a Wallaby Session using this driver with the following command:
```
{:ok, session} = Wallaby.start_session()
```
## Configuration
### Headless
Chrome will run headlessly by default.
You can disable this behaviour using the following configuration.
This will override the default capabilities and capabilities set with application configuration.
This will _not_ override capabilities passed in directly to `Wallaby.start_session/1`.
```
config :wallaby,
chromedriver: [
headless: false
]
```
### Capabilities
These capabilities will override the default capabilities.
```
config :wallaby,
chromedriver: [
capabilities: %{
# something
}
]
```
### ChromeDriver binary
If ChromeDriver is not available in your path, you can specify it's location.
```
config :wallaby,
chromedriver: [
path: "path/to/chrome"
]
```
### Chrome binary
This configures which instance of Google Chrome to use.
This will override the default capabilities and capabilities set with application configuration.
This will _not_ override capabilities passed in directly to `Wallaby.start_session/1`.
```
config :wallaby,
chromedriver: [
binary: "path/to/chrome"
]
```
## Default Capabilities
By default, Chromdriver will use the following capabilities
You can read more about capabilities in the [JSON Wire Protocol](https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#capabilities-json-object) documentation and the [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/capabilities) documentation.
```elixir
%{
javascriptEnabled: false,
loadImages: false,
version: "",
rotatable: false,
takesScreenshot: true,
cssSelectorsEnabled: true,
nativeEvents: false,
platform: "ANY",
unhandledPromptBehavior: "accept",
loggingPrefs: %{
browser: "DEBUG"
},
chromeOptions: %{
args: [
"--no-sandbox",
"window-size=1280,800",
"--disable-gpu",
"--headless",
"--fullscreen",
"--user-agent=Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
]
}
}
```
## Notes
This driver requires [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) to be installed in your path.
"""
use Supervisor
@behaviour Wallaby.Driver
@default_readiness_timeout 5_000
@chromedriver_version_regex ~r/^ChromeDriver (\d+)\.(\d+)/
alias Wallaby.{DependencyError, Metadata}
alias Wallaby.Chrome.{Chromedriver}
alias Wallaby.WebdriverClient
import Wallaby.Driver.LogChecker
@typedoc """
Options to pass to Wallaby.start_session/1
```elixir
Wallaby.start_session(
capabilities: %{chromeOptions: %{args: ["--headless"]}},
create_session_fn: fn url, capabilities ->
WebdriverClient.create_session(url, capabilities)
end
)
```
* `:capabilities` - capabilities to pass to chromedriver on session startup
* `create_session_fn` - Deprecated and to be removed
* `:readiness_timeout` - milliseconds to wait for chromedriver server to be ready
before raising a timeout error. (Default: #{@default_readiness_timeout})
"""
@type start_session_opts ::
{:capabilities, map}
| {:readiness_timeout, timeout()}
| {:create_session_fn, (String.t(), map -> {:ok, %{}})}
@doc false
def start_link(opts \\ []) do
Supervisor.start_link(__MODULE__, :ok, opts)
end
@doc false
def init(:ok) do
children = [
Wallaby.Driver.LogStore,
Wallaby.Chrome.Chromedriver
]
Supervisor.init(children, strategy: :one_for_one)
end
@doc false
@spec validate() :: :ok | {:error, DependencyError.t()}
def validate do
with {:ok, executable} <- find_chromedriver_executable() do
{version, 0} = System.cmd(executable, ["--version"])
@chromedriver_version_regex
|> Regex.run(version)
|> Enum.drop(1)
|> Enum.map(&String.to_integer/1)
|> version_check()
end
end
@doc false
@spec find_chromedriver_executable :: {:ok, String.t()} | {:error, DependencyError.t()}
def find_chromedriver_executable do
chromedriver_path =
:wallaby
|> Application.get_env(:chromedriver, [])
|> Keyword.get(:path, "chromedriver")
[Path.expand(chromedriver_path), chromedriver_path]
|> Enum.find(&System.find_executable/1)
|> case do
path when is_binary(path) ->
{:ok, path}
nil ->
exception =
DependencyError.exception("""
Wallaby can't find chromedriver. Make sure you have chromedriver installed
and included in your path.
You can also provide a path using `config :wallaby, chromedriver: <path>`.
""")
{:error, exception}
end
end
defp version_check([major_version, _minor_version]) when major_version > 2 do
:ok
end
defp version_check([major_version, minor_version])
when major_version == 2 and minor_version >= 30 do
:ok
end
defp version_check(_version) do
exception =
DependencyError.exception("""
Looks like you're trying to run an older version of chromedriver. Wallaby needs at least
chromedriver 2.30 to run correctly.
""")
{:error, exception}
end
@doc false
@spec start_session([start_session_opts]) :: Wallaby.Driver.on_start_session() | no_return
def start_session(opts \\ []) do
opts |> Keyword.get(:readiness_timeout, @default_readiness_timeout) |> wait_until_ready!()
base_url = Chromedriver.base_url()
create_session_fn = Keyword.get(opts, :create_session_fn, &WebdriverClient.create_session/2)
capabilities = Keyword.get(opts, :capabilities, capabilities_from_config(opts))
with {:ok, response} <- create_session_fn.(base_url, capabilities) do
id = response["sessionId"]
session = %Wallaby.Session{
session_url: base_url <> "session/#{id}",
url: base_url <> "session/#{id}",
id: id,
driver: __MODULE__,
server: Chromedriver,
capabilities: capabilities
}
if window_size = Keyword.get(opts, :window_size),
do: {:ok, _} = set_window_size(session, window_size[:width], window_size[:height])
{:ok, session}
end
end
defp capabilities_from_config(opts) do
:wallaby
|> Application.get_env(:chromedriver, [])
|> Keyword.get(:capabilities, default_capabilities(opts))
|> put_headless_config()
|> put_binary_config()
end
@spec wait_until_ready!(timeout) :: :ok | no_return
defp wait_until_ready!(timeout) do
case Chromedriver.wait_until_ready(timeout) do
:ok -> :ok
{:error, :timeout} -> raise "timeout waiting for chromedriver to be ready"
end
end
@doc false
def end_session(%Wallaby.Session{} = session, opts \\ []) do
end_session_fn = Keyword.get(opts, :end_session_fn, &WebdriverClient.delete_session/1)
end_session_fn.(session)
:ok
end
@doc false
def blank_page?(session) do
case current_url(session) do
{:ok, url} ->
url == "data:,"
_ ->
false
end
end
defp delegate(fun, element_or_session, args \\ []) do
check_logs!(element_or_session, fn ->
apply(WebdriverClient, fun, [element_or_session | args])
end)
end
@doc false
defdelegate accept_alert(session, fun), to: WebdriverClient
@doc false
defdelegate dismiss_alert(session, fun), to: WebdriverClient
@doc false
defdelegate accept_confirm(session, fun), to: WebdriverClient
@doc false
defdelegate dismiss_confirm(session, fun), to: WebdriverClient
@doc false
defdelegate accept_prompt(session, input, fun), to: WebdriverClient
@doc false
defdelegate dismiss_prompt(session, fun), to: WebdriverClient
@doc false
defdelegate parse_log(log), to: Wallaby.Chrome.Logger
@doc false
def window_handle(session), do: delegate(:window_handle, session)
@doc false
def window_handles(session), do: delegate(:window_handles, session)
@doc false
def focus_window(session, window_handle), do: delegate(:focus_window, session, [window_handle])
@doc false
def close_window(session), do: delegate(:close_window, session)
@doc false
def get_window_size(session), do: delegate(:get_window_size, session)
@doc false
def set_window_size(session, width, height),
do: delegate(:set_window_size, session, [width, height])
@doc false
def get_window_position(session), do: delegate(:get_window_position, session)
@doc false
def set_window_position(session, x, y), do: delegate(:set_window_position, session, [x, y])
@doc false
def maximize_window(session), do: delegate(:maximize_window, session)
@doc false
def focus_frame(session, frame), do: delegate(:focus_frame, session, [frame])
@doc false
def focus_parent_frame(session), do: delegate(:focus_parent_frame, session)
@doc false
def cookies(session), do: delegate(:cookies, session)
@doc false
def current_path(session), do: delegate(:current_path, session)
@doc false
def current_url(session), do: delegate(:current_url, session)
@doc false
def page_title(session), do: delegate(:page_title, session)
@doc false
def page_source(session), do: delegate(:page_source, session)
@doc false
def set_cookie(session, key, value), do: delegate(:set_cookie, session, [key, value])
@doc false
def visit(session, url), do: delegate(:visit, session, [url])
@doc false
def attribute(element, name), do: delegate(:attribute, element, [name])
@doc false
def click(element), do: delegate(:click, element)
@doc false
def click(parent, button), do: delegate(:click, parent, [button])
@doc false
def double_click(parent), do: delegate(:double_click, parent)
@doc false
def button_down(parent, button), do: delegate(:button_down, parent, [button])
@doc false
def button_up(parent, button), do: delegate(:button_up, parent, [button])
@doc false
def hover(element), do: delegate(:move_mouse_to, element, [element])
@doc false
def move_mouse_by(parent, x_offset, y_offset),
do: delegate(:move_mouse_to, parent, [nil, x_offset, y_offset])
@doc false
def clear(element), do: delegate(:clear, element)
@doc false
def displayed(element), do: delegate(:displayed, element)
@doc false
def selected(element), do: delegate(:selected, element)
@doc false
def set_value(element, value), do: delegate(:set_value, element, [value])
@doc false
def text(element), do: delegate(:text, element)
@doc false
def execute_script(session_or_element, script, args \\ [], opts \\ []) do
check_logs = Keyword.get(opts, :check_logs, true)
request_fn = fn ->
WebdriverClient.execute_script(session_or_element, script, args)
end
if check_logs do
check_logs!(session_or_element, request_fn)
else
request_fn.()
end
end
@doc false
def execute_script_async(session_or_element, script, args \\ [], opts \\ []) do
check_logs = Keyword.get(opts, :check_logs, true)
request_fn = fn ->
WebdriverClient.execute_script_async(session_or_element, script, args)
end
if check_logs do
check_logs!(session_or_element, request_fn)
else
request_fn.()
end
end
@doc false
def find_elements(session_or_element, compiled_query),
do: delegate(:find_elements, session_or_element, [compiled_query])
@doc false
def send_keys(session_or_element, keys), do: delegate(:send_keys, session_or_element, [keys])
@doc false
def take_screenshot(session_or_element), do: delegate(:take_screenshot, session_or_element)
@doc false
defdelegate log(session_or_element), to: WebdriverClient
@doc false
def default_capabilities(opts \\ []) do
user_agent =
Metadata.append(
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
opts[:metadata]
)
%{
javascriptEnabled: false,
loadImages: false,
version: "",
rotatable: false,
takesScreenshot: true,
cssSelectorsEnabled: true,
nativeEvents: false,
platform: "ANY",
unhandledPromptBehavior: "accept",
loggingPrefs: %{
browser: "DEBUG"
},
chromeOptions: %{
args: [
"--no-sandbox",
"window-size=1280,800",
"--disable-gpu",
"--headless",
"--fullscreen",
"--user-agent=#{user_agent}"
]
}
}
end
defp put_headless_config(capabilities) do
headless? = Application.get_env(:wallaby, :chromedriver, []) |> Keyword.get(:headless)
capabilities
|> update_unless_nil(:args, headless?, fn args ->
if headless? do
(args ++ ["--headless"])
|> Enum.uniq()
else
args -- ["--headless"]
end
end)
end
defp put_binary_config(capabilities) do
binary_path = Application.get_env(:wallaby, :chromedriver, []) |> Keyword.get(:binary)
capabilities
|> update_unless_nil(:binary, binary_path, fn _ ->
binary_path
end)
end
defp update_unless_nil(capabilities, _key, nil, _updater), do: capabilities
defp update_unless_nil(capabilities, key, _, updater) do
capabilities
|> update_in([:chromeOptions, key], updater)
end
end