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