File Coverage

blib/lib/Weasel/Driver/Selenium2.pm
Criterion Covered Total %
statement 30 122 24.5
branch 0 32 0.0
condition 0 7 0.0
subroutine 10 34 29.4
pod 22 22 100.0
total 62 217 28.5


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