diff --git a/Changes b/Changes index 09fd2474..8b1fb62b 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,14 @@ Revision history for Selenium-Remote-Driver +1.47 2022-05-02 TEODESIAN + - Add DWIM to inputs accepted by ActionChains send_keys, key_up & key_down, and add some docu + +1.46 2021-12-04 TEODESIAN + - Document the keys of WDKEYS hash in POD. Contribution by Yuki Kimoto. + +1.45 2021-10-21 TEODESIAN + - Remove ill-advised test reaching out to saucelabs at install-time, vendors are the users' problem + 1.44 2021-03-26 TEODESIAN - Remove all usage of default profiles for Firefox when in direct binary mode. diff --git a/dist.ini b/dist.ini index 70b4449f..31f192bc 100644 --- a/dist.ini +++ b/dist.ini @@ -1,5 +1,5 @@ name = Selenium-Remote-Driver -version = 1.44 +version = 1.47 author = George S. Baugh author = Aditya Ivaturi author = Daniel Gempesaw diff --git a/lib/Selenium/ActionChains.pm b/lib/Selenium/ActionChains.pm index 11eed73d..64eec836 100644 --- a/lib/Selenium/ActionChains.pm +++ b/lib/Selenium/ActionChains.pm @@ -127,11 +127,12 @@ sub move_to_element_with_offset { } sub key_down { - my $self = shift; - my ( $value, $element ) = @_; - if ( defined($element) ) { - $self->click($element); - } + my ( $self, $value, $element ) = @_; + + #DWIM + $value = [$value] unless ref $value eq 'ARRAY'; + + $self->click($element) if defined $element; foreach my $v (@$value) { push @{ $self->actions }, sub { $self->driver->general_action( actions => [ { type => 'key', id => 'key', actions => [ { type => 'keyDown', value => $v } ] } ] ) }; @@ -140,30 +141,54 @@ sub key_down { } sub key_up { - my $self = shift; - my ( $value, $element ) = @_; - if ( defined($element) ) { - $self->click($element); - } + my ( $self, $value, $element ) = @_; + + #DWIM + $value = [$value] unless ref $value eq 'ARRAY'; + + $self->click($element) if defined $element; foreach my $v (@$value) { push @{ $self->actions }, - sub { $self->driver->$self->driver->general_action( actions => [ { type => 'key', id => 'key', actions => [ { type => 'keyUp', value => $v } ] } ] ) }; + sub { $self->driver->general_action( actions => [ { type => 'key', id => 'key', actions => [ { type => 'keyUp', value => $v } ] } ] ) }; } return $self; } sub send_keys { - my $self = shift; - my $keys = shift; + my ($self,$keys) =@_; + + # Do nothing if there are no keys to send + return unless $keys; + + # DWIM + $keys = [split('',$keys)] unless ref $keys eq 'ARRAY'; + push @{ $self->actions }, - sub { $self->driver->get_active_element->send_keys($keys) }; + sub { + foreach my $key (@$keys) { + $self->key_down($key, $self->driver->get_active_element); + $self->key_up($key, $self->driver->get_active_element); + } + }; $self; } sub send_keys_to_element { - my $self = shift; - my ( $element, $keys ) = @_; - push @{ $self->actions }, sub { $element->send_keys($keys) }; + my ($self, $element, $keys) =@_; + + # Do nothing if there are no keys to send + return unless $keys; + + # DWIM + $keys = [split('',$keys)] unless ref $keys eq 'ARRAY'; + + push @{ $self->actions }, + sub { + foreach my $key (@$keys) { + $self->key_down($key,$element); + $self->key_up($key,$element); + } + }; $self; } @@ -311,7 +336,9 @@ LIMITATIONS. =head2 key_down Sends key presses only, without releasing them. -Should be used only with modifier keys (Control, Alt, Shift) +Useful when modifier keys are requried + +Will DWIM your input and accept either a string or ARRAYREF of keys. Args: An array ref to keys to send. Use the KEY constant from Selenium::Remote::WDKeys @@ -319,12 +346,16 @@ Should be used only with modifier keys (Control, Alt, Shift) Usage: use Selenium::Remote::WDKeys 'KEYS'; - $action_chains->key_down( [ KEYS->{'alt'} ] ); + # DEFINITELY cut and paste this in without looking + $action_chains->key_down( [ KEYS->{'alt'}, KEYS->{'F4'} ] ); =head2 key_up -Releases a mofifier key. +Releases prior key presses. +Useful when modifier keys are requried + +Will DWIM your input and accept either a string or ARRAYREF of keys. Args: An array ref to keys to send. Use the KEY constant from Selenium::Remote::WDKeys @@ -332,8 +363,10 @@ Releases a mofifier key. Usage: use Selenium::Remote::WDKeys 'KEYS'; + # Fullscreen the foo element my $element = $driver->find_element('foo','id'); - $action_chains->key_up( [ KEYS->{'alt'} ],$element); + $action_chains->key_down( [ KEYS->{'alt'}, KEYS->{'enter'} ], $element ); + $action_chains->key_up( [ KEYS->{'alt'}, KEYS->{'enter'} ], $element); =head2 move_by_offset @@ -388,7 +421,10 @@ Releases a held mouse_button =head2 send_keys -Sends keys to the currently focused element +Sends keys to the currently focused element. +Essentially an alias around key_down then key_up. + +Will DWIM your input and accept either a string or ARRAYREF of keys. Args: The keys to send @@ -398,7 +434,9 @@ Sends keys to the currently focused element =head2 send_keys_to_element -Sends keys to an element +Sends keys to an element in much the same fashion as send_keys. + +Will DWIM your input and accept either a string or ARRAYREF of keys. Args: A Selenium::Remote::WebElement diff --git a/lib/Selenium/Remote/WDKeys.pm b/lib/Selenium/Remote/WDKeys.pm index 630a3550..db1c00fc 100644 --- a/lib/Selenium/Remote/WDKeys.pm +++ b/lib/Selenium/Remote/WDKeys.pm @@ -6,6 +6,82 @@ package Selenium::Remote::WDKeys; The constant KEYS is defined here. +=head1 SYNOPSIS + + use Selenium::Remote::WDKeys; + + my $space_key = KEYS->{'space'}; + my $enter_key = KEYS->{'enter'}; + +=head1 CONSTANT KEYS + + null + cancel + help + backspace + tab + clear + return + enter + shift + control + alt + pause + escape + space + page_up + page_down + end + home + left_arrow + up_arrow + right_arrow + down_arrow + insert + delete + semicolon + equals + numpad_0 + numpad_1 + numpad_2 + numpad_3 + numpad_4 + numpad_5 + numpad_6 + numpad_7 + numpad_8 + numpad_9 + multiply + add + separator + subtract + decimal + divide + f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10 + f11 + f12 + command_meta + ZenkakuHankaku + +=head1 FUNCTIONS + +Functions of Selenium::Remote::WDKeys. + +=head2 KEYS + + my $keys = KEYS(); + +A hash reference that contains constant keys. This function is exported by default. + =cut use strict; diff --git a/lib/Selenium/Waiter.pm b/lib/Selenium/Waiter.pm index f4326751..d17ba2cc 100644 --- a/lib/Selenium/Waiter.pm +++ b/lib/Selenium/Waiter.pm @@ -118,6 +118,8 @@ sub wait_until (&%) { return $try_ret if $try_ret; } + warn 'timeout' if $args->{debug}; + # No need to repeat ourselves if we're already debugging. warn $exception if $exception && !$args->{debug}; return ''; diff --git a/lib/Test/Selenium/Remote/Role/DoesTesting.pm b/lib/Test/Selenium/Remote/Role/DoesTesting.pm index fb38d0c2..c4caa615 100644 --- a/lib/Test/Selenium/Remote/Role/DoesTesting.pm +++ b/lib/Test/Selenium/Remote/Role/DoesTesting.pm @@ -23,9 +23,11 @@ has _builder => ( sub _get_finder_key { my $self = shift; my $finder_value = shift; + foreach my $k ( keys %{ $self->FINDERS } ) { return $k if ( $self->FINDERS->{$k} eq $finder_value ); } + return; } @@ -56,7 +58,7 @@ sub _check_method { sub _check_ok { my $self = shift; my $method = shift; - my $real_method = ''; + my @args = @_; my ( $rv, $num_of_args, @r_args ); try { @@ -86,48 +88,50 @@ sub _check_ok { # quick hack to fit 'find_no_element' into check_ok logic if ( $method eq 'find_no_element' ) { - $real_method = $method; - # If we use `find_element` and find nothing, the error # handler is incorrectly invoked. Doing a `find_elements` # and checking that it returns an empty array does not # invoke the error_handler. See # https://github.com/gempesaw/Selenium-Remote-Driver/issues/253 - $method = 'find_elements'; - my $elements = $self->$method(@r_args); - if ( scalar(@$elements) ) { + my $elements = $self->find_elements(@r_args); + if ( @{$elements} ) { $rv = $elements->[0]; } else { - $rv = 1; + $rv = 1; # empty list means success } } else { - $rv = $self->$method(@r_args); + $rv = $self->$method(@r_args); # a true $rv means success } } catch { - if ($real_method) { - $method = $real_method; - $rv = 1; + if ($method eq 'find_no_element') { + $rv = 1; # an exception from find_elements() means success } else { $self->croak($_); } }; - my $default_test_name = $method; - $default_test_name .= "'" . join( "' ", @r_args ) . "'" - if $num_of_args > 0; + # test description might have been explicitly passed + my $test_name = pop @args; - my $test_name = pop @args // $default_test_name; + # generic test description when no explicit test description was passed + if ( ! defined $test_name ) { + $test_name = $num_of_args > 0 ? + join( ' ', $method, map { q{'$_'} } @r_args ) + : + $method; + } # case when find_no_element found an element, we should croak - if ( $real_method eq 'find_no_element' ) { + if ( $method eq 'find_no_element' ) { if ( blessed($rv) && $rv->isa('Selenium::Remote::WebElement') ) { $self->croak($test_name); } } + return $self->ok( $rv, $test_name ); } @@ -136,39 +140,50 @@ sub _check_ok { sub _build_sub { my $self = shift; my $meth_name = shift; - my @func_args; - my $comparators = { + + # e.g. for $meth_name = 'find_no_element_ok': + # $meth_comp = 'ok' + # $meth_without_comp = 'find_no_element' + my @meth_elements = split '_', $meth_name; + my $meth_comp = pop @meth_elements; + my $meth_without_comp = join '_', @meth_elements; + + # handle the ok testing methods + if ( $meth_comp eq 'ok' ) { + return sub { + my $self = shift; + + local $Test::Builder::Level = $Test::Builder::Level + 2; + + return $self->_check_ok($meth_without_comp, @_); + }; + } + + # find the Test::More comparator method + my %comparators = ( is => 'is_eq', isnt => 'isnt_eq', like => 'like', unlike => 'unlike', - }; - my @meth_elements = split( '_', $meth_name ); - my $meth = '_check_ok'; - my $meth_comp = pop @meth_elements; - if ( $meth_comp eq 'ok' ) { - push @func_args, join( '_', @meth_elements ); - } - else { - if ( defined( $comparators->{$meth_comp} ) ) { - $meth = '_check_method'; - push @func_args, join( '_', @meth_elements ), - $comparators->{$meth_comp}; - } - else { - return sub { - my $self = shift; - $self->croak("Sub $meth_name could not be defined"); - } - } + ); + + # croak on unknown comparator methods + if ( ! exists $comparators{$meth_comp} ) { + return sub { + my $self = shift; + + return $self->croak("Sub $meth_name could not be defined"); + }; } + # handle check in _check_method() return sub { my $self = shift; + local $Test::Builder::Level = $Test::Builder::Level + 2; - $self->$meth( @func_args, @_ ); - }; + return $self->_check_method( $meth_without_comp, $comparators{$meth_comp}, @_ ); + }; } 1; diff --git a/t/13-waiter.t b/t/13-waiter.t new file mode 100644 index 00000000..009ab0fd --- /dev/null +++ b/t/13-waiter.t @@ -0,0 +1,49 @@ +use strict; +use warnings; + +use Selenium::Waiter; + +use FindBin; +use lib $FindBin::Bin . '/lib'; +use Test::More; + +my $res; + +subtest 'basic' => sub { + my @warning; + local $SIG{__WARN__} = sub { push( @warning, $_[0] ) }; + + $res = wait_until { 1 }; + is $res, 1, 'right return value'; + + $res = wait_until { 0 } timeout => 1; + is $res, '', 'right return value'; + + is( scalar @warning, 0, 'no warnings' ); +}; + +subtest 'exception' => sub { + my @warning; + local $SIG{__WARN__} = sub { push( @warning, $_[0] ) }; + + $res = wait_until { die 'case1' } debug => 0, timeout => 1; + is $res, '', 'right return value'; + is( scalar @warning, 1, 'right number of warnings' ); + like( $warning[0], qr{^case1}, 'right warning' ); + + @warning = (); + eval { + $res = wait_until { die 'case2' } die => 1, timeout => 1; + }; + like $@, qr{case2}, 'right error'; + is $res, '', 'right return value'; + is( scalar @warning, 0, 'right number of warnings' ); + + @warning = (); + $res = wait_until { 0 } debug => 1, timeout => 1; + is $res, '', 'right return value'; + is( scalar @warning, 1, 'right number of warnings' ); + like( $warning[0], qr{timeout}i, 'timeout is reported' ); +}; + +done_testing; diff --git a/t/Test-Selenium-Remote-Driver-google.t b/t/Test-Selenium-Remote-Driver-google.t index 04ab1115..99663cbc 100644 --- a/t/Test-Selenium-Remote-Driver-google.t +++ b/t/Test-Selenium-Remote-Driver-google.t @@ -18,7 +18,6 @@ my $harness = TestHarness->new( ); my %selenium_args = %{ $harness->base_caps }; -use Carp::Always; my $selfmock = Test::MockModule->new('Selenium::Remote::Driver'); $selfmock->mock('new_session', sub { my $self = shift; $self->{session_id} = "58aff7be-e46c-42c0-ae5e-571ea1c1f466" }); diff --git a/t/error.t b/t/error.t index f52cf9e0..888dcc26 100644 --- a/t/error.t +++ b/t/error.t @@ -43,29 +43,4 @@ LOCAL: { 'Error message for not finding a selenium server is helpful' ); } -SAUCE: { - SKIP: { - my $host = 'ondemand.saucelabs.com'; - my $port = 80; - my $sock = IO::Socket::INET->new( - PeerAddr => $host, - PeerPort => $port, - ); - - skip 'Cannot reach saucelabs for Sauce error case ', 1 - unless $sock; - - like(exception { - Selenium::Remote::Driver->new_from_caps( - remote_server_addr => $host, - port => $port, - desired_capabilities => { - browserName => 'invalid' - } - ); - }, qr/Sauce Labs/, - 'Saucelabs errors are passed to user'); - - } -} done_testing;