teodesian / Selenium-Remote-Driver

Perl Bindings to the Selenium Webdriver server
173 stars 90 forks source link

fireEvent new command #130

Open ghost opened 10 years ago

ghost commented 10 years ago

Since recently, there is a new command in the selenium command list : fireEvent Here is an extract from a script :

fireEvent id=searchq blur

It is used to simulate events on some fields. Does anyone had a look on it and how to implement it in perl ? I had a look in selenium server the sources, it seems to be translated in a javascript that find the element and send the event on it. something like document.getElementById('foobar').onblur();

Any idea what has to be sent to the server ?

gempesaw commented 10 years ago

I did a cursory google search and saw some results from 2009/2010 referring to selenium RC? I didn't see anything about it in the JSONWireProtocol document. It does sound quite useful though so it would be nice if it were an up and coming change...

ghost commented 10 years ago

I had a deeper look in the java client code and in the file java/client/src/com/thoughworks/selenium/DefaultSelenium, there is a "fireEvent" command that is sent to the selenium server. I'm trying that and let you know

ghost commented 10 years ago

Selenium package is now using some javascript libraries to execute commands like fireEvent. When recompiling the server sources, all those libraries are built and could be imported / used to realize the job. For example, fireEvent blur is translated in "return ('fireEvent.js').apply(null, arguments);" The fireEvent.js is built during the server compilation process. I did a test with execute_script and it works fine ! How could we import those scripts and enhance our perl webDriver to provide all those new possibilities ?

gempesaw commented 10 years ago

For example, fireEvent blur is translated in "return ('fireEvent.js').apply(null, arguments);" The fireEvent.js is built during the server compilation process. I did a test with execute_script and it works fine !

Is fireEvent.js is automatically loaded/included with the standalone server? What did your execute_script test look like?

How could we import those scripts and enhance our perl webDriver to provide all those new possibilities ?

Making an assumption or two, in WebElement.pm, a suite of event functions that look something like

sub blur {
    my ($self) = @_;

    return $self->execute_script('return ("fireEvent.js").apply(null, arguments);', $self, 'blur');
}

with usage like

my $elem = $driver->find_element('unique', 'id');
$elem->blur;

???

(oh and hmm we'd need to update WebElement's driver attribute to handle execute_script as well, nbd...

ghost commented 10 years ago

The file fireEvent.js is used for multiple purpose first, to retrieve the element from its locator, then to send the event to the element. so the code should look like

$driver->fire_event('id=unique', 'blur')

So fire_event could look like

sub fire_event {
  my ($self, @args) = @_ ;
  my $script = this->_some_function_to_retreive_js_file('fireEvent.js') ;
  return this->execute_script("return (" . $script . ").apply(null, arguments);", @args) ;
}

The fireEvent.js script is built during the compilation of the selenium-sources package (client and server side). In the java client, it is built in a library file so using some "getResource" function will find the file and load it... Then i guess that we should import those files in the perl package

gempesaw commented 10 years ago

The fireEvent.js script is built during the compilation of the selenium-sources package (client and server side). In the java client, it is built in a library file so using some "getResource" function will find the file and load it...

Would you mind providing a few links to the references where you found this stuff? Feeling a bit lazy about digging through docs at the moment :)

On different note, do you know if there are events that we want to fire against the entire page, ie, not against a particular element? If the events are always fired against elements, I think they'd fit better in the WebElement class instead of on the Driver - it's more consistent with the rest of the 'element only' actions being on WebElement..

ghost commented 10 years ago

Sorry for the delay,

You may download the selenium-standalone version from git : http://selenium.googlecode.com/git/ Then use the "go" shell script to generate the packages and have a look in build/javascript/selenium-atoms/ you will find the javascript files needed.

About implementation, I think it should be one of Driver's methods. The first argument is the locator of the field. This locator is an argument of the javascript. The driver doesn't need to retreive the webElement to ask it to execute the fireEvent script on it. But, the driver should ask to the browser to execute the script with 2 arguments : element locator and event name.

About events that could be sent on the entire page, I'm not sure. What I have seen so far, is events like 'rotate', 'shake', 'tap or double tap', etc... all events what comes with devices like phones.

ghost commented 10 years ago

I do have a problem with the result type of _execute_command. If a script does not return a webElement, then undef value is returned. But undef might be a valid response.

So is it possible to change the return value of _execute_command to check if the caller wants an array :

    return $resp->{cmd_return};

should by replaced by

    return wantarray ? ($resp->{cmd_status}, $resp->{cmd_return}) : $resp->{cmd_return};

The execute_script should also be adapted :

  my $ret = $self->_execute_command( $res, $params );
  return $self->_convert_to_webelement($ret) ; 

could be replaced by

  my @ret = $self->_execute_command( $res, $params );

  return wantarray ? ($ret[0], $self->_convert_to_webelement($ret[1])) : 
                    $self->_convert_to_webelement($ret[1])  ;

here the diff :

440c440,441
<                 return $resp->{cmd_return};
---
>               return wantarray ? ($resp->{cmd_status}, $resp->{cmd_return}) : $resp->{cmd_return};
>               # return $resp->{cmd_return};
1269c1270,1274
<         my $ret = $self->_execute_command( $res, $params );
---
>         my @ret = $self->_execute_command( $res, $params );
>         # my $ret = $self->_execute_command( $res, $params );
> 
>         return wantarray ? ($ret[0], $self->_convert_to_webelement($ret[1])) : $self->_convert_to_webelement($ret[1])  ;
>         # return $self->_convert_to_webelement($ret);
1271d1275
<         return $self->_convert_to_webelement($ret);

Let me know what you think about it

ghost commented 10 years ago

Any comment on my proposal ?

gempesaw commented 10 years ago

Hm, it does seem to be directly related to #135 - the return value of _execute_command is causing problems.

I'm reluctant to change the behavior of _execute_command, as it would impact every sub. The test coverage is pretty strong but I don't think it's 100%. I'm additionally wary about wantarray. Finally, as stated in the "USAGE" section of the docs,

If no error occurred, then the subroutine called will return the value sent back from the server (if a return value was sent).

So a rule of thumb while invoking methods on the driver is if the method did not croak when called, then you can safely assume the command was successful even if nothing was returned by the method.

which would imply that getting back undef from your executed script can?/should? be interpreted as success. That being said, things get a lot more complicated, especially with execute_script, so. I'm not really sure.

teodesian commented 7 years ago

It sounds like this is a javascript extension to the selenium RC; as such this should probably be implemented as a convenience wrapper around execute_script(), rather than fiddling with something fundamental like execute_script.

There are actually a wide variety of JS convenience methods what should probably be added to the module (such as waiting on events to fire, which is the only real way to conquer race conditions).