Tuesday, April 9, 2013

Using Jquery to find WebElements

One of the interesting things I've discovered using the Selenium Webdriver's 'execute_script()' method is anytime you execute code that returns a WebElement node, it automatically gets converted to a Selenium WebElement. Try the following code snippet using the python interactive shell.
david$ python
Python 2.7.2 (default, Oct 11 2012, 20:14:37)
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from selenium import webdriver
>>> driver = webdriver.Firefox()
>>> driver.get("http://www.google.com")
>>> input_field = driver.execute_script("return document.getElementById('gbqfq');")
>>> type(input_field)
<class 'selenium.webdriver.remote.webelement.WebElement'>

>>> input_field.send_keys("hello world")
>>>
 Notice that when I called execute script that returns and HTML DOM element, it automatically gets wrapped in a selenium WebElement object, which we can treat as a webelement.

Because of this, we can do some interesting tricks, one of which is using JQuery's APIs for searching for a target webelement.  We can write a simple function that injects jQuery into any target webpage given a webdriver, then will execute a jquery call and return the result.  The trick here is to return [0], which will get you a reference directly to the underlying DOM node.


def find_by_jquery(driver, jquery_selector):
    """Injects JQuery into the page.  This will allow the page to accept jquery javascript
    commands for automating some of the items selenium may have troubles with.
    @param webdriver_or_page_object: Selenium webdriver or a WTF PageObject."""
    jquery = open('jquery.min.js').read()
    webdriver.execute_script(jquery)
    javascript = "return $(\"{0}\")[0]".format(jquery_selector)
    return driver.execute_script(javascript)

Now we can do some interesting selection using jQuery's powerful selection criteria.  For example, say I want to get a reference to a deeply buried webelement.  Like the PE ratio from Yahoo's stock quote page.  This would normally require a complicated xpath that will likely break on IE or Safari, but using jquery, we have a simple elegant selector that's simply finds the text we're looking for, then look at the next sibling of the desired type.
driver = webdriver.Firefox()
driver.get("http://finance.yahoo.com/q?s=GOOG%2C+&ql=1")
PE_cell = find_by_jquery(driver, "th:contains('P/E')~td")
print "The PE is: ", PE_cell.text
This is also very useful for shops who develop their script in jQuery.  If the developers are using jquery to find all their elements, then this allows on the automation end to leverage the same selectors the devs are using.  This technique can also be adapted to work with other JS frameworks.



2 comments:

Iyer the Great! said...

Kinda confused around this :


jquery = open('jquery.min.js').read()
webdriver.execute_script(jquery_selector)
javascript = "return $(\"{0}\")[0]".format(jquery_selector)
return driver.execute_script(javascript)

Am no python literate, but here's a calculated guess..

so you are reading the contents of the query.min.js into a variable named jquery.

But I dont see it being used anywhere else. So what is the idea of reading it ? Am I missing something there ?

David Lai said...

That is correct. I'm reading the contents of the minimized jquery file into a python string. You can find the file here, http://code.jquery.com/jquery-1.10.2.min.js

I also had a small mistake in the code. The following execute_script statement should of been:
webdriver.execute_script(jquery)

This is the part that injects jquery into the page's context.