File Coverage

blib/lib/Selenium/Element.pm
Criterion Covered Total %
statement 18 186 9.6
branch 0 178 0.0
condition 0 55 0.0
subroutine 6 34 17.6
pod 27 27 100.0
total 51 480 10.6


line stmt bran cond sub pod time code
1             # ABSTRACT: Unified way of interacting with Selenium & WebDriver elements, with a focus on inputs.
2             # PODNAME: Selenium::Element
3              
4             package Selenium::Element;
5             $Selenium::Element::VERSION = '0.012';
6 1     1   12 use 5.010;
  1         3  
  1         31  
7 1     1   3 use strict;
  1         1  
  1         27  
8 1     1   3 use warnings;
  1         2  
  1         18  
9              
10 1     1   4 use Carp;
  1         1  
  1         48  
11 1     1   3 use Scalar::Util qw(blessed reftype looks_like_number);
  1         1  
  1         37  
12 1     1   489 use Data::Random;
  1         2350  
  1         1866  
13              
14              
15             sub new {
16 0     0 1   my ($class,$element,$driver,$selector) = @_;
17 0 0         confess("Constructor must be called statically, not by an instance") if ref($class);
18 0 0         return undef if !$element;
19 0 0 0       confess("Element driver invalid: must be WWW::Selenium object or false (element is a Selenium::Remote::Webelement)") unless $driver == 0 || (blessed($driver) && blessed($driver) eq 'WWW::Selenium' );
      0        
20              
21 0           my $self = {
22             'driver' => $driver,
23             'element' => $element,
24             'selector' => $selector
25             };
26              
27 0           bless $self, $class;
28 0           return $self;
29             }
30              
31              
32             sub get_tag_name {
33 0     0 1   my ($self) = @_;
34 0 0         confess("Object parameters must be called by an instance") unless ref($self);
35 0 0         if ($self->{'driver'}) {
36 0           my @parts = split(qr/=/,$self-{'element'});
37             #TODO If you can't do it with both of these, you have no business doing it...but this could be expanded to everything eventually...
38 0 0         confess('WWW::Selenium drivers can only get tag name if selector is of type "id" or "css"') unless scalar(grep {$_ eq $parts[0]} qw(id css));
  0            
39 0 0         my $js = $parts[0] eq 'id' ? 'document.getElementById("'.$parts[1].'").nodeName' : 'document.querySelectorAll("'.$parts[1].'")[0].nodeName';
40 0           return lc($self->javascript($js));
41             }
42 0           return $self->{'element'}->get_tag_name();
43             }
44              
45              
46             sub get_type {
47 0     0 1   my $self = shift;
48 0 0         confess("Object parameters must be called by an instance") unless ref($self);
49 0 0         return undef unless $self->is_input;
50 0 0         return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'}.'@type') : $self->{'element'}->get_attribute('type');
51             }
52              
53             #Input specific stuf
54             #TODO cache the results of all this stuff?
55              
56              
57             sub is_input {
58 0     0 1   my ($self) = @_;
59 0 0         confess("Object parameters must be called by an instance") unless ref($self);
60 0           return $self->get_tag_name() eq 'input';
61             }
62              
63              
64             sub is_textinput {
65 0     0 1   my ($self) = @_;
66 0 0         confess("Object parameters must be called by an instance") unless ref($self);
67 0           my $itype = $self->get_type();
68 0           my $ret = scalar(grep {$_ eq $itype} ('password', 'text'));
  0            
69 0   0       return $ret || $self->get_tag_name() eq 'textarea';
70             }
71              
72              
73             sub is_select {
74 0     0 1   my ($self) = @_;
75 0 0         confess("Object parameters must be called by an instance") unless ref($self);
76 0           return $self->get_tag_name() eq 'select';
77             }
78              
79              
80             sub is_multiselect {
81 0     0 1   my $self = shift;
82 0 0         confess("Object parameters must be called by an instance") unless ref($self);
83 0 0         return 0 if !$self->is_select;
84 0 0         return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'multiple') : $self->{'element'}->get_attribute('multiple');
85             }
86              
87              
88             sub is_radio {
89 0     0 1   my ($self) = @_;
90 0 0         confess("Object parameters must be called by an instance") unless ref($self);
91 0           return $self->get_type() eq 'radio';
92             }
93              
94              
95             sub is_checkbox {
96 0     0 1   my ($self) = @_;
97 0 0         confess("Object parameters must be called by an instance") unless ref($self);
98 0           return $self->get_type() eq 'checkbox';
99             }
100              
101              
102             sub is_submit {
103 0     0 1   my ($self) = @_;
104 0 0         confess("Object parameters must be called by an instance") unless ref($self);
105 0           return $self->get_type() eq 'submit';
106             }
107              
108              
109             sub is_fileinput {
110 0     0 1   my ($self) = @_;
111 0 0         confess("Object parameters must be called by an instance") unless ref($self);
112 0           return $self->get_type() eq 'file';
113             }
114              
115              
116             sub is_form {
117 0     0 1   my ($self) = @_;
118 0 0         confess("Object parameters must be called by an instance") unless ref($self);
119 0 0         confess("WWW::Selenium does not support getting tag type of elements") if $self->{'driver'};
120 0           return $self->get_tag_name() eq 'form';
121             }
122              
123              
124             sub is_option {
125 0     0 1   my ($self) = @_;
126 0 0         confess("Object parameters must be called by an instance") unless ref($self);
127 0 0         confess("WWW::Selenium does not support getting tag type of elements") if $self->{'driver'};
128 0           return $self->get_tag_name() eq 'option';
129             }
130              
131              
132             sub is_hiddeninput {
133 0     0 1   my $self = shift;
134 0 0         confess("Object parameters must be called by an instance") unless ref($self);
135 0           return $self->get_type() eq 'hidden';
136             }
137              
138              
139             sub is_enabled {
140 0     0 1   my ($self) = @_;
141 0 0         confess("Object parameters must be called by an instance") unless ref($self);
142             #Note that this will be more or less a no-op for WWW::Selenium, as there's no real way to get the tag name, so we will never see this branch
143 0 0         return $self->{'driver'} ? $self->{'driver'}->is_editable($self->{'element'}) : $self->{'element'}->is_enabled();
144             }
145              
146              
147             sub get_options {
148 0     0 1   my $self = shift;
149 0 0         confess("Object parameters must be called by an instance") unless ref($self);
150 0 0         return () unless $self->is_select();
151 0 0         if ($self->{'driver'}) {
152             #XXX obviously not reliable
153 0           carp("WARNING: WWW::Selenium has reduced ability to get options! This may not work as you expect.");
154 0           my @labels = $self->{'driver'}->get_select_options($self->{'element'});
155 0           return map {Selenium::Element->new("css=option[value=$_]",$self->{'driver'})} @labels;
  0            
156             }
157 0           my @opts = $self->{'element'}->{'driver'}->find_child_elements($self->{'element'},'option','tag_name');
158 0           return map {Selenium::Element->new($_,0)} @opts;
  0            
159             }
160              
161              
162             #Convenience method for selects
163             sub has_option {
164 0     0 1   my ($self,$option) = @_;
165 0 0         confess("Object parameters must be called by an instance") unless ref($self);
166 0 0         confess("Option must be passed as argument") unless defined($option);
167 0 0         return 0 if !$self->is_select();
168 0           return scalar(grep {$_->name eq $option} $self->get_options());
  0            
169             }
170              
171              
172             sub is_selected {
173 0     0 1   my $self = shift;
174 0 0         confess("Object parameters must be called by an instance") unless ref($self);
175 0 0         confess("Element must be option to check if selected") unless $self->is_option;
176 0 0         return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'selected') : $self->{'element'}->get_attribute('selected');
177             }
178              
179              
180             sub get {
181 0     0 1   my ($self) = @_;
182 0 0         confess("Object parameters must be called by an instance") unless ref($self);
183              
184             #Try to get various stuff based on what it is
185 0 0 0       if ($self->is_checkbox || $self->is_radio) {
    0 0        
    0 0        
    0          
186 0 0         return $self->{'driver'} ? $self->{'driver'}->is_checked() : $self->{'element'}->is_selected();
187             } elsif ($self->is_select) {
188 0 0         if ($self->is_multiselect) {
189 0 0         my @options = grep {defined $_} map {$_->is_selected ? $_->name : undef} $self->get_options;
  0            
  0            
190 0           return \@options;
191             } else {
192 0 0         return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'value') : $self->{'element'}->get_attribute('value');
193             }
194             } elsif ( $self->is_hiddeninput || $self->is_fileinput || $self->is_textinput) {
195 0 0         return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'value') : $self->{'element'}->get_attribute('value');
196             } elsif ($self->is_option) {
197 0 0         return $self->{'driver'} ? defined $self->{'driver'}->get_attribute($self->{'element'},'selected') : defined $self->{'element'}->get_attribute('selected');
198             } else {
199 0 0         $self->{'driver'} ? $self->{'driver'}->get_text($self->{'element'}) : $self->{'element'}->get_text();
200             }
201             }
202              
203              
204             sub id {
205 0     0 1   my $self = shift;
206 0 0         return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'id') : $self->{'element'}->get_attribute('id');
207             }
208              
209              
210             sub name {
211 0     0 1   my $self = shift;
212 0 0         return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'name') : $self->{'element'}->get_attribute('name');
213             }
214              
215              
216             sub clear {
217 0     0 1   my ($self) = @_;
218 0 0         confess("Object parameters must be called by an instance") unless ref($self);
219 0 0         confess("Element must be text") unless $self->is_textinput();
220 0 0         if ($self->{'driver'}) {
221             #TODO If you can't do it with both of these, you have no business doing it...but this could be expanded to everything eventually...
222 0 0         confess('WWW::Selenium drivers can only clear text if selector is of type "id" or "css"') unless scalar(grep {$_ eq $self->{'selector'}->[1]} qw(id css));
  0            
223 0 0         my $js = $self->{'selector'}->[1] eq 'id' ? 'document.getElementById("'.$self->{'selector'}->[0].'").value = ""' : 'document.querySelectorAll("'.$self->{'selector'}->[0].'")[0].value = ""';
224 0           $self->javascript($js);
225             } else {
226 0           $self->{'element'}->clear();
227             }
228 0           return 1;
229             }
230              
231              
232             sub set {
233 0     0 1   my ($self,$value,$callback) = @_;
234 0 0         confess("Object parameters must be called by an instance") unless ref($self);
235 0 0         confess("Value must be passed to set") unless defined($value);
236 0 0 0       confess("Callback must be subroutine") if defined($callback) && reftype($callback) ne 'CODE';
237              
238 0           my $enabled = $self->is_enabled();
239 0 0         carp "Attempting to set disabled element" unless $enabled;
240 0 0         return undef unless $enabled;
241 0           my $ret = 0;
242              
243             #Try to set various stuff based on what it is
244             SETBLOCK : {
245 0 0 0       if ($self->is_checkbox || $self->is_radio) {
  0 0          
    0          
    0          
    0          
    0          
246 0 0         my $selected = $self->{'driver'} ? $self->{'driver'}->is_checked() : $self->{'element'}->is_selected();
247 0 0 0       last SETBLOCK if ($selected && $value) || (!$selected && !$value); #Return false if state hasn't changed
      0        
      0        
248 0 0         $self->{'driver'} ? $self->{'driver'}->click($self->{'element'}) : $self->{'element'}->click();
249 0           $ret = 1;
250             } elsif ($self->is_textinput) {
251 0           $self->clear();
252 0 0         $self->{'driver'} ? $self->{'driver'}->type_keys($self->{'element'},$value) : $self->{'element'}->send_keys($value);
253 0           $ret = 1;
254             } elsif ($self->is_fileinput) {
255 0 0         if ($self->{'driver'}) {
256 0           $self->{'driver'}->attach_file($self->{'element'},$value);
257             } else {
258 0           $self->{'element'}->send_keys($value);
259             }
260 0           $ret = 1;
261             } elsif ($self->is_hiddeninput) {
262             #TODO make this work a bit more universally if possible
263 0 0         confess("Setting values on hidden elements without IDs not supported") unless $self->id;
264 0           carp("Setting value of hidden element, this may result in unexpected behavior!");
265 0           my $js = 'document.getElementById("'.$self->id.'").value = \''.$value.'\';';
266 0           $self->javascript($js);
267 0           $ret = 1;
268             } elsif ($self->is_select) {
269 0 0         $value = [$value] if reftype($value) ne 'ARRAY';
270 0 0         if ($self->{'driver'}) {
271 0           foreach my $val (@$value) {
272 0           $self->{'driver'}->type($self->{'element'},$val);
273             }
274             } else {
275 0           foreach my $val ($self->get_options()) {
276 0 0         if (grep {$val->{'element'}->get_attribute('name') eq $_ } @$value) {
  0            
277             #Leave values high if they are requested
278 0 0         $val->click if !$val->is_selected;
279             } else {
280             #otherwise ensure low values
281 0 0         $val->click if $val->is_selected;
282             }
283             }
284             }
285 0           $ret = 1;
286             } elsif ($self->is_option) {
287 0           my $current = $self->get;
288 0 0 0       $self->click if ( (!$current && $value) || ($current && !$value) );
      0        
      0        
289             } else {
290 0           confess("Don't know how to set value to a non-input element!");
291             }
292             }
293              
294             #Can't set anything else!
295 0   0       return $self->_doCallback($callback) || $ret;
296             }
297              
298             sub _doCallback {
299 0     0     my ($self,$cb) = @_;
300 0 0         return 0 if !$cb;
301 0 0         return &$cb($self,$self->{'driver'} ? $self->{'driver'} : $self->{'element'}->{'driver'});
302             }
303              
304              
305 0     0 1   sub randomize {
306              
307             }
308              
309              
310             sub javascript {
311 0     0 1   my ($self, $js) = @_;
312 0 0         confess("Object parameters must be called by an instance") unless ref($self);
313 0 0         return $self->{'driver'} ? $self->{'element'}->get_eval($js) : $self->{'element'}->{'driver'}->execute_script($js);
314             }
315              
316              
317             sub click {
318 0     0 1   my ($self,$callback) = @_;
319 0 0         confess("Object parameters must be called by an instance") unless ref($self);
320 0 0 0       confess("Callback must be subroutine") if defined($callback) && reftype($callback) ne 'CODE';
321 0 0         $self->{'driver'} ? $self->{'driver'}->click($self->{'element'}) : $self->{'element'}->click();
322              
323 0   0       return $self->_doCallback($callback) || 1;
324             }
325              
326              
327             sub submit {
328 0     0 1   my ($self,$callback) = @_;
329 0 0         confess("Object parameters must be called by an instance") unless ref($self);
330 0 0 0       confess("Callback must be subroutine") if defined($callback) && reftype($callback) ne 'CODE';
331 0 0         return 0 if !$self->is_form();
332 0 0         $self->{'driver'} ? $self->{'driver'}->submit($self->{'element'}) : $self->{'element'}->submit();
333              
334 0   0       return $self->_doCallback($callback) || 1;
335             }
336              
337             1;
338              
339             __END__