# NAME
Test::Mojo::Role::Selenium - Test::Mojo in a real browser
# SYNOPSIS
## External app
use Mojo::Base -strict;
use Test::More;
$ENV{MOJO_SELENIUM_BASE_URL} ||= 'http://mojolicious.org';
$ENV{MOJO_SELENIUM_DRIVER} ||= 'Selenium::Chrome';
my $t = Test::Mojo->with_roles("+Selenium")->new->setup_or_skip_all;
$t->navigate_ok('/perldoc')
->live_text_is('a[href="#GUIDES"]' => 'GUIDES');
$t->driver->execute_script(qq[document.querySelector("form").removeAttribute("target")]);
$t->element_is_displayed("input[name=q]")
->send_keys_ok("input[name=q]", ["render", \"return"]);
$t->wait_until(sub { $_->get_current_url =~ qr{q=render} })
->live_value_is("input[name=search]", "render");
done_testing;
## Internal app
use Mojo::Base -strict;
use Test::More;
my $t = Test::Mojo->with_roles("+Selenium")->new("MyApp")->setup_or_skip_all;
# All the standard Test::Mojo methods are available
ok $t->isa("Test::Mojo");
ok $t->does("Test::Mojo::Role::Selenium");
$t->navigate_ok("/")
->status_is(200)
->header_is("Server" => "Mojolicious (Perl)")
->text_is("div#message" => "Hello!")
->live_text_is("div#message" => "Hello!")
->live_element_exists("nav")
->element_is_displayed("nav")
->active_element_is("input[name=q]")
->send_keys_ok("input[name=q]", "Mojo")
->capture_screenshot;
$t->submit_ok("form")
->status_is(200)
->current_url_like(qr{q=Mojo})
->live_element_exists("input[name=q][value=Mojo]");
$t->click_ok("nav a.logo")->status_is(200);
done_testing;
# DESCRIPTION
[Test::Mojo::Role::Selenium](https://metacpan.org/pod/Test::Mojo::Role::Selenium) is a role that extends [Test::Mojo](https://metacpan.org/pod/Test::Mojo) with
additional methods which checks behaviour in a browser. All the heavy lifting
is done by [Selenium::Remote::Driver](https://metacpan.org/pod/Selenium::Remote::Driver).
Some of the [Selenium::Remote::Driver](https://metacpan.org/pod/Selenium::Remote::Driver) methods are available directly in this
role, while the rest are available through the object held by the ["driver"](#driver)
attribute. Please create an issue if you think more tests or methods should be
provided directly by [Test::Mojo::Role::Selenium](https://metacpan.org/pod/Test::Mojo::Role::Selenium).
# OPTIONAL DEPENDENCIES
[Selenium::Remote::Driver](https://metacpan.org/pod/Selenium::Remote::Driver) require some external dependencies to work. Here
are a quick intro to install some of the dependencies to make this module work.
- [Selenium::Chrome](https://metacpan.org/pod/Selenium::Chrome)
# macOS
$ brew install chromedriver
# Ubuntu
$ sudo apt-get install chromium-chromedriver
# Run tests
$ MOJO_SELENIUM_DRIVER=Selenium::Chrome prove -l
# CAVEAT
["tx" in Test::Mojo](https://metacpan.org/pod/Test::Mojo#tx) is only populated, if the request went through an ["Internal app"](#internal-app).
This means that methods such as ["header\_is" in Test::Mojo](https://metacpan.org/pod/Test::Mojo#header_is) will not work or
probably fail completely when testing an ["External app"](#external-app).
# ENVIRONMENT VARIABLES
## MOJO\_SELENIUM\_BASE\_URL
Setting this variable will make this test send the requests to a remote server,
instead of starting a local server. Note that this will disable [Test::Mojo](https://metacpan.org/pod/Test::Mojo)
methods such as ["status\_is"](#status_is), since ["tx" in Test::Mojo](https://metacpan.org/pod/Test::Mojo#tx) will not be set. See
also ["CAVEAT"](#caveat).
This variable will get the value of ["TEST\_SELENIUM"](#test_selenium) if it looks like a URL.
## MOJO\_SELENIUM\_TEST\_HOST
In some cases you may want to override the host of your test server, when
running Selenium on a separate server or in a pod-style networking environment
this still retains the automatically generated port. This will not disable the
[Test::Mojo](https://metacpan.org/pod/Test::Mojo) methods.
## MOJO\_SELENIUM\_DRIVER
This variable can be set to a classname, such as [Selenium::Chrome](https://metacpan.org/pod/Selenium::Chrome) which will
force the selenium driver. It can also be used to pass on arguments to the
driver's constructor. Example:
MOJO_SELENIUM_DRIVER='Selenium::Remote::Driver&browser_name=firefox&port=4444'
The arguments will be read using ["parse" in Mojo::Parameters](https://metacpan.org/pod/Mojo::Parameters#parse), which means they
follow standard URL format rules.
## TEST\_SELENIUM
This variable must be set to a true value for ["setup\_or\_skip\_all"](#setup_or_skip_all) to not skip
this test. Will also set ["MOJO\_SELENIUM\_BASE\_URL"](#mojo_selenium_base_url) if it looks like an URL.
# ATTRIBUTES
## driver
$driver = $self->driver;
An instance of [Selenium::Remote::Driver](https://metacpan.org/pod/Selenium::Remote::Driver).
## driver\_args
$hash = $self->driver_args;
$self = $self->driver_args({driver_class => "Selenium::Chrome"});
Used to set args passed on to the ["driver"](#driver) on construction time. In addition,
a special key "driver\_class" can be set to use another driver class, than the
default.
Note that the environment variavble `MOJO_SELENIUM_DRIVER` can also be used to
override the driver class.
## screenshot\_directory
$path = $self->screenshot_directory;
$self = $self->screenshot_directory(File::Spec->tmpdir);
Where screenshots are saved.
## screenshots
$array = $self->screenshots;
Holds an array ref with paths to all the screenshots taken with
["capture\_screenshot"](#capture_screenshot).
# METHODS
## active\_element\_is
$self = $self->active_element_is("input[name=username]");
Checks that the current active element on the page match the selector.
## capture\_screenshot
$self = $self->capture_screenshot;
$self = $self->capture_screenshot("%t-page-x");
$self = $self->capture_screenshot("%0-%t-%n"); # default
Capture screenshot to ["screenshot\_directory"](#screenshot_directory) with filename specified by the
input format. The format supports these special strings:
Format | Description
-------|----------------------
%t | Start time for script
%0 | Name of script
%n | Auto increment
## click\_ok
$self = $self->click_ok("a");
$self = $self->click_ok;
Click on an element matching the selector or click on the currently active
element.
## current\_url\_is
$self = $self->current_url_is("http://mojolicious.org/");
$self = $self->current_url_is("/whatever");
Test the current browser URL against an absolute URL. A relative URL will be
converted to an absolute URL, using ["MOJO\_SELENIUM\_BASE\_URL"](#mojo_selenium_base_url).
## current\_url\_like
$self = $self->current_url_like(qr{/whatever});
Test the current browser URL against a regex.
## element\_is\_displayed
$self = $self->element_is_displayed("nav");
Test if an element is displayed on the web page.
See ["is\_displayed" in Selenium::Remote::WebElement](https://metacpan.org/pod/Selenium::Remote::WebElement#is_displayed).
## element\_is\_hidden
$self = $self->element_is_hidden("nav");
Test if an element is hidden on the web page.
See ["is\_hidden" in Selenium::Remote::WebElement](https://metacpan.org/pod/Selenium::Remote::WebElement#is_hidden).
## go\_back
$self = $self->go_back;
Equivalent to hitting the back button on the browser.
See ["go\_back" in Selenium::Remote::Driver](https://metacpan.org/pod/Selenium::Remote::Driver#go_back).
## go\_forward
$self = $self->go_forward;
Equivalent to hitting the forward button on the browser.
See ["go\_forward" in Selenium::Remote::Driver](https://metacpan.org/pod/Selenium::Remote::Driver#go_forward).
## if\_tx
$self = $self->if_tx(sub { ... }, @args);
$self = $self->if_tx($method, @args);
Call either a code ref or a method on `$self` if ["tx" in Test::Mojo](https://metacpan.org/pod/Test::Mojo#tx) is defined.
`tx()` is undefined if ["navigate\_ok"](#navigate_ok) is called on an external resource.
Examples:
$self->if_tx(status_is => 200);
## live\_element\_count\_is
$self = $self->live_element_count_is("a", 12);
Checks that the selector finds the correct number of elements in the browser.
See ["element\_count\_is" in Test::Mojo](https://metacpan.org/pod/Test::Mojo#element_count_is).
## live\_element\_exists
$self = $self->live_element_exists("div.content");
Checks that the selector finds an element in the browser.
See ["element\_exists" in Test::Mojo](https://metacpan.org/pod/Test::Mojo#element_exists).
## live\_element\_exists\_not
$self = $self->live_element_exists_not("div.content");
Checks that the selector does not find an element in the browser.
$self = $self->live_element_exists("div.foo");
See ["element\_exists\_not" in Test::Mojo](https://metacpan.org/pod/Test::Mojo#element_exists_not).
## live\_text\_is
$self = $self->live_text_is("div.name", "Mojo");
Checks text content of the CSS selectors first matching HTML element in the
browser matches the given string.
## live\_text\_like
$self = $self->live_text_is("div.name", qr{Mojo});
Checks text content of the CSS selectors first matching HTML element in the
browser matches the given regex.
## live\_value\_is
$self = $self->live_value_is("div.name", "Mojo");
Checks value of the CSS selectors first matching HTML element in the browser
matches the given string.
## live\_value\_like
$self = $self->live_value_like("div.name", qr{Mojo});
Checks value of the CSS selectors first matching HTML element in the browser
matches the given regex.
## navigate\_ok
$self = $self->navigate_ok("/");
$self = $self->navigate_ok("http://mojolicious.org/");
Open a browser window and go to the given location.
## new
$self = $class->new;
$self = $class->new($app);
Same as ["new" in Test::Mojo](https://metacpan.org/pod/Test::Mojo#new), but will not build `$app` if
["MOJO\_SELENIUM\_BASE\_URL"](#mojo_selenium_base_url) is set.
## refresh
$self = $self->refresh;
Equivalent to hitting the refresh button on the browser.
See ["refresh" in Selenium::Remote::Driver](https://metacpan.org/pod/Selenium::Remote::Driver#refresh).
## send\_keys\_ok
$self->send_keys_ok("input[name=name]", ["web", \"space", "framework"]);
$self->send_keys_ok(undef, [\"return"]);
Used to send keys to a given element. Scalar refs will be sent as
[Selenium::Remote::WDKeys](https://metacpan.org/pod/Selenium::Remote::WDKeys) strings. Passing in `undef` as the first argument
will cause the keys to be sent to the currently active element.
List of some of the special keys:
- alt, control, shift
- right\_arrow, down\_arrow, left\_arrow, up\_arrow
- backspace, clear, delete, enter, return, escape, space, tab
- f1, f2, ..., f12
- command\_meta, pause
## set\_window\_size
$self = $self->set_window_size([$width, $height]);
$self = $self->set_window_size([375, 667]);
Set the browser window size.
## setup\_or\_skip\_all
$self = $self->setup_or_skip_all;
Will ["skip\_all" in skip all#Test::More](https://metacpan.org/pod/skip all#Test::More#skip_all) tests unless `TEST_SELENIUM` is set and
and ["driver"](#driver) can be built.
Will also set ["MOJO\_SELENIUM\_BASE\_URL"](#mojo_selenium_base_url) if `TEST_SELENIUM` looks like a URL.
## submit\_ok
$self = $self->submit_ok("form");
Submit a form, either by selector or the current active form.
See ["submit" in Selenium::Remote::WebElement](https://metacpan.org/pod/Selenium::Remote::WebElement#submit).
## toggle\_checked\_ok
$self = $self->toggle_checked_ok("input[name=human]");
Used to toggle the "checked" attribute either with a click event or fallback to
javascript.
## wait\_for
$self = $self->wait_for(0.2);
$self = $self->wait_for('[name="agree"]', "test description");
$self = $self->wait_for('[name="agree"]:enabled', {interval => 1.5, timeout => 10});
$self = $self->wait_for('[name="agree"]:selected');
$self = $self->wait_for('[href="/"]:visible');
$self = $self->wait_for('[href="/hidden"]:hidden');
$self = $self->wait_for('[name=checkbox]:checked');
Simpler version of ["wait\_until"](#wait_until) for the most common use cases:
- Number
Allows the browser and server to run for a given interval in seconds. This is
useful if you want the browser to receive data from the server or simply let
`setTimeout()` in JavaScript run.
- String
Wait for an element matching the CSS selector with some additional modifiers:
[:enabled](https://metacpan.org/pod/Selenium::Remote::WebElement#is_enabled),
[:hidden](https://metacpan.org/pod/Selenium::Remote::WebElement#is_hidden),
[:selected](https://metacpan.org/pod/Selenium::Remote::WebElement#is_selected) and
[:visible](https://metacpan.org/pod/Selenium::Remote::WebElement#is_displayed).
Check out [Selenium::Remote::WebElement](https://metacpan.org/pod/Selenium::Remote::WebElement) for details about the modifiers.
## wait\_until
$self = $self->wait_until(sub { my $self = shift; return 1 }, \%args);
$self = $self->wait_until(sub { $_->get_current_url =~ /foo/ }, \%args);
# Use it as a sleep(0.8)
$self = $self->wait_until(sub { 0 }, {timeout => 0.8, skip => 1});
Start [Mojo::IOLoop](https://metacpan.org/pod/Mojo::IOLoop) and run it until the callback returns true. Note that
`$_[0]` is `$self` and `$_` is ["driver"](#driver). `%args` is optional, but can
contain these values:
{
interval => $seconds, # Default: 0.5
timeout => $seconds, # Default: 60
skip => $bool, # Default: 0
}
## window\_size\_is
$self = $self->window_size_is([$width, $height]);
$self = $self->window_size_is([375, 667]);
Test if window has the expected width and height.
# AUTHOR
Jan Henning Thorsen
# COPYRIGHT AND LICENSE
Copyright (C) 2014, Jan Henning Thorsen
This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.
# SEE ALSO
[Test::Mojo](https://metacpan.org/pod/Test::Mojo).
[Selenium::Remote::Driver](https://metacpan.org/pod/Selenium::Remote::Driver)