# 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%3A%3AMojo%3A%3ARole%3A%3ASelenium) is a role that extends [Test::Mojo](https://metacpan.org/pod/Test%3A%3AMojo) with additional methods which checks behaviour in a browser. All the heavy lifting is done by [Selenium::Remote::Driver](https://metacpan.org/pod/Selenium%3A%3ARemote%3A%3ADriver). Some of the [Selenium::Remote::Driver](https://metacpan.org/pod/Selenium%3A%3ARemote%3A%3ADriver) 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%3A%3AMojo%3A%3ARole%3A%3ASelenium). # OPTIONAL DEPENDENCIES [Selenium::Remote::Driver](https://metacpan.org/pod/Selenium%3A%3ARemote%3A%3ADriver) 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%3A%3AChrome) # 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%3A%3AMojo#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%3A%3AMojo#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%3A%3AMojo) methods such as ["status\_is"](#status_is), since ["tx" in Test::Mojo](https://metacpan.org/pod/Test%3A%3AMojo#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%3A%3AMojo) methods. ## MOJO\_SELENIUM\_DRIVER This variable can be set to a classname, such as [Selenium::Chrome](https://metacpan.org/pod/Selenium%3A%3AChrome) 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%3A%3AParameters#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%3A%3ARemote%3A%3ADriver). ## 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%3A%3ARemote%3A%3AWebElement#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%3A%3ARemote%3A%3AWebElement#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%3A%3ARemote%3A%3ADriver#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%3A%3ARemote%3A%3ADriver#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%3A%3AMojo#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%3A%3AMojo#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%3A%3AMojo#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%3A%3AMojo#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%3A%3AMojo#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%3A%3ARemote%3A%3ADriver#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%3A%3ARemote%3A%3AWDKeys) 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%20all%23Test%3A%3AMore#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%3A%3ARemote%3A%3AWebElement#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%3A%3ARemote%3A%3AWebElement%23is_enabled), [:hidden](https://metacpan.org/pod/Selenium%3A%3ARemote%3A%3AWebElement%23is_hidden), [:selected](https://metacpan.org/pod/Selenium%3A%3ARemote%3A%3AWebElement%23is_selected) and [:visible](https://metacpan.org/pod/Selenium%3A%3ARemote%3A%3AWebElement%23is_displayed). Check out [Selenium::Remote::WebElement](https://metacpan.org/pod/Selenium%3A%3ARemote%3A%3AWebElement) 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%3A%3AIOLoop) 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%3A%3AMojo). [Selenium::Remote::Driver](https://metacpan.org/pod/Selenium%3A%3ARemote%3A%3ADriver)