line
stmt
bran
cond
sub
pod
time
code
1
package Selenium::Element;
2
3
1
1
4
use strict;
1
2
1
28
4
1
1
4
use warnings;
1
1
1
18
5
6
1
1
3
use Carp;
1
1
1
45
7
1
1
4
use Scalar::Util qw(blessed reftype looks_like_number);
1
1
1
75
8
1
1
516
use Data::Random;
1
2351
1
2015
9
10
=head1 NAME
11
12
Selenium::Element - Unified way of interacting with Selenium & WebDriver elements, with a focus on inputs.
13
14
=head1 SYNOPSIS
15
16
Smooths out the interface between WWW::Selenium and Selenium::Remote::Driver elements.
17
Also creates a unified set/get interface for all inputs.
18
19
=head1 CONSTRUCTOR
20
21
=head2 new(ELEMENT,DRIVER,SELECTOR)
22
23
Create a new Selenium::Element. You should never have to use/override this except in the most extreme of circumstances.
24
Use getElement/getElements instead.
25
26
B:
27
28
I - Either the WWW::Selenium locator string or a Selenium::Remote::WebElement, depending on your driver
29
30
I - Either a WWW::Selenium element or false, depending on your driver (the WebElement has the driver in the latter case)
31
32
I - Arrayref of the form [selector,selectortype]
33
34
B:
35
36
new Selenium::Element
37
38
=cut
39
40
sub new {
41
0
0
1
my ($class,$element,$driver,$selector) = @_;
42
0
0
confess("Constructor must be called statically, not by an instance") if ref($class);
43
0
0
return undef if !$element;
44
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
45
46
0
my $self = {
47
'driver' => $driver,
48
'element' => $element,
49
'selector' => $selector
50
};
51
52
0
bless $self, $class;
53
0
return $self;
54
}
55
56
=head1 GETTERS
57
58
=head2 get_tag_name
59
60
Returns the tag name of the Element object.
61
62
=cut
63
64
sub get_tag_name {
65
0
0
1
my ($self) = @_;
66
0
0
confess("Object parameters must be called by an instance") unless ref($self);
67
0
0
if ($self->{'driver'}) {
68
0
my @parts = split(qr/=/,$self-{'element'});
69
#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...
70
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
71
0
0
my $js = $parts[0] eq 'id' ? 'document.getElementById("'.$parts[1].'").nodeName' : 'document.querySelectorAll("'.$parts[1].'")[0].nodeName';
72
0
return lc($self->javascript($js));
73
}
74
0
return $self->{'element'}->get_tag_name();
75
}
76
77
=head2 get_type
78
79
Returns the type of the Element object if it is an input tag.
80
81
=cut
82
83
sub get_type {
84
0
0
1
my $self = shift;
85
0
0
confess("Object parameters must be called by an instance") unless ref($self);
86
0
0
return undef unless $self->is_input;
87
0
0
return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'}.'@type') : $self->{'element'}->get_attribute('type');
88
}
89
90
#Input specific stuf
91
#TODO cache the results of all this stuff?
92
93
=head2 is_input
94
95
Returns whether the element is an input.
96
97
=cut
98
99
sub is_input {
100
0
0
1
my ($self) = @_;
101
0
0
confess("Object parameters must be called by an instance") unless ref($self);
102
0
return $self->get_tag_name() eq 'input';
103
}
104
105
=head2 is_textinput
106
107
Returns whether the element is an input with type 'text' or 'password' or a textarea.
108
109
=cut
110
111
sub is_textinput {
112
0
0
1
my ($self) = @_;
113
0
0
confess("Object parameters must be called by an instance") unless ref($self);
114
0
my $itype = $self->get_type();
115
0
my $ret = scalar(grep {$_ eq $itype} ('password', 'text'));
0
116
0
0
return $ret || $self->get_tag_name() eq 'textarea';
117
}
118
119
=head2 is_select
120
121
Returns whether the element is a select.
122
123
=cut
124
125
sub is_select {
126
0
0
1
my ($self) = @_;
127
0
0
confess("Object parameters must be called by an instance") unless ref($self);
128
0
return $self->get_tag_name() eq 'select';
129
}
130
131
=head2 is_multiselect
132
133
Returns whether the element is a select with the 'multiple' attribute.
134
135
=cut
136
137
sub is_multiselect {
138
0
0
1
my $self = shift;
139
0
0
confess("Object parameters must be called by an instance") unless ref($self);
140
0
0
return 0 if !$self->is_select;
141
0
0
return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'multiple') : $self->{'element'}->get_attribute('multiple');
142
}
143
144
=head2 is_radio
145
146
Returns whether the element is a radio button.
147
148
=cut
149
150
sub is_radio {
151
0
0
1
my ($self) = @_;
152
0
0
confess("Object parameters must be called by an instance") unless ref($self);
153
0
return $self->get_type() eq 'radio';
154
}
155
156
=head2 is_checkbox
157
158
Returns whether the element is a checkbox.
159
160
=cut
161
162
sub is_checkbox {
163
0
0
1
my ($self) = @_;
164
0
0
confess("Object parameters must be called by an instance") unless ref($self);
165
0
return $self->get_type() eq 'checkbox';
166
}
167
168
=head2 is_submit
169
170
Returns whether the element is an input of the type 'submit'.
171
172
=cut
173
174
sub is_submit {
175
0
0
1
my ($self) = @_;
176
0
0
confess("Object parameters must be called by an instance") unless ref($self);
177
0
return $self->get_type() eq 'submit';
178
}
179
180
=head2 is_fileinput
181
182
Returns whether the element is an input of the type 'file'.
183
184
=cut
185
186
sub is_fileinput {
187
0
0
1
my ($self) = @_;
188
0
0
confess("Object parameters must be called by an instance") unless ref($self);
189
0
return $self->get_type() eq 'file';
190
}
191
192
=head2 is_fileinput
193
194
Returns whether the element is an input of the type 'file'.
195
196
=cut
197
198
sub is_form {
199
0
0
0
my ($self) = @_;
200
0
0
confess("Object parameters must be called by an instance") unless ref($self);
201
0
0
confess("WWW::Selenium does not support getting tag type of elements") if $self->{'driver'};
202
0
return $self->get_tag_name() eq 'form';
203
}
204
205
=head2 is_option
206
207
Returns whether the element is an option.
208
209
=cut
210
211
sub is_option {
212
0
0
1
my ($self) = @_;
213
0
0
confess("Object parameters must be called by an instance") unless ref($self);
214
0
0
confess("WWW::Selenium does not support getting tag type of elements") if $self->{'driver'};
215
0
return $self->get_tag_name() eq 'option';
216
}
217
218
=head2 is_hiddeninput
219
220
Returns whether the element is an input of type 'hidden'.
221
222
=cut
223
224
sub is_hiddeninput {
225
0
0
1
my $self = shift;
226
0
0
confess("Object parameters must be called by an instance") unless ref($self);
227
0
return $self->get_type() eq 'hidden';
228
}
229
230
=head2 is_enabled
231
232
Returns whether the element is a disabled input.
233
234
=cut
235
236
sub is_enabled {
237
0
0
1
my ($self) = @_;
238
0
0
confess("Object parameters must be called by an instance") unless ref($self);
239
#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
240
0
0
return $self->{'driver'} ? $self->{'driver'}->is_editable($self->{'element'}) : $self->{'element'}->is_enabled();
241
}
242
243
=head2 get_options
244
245
Returns a list containing Selenium::Element objects that are child options, if this object is a select.
246
247
=cut
248
249
sub get_options {
250
0
0
1
my $self = shift;
251
0
0
confess("Object parameters must be called by an instance") unless ref($self);
252
0
0
return () unless $self->is_select();
253
0
my @options = ();
254
0
0
if ($self->{'driver'}) {
255
#XXX obviously not reliable
256
0
carp("WARNING: WWW::Selenium has reduced ability to get options! This may not work as you expect.");
257
0
my @labels = $self->{'driver'}->get_select_options($self->{'element'});
258
0
return map {Selenium::Element->new("css=option[value=$_]",$self->{'driver'})} @labels;
0
259
}
260
0
my @opts = $self->{'element'}->{'driver'}->find_child_elements($self->{'element'},'option','tag_name');
261
0
return map {Selenium::Element->new($_,0)} @opts;
0
262
}
263
264
=head2 has_option(option)
265
266
Returns whether this element has a child option with the provided name, provided this object is a select.
267
268
B :
269
I - the name of the desired option
270
271
B:
272
I - whether this object has said option as a child
273
274
=cut
275
276
#Convenience method for selects
277
sub has_option {
278
0
0
1
my ($self,$option) = @_;
279
0
0
confess("Object parameters must be called by an instance") unless ref($self);
280
0
0
confess("Option must be passed as argument") unless defined($option);
281
0
0
return 0 if !$self->is_select();
282
0
return scalar(grep {$_->name eq $option} $self->get_options());
0
283
}
284
285
=head2 is_selected
286
287
Returns whether the element is selected.
288
289
=cut
290
291
sub is_selected {
292
0
0
1
my $self = shift;
293
0
0
confess("Object parameters must be called by an instance") unless ref($self);
294
0
0
confess("Element must be option to check if selected") unless $self->is_option;
295
0
0
return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'selected') : $self->{'element'}->get_attribute('selected');
296
}
297
298
=head2 get
299
300
Returns the current value of the element.
301
302
B:
303
304
I - Depends on the type of element.
305
Boolean for checkboxes, options and radiobuttons
306
Arrayrefs of option names for multi-selects
307
Strings for single selects, text/hidden inputs and non-inputs like paragraphs, table cells, etc.
308
309
=cut
310
311
sub get {
312
0
0
1
my ($self) = @_;
313
0
0
confess("Object parameters must be called by an instance") unless ref($self);
314
315
0
my $ret = 0;
316
317
#Try to get various stuff based on what it is
318
0
0
0
if ($self->is_checkbox || $self->is_radio) {
0
0
0
0
0
319
0
0
return $self->{'driver'} ? $self->{'driver'}->is_checked() : $self->{'element'}->is_selected();
320
} elsif ($self->is_select) {
321
0
0
if ($self->is_multiselect) {
322
0
0
my @options = grep {defined $_} map {$_->is_selected ? $_->name : undef} $self->get_options;
0
0
323
0
return \@options;
324
} else {
325
0
0
return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'value') : $self->{'element'}->get_attribute('value');
326
}
327
} elsif ( $self->is_hiddeninput || $self->is_fileinput || $self->is_textinput) {
328
0
0
return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'value') : $self->{'element'}->get_attribute('value');
329
} elsif ($self->is_option) {
330
0
0
return $self->{'driver'} ? defined $self->{'driver'}->get_attribute($self->{'element'},'selected') : defined $self->{'element'}->get_attribute('selected');
331
} else {
332
0
0
$self->{'driver'} ? $self->{'driver'}->get_text($self->{'element'}) : $self->{'element'}->get_text();
333
}
334
}
335
336
=head2 id
337
338
Returns the element's id.
339
340
=cut
341
342
sub id {
343
0
0
1
my $self = shift;
344
0
0
return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'id') : $self->{'element'}->get_attribute('id');
345
}
346
347
=head2 name
348
349
Returns the element's name.
350
351
=cut
352
353
sub name {
354
0
0
1
my $self = shift;
355
0
0
return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'name') : $self->{'element'}->get_attribute('name');
356
}
357
358
=head1 SETTERS
359
360
=head2 clear
361
362
Clear a text input.
363
364
=cut
365
366
sub clear {
367
0
0
1
my ($self) = @_;
368
0
0
confess("Object parameters must be called by an instance") unless ref($self);
369
0
0
confess("Element must be text") unless $self->is_textinput();
370
0
0
if ($self->{'driver'}) {
371
#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...
372
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
373
0
0
my $js = $self->{'selector'}->[1] eq 'id' ? 'document.getElementById("'.$self->{'selector'}->[0].'").value = ""' : 'document.querySelectorAll("'.$self->{'selector'}->[0].'")[0].value = ""';
374
0
$self->javascript($js);
375
} else {
376
0
$self->{'element'}->clear();
377
}
378
0
return 1;
379
}
380
381
=head2 set(value,[callback])
382
383
Set the value of the input to the provided value, and execute the provided callback if provided.
384
The callback will be provided with the caller and the selenium driver as arguments.
385
386
B :
387
388
I - STRING, BOOLEAN or ARRAYREF, depending on the type of element you are attempting to set.
389
Strings are for textinputs, hiddens or non-multi selects, Booleans for radiobuttons, checkboxes and options, and Arrayrefs of strings for multiselects.
390
Selects take the name of the option as arguments.
391
392
I - some anonymous function
393
394
B:
395
396
I - whether the set succeeded, or whatever your callback feels like returning, supposing you provided one.
397
398
=cut
399
400
sub set {
401
0
0
1
my ($self,$value,$callback) = @_;
402
0
0
confess("Object parameters must be called by an instance") unless ref($self);
403
0
0
confess("Value must be passed to set") unless defined($value);
404
0
0
0
confess("Callback must be subroutine") if defined($callback) && reftype($callback) ne 'CODE';
405
406
0
my $enabled = $self->is_enabled();
407
0
0
carp "Attempting to set disabled element" unless $enabled;
408
0
0
return undef unless $enabled;
409
0
my $ret = 0;
410
411
#Try to set various stuff based on what it is
412
SETBLOCK : {
413
0
0
0
if ($self->is_checkbox || $self->is_radio) {
0
0
0
0
0
0
414
0
0
my $selected = $self->{'driver'} ? $self->{'driver'}->is_checked() : $self->{'element'}->is_selected();
415
0
0
0
last SETBLOCK if ($selected && $value) || (!$selected && !$value); #Return false if state hasn't changed
0
0
416
0
0
$self->{'driver'} ? $self->{'driver'}->click($self->{'element'}) : $self->{'element'}->click();
417
0
$ret = 1;
418
} elsif ($self->is_textinput) {
419
0
$self->clear();
420
0
0
$self->{'driver'} ? $self->{'driver'}->type_keys($self->{'element'},$value) : $self->{'element'}->send_keys($value);
421
0
$ret = 1;
422
} elsif ($self->is_fileinput) {
423
0
0
if ($self->{'driver'}) {
424
0
$self->{'driver'}->attach_file($self->{'element'},$value);
425
} else {
426
0
$self->{'element'}->send_keys($value);
427
}
428
0
$ret = 1;
429
} elsif ($self->is_hiddeninput) {
430
#TODO make this work a bit more universally if possible
431
0
0
confess("Setting values on hidden elements without IDs not supported") unless $self->id;
432
0
carp("Setting value of hidden element, this may result in unexpected behavior!");
433
0
my $js = 'document.getElementById("'.$self->id.'").value = \''.$value.'\';';
434
0
$self->javascript($js);
435
0
$ret = 1;
436
} elsif ($self->is_select) {
437
0
0
$value = [$value] if reftype($value) ne 'ARRAY';
438
0
0
if ($self->{'driver'}) {
439
0
foreach my $val (@$value) {
440
0
$self->{'driver'}->type($self->{'element'},$value);
441
}
442
} else {
443
0
foreach my $val ($self->get_options()) {
444
0
0
if (grep {$val->{'element'}->get_attribute('name') eq $_ } @$value) {
0
445
#Leave values high if they are requested
446
0
0
$val->click if !$val->is_selected;
447
} else {
448
#otherwise ensure low values
449
0
0
$val->click if $val->is_selected;
450
}
451
}
452
}
453
0
$ret = 1;
454
} elsif ($self->is_option) {
455
0
my $current = $self->get;
456
0
0
0
$self->click if ( (!$current && $value) || ($current && !$value) );
0
0
457
} else {
458
0
confess("Don't know how to set value to a non-input element!");
459
}
460
}
461
462
#Can't set anything else!
463
0
0
return $self->_doCallback($callback) || $ret;
464
}
465
466
sub _doCallback {
467
0
0
my ($self,$cb) = @_;
468
0
0
return 0 if !$cb;
469
0
0
return &$cb($self,$self->{'driver'} ? $self->{'driver'} : $self->{'element'}->{'driver'});
470
}
471
472
=head2 randomize(options)
473
474
Randomizes the input, depending on the type of element. Useful for fuzzing.
475
476
B :
477
478
I: Options appropraite to the relevant Data::Random method.
479
480
B:
481
482
I - Random value that has been set into the field, or false on failure.
483
484
=cut
485
486
0
0
1
sub randomize {
487
488
}
489
490
=head1 STATE CHANGE METHODS
491
492
=head2 javascript(js)
493
494
Execute an arbitrary Javascript string and return the output.
495
Handy in callbacks that wait for JS events.
496
497
B :
498
499
I - any valid javascript string
500
501
B:
502
503
I - depends on your javascript's output.
504
505
=cut
506
507
sub javascript {
508
0
0
1
my ($self, $js) = @_;
509
0
0
confess("Object parameters must be called by an instance") unless ref($self);
510
0
0
return $self->{'driver'} ? $self->{'element'}->get_eval($js) : $self->{'element'}->{'driver'}->execute_script($js);
511
}
512
513
=head2 click
514
515
Click the element.
516
517
=cut
518
519
sub click {
520
0
0
1
my ($self,$callback) = @_;
521
0
0
confess("Object parameters must be called by an instance") unless ref($self);
522
0
0
0
confess("Callback must be subroutine") if defined($callback) && reftype($callback) ne 'CODE';
523
0
0
$self->{'driver'} ? $self->{'driver'}->click($self->{'element'}) : $self->{'element'}->click();
524
525
0
0
return $self->_doCallback($callback) || 1;
526
}
527
528
=head2 submit([callback])
529
530
Submit the element, supposing it's a form
531
532
B :
533
534
I - anonymous function
535
536
B:
537
538
I - Whether the action succeeded or whatever your callback returns, supposing it was provided.
539
540
=cut
541
542
sub submit {
543
0
0
1
my ($self,$callback) = @_;
544
0
0
confess("Object parameters must be called by an instance") unless ref($self);
545
0
0
0
confess("Callback must be subroutine") if defined($callback) && reftype($callback) ne 'CODE';
546
0
0
return 0 if !$self->is_form();
547
0
0
$self->{'driver'} ? $self->{'driver'}->submit($self->{'element'}) : $self->{'element'}->submit();
548
549
0
0
return $self->_doCallback($callback) || 1;
550
}
551
552
1;
553
554
__END__