SeleniumHQ/selenium — pull request #134 by j1z0 (2013-11-09 14:02:16+00:00)
Title: Fix for Iissue 6026 - All Webdriver implementations should provide standard creation functions

Please see [Issue 6026](http://code.google.com/p/selenium/issues/detail?id=6026)
The idea is this. You should be able to pass everything into the web
driver init function through the desired_capabilities. So I'm creating a
function decorator that will decorate all the __init__ functions and it
will look for entries in the desired_capabilities dictionary that have
the same name as one of the keyword arguments of the init function and
then it will pull the value out of the desired_capabilities and create a
keyword argument for it that get's passed to the init function.

To that end there is a a new decorator class called
`AllowDesiredCapabilitiesOverrides' which handles all the magic to make
this happen.

The advantages of this approach are:

* 'backwards compatibility' will be maintained.
* I have left all arguments the same and added an argument called
`desired_capabilities` where needed. You can still use the old
`capabilities` argument but that argument won't trigger the
DesiredCapabilitiesOverides, if you do use `desired_capabilities` it
will overwrite `capabilities` for ie and firefox.
* if / when new options are added to the driver `__init__` functions
they will automatically be handled, so we should be able to rely on
using desired_capabilities for everything for now and into the future.

* There is also sytax to make it easy to pass in complex object, like
`FirefoxProfile`

A copule of usage examples may make it more clear. Let's start off
with something simple like phantomjs. First the phantomjs `__init__`
now looks like:

```python
@AllowDesiredCapabilitiesOverrides()
def __init__(self, executable_path="phantomjs",
port=0, desired_capabilities=DesiredCapabilities.PHANTOMJS,
service_args=None, service_log_path=None):
```
So actually we only added the decorator.

Then to use it we could do something like this:

```python
caps = {'init.service_args' : ['arg1', 'arg2'], 'init.port':55}
driver = webdriver.PhantomJS(desired_capabilities = caps)
```
This just allows for us to pass in all arguments through the
desired_capabilities object and will function the same as if we had
passed in `service_args` and `port` through the standard `__init__`
arguments. Again the idea is to not break backward compatibility but
to be able to configure not only the remote webdriver but also the
`service` through desired_capabilities to simplify the usage by third
part tools.

A slightly more complicated, but still pretty simple example would be
firefox. First the `__init__`

```python
@AllowDesiredCapabilitiesOverrides(constructors={'firefox_profile':FirefoxProfile,
'firefox_binary':FirefoxBinary,
'proxy':Proxy})
def __init__(self, firefox_profile=None, firefox_binary=None,
timeout=30, capabilities=None, proxy=None, desired_capabilities=None):
if not desired_capabilities is None: capabilities = desired_capabilities
#rest of function remains unchanged
```

The difference here is we want to be able to pass in simple arguments
to create the necessary helper objects for firefox, i.e.
`FirefoxProfile` rather than having to create the profile and pass it
in. So the `constructors` argument from
`AllowDesiredCapabilitieiesOverrides` allow you to pass in a dictionary
of init argument names maped to "constructor" callables. This way you can
simply pass in the values you want for the object you want to
construct. Here is an example of how to use.

```python
caps = {"init.firefox_profile" : ["/path/to/FirefoxProfile"] }
driver = webdriver.Firefox(desired_capabilities=caps)

```
When making the above call `FirefoxProfile('/path/to/FirefoxProfile')` will be called
and then the newly created FirefoxProfile will be passed into the
Firefox webdriver.__init__ function.

You can also call via keyward arguments as such:
```python
caps = {"init.firefox_binary" : {'firefox_path' : 'path',
'log_file':'log'}}

driver = webdriver.Firefox(desired_capabilities=caps)
```

I hope this all makes sense. This I belive should provide the
functionality we are looking for in issue 6026 while not breaking
backward compatibility and keeping all the changes / magic in one place.

I've also written extensive tests so I'm pretty sure it works OK, but
I'm happy to make any changes needed to get the pull request accepted.

Also I have signed the commiters agreement.

@lukeis -- appreciate if you could have a look at this and see if it
is going to work.