Last week I wrote about combining Selenium and py.test and I promised to also talk about my function find_elements_by_jquery().
Selenium by default can find elements by id, CSS selector and XPath, but I often find I already know the query as a jQuery selector, and so frequently it’s easiest just to use that.
We start by overloading the Selenium webdriver. Since the webdriver is exposed through several classes (one per web browser), we do this in a particularly meta way.
from selenium.webdriver.remote.webdriver import WebElement
from selenium.common.exceptions import InvalidSelectorException
def MyWebDriver(base, **kwargs):
return type('MyWebDriver', (_MyWebDriver, base), kwargs)
class _MyWebDriver(object):
def create_web_element(self, element_id):
return MyWebElement(self, element_id)
def find_elements_by_jquery(self, jq):
return self.execute_script('''return $('%s').get();''' % jq)
def find_element_by_jquery(self, jq):
elems = self.find_elements_by_jquery(jq)
if len(elems) == 1:
return elems[0]
else:
raise InvalidSelectorException(
"jQuery selector returned %i elements, expected 1" % len(elems))
We then do a similar implementation for the webelement:
class MyWebElement(WebElement):
def __repr__(self):
"""Return a pretty name for an element"""
id = self.get_attribute('id')
class_ = self.get_attribute('class')
if len(id) > 0:
return '#' + id
elif len(class_) > 0:
return '.'.join([self.tag_name] + class_.split(' '))
else:
return self.tag_name
def find_elements_by_jquery(self, jq):
return self.parent.execute_script(
'''return $(arguments[0]).find('%s').get();''' % jq, self)
def find_element_by_jquery(self, jq):
elems = self.find_elements_by_jquery(jq)
if len(elems) == 1:
return elems[0]
else:
raise InvalidSelectorException(
"jQuery selector returned %i elements, expected 1" % len(elems))
We can now pass in jQuery selectors for instance b.find_element_by_jquery('#region option:selected'). Or form.find_elements_by_jquery(':input'). It’s especially incredibly powerful when all of your DOM manipulation already works in terms of jQuery selectors.
As an added bonus, overloading the classes lets us add functionality like Firebug style element names (MyWebElement.__repr__) or wrap things like the Wait utility into the webdriver, e.g.
from selenium.webdriver.support.ui import WebDriverWait as Wait
from selenium.common.exceptions import TimeoutException
class FrontendError(Exception):
pass
# class _MyWebDriver...
def wait(self, event, timeout=10):
try:
Wait(self, timeout).until(event)
except (TimeoutException, FrontendError) as e:
# do we have an error dialog
dialog = self.find_element_by_id('error-dialog')
if dialog.is_displayed():
content = dialog.find_element_by_id('error-dialog-content')
raise FrontendError(content.text)
else:
raise e

This looks really useful; reckon you could do put it up on pypi ?
Hi Danielle,
This looks interesting. We’re building a service where writing and *maintaining* Selenium tests becomes easy. We might be interested in integrating your jquery selector support in, as it is the da-facto way to define selectors nowadays.
Check out http://usetrace.com for more information, and apply for an invite to see more.
Cheers,
–Arto