File Coverage

blib/lib/Weasel/Driver/Selenium2.pm
Criterion Covered Total %
statement 30 127 23.6
branch 0 34 0.0
condition 0 7 0.0
subroutine 10 34 29.4
pod 22 22 100.0
total 62 224 27.6


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Weasel::Driver::Selenium2 - Weasel driver wrapping Selenium::Remote::Driver
5              
6             =head1 VERSION
7              
8             version 0.15
9              
10             =head1 SYNOPSIS
11              
12             use Weasel;
13             use Weasel::Session;
14             use Weasel::Driver::Selenium2;
15              
16             my %opts = (
17             wait_timeout => 3000, # 3000 msec == 3s
18             window_size => '1024x1280',
19             caps => {
20             port => 4444,
21             # ... and other Selenium::Remote::Driver capabilities options
22             },
23             );
24             my $weasel = Weasel->new(
25             default_session => 'default',
26             sessions => {
27             default => Weasel::Session->new(
28             driver => Weasel::Driver::Selenium2->new(%opts),
29             ),
30             });
31              
32             $weasel->session->get('http://localhost/index');
33              
34              
35             =head1 DESCRIPTION
36              
37             This module implements the L<Weasel::DriverRole> protocol, wrapping
38             Selenium::Remote::Driver.
39              
40             =cut
41              
42              
43             =head1 DEPENDENCIES
44              
45             This module wraps L<Selenium::Remote::Driver>, version 2.
46              
47             =cut
48              
49              
50             package Weasel::Driver::Selenium2 0.15;
51              
52 1     1   371869 use strict;
  1         2  
  1         45  
53 1     1   6 use warnings;
  1         2  
  1         72  
54              
55 1     1   658 use namespace::autoclean;
  1         25034  
  1         5  
56              
57 1     1   728 use MIME::Base64;
  1         902  
  1         100  
58 1     1   1433 use Selenium::Remote::Driver;
  1         301439  
  1         63  
59 1     1   30 use Time::HiRes qw/ time sleep /;
  1         3  
  1         11  
60 1     1   944 use Weasel::DriverRole;
  1         693128  
  1         59  
61 1     1   940 use Carp::Clan qw(^Weasel::);
  1         2921  
  1         9  
62 1     1   888 use English qw(-no_match_vars);
  1         1349  
  1         8  
63              
64 1     1   476 use Moose;
  1         2  
  1         9  
65             with 'Weasel::DriverRole';
66              
67             =head1 ATTRIBUTES
68              
69             =over
70              
71             =item _driver
72              
73             Internal. Holds the reference to the C<Selenium::Remote::Driver> instance.
74              
75             =cut
76              
77             has '_driver' => (is => 'rw',
78             isa => 'Selenium::Remote::Driver',
79             );
80              
81             =item wait_timeout
82              
83             The number of miliseconds to wait before failing to find a tag
84             or completing a wait condition, turns into an error.
85              
86             Change by calling C<set_wait_timeout>.
87              
88             =cut
89              
90             has 'wait_timeout' => (is => 'rw',
91             writer => '_set_wait_timeout',
92             isa => 'Int',
93             );
94              
95              
96             =item window_size
97              
98             String holding '<height>x<width>', the window size to be used. E.g., to
99             set the window size to 1280(wide) by 1024(high), set to: '1024x1280'.
100              
101             Change by calling C<set_window_size>.
102              
103             =cut
104              
105             has 'window_size' => (is => 'rw',
106             writer => '_set_window_size',
107             );
108              
109             =item caps
110              
111             Capabilities to be passed to the Selenium::Remote::Driver constructor
112             when C<start> is being called. Changes won't take effect until the
113             session is stopped and started or restarted.
114              
115             =cut
116              
117             has 'caps' => (is => 'ro',
118             isa => 'HashRef',
119             required => 1,
120             );
121              
122             =back
123              
124             =head1 IMPLEMENTATION OF Weasel::DriverRole
125              
126             For the documentation of the methods in this section,
127             see L<Weasel::DriverRole>.
128              
129             =over
130              
131             =item implements
132              
133             =cut
134              
135             sub implements {
136 0     0 1   return '0.03';
137             }
138              
139             =item start
140              
141             A few capabilities can be specified in t/.pherkin.yaml
142             Some can even be specified as environment variables, they will be expanded here if present.
143              
144             =cut
145              
146             sub start {
147 0     0 1   my $self = shift;
148              
149             do {
150 0 0         if ( defined $self->{caps}{$_}) {
151 0           my $capability_name = $_;
152 0 0         if ( $self->{caps}{$capability_name} =~
153             /\$\{ # a dollar sign and opening brace
154             ([^\}]+) # any character not a closing brace
155             \}/x # a closing brace
156             ) {
157 0           $self->{caps}{$capability_name} = $ENV{$1};
158             }
159             }
160 0           } for (qw/browser_name remote_server_addr version platform/);
161              
162 0           my $driver = Selenium::Remote::Driver->new(%{$self->caps});
  0            
163              
164 0           $self->_driver($driver);
165 0           $self->set_wait_timeout($self->wait_timeout);
166 0           $self->set_window_size($self->window_size);
167 0           return $self->started(1);
168             }
169              
170             =item stop
171              
172             =cut
173              
174             sub stop {
175 0     0 1   my $self = shift;
176 0           my $driver = $self->_driver;
177              
178 0 0         $driver->quit if defined $driver;
179 0           return $self->started(0);
180             }
181              
182             =item find_all
183              
184             =cut
185              
186             sub find_all {
187 0     0 1   my ($self, $parent_id, $locator, $scheme) = @_;
188             # $parent_id is either a string containing an xpath
189             # or a native Selenium::Remote::WebElement
190              
191 0           my @rv;
192 0           my $_driver = $self->_driver;
193 0 0         if ($parent_id eq '/html') {
194 0   0       @rv = $_driver->find_elements($locator, $scheme // 'xpath');
195             }
196             else {
197 0           $parent_id = $self->_resolve_id($parent_id);
198 0   0       @rv = $_driver->find_child_elements($parent_id, $locator,
199             $scheme // 'xpath');
200             }
201 0 0         return wantarray ? @rv : \@rv;
202             }
203              
204             =item get
205              
206             =cut
207              
208             sub get {
209 0     0 1   my ($self, $url) = @_;
210              
211 0           return $self->_driver->get($url);
212             }
213              
214             =item wait_for
215              
216             =cut
217              
218             sub wait_for {
219 0     0 1   my ($self, $callback, %args) = @_;
220              
221             # Do NOT use Selenium::Waiter, it eats all exceptions!
222 0           my $end = time() + $args{retry_timeout};
223 0           my $rv;
224 0           my $count = 0;
225 0           while (1) {
226 0           $count++;
227 0           $rv = $callback->();
228 0 0         return $rv if $rv;
229              
230 0 0         if (time() <= $end) {
    0          
231 0           sleep $args{poll_delay};
232             }
233             elsif ($args{on_timeout}) {
234 0           $args{on_timeout}->();
235             }
236             else {
237             croak "wait_for deadline expired ($args{retry_timeout}s; $count poll attempts) waiting for: $args{description}"
238 0 0         if defined $args{description};
239              
240 0           croak "wait_for deadline expired ($args{retry_timeout}s; $count poll attempts); consider increasing the deadline";
241             }
242             }
243              
244 0           return;
245             }
246              
247              
248             =item clear
249              
250             =cut
251              
252             sub clear {
253 0     0 1   my ($self, $id) = @_;
254              
255 0           return $self->_resolve_id($id)->clear;
256             }
257              
258             =item click
259              
260             =cut
261              
262             sub click {
263 0     0 1   my ($self, $element_id) = @_;
264              
265 0 0         if (defined $element_id) {
266 0           return $self->_scroll($self->_resolve_id($element_id))->click;
267             }
268             else {
269 0           return $self->_driver->click;
270             }
271             }
272              
273             =item dblclick
274              
275             =cut
276              
277             sub dblclick {
278 0     0 1   my ($self) = @_;
279              
280 0           return $self->_driver->dblclick;
281             }
282              
283             =item execute_script
284              
285             =cut
286              
287             sub execute_script {
288 0     0 1   my $self = shift;
289 0           return $self->_driver->execute_script(@_);
290             }
291              
292             =item get_attribute($id, $att_name)
293              
294             =cut
295              
296             sub get_attribute {
297 0     0 1   my ($self, $id, $att) = @_;
298              
299 0           my $element = $self->_resolve_id($id);
300 0           my $value;
301             $value = $element->get_attribute($att) # Try with property/attribute
302 0 0         if $self->_driver->{is_wd3};
303 0 0         if (ref $value) {
304             # there is a bug in Selenium::Remote::Driver which returns a
305             # hash reference when asked for an element's "id", in some cases
306             # when running against a WebDriver 3 implementation
307 0           $value = undef;
308             }
309 0   0       $value //= $element->get_attribute($att,1); # Force using attribute
310 0           return $value;
311             }
312              
313             =item get_page_source($fh)
314              
315             =cut
316              
317             sub get_page_source {
318 0     0 1   my ($self,$fh) = @_;
319              
320 0 0         print {$fh} $self->_driver->get_page_source()
  0            
321             or croak "error saving page source: $ERRNO";
322 0           return;
323             }
324              
325             =item get_text($id)
326              
327             =cut
328              
329             sub get_text {
330 0     0 1   my ($self, $id) = @_;
331              
332 0           return $self->_resolve_id($id)->get_text;
333             }
334              
335             =item is_displayed($id)
336              
337             =cut
338              
339             my $check_displayed_script = <<'SCRIPT';
340             var elm = arguments[0];
341              
342             if (!elm) return false;
343             var style = window.getComputedStyle(elm);
344             if (style.display === 'none') return false;
345             if (style.visibility === 'hidden') return false;
346             if (style.opacity === '0') return false;
347              
348             if (elm.offsetWidth === 0 || elm.offsetHeight === 0) return false;
349             var rect = elm.getBoundingClientRect();
350             if (rect.width === 0 || rect.height === 0) return false;
351              
352             return true;
353             SCRIPT
354              
355             sub is_displayed {
356 0     0 1   my ($self, $id) = @_;
357              
358 0           return $self->execute_script($check_displayed_script, $self->_resolve_id($id));
359             }
360              
361             =item set_attribute($id, $att_name, $value)
362              
363             =cut
364              
365             sub set_attribute {
366 0     0 1   my ($self, $id, $att, $value) = @_;
367              
368 0           return $self->_resolve_id($id)->set_attribute($att, $value);
369             }
370              
371             =item get_selected($id)
372              
373             =cut
374              
375             sub get_selected {
376 0     0 1   my ($self, $id) = @_;
377              
378 0           return $self->_resolve_id($id)->is_selected;
379             }
380              
381             =item set_selected($id, $value)
382              
383             =cut
384              
385             sub set_selected {
386 0     0 1   my ($self, $id, $value) = @_;
387              
388             # Note: we're using a deprecated method here, but...
389             # as long as it's there... why not?
390             # The other solution is to use is_selected to verify the current state
391             # and toggling by click()ing
392 0           return $self->_resolve_id($id)->set_selected($value);
393             }
394              
395             =item screenshot($fh)
396              
397             =cut
398              
399             sub screenshot {
400 0     0 1   my ($self, $fh) = @_;
401              
402 0 0         print {$fh} MIME::Base64::decode($self->_driver->screenshot)
  0            
403             or croak "error saving screenshot: $ERRNO";
404 0           return;
405             }
406              
407             =item send_keys($element_id, @keys)
408              
409             =cut
410              
411             sub send_keys {
412 0     0 1   my ($self, $element_id, @keys) = @_;
413              
414 0           return $self->_resolve_id($element_id)->send_keys(@keys);
415             }
416              
417             =item tag_name($elem)
418              
419             =cut
420              
421             sub tag_name {
422 0     0 1   my ($self, $element_id) = @_;
423              
424 0           return $self->_resolve_id($element_id)->get_tag_name;
425             }
426              
427             =back
428              
429             =head1 SUBROUTINES/METHODS
430              
431             This module implements the following methods in addition to the
432             Weasel::DriverRole protocol methods:
433              
434             =over
435              
436             =item set_wait_timeout
437              
438             Sets the C<wait_timeut> attribute of the object as well as
439             of the Selenium::Remote::Driver object, if a session has been
440             started.
441              
442             =cut
443              
444             sub set_wait_timeout {
445 0     0 1   my ($self, $value) = @_;
446 0           my $driver = $self->_driver;
447              
448 0 0         $driver->set_implicit_wait_timeout($value)
449             if defined $driver;
450 0           return $self->_set_wait_timeout($value);
451             }
452              
453             =item set_window_size
454              
455             Sets the C<window_size> attribute of the object as well as the
456             window size of the currently active window of the Selenium::Remote::Driver
457             object, if a session has been started.
458              
459             =cut
460              
461             sub set_window_size {
462 0     0 1   my ($self, $value) = @_;
463 0           my $driver = $self->_driver;
464              
465 0 0         $driver->set_window_size(split /x/, $value)
466             if defined $driver;
467 0           return $self->_set_window_size($value);
468             }
469              
470             =back
471              
472             =cut
473              
474             # PRIVATE IMPLEMENTATIONS
475              
476              
477             sub _resolve_id {
478 0     0     my ($self, $id) = @_;
479              
480 0 0         if (ref $id) {
481 0           return $id;
482             }
483             else {
484 0           my @rv = $self->_driver->find_elements($id,'xpath');
485 0           return (shift @rv);
486             }
487             }
488              
489             sub _scroll {
490 0     0     my ($self, $id) = @_;
491              
492 0           $self->_driver->execute_script('arguments[0].scrollIntoView('
493             . '{block: "center", '
494             . 'inline: "center", '
495             . 'behavior: "smooth"'
496             . '});',
497             $id);
498 0           return $id;
499             }
500              
501             __PACKAGE__->meta()->make_immutable();
502              
503             =head1 AUTHOR
504              
505             Erik Huelsmann
506              
507             =head1 CONTRIBUTORS
508              
509             =over
510              
511             =item Erik Huelsmann
512              
513             =item Yves Lavoie
514              
515             =back
516              
517             =head1 MAINTAINERS
518              
519             Erik Huelsmann
520              
521             =head1 BUGS AND LIMITATIONS
522              
523             Bugs can be filed in the GitHub issue tracker for the Weasel project:
524             https://github.com/perl-weasel/weasel-driver-selenium2/issues
525              
526             =head1 SOURCE
527              
528             The source code repository for Weasel is at
529             https://github.com/perl-weasel/weasel-driver-selenium2
530              
531             =head1 SUPPORT
532              
533             Community support is available through
534             L<perl-weasel@googlegroups.com|mailto:perl-weasel@googlegroups.com>.
535              
536             =head1 LICENSE AND COPYRIGHT
537              
538             (C) 2016-2020 Erik Huelsmann
539              
540             Licensed under the same terms as Perl.
541              
542             =cut
543              
544             1;