File Coverage

blib/lib/Test/Mojo/Role/Selenium.pm
Criterion Covered Total %
statement 80 230 34.7
branch 19 82 23.1
condition 19 57 33.3
subroutine 19 51 37.2
pod 28 28 100.0
total 165 448 36.8


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