line
stmt
bran
cond
sub
pod
time
code
1
package Selenium::Element;
2
3
1
1
4
use strict;
1
2
1
30
4
1
1
4
use warnings;
1
2
1
21
5
6
1
1
4
use Carp;
1
1
1
53
7
1
1
4
use Scalar::Util qw(blessed reftype looks_like_number);
1
2
1
55
8
1
1
1808
use Data::Random;
0
0
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
my ($class,$element,$driver,$selector) = @_;
42
confess("Constructor must be called statically, not by an instance") if ref($class);
43
return undef if !$element;
44
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' );
45
46
my $self = {
47
'driver' => $driver,
48
'element' => $element,
49
'selector' => $selector
50
};
51
52
bless $self, $class;
53
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
my ($self) = @_;
66
confess("Object parameters must be called by an instance") unless ref($self);
67
if ($self->{'driver'}) {
68
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
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));
71
my $js = $parts[0] eq 'id' ? 'document.getElementById("'.$parts[1].'").nodeName' : 'document.querySelectorAll("'.$parts[1].'")[0].nodeName';
72
return lc($self->javascript($js));
73
}
74
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
my $self = shift;
85
confess("Object parameters must be called by an instance") unless ref($self);
86
return undef unless $self->is_input;
87
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
my ($self) = @_;
101
confess("Object parameters must be called by an instance") unless ref($self);
102
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
my ($self) = @_;
113
confess("Object parameters must be called by an instance") unless ref($self);
114
my $itype = $self->get_type();
115
my $ret = scalar(grep {$_ eq $itype} ('password', 'text'));
116
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
my ($self) = @_;
127
confess("Object parameters must be called by an instance") unless ref($self);
128
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
my $self = shift;
139
confess("Object parameters must be called by an instance") unless ref($self);
140
return 0 if !$self->is_select;
141
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
my ($self) = @_;
152
confess("Object parameters must be called by an instance") unless ref($self);
153
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
my ($self) = @_;
164
confess("Object parameters must be called by an instance") unless ref($self);
165
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
my ($self) = @_;
176
confess("Object parameters must be called by an instance") unless ref($self);
177
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
my ($self) = @_;
188
confess("Object parameters must be called by an instance") unless ref($self);
189
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
my ($self) = @_;
200
confess("Object parameters must be called by an instance") unless ref($self);
201
confess("WWW::Selenium does not support getting tag type of elements") if $self->{'driver'};
202
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
my ($self) = @_;
213
confess("Object parameters must be called by an instance") unless ref($self);
214
confess("WWW::Selenium does not support getting tag type of elements") if $self->{'driver'};
215
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
my $self = shift;
226
confess("Object parameters must be called by an instance") unless ref($self);
227
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
my ($self) = @_;
238
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
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
my $self = shift;
251
confess("Object parameters must be called by an instance") unless ref($self);
252
return () unless $self->is_select();
253
my @options = ();
254
if ($self->{'driver'}) {
255
#XXX obviously not reliable
256
carp("WARNING: WWW::Selenium has reduced ability to get options! This may not work as you expect.");
257
my @labels = $self->{'driver'}->get_select_options($self->{'element'});
258
return map {Selenium::Element->new("css=option[value=$_]",$self->{'driver'})} @labels;
259
}
260
my @opts = $self->{'element'}->{'driver'}->find_child_elements($self->{'element'},'option','tag_name');
261
return map {Selenium::Element->new($_,0)} @opts;
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
my ($self,$option) = @_;
279
confess("Object parameters must be called by an instance") unless ref($self);
280
confess("Option must be passed as argument") unless defined($option);
281
return 0 if !$self->is_select();
282
return scalar(grep {$_->name eq $option} $self->get_options());
283
}
284
285
=head2 is_selected
286
287
Returns whether the element is selected.
288
289
=cut
290
291
sub is_selected {
292
my $self = shift;
293
confess("Object parameters must be called by an instance") unless ref($self);
294
confess("Element must be option to check if selected") unless $self->is_option;
295
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
my ($self) = @_;
313
confess("Object parameters must be called by an instance") unless ref($self);
314
315
my $ret = 0;
316
317
#Try to get various stuff based on what it is
318
if ($self->is_checkbox || $self->is_radio) {
319
return $self->{'driver'} ? $self->{'driver'}->is_checked() : $self->{'element'}->is_selected();
320
} elsif ($self->is_select) {
321
if ($self->is_multiselect) {
322
my @options = grep {defined $_} map {$_->is_selected ? $_->name : undef} $self->get_options;
323
return \@options;
324
} else {
325
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
return $self->{'driver'} ? $self->{'driver'}->get_attribute($self->{'element'},'value') : $self->{'element'}->get_attribute('value');
329
} elsif ($self->is_option) {
330
return $self->{'driver'} ? defined $self->{'driver'}->get_attribute($self->{'element'},'selected') : defined $self->{'element'}->get_attribute('selected');
331
} else {
332
$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
my $self = shift;
344
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
my $self = shift;
355
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
my ($self) = @_;
368
confess("Object parameters must be called by an instance") unless ref($self);
369
confess("Element must be text") unless $self->is_textinput();
370
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
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));
373
my $js = $self->{'selector'}->[1] eq 'id' ? 'document.getElementById("'.$self->{'selector'}->[0].'").value = ""' : 'document.querySelectorAll("'.$self->{'selector'}->[0].'")[0].value = ""';
374
$self->javascript($js);
375
} else {
376
$self->{'element'}->clear();
377
}
378
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
my ($self,$value,$callback) = @_;
402
confess("Object parameters must be called by an instance") unless ref($self);
403
confess("Value must be passed to set") unless defined($value);
404
confess("Callback must be subroutine") if defined($callback) && reftype($callback) ne 'CODE';
405
406
my $enabled = $self->is_enabled();
407
carp "Attempting to set disabled element" unless $enabled;
408
return undef unless $enabled;
409
my $ret = 0;
410
411
#Try to set various stuff based on what it is
412
SETBLOCK : {
413
if ($self->is_checkbox || $self->is_radio) {
414
my $selected = $self->{'driver'} ? $self->{'driver'}->is_checked() : $self->{'element'}->is_selected();
415
last SETBLOCK if ($selected && $value) || (!$selected && !$value); #Return false if state hasn't changed
416
$self->{'driver'} ? $self->{'driver'}->click($self->{'element'}) : $self->{'element'}->click();
417
$ret = 1;
418
} elsif ($self->is_textinput) {
419
$self->clear();
420
$self->{'driver'} ? $self->{'driver'}->type_keys($self->{'element'},$value) : $self->{'element'}->send_keys($value);
421
$ret = 1;
422
} elsif ($self->is_fileinput) {
423
if ($self->{'driver'}) {
424
$self->{'driver'}->attach_file($self->{'element'},$value);
425
} else {
426
$self->{'element'}->send_keys($value);
427
}
428
$ret = 1;
429
} elsif ($self->is_hiddeninput) {
430
#TODO make this work a bit more universally if possible
431
confess("Setting values on hidden elements without IDs not supported") unless $self->id;
432
carp("Setting value of hidden element, this may result in unexpected behavior!");
433
my $js = 'document.getElementById("'.$self->id.'").value = \''.$value.'\';';
434
$self->javascript($js);
435
$ret = 1;
436
} elsif ($self->is_select) {
437
$value = [$value] if reftype($value) ne 'ARRAY';
438
if ($self->{'driver'}) {
439
foreach my $val (@$value) {
440
$self->{'driver'}->type($self->{'element'},$value);
441
}
442
} else {
443
foreach my $val ($self->get_options()) {
444
if (grep {$val->{'element'}->get_attribute('name') eq $_ } @$value) {
445
#Leave values high if they are requested
446
$val->click if !$val->is_selected;
447
} else {
448
#otherwise ensure low values
449
$val->click if $val->is_selected;
450
}
451
}
452
}
453
$ret = 1;
454
} elsif ($self->is_option) {
455
my $current = $self->get;
456
$self->click if ( (!$current && $value) || ($current && !$value) );
457
} else {
458
confess("Don't know how to set value to a non-input element!");
459
}
460
}
461
462
#Can't set anything else!
463
return $self->_doCallback($callback) || $ret;
464
}
465
466
sub _doCallback {
467
my ($self,$cb) = @_;
468
return 0 if !$cb;
469
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
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
my ($self, $js) = @_;
509
confess("Object parameters must be called by an instance") unless ref($self);
510
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
my ($self,$callback) = @_;
521
confess("Object parameters must be called by an instance") unless ref($self);
522
confess("Callback must be subroutine") if defined($callback) && reftype($callback) ne 'CODE';
523
$self->{'driver'} ? $self->{'driver'}->click($self->{'element'}) : $self->{'element'}->click();
524
525
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
my ($self,$callback) = @_;
544
confess("Object parameters must be called by an instance") unless ref($self);
545
confess("Callback must be subroutine") if defined($callback) && reftype($callback) ne 'CODE';
546
return 0 if !$self->is_form();
547
$self->{'driver'} ? $self->{'driver'}->submit($self->{'element'}) : $self->{'element'}->submit();
548
549
return $self->_doCallback($callback) || 1;
550
}
551
552
1;
553
554
__END__