File Coverage

blib/lib/Test/Mojo/Role/Selenium.pm
Criterion Covered Total %
statement 82 258 31.7
branch 19 84 22.6
condition 22 60 36.6
subroutine 21 54 38.8
pod 28 29 96.5
total 172 485 35.4


line stmt bran cond sub pod time code
1             package Test::Mojo::Role::Selenium;
2 8     8   65152 use Mojo::Base -base;
  8         19  
  8         60  
3 8     8   1507 use Role::Tiny;
  8         17  
  8         63  
4              
5 8     8   1393 use Carp 'croak';
  8         18  
  8         393  
6 8     8   55 use File::Basename ();
  8         24  
  8         155  
7 8     8   40 use File::Spec;
  8         17  
  8         223  
8 8     8   56 use Mojo::Parameters;
  8         17  
  8         67  
9 8     8   268 use Mojo::Util 'encode';
  8         41  
  8         454  
10 8     8   3872 use Selenium::Remote::WDKeys ();
  8         4815  
  8         289  
11              
12 8   50 8   54 use constant DEBUG => $ENV{MOJO_SELENIUM_DEBUG} || 0;
  8         17  
  8         531  
13 8   50 8   45 use constant WAIT_INTERVAL => $ENV{MOJO_SELENIUM_WAIT_INTERVAL} || 0.5;
  8         18  
  8         479  
14 8   50 8   48 use constant WAIT_TIMEOUT => $ENV{MOJO_SELENIUM_WAIT_TIMEOUT} || 60;
  8         18  
  8         38811  
15              
16             $ENV{TEST_SELENIUM} //= '0';
17             $ENV{MOJO_SELENIUM_BASE_URL} ||= $ENV{TEST_SELENIUM} =~ /^http/ ? $ENV{TEST_SELENIUM} : '';
18              
19 0     0 0 0 sub S { Mojo::JSON::encode_json($_[0]) }
20              
21             our $VERSION = '0.16';
22              
23             my $SCRIPT_NAME = File::Basename::basename($0);
24             my $SCREENSHOT = 1;
25              
26             has driver => sub {
27             my $self = shift;
28             my $args = $self->driver_args;
29             my ($driver, $env) = split /\&/, +($ENV{MOJO_SELENIUM_DRIVER} || ''), 2;
30             $env = Mojo::Parameters->new($env || '')->to_hash;
31             $driver ||= $args->{driver_class} || 'Selenium::Chrome';
32             eval "require $driver;1" or croak "require $driver: $@";
33             warn "[Selenium] Using $driver\n" if DEBUG;
34             $driver = $driver->new(%$args, %$env, ua => $self->ua);
35             $driver->debug_on if DEBUG > 1;
36             $driver->default_finder('css');
37             $driver;
38             };
39              
40             has driver_args => sub { +{} };
41             has screenshot_directory => sub { File::Spec->tmpdir };
42             has screenshots => sub { +[] };
43              
44             has _live_base => sub {
45             my $self = shift;
46             return Mojo::URL->new($ENV{MOJO_SELENIUM_BASE_URL}) if $ENV{MOJO_SELENIUM_BASE_URL};
47             $self->{live_port} = Mojo::IOLoop::Server->generate_port;
48             my $test_hostname = $ENV{MOJO_SELENIUM_TEST_HOST} || '127.0.0.1';
49             return Mojo::URL->new("http://${test_hostname}:$self->{live_port}");
50             };
51              
52             has _live_server => sub {
53             my $self = shift;
54             my $app = $self->app or croak 'Cannot start server without $t->app(...) set';
55             my $server = Mojo::Server::Daemon->new(silent => DEBUG ? 0 : 1);
56              
57             Scalar::Util::weaken($self);
58             $server->on(
59             request => sub {
60             my ($server, $tx) = @_;
61             $self->tx($tx) if $tx->req->url->to_abs eq $self->_live_url;
62             }
63             );
64              
65             $server->app($app)->listen([$self->_live_base->to_string])
66             ->start->ioloop->acceptor($server->acceptors->[0]);
67              
68             return $server;
69             };
70              
71             has _live_url => sub { Mojo::URL->new };
72              
73             sub active_element_is {
74 0     0 1 0 my ($self, $selector, $desc) = @_;
75 0         0 my $driver = $self->driver;
76 0         0 my $active = $driver->get_active_element;
77 0         0 my $el = $self->_find_element($selector);
78 0 0 0     0 my $same = $active && $el ? $driver->compare_elements($active, $el) : 0;
79              
80 0         0 return $self->_test('ok', $same, _desc($desc, "active element is @{[S $selector]}"));
  0         0  
81             }
82              
83             sub capture_screenshot {
84 0     0 1 0 my ($self, $path) = @_;
85 0 0       0 $path = _screenshot_name($path ? "$path.png" : "%0-%t-%n.png");
86 0         0 $path = File::Spec->catfile($self->screenshot_directory, $path);
87 0         0 Test::More::diag("Saving screenshot to $path");
88 0         0 $self->driver->capture_screenshot($path);
89 0         0 push @{$self->screenshots}, $path;
  0         0  
90 0         0 return $self;
91             }
92              
93             sub click_ok {
94 0     0 1 0 my ($self, $selector) = @_;
95 0 0       0 my $el = $selector ? $self->_find_element($selector) : $self->driver->get_active_element;
96 0         0 my $err = 'no such element';
97              
98 0 0       0 if ($el) {
99 0 0       0 eval { $self->driver->mouse_move_to_location(element => $el) } unless $el->is_displayed;
  0         0  
100 0   0     0 $err = $@ || 'unable to click';
101 0 0       0 $err = '' if $el->click;
102             }
103              
104 0         0 return $self->_test('ok', !$err, _desc("click on @{[S $selector]} $err"));
  0         0  
105             }
106              
107             sub current_url_is {
108 0     0 1 0 my $self = shift;
109 0         0 my $url = $self->_live_abs_url(shift);
110              
111 0         0 return $self->_test('is', $self->driver->get_current_url,
112             $url->to_string, _desc('exact match for current url'));
113             }
114              
115             sub current_url_like {
116 0     0 1 0 my ($self, $match, $desc) = @_;
117 0         0 return $self->_test('like', $self->driver->get_current_url,
118             $match, _desc($desc, 'current url is similar'));
119             }
120              
121             sub element_is_displayed {
122 0     0 1 0 my ($self, $selector, $desc) = @_;
123 0         0 my $el = $self->_find_element($selector);
124 0   0     0 return $self->_test(
125             'ok',
126             ($el && $el->is_displayed),
127 0         0 _desc($desc, "element @{[S $selector]} is displayed")
128             );
129             }
130              
131             sub element_is_hidden {
132 0     0 1 0 my ($self, $selector, $desc) = @_;
133 0         0 my $el = $self->_find_element($selector);
134 0   0     0 return $self->_test('ok', ($el && $el->is_hidden),
135 0         0 _desc($desc, "element @{[S $selector]} is hidden"));
136             }
137              
138 0     0 1 0 sub go_back { $_[0]->_proxy('go_back'); $_[0] }
  0         0  
139 0     0 1 0 sub go_forward { $_[0]->_proxy('go_forward'); $_[0] }
  0         0  
140              
141             sub if_tx {
142 0     0 1 0 my ($self, $method) = (shift, shift);
143              
144             SKIP: {
145 0 0       0 my $desc = ref $method ? '__SUB__' : $method;
  0         0  
146 0 0       0 Test::More::skip("\$t->tx() is not defined ($desc)", 1) unless $self->tx;
147 0         0 $self->$method(@_);
148             }
149              
150 0         0 return $self;
151             }
152              
153             sub live_element_count_is {
154 0     0 1 0 my ($self, $selector, $count, $desc) = @_;
155 0         0 my $els = $self->_proxy(find_elements => $selector);
156 0         0 return $self->_test('is', int(@$els), $count,
157 0         0 _desc($desc, "element count for selector @{[S $selector]}"));
158             }
159              
160             sub live_element_exists {
161 0     0 1 0 my ($self, $selector, $desc) = @_;
162 0         0 $desc = _desc($desc, "element for selector @{[S $selector]} exists");
  0         0  
163 0         0 return $self->_test('ok', $self->_find_element($selector), $desc);
164             }
165              
166             sub live_element_exists_not {
167 0     0 1 0 my ($self, $selector, $desc) = @_;
168 0         0 $desc = _desc($desc, "no element for selector @{[S $selector]}");
  0         0  
169 0         0 return $self->_test('ok', !$self->_find_element($selector), $desc);
170             }
171              
172             sub live_text_is {
173 0     0 1 0 my ($self, $selector, $value, $desc) = @_;
174 0         0 return $self->_test(
175             'is', $self->_element_data(get_text => $selector),
176 0         0 $value, _desc($desc, "exact text for selector @{[S $selector]}")
177             );
178             }
179              
180             sub live_text_like {
181 0     0 1 0 my ($self, $selector, $regex, $desc) = @_;
182 0         0 return $self->_test(
183             'like', $self->_element_data(get_text => $selector),
184 0         0 $regex, _desc($desc, "similar text for selector @{[S $selector]}")
185             );
186             }
187              
188             sub live_value_is {
189 0     0 1 0 my ($self, $selector, $value, $desc) = @_;
190 0         0 return $self->_test(
191             'is', $self->_element_data(get_value => $selector),
192 0         0 $value, _desc($desc, "exact value for selector @{[S $selector]}")
193             );
194             }
195              
196             sub live_value_like {
197 0     0 1 0 my ($self, $selector, $regex, $desc) = @_;
198 0         0 return $self->_test(
199             'like', $self->_element_data(get_value => $selector),
200 0         0 $regex, _desc($desc, "similar value for selector @{[S $selector]}")
201             );
202             }
203              
204             sub navigate_ok {
205 3     3 1 34683 my $self = shift;
206 3         15 my $url = $self->_live_abs_url(shift);
207 3         7 my ($desc, $err);
208              
209 3         24 $self->tx(undef)->_live_url($url);
210 3 100       56 $self->_live_server if $self->{live_port}; # Make sure server is running
211 3         17 $self->driver->get($url->to_string);
212              
213 3 50       986 if ($self->tx) {
214 0         0 $desc = "navigate to $url";
215 0         0 $err = $self->tx->error;
216 0 0 0     0 Test::More::diag($err->{message}) if $err and $err->{message};
217             }
218             else {
219 3         25 $desc = "navigate to $url (\$t->tx() is not set)";
220             }
221              
222 3         523 return $self->_test('ok', !$err, _desc($desc));
223             }
224              
225             sub new {
226 13     13 1 31892 my $self = shift->SUPER::new;
227 13         133 $self->ua(Test::Mojo::Role::Selenium::UserAgent->new->ioloop(Mojo::IOLoop->singleton));
228 13 100       444 return $self if $ENV{MOJO_SELENIUM_BASE_URL};
229 12 100       100 return $self unless my $app = shift;
230 3 100       12 my @args = @_ ? {config => {config_override => 1, %{shift()}}} : ();
  1         22  
231 3 50       20 return $self->app(ref $app ? $app : Mojo::Server->new->build_app($app, @args));
232             }
233              
234 0     0 1 0 sub refresh { $_[0]->_proxy('refresh'); $_[0] }
  0         0  
235              
236             sub send_keys_ok {
237 0     0 1 0 my ($self, $selector, $keys, $desc) = @_;
238 0 0       0 my $el = $selector ? $self->_find_element($selector) : $self->driver->get_active_element;
239              
240 0   0     0 $selector ||= 'active element';
241 0 0       0 $keys = [ref $keys ? $keys : split //, $keys] unless ref $keys eq 'ARRAY';
    0          
242              
243 0         0 for (@$keys) {
244 0 0       0 my $key = ref $_ ? Selenium::Remote::WDKeys::KEYS()->{$$_} : $_;
245 0 0       0 croak "Invalid key '@{[ref $_ ? $$_ : $_]}'" unless defined $key;
  0 0       0  
246 0         0 $_ = $key;
247             }
248              
249 0 0       0 if ($el) {
250             eval {
251 0         0 for my $key (@$keys) {
252 0         0 warn "[Selenium] send_keys $selector <- @{[Mojo::Util::url_escape($key)]}\n" if DEBUG;
253 0         0 $el->send_keys($key);
254             }
255 0         0 1;
256 0 0       0 } or do {
257 0         0 Test::More::diag($@);
258 0         0 $el = undef;
259             };
260             }
261              
262 0         0 $keys = Mojo::Util::url_escape(join '', @$keys);
263 0         0 return $self->_test('ok', $el, _desc($desc, "keys ($keys) sent to @{[S $selector]}"));
  0         0  
264             }
265              
266             sub set_window_size {
267 0     0 1 0 my ($self, $size, $desc) = @_;
268 0         0 $self->driver->set_window_size(reverse @$size);
269 0         0 return $self;
270             }
271              
272             sub setup_or_skip_all {
273 7     7 1 3060 my $self = shift;
274              
275 7         12 local $@;
276             Test::More::plan(skip_all => $@ || 'TEST_SELENIUM=1 or TEST_SELENIUM=http://...')
277 7 100 100     58 unless $ENV{TEST_SELENIUM} and eval { $self->driver };
  3   100     13  
278              
279 4 100 33     60 $ENV{MOJO_SELENIUM_BASE_URL} ||= $ENV{TEST_SELENIUM} if $ENV{TEST_SELENIUM} =~ /^http/;
280              
281 4         11 return $self;
282             }
283              
284             sub submit_ok {
285 0     0 1 0 my ($self, $selector, $desc) = @_;
286 0         0 my $el = $self->_find_element($selector);
287 0 0       0 $el->submit if $el;
288 0         0 return $self->_test('ok', $el, _desc($desc, "click on @{[S $selector]}"));
  0         0  
289             }
290              
291             sub toggle_checked_ok {
292 0     0 1 0 my ($self, $selector) = @_;
293 0         0 my $el = $self->_find_element($selector);
294              
295 0 0       0 if ($el) {
296 0 0       0 if ($el->is_displayed) {
297 0         0 $el->click;
298             }
299             else {
300 0         0 my $sel = $selector;
301 0         0 $sel =~ s!"!\\"!g;
302 0         0 $self->driver->execute_script(
303             qq[var el=document.querySelector("$sel");el.setAttribute("checked", !el.getAttribute("checked"))]
304             );
305             }
306             }
307              
308 0         0 return $self->_test('ok', $el, _desc("click on @{[S $selector]}"));
  0         0  
309             }
310              
311             sub wait_for {
312 0     0 1 0 my $self = shift;
313 0         0 my $sel = shift;
314 0 0       0 my $args = ref $_[0] eq 'HASH' ? shift : {};
315 0   0     0 my $desc = shift || "waited for element $sel";
316 0         0 my @checks;
317              
318 0 0       0 if (Scalar::Util::looks_like_number($sel)) {
319 0     0   0 $self->ua->ioloop->timer($sel => sub { shift->stop });
  0         0  
320 0         0 $self->ua->ioloop->start;
321 0         0 return $self;
322             }
323              
324 0 0       0 push @checks, 'is_displayed' if $sel =~ s!:visible\b!!;
325 0 0       0 push @checks, 'is_enabled' if $sel =~ s!:enabled\b!!;
326 0 0       0 push @checks, 'is_hidden' if $sel =~ s!:hidden\b!!;
327 0 0       0 push @checks, 'is_selected' if $sel =~ s!:selected\b!!;
328              
329 0         0 my $driver = $self->driver;
330 0         0 my $prev_implicit_timeout = $driver->get_timeouts->{implicit};
331 0   0     0 $driver->set_timeout(implicit => $args->{timeout} || WAIT_TIMEOUT);
332              
333 0         0 my $ok;
334             $self->wait_until(
335             sub {
336 0     0   0 my $e = $_->find_element($sel);
337 0   0     0 return $ok = $e && @checks == grep { $e->$_ } @checks;
338             },
339 0         0 {%$args, skip => 1},
340             );
341              
342 0         0 $driver->set_timeout(implicit => $prev_implicit_timeout);
343              
344 0         0 return $self->_test('ok', $ok, _desc($desc));
345             }
346              
347             sub wait_until {
348 4     4 1 2905 my ($self, $cb, $args) = @_;
349 4         21 my $ioloop = $self->ua->ioloop;
350              
351 4         44 my $err;
352             my @tid = (
353 3     3   303156 $ioloop->timer($args->{timeout} || WAIT_TIMEOUT, sub { $err = 'Timeout' }),
354             $ioloop->recurring(
355             $args->{interval} || WAIT_INTERVAL,
356             sub {
357 25 100 100 25   5706408 return shift->stop if $err || eval { local $_ = $self->driver; $self->$cb($args) };
  22         161  
  22         228  
358 21 50 50     1134 Test::More::diag("[Selenium] wait_until: $@") if $@ and ($args->{debug} or DEBUG);
      66        
359             }
360 4   100     41 ),
      100        
361             );
362              
363 4         640 my $t0 = time;
364 4         19 $ioloop->start;
365 4         301 $ioloop->remove($_) for @tid;
366              
367 4 50       389 return $self if $args->{skip};
368 4   33     32 return $self->_test('ok', !$err, _desc($args->{desc} || "waited for @{[time - $t0]}s"));
369             }
370              
371             sub window_size_is {
372 0     0 1 0 my ($self, $exp, $desc) = @_;
373 0         0 my $size = $self->driver->get_window_size;
374              
375 0         0 return $self->_test('is_deeply', [@$size{qw(width height)}],
376             $exp, _desc($desc, "window size is $exp->[0]x$exp->[1]"));
377             }
378              
379 7   33 7   53 sub _desc { encode 'UTF-8', shift || shift }
380              
381             sub _find_element {
382 0     0   0 my ($self, $selector) = @_;
383 0 0       0 return $self->_proxy(find_element => $selector) unless ref $selector;
384              
385 0         0 my ($by) = keys %$selector;
386 0 0       0 return $self->_proxy("find_element_by_$by" => $selector->{$by}) unless ref $selector;
387             }
388              
389             sub _live_abs_url {
390 3     3   8 my $self = shift;
391 3         18 my $url = Mojo::URL->new(shift);
392              
393 3 50       292 unless ($url->is_abs) {
394 3         38 my $base = $self->_live_base;
395 3         513 $url->scheme($base->scheme)->host($base->host)->port($base->port);
396             }
397              
398 3         61 return $url;
399             }
400              
401             sub _proxy {
402 0     0     my ($self, $method) = (shift, shift);
403 0           my $res = eval { $self->driver->$method(@_) };
  0            
404 0           warn $@ if DEBUG and $@;
405 0           return $res;
406             }
407              
408             sub _element_data {
409 0     0     my ($self, $method) = (shift, shift);
410 0           my $el = $self->_find_element(shift);
411 0 0         return $el ? $el->$method : '';
412             }
413              
414             sub _screenshot_name {
415 0     0     local $_ = shift;
416 0           s!\%0\b!{$SCRIPT_NAME}!ge;
  0            
  0            
417 0           s!\%n\b!{sprintf '%04s', $SCREENSHOT++}!ge;
  0            
  0            
418 0           s!\%t\b!{$^T}!ge;
  0            
  0            
419 0           return $_;
420             }
421              
422             package # hide from pause
423             Test::Mojo::Role::Selenium::UserAgent;
424 8     8   85 use Mojo::Base 'Mojo::UserAgent';
  8         29  
  8         56  
425              
426 8   50 8   1456 use constant DEBUG => $ENV{MOJO_SELENIUM_DEBUG} || 0;
  8         100  
  8         3297  
427              
428             sub request {
429 0     0     my ($ua, $req) = @_;
430 0   0       my $method = uc($req->method || 'get');
431 0           my $tx = $ua->build_tx($method, $req->uri->as_string, {$req->headers->flatten}, $req->content);
432 0           my $done;
433              
434 0           warn "[Selenium] $method @{[$req->uri->as_string]}\n" if DEBUG;
435              
436             # This is super ugly and need to be implemented differently,
437             # but I'm not sure how to implement wait_until() without this
438             # one_tick() hack.
439 0 0         if ($ua->ioloop->is_running) {
440 0     0     $ua->start($tx, sub { $done = 1 });
  0            
441 0           $ua->ioloop->reactor->one_tick until $done;
442             }
443             else {
444 0           $ua->start($tx);
445             }
446              
447 0           return HTTP::Response->parse($tx->res->to_string);
448             }
449              
450             # Should not say "... during global destruction."
451             # sub DESTROY { warn 'no circular refs?' }
452              
453             1;
454              
455             =encoding utf8
456              
457             =head1 NAME
458              
459             Test::Mojo::Role::Selenium - Test::Mojo in a real browser
460              
461             =head1 SYNOPSIS
462              
463             =head2 External app
464              
465             use Mojo::Base -strict;
466             use Test::More;
467              
468             $ENV{MOJO_SELENIUM_BASE_URL} ||= 'http://mojolicious.org';
469             $ENV{MOJO_SELENIUM_DRIVER} ||= 'Selenium::Chrome';
470              
471             my $t = Test::Mojo->with_roles("+Selenium")->new->setup_or_skip_all;
472              
473             $t->navigate_ok('/perldoc')
474             ->live_text_is('a[href="#GUIDES"]' => 'GUIDES');
475              
476             $t->driver->execute_script(qq[document.querySelector("form").removeAttribute("target")]);
477             $t->element_is_displayed("input[name=q]")
478             ->send_keys_ok("input[name=q]", ["render", \"return"]);
479              
480             $t->wait_until(sub { $_->get_current_url =~ qr{q=render} })
481             ->live_value_is("input[name=search]", "render");
482              
483             done_testing;
484              
485             =head2 Internal app
486              
487             use Mojo::Base -strict;
488             use Test::More;
489              
490             my $t = Test::Mojo->with_roles("+Selenium")->new("MyApp")->setup_or_skip_all;
491              
492             # All the standard Test::Mojo methods are available
493             ok $t->isa("Test::Mojo");
494             ok $t->does("Test::Mojo::Role::Selenium");
495              
496             $t->navigate_ok("/")
497             ->status_is(200)
498             ->header_is("Server" => "Mojolicious (Perl)")
499             ->text_is("div#message" => "Hello!")
500             ->live_text_is("div#message" => "Hello!")
501             ->live_element_exists("nav")
502             ->element_is_displayed("nav")
503             ->active_element_is("input[name=q]")
504             ->send_keys_ok("input[name=q]", "Mojo")
505             ->capture_screenshot;
506              
507             $t->submit_ok("form")
508             ->status_is(200)
509             ->current_url_like(qr{q=Mojo})
510             ->live_element_exists("input[name=q][value=Mojo]");
511              
512             $t->click_ok("nav a.logo")->status_is(200);
513              
514             done_testing;
515              
516             =head1 DESCRIPTION
517              
518             L is a role that extends L with
519             additional methods which checks behaviour in a browser. All the heavy lifting
520             is done by L.
521              
522             Some of the L methods are available directly in this
523             role, while the rest are available through the object held by the L
524             attribute. Please create an issue if you think more tests or methods should be
525             provided directly by L.
526              
527             =head1 OPTIONAL DEPENDENCIES
528              
529             L require some external dependencies to work. Here
530             are a quick intro to install some of the dependencies to make this module work.
531              
532             =over 2
533              
534             =item * L
535              
536             # macOS
537             $ brew install chromedriver
538              
539             # Ubuntu
540             $ sudo apt-get install chromium-chromedriver
541              
542             # Run tests
543             $ MOJO_SELENIUM_DRIVER=Selenium::Chrome prove -l
544              
545             =back
546              
547             =head1 CAVEAT
548              
549             L is only populated, if the request went through an L.
550             This means that methods such as L will not work or
551             probably fail completely when testing an L.
552              
553             =head1 ENVIRONMENT VARIABLES
554              
555             =head2 MOJO_SELENIUM_BASE_URL
556              
557             Setting this variable will make this test send the requests to a remote server,
558             instead of starting a local server. Note that this will disable L
559             methods such as L, since L will not be set. See
560             also L.
561              
562             This variable will get the value of L if it looks like a URL.
563              
564             =head2 MOJO_SELENIUM_TEST_HOST
565              
566             In some cases you may want to override the host of your test server, when
567             running Selenium on a separate server or in a pod-style networking environment
568             this still retains the automatically generated port. This will not disable the
569             L methods.
570              
571             =head2 MOJO_SELENIUM_DRIVER
572              
573             This variable can be set to a classname, such as L which will
574             force the selenium driver. It can also be used to pass on arguments to the
575             driver's constructor. Example:
576              
577             MOJO_SELENIUM_DRIVER='Selenium::Remote::Driver&browser_name=firefox&port=4444'
578              
579             The arguments will be read using L, which means they
580             follow standard URL format rules.
581              
582             =head2 TEST_SELENIUM
583              
584             This variable must be set to a true value for L to not skip
585             this test. Will also set L if it looks like an URL.
586              
587             =head1 ATTRIBUTES
588              
589             =head2 driver
590              
591             $driver = $self->driver;
592              
593             An instance of L.
594              
595             =head2 driver_args
596              
597             $hash = $self->driver_args;
598             $self = $self->driver_args({driver_class => "Selenium::Chrome"});
599              
600             Used to set args passed on to the L on construction time. In addition,
601             a special key "driver_class" can be set to use another driver class, than the
602             default.
603              
604             Note that the environment variavble C can also be used to
605             override the driver class.
606              
607             =head2 screenshot_directory
608              
609             $path = $self->screenshot_directory;
610             $self = $self->screenshot_directory(File::Spec->tmpdir);
611              
612             Where screenshots are saved.
613              
614             =head2 screenshots
615              
616             $array = $self->screenshots;
617              
618             Holds an array ref with paths to all the screenshots taken with
619             L.
620              
621             =head1 METHODS
622              
623             =head2 active_element_is
624              
625             $self = $self->active_element_is("input[name=username]");
626              
627             Checks that the current active element on the page match the selector.
628              
629             =head2 capture_screenshot
630              
631             $self = $self->capture_screenshot;
632             $self = $self->capture_screenshot("%t-page-x");
633             $self = $self->capture_screenshot("%0-%t-%n"); # default
634              
635             Capture screenshot to L with filename specified by the
636             input format. The format supports these special strings:
637              
638             Format | Description
639             -------|----------------------
640             %t | Start time for script
641             %0 | Name of script
642             %n | Auto increment
643              
644             =head2 click_ok
645              
646             $self = $self->click_ok("a");
647             $self = $self->click_ok;
648              
649             Click on an element matching the selector or click on the currently active
650             element.
651              
652             =head2 current_url_is
653              
654             $self = $self->current_url_is("http://mojolicious.org/");
655             $self = $self->current_url_is("/whatever");
656              
657             Test the current browser URL against an absolute URL. A relative URL will be
658             converted to an absolute URL, using L.
659              
660             =head2 current_url_like
661              
662             $self = $self->current_url_like(qr{/whatever});
663              
664             Test the current browser URL against a regex.
665              
666             =head2 element_is_displayed
667              
668             $self = $self->element_is_displayed("nav");
669              
670             Test if an element is displayed on the web page.
671              
672             See L.
673              
674             =head2 element_is_hidden
675              
676             $self = $self->element_is_hidden("nav");
677              
678             Test if an element is hidden on the web page.
679              
680             See L.
681              
682             =head2 go_back
683              
684             $self = $self->go_back;
685              
686             Equivalent to hitting the back button on the browser.
687              
688             See L.
689              
690             =head2 go_forward
691              
692             $self = $self->go_forward;
693              
694             Equivalent to hitting the forward button on the browser.
695              
696             See L.
697              
698             =head2 if_tx
699              
700             $self = $self->if_tx(sub { ... }, @args);
701             $self = $self->if_tx($method, @args);
702              
703             Call either a code ref or a method on C<$self> if L is defined.
704             C is undefined if L is called on an external resource.
705              
706             Examples:
707              
708             $self->if_tx(status_is => 200);
709              
710             =head2 live_element_count_is
711              
712             $self = $self->live_element_count_is("a", 12);
713              
714             Checks that the selector finds the correct number of elements in the browser.
715              
716             See L.
717              
718             =head2 live_element_exists
719              
720             $self = $self->live_element_exists("div.content");
721              
722             Checks that the selector finds an element in the browser.
723              
724             See L.
725              
726             =head2 live_element_exists_not
727              
728             $self = $self->live_element_exists_not("div.content");
729              
730             Checks that the selector does not find an element in the browser.
731              
732             $self = $self->live_element_exists("div.foo");
733              
734             See L.
735              
736             =head2 live_text_is
737              
738             $self = $self->live_text_is("div.name", "Mojo");
739              
740             Checks text content of the CSS selectors first matching HTML element in the
741             browser matches the given string.
742              
743             =head2 live_text_like
744              
745             $self = $self->live_text_is("div.name", qr{Mojo});
746              
747             Checks text content of the CSS selectors first matching HTML element in the
748             browser matches the given regex.
749              
750             =head2 live_value_is
751              
752             $self = $self->live_value_is("div.name", "Mojo");
753              
754             Checks value of the CSS selectors first matching HTML element in the browser
755             matches the given string.
756              
757             =head2 live_value_like
758              
759             $self = $self->live_value_like("div.name", qr{Mojo});
760              
761             Checks value of the CSS selectors first matching HTML element in the browser
762             matches the given regex.
763              
764             =head2 navigate_ok
765              
766             $self = $self->navigate_ok("/");
767             $self = $self->navigate_ok("http://mojolicious.org/");
768              
769             Open a browser window and go to the given location.
770              
771             =head2 new
772              
773             $self = $class->new;
774             $self = $class->new($app);
775              
776             Same as L, but will not build C<$app> if
777             L is set.
778              
779             =head2 refresh
780              
781             $self = $self->refresh;
782              
783             Equivalent to hitting the refresh button on the browser.
784              
785             See L.
786              
787             =head2 send_keys_ok
788              
789             $self->send_keys_ok("input[name=name]", ["web", \"space", "framework"]);
790             $self->send_keys_ok(undef, [\"return"]);
791              
792             Used to send keys to a given element. Scalar refs will be sent as
793             L strings. Passing in C as the first argument
794             will cause the keys to be sent to the currently active element.
795              
796             List of some of the special keys:
797              
798             =over 2
799              
800             =item * alt, control, shift
801              
802             =item * right_arrow, down_arrow, left_arrow, up_arrow
803              
804             =item * backspace, clear, delete, enter, return, escape, space, tab
805              
806             =item * f1, f2, ..., f12
807              
808             =item * command_meta, pause
809              
810             =back
811              
812             =head2 set_window_size
813              
814             $self = $self->set_window_size([$width, $height]);
815             $self = $self->set_window_size([375, 667]);
816              
817             Set the browser window size.
818              
819             =head2 setup_or_skip_all
820              
821             $self = $self->setup_or_skip_all;
822              
823             Will L tests unless C is set and
824             and L can be built.
825              
826             Will also set L if C looks like a URL.
827              
828             =head2 submit_ok
829              
830             $self = $self->submit_ok("form");
831              
832             Submit a form, either by selector or the current active form.
833              
834             See L.
835              
836             =head2 toggle_checked_ok
837              
838             $self = $self->toggle_checked_ok("input[name=human]");
839              
840             Used to toggle the "checked" attribute either with a click event or fallback to
841             javascript.
842              
843             =head2 wait_for
844              
845             $self = $self->wait_for(0.2);
846             $self = $self->wait_for('[name="agree"]', "test description");
847             $self = $self->wait_for('[name="agree"]:enabled', {interval => 1.5, timeout => 10});
848             $self = $self->wait_for('[name="agree"]:selected');
849             $self = $self->wait_for('[href="/"]:visible');
850             $self = $self->wait_for('[href="/hidden"]:hidden');
851             $self = $self->wait_for('[name=checkbox]:checked');
852              
853             Simpler version of L for the most common use cases:
854              
855             =over 2
856              
857             =item Number
858              
859             Allows the browser and server to run for a given interval in seconds. This is
860             useful if you want the browser to receive data from the server or simply let
861             C in JavaScript run.
862              
863             =item String
864              
865             Wait for an element matching the CSS selector with some additional modifiers:
866             L<:enabled|Selenium::Remote::WebElement#is_enabled>,
867             L<:hidden|Selenium::Remote::WebElement#is_hidden>,
868             L<:selected|Selenium::Remote::WebElement#is_selected> and
869             L<:visible|Selenium::Remote::WebElement#is_displayed>.
870              
871             Check out L for details about the modifiers.
872              
873             =back
874              
875             =head2 wait_until
876              
877             $self = $self->wait_until(sub { my $self = shift; return 1 }, \%args);
878             $self = $self->wait_until(sub { $_->get_current_url =~ /foo/ }, \%args);
879              
880             # Use it as a sleep(0.8)
881             $self = $self->wait_until(sub { 0 }, {timeout => 0.8, skip => 1});
882              
883             Start L and run it until the callback returns true. Note that
884             C<$_[0]> is C<$self> and C<$_> is L. C<%args> is optional, but can
885             contain these values:
886              
887             {
888             interval => $seconds, # Default: 0.5
889             timeout => $seconds, # Default: 60
890             skip => $bool, # Default: 0
891             }
892              
893             =head2 window_size_is
894              
895             $self = $self->window_size_is([$width, $height]);
896             $self = $self->window_size_is([375, 667]);
897              
898             Test if window has the expected width and height.
899              
900             =head1 AUTHOR
901              
902             Jan Henning Thorsen
903              
904             =head1 COPYRIGHT AND LICENSE
905              
906             Copyright (C) 2014, Jan Henning Thorsen
907              
908             This program is free software, you can redistribute it and/or modify it under
909             the terms of the Artistic License version 2.0.
910              
911             =head1 SEE ALSO
912              
913             L.
914              
915             L
916              
917             =cut