Finding Elements

One of the most common and yet often most difficult tasks in Marionette is finding a DOM element on a webpage or in the chrome UI. Marionette provides several different search strategies to use when finding elements. All search strategies work with both find_element() and find_elements(), though some strategies are not implemented in chrome scope.

In the event that more than one element is matched by the query, find_element() will only return the first element found. In the event that no elements are matched by the query, find_element() will raise NoSuchElementException while find_elements() will return an empty list.

Search Strategies

Search strategies are defined in the By class:

from marionette import By
print(By.ID)

The strategies are:

  • id - The easiest way to find an element is to refer to its id directly:

    container = client.find_element(By.ID, 'container')
    
  • class name - To find elements belonging to a certain class, use class name:

    buttons = client.find_elements(By.CLASS_NAME, 'button')
    
  • css selector - It’s also possible to find elements using a css selector:

    container_buttons = client.find_elements(By.CSS_SELECTOR, '#container .buttons')
    
  • name - Find elements by their name attribute (not implemented in chrome scope):

    form = client.find_element(By.NAME, 'signup')
    
  • tag name - To find all the elements with a given tag, use tag name:

    paragraphs = client.find_elements(By.TAG_NAME, 'p')
    
  • link text - A convenience strategy for finding link elements by their innerHTML (not implemented in chrome scope):

    link = client.find_element(By.LINK_TEXT, 'Click me!')
    
  • partial link text - Same as link text except substrings of the innerHTML are matched (not implemented in chrome scope):

    link = client.find_element(By.PARTIAL_LINK_TEXT, 'Clic')
    
  • xpath - Find elements using an xpath query:

    elem = client.find_element(By.XPATH, './/*[@id="foobar"')
    

Chaining Searches

In addition to the methods on the Marionette object, HTMLElement objects also provide find_element() and find_elements() methods. The difference is that only child nodes of the element will be searched. Consider the following html snippet:

<div id="content">
    <span id="main"></span>
</div>
<div id="footer"></div>

Doing the following will work:

client.find_element(By.ID, 'container').find_element(By.ID, 'main')

But this will raise a NoSuchElementException:

client.find_element(By.ID, 'container').find_element(By.ID, 'footer')

Finding Anonymous Nodes

When working in chrome scope, for example manipulating the Firefox user interface, you may run into something called an anonymous node.

Firefox uses a markup language called XUL for its interface. XUL is similar to HTML in that it has a DOM and tags that render controls on the display. One ability of XUL is to create re-useable widgets that are made up out of several smaller XUL elements. These widgets can be bound to the DOM using something called the XML binding language (XBL).

The end result is that the DOM sees the widget as a single entity. It doesn’t know anything about how that widget is made up. All of the smaller XUL elements that make up the widget are called anonymous content. It is not possible to query such elements using traditional DOM methods like getElementById.

Marionette provides two special strategies used for finding anonymous content. Unlike normal elements, anonymous nodes can only be seen by their parent. So it’s necessary to first find the parent element and then search for the anonymous children from there.

  • anon - Finds all anonymous children of the element, there is no search term so None must be passed in:

    anon_children = client.find_element('id', 'parent').find_elements('anon', None)
    
  • anon attribute - Find an anonymous child based on an attribute. An unofficial convention is for anonymous nodes to have an anonid attribute:

    anon_child = client.find_element('id', 'parent').find_element('anon attribute', {'anonid': 'container'})