First off, here's the code:
@classmethod def find(cls, query, selector='id', catch_all=False): if catch_all: finder = 'find_elements_by_' else: finder = 'find_element_by_' for i in xrange(30): try: elements = getattr( cls.driver, finder + selector )(query) break except StaleElementReferenceException: print 'attempting to recover \ from StaleElementReferenceException ...' time.sleep(1) if elements: return elements else: raise Exception( 'Could not solve stale reference' )
This code achieves several goals. First off, it can PEP8 the code a little by reducing a function call that looks something like the following:
into the following:
Also, by wrapping all the different calls to Selenium's find_element functions, we can add pieces of functionality that solve some of the inherent problems in the different browser implementations. The code we saw covers stale reference exceptions, that are very common when testing in IE. It would explicitly wait for 30 seconds, trying repeatedly to fetch the element, and breaks out of the loop only when the element is caught. A possible
if type(cls.driver) == type(webdriver.Ie) could be added so that this logic would only apply to explorer tests, but this isn't really necessary, and wouldn't really work if the tests were migrated to Sauce, for example (where the browser type is always Remote).
Asimilar route could be followed to try and solve invisible element problems. Those occur very often in PhantomJS, due to the browser's speed, at which some JS driven elements might not be visible on time. The solution to such a problem could be something like the following:
for i in xrange(30): try: elements = getattr( cls.driver, finder + selector )(query) break except StaleElementReferenceException: print 'caught StaleElementReferenceException' time.sleep(1) except ElementNotVisibleException: print 'caught ElementNotVisibleException' time.sleep(1)
I've also seen this happen on all browsers when the mouse hovers over elements that expand, thus hiding other elements while Selenium is trying to reach them. A solution to this sort of problem would be to gradually move the mouse until the popped up element is left, and the concealed elements are visible again. Here's the sample code to illustrate this:
for i in xrange(30): try: elements = getattr( cls.driver, finder + selector )(query) break except StaleElementReferenceException: print 'caught StaleElementReferenceException' time.sleep(1) except ElementNotVisibleException: print 'caught ElementNotVisibleException' action_chain = webdriver.ActionChains( self.driver ) action_chain.move_by_offset(50, 50).perform() time.sleep(1)
I hope this minor contribution to the code pool that is the blogosphere would make someone's life a little bit easier some day.
'Till then, be safe, and Godspeed.