Skip to main content

Building Selectors


Make sure you've gone through Selectors before going ahead.

Writing selectors that just work is easy but writing selectors that are resilient to page changes require some practice. Carefully using test specific element identifiers in application helps in building strong selectors.

Available selector types and guidance on appropriate usage#

  • Element's text#

    This is most useful type of selector. It selects elements using their visible text. Using it makes your selectors transparent to changes as elements are selected by their visible identity and not something that is hidden in html source.

  • Element's attributes#

    • data-testid: Selectors that use data-testid are considered safe and resilient because this attribute is mainly used for testing and most changes to page shouldn't have an effect on these. It's a good practice to add it to page elements that need to be selected but don't have other strong selectors. Several popular applications on the web use data-testid to aid testing and there is absolutely no harm in keeping it in production.
    • Some elements that serve a specific purposes (such as text input or action buttons) usually have identifiers that don't change often such as name, title, aria-label and role. Using these as selectors is considered safe.
    • Attribute id must be used carefully as some elements may have dynamic ids that must not be used. You can also make it a rule within your team to not use id to prevent accidental use of dynamic ids.
    • Use of attributes such as class is discouraged and are prone to failure.
  • Element's tag#

    • May be used sparingly for specific purposes. For example if you want to select the header element of a page, selecting it by tag is fine as there will be only one header element at the top browsing context of a page.
  • Css selector#

    Css selectors that depend on a specific DOM structure aren't considered safe and should only be used as last resort as they may break with changes to the page. See following example that searches a term in google and verifies the first result is intended:

    openUrl('')type(findElement('Search', by.ariaLabel), 'wikipedia', keys.enter)firstResult = findElement('div[role="main"] div[data-async-context="query:wikipedia"] > :first-child', by.cssSelector)assertTrue(containsString(getElementText(firstResult), 'Wikipedia, the free encyclopedia'))

    In 3rd line we've used a css selector that does the job of finding the first result but it is not safe and prone to failure in future. It assumes that inside the main div, there is an inner div with attribute 'data-async-context' that is the container of all results and we want it's first child to get the first result. If in future, the result container is something else than the one we're assuming is or the result container start to contain some other node as first child rather than the intended result, this selector will fail.

    If you find yourself in similar situations, assigning data-testid to elements almost always solves the problem. For the google search example, imagine if every result contains a data-testid = searchResult we could've changed our selector to a much cleaner and stronger one like so:

    firstResult = findElement('searchResult', by.testId)

    Which selects the first result and it is extremely safer and faster than before.

Things to consider while building selectors#

  • Never use a 'tool' to auto generate selectors for your application.#

    Instead, understand your application and your tests requirements. Little additions to html can make it very easy to write strong selectors. If you follow the guidance on appropriate usage, you'd soon realise the selectors you really ever need are the ones that need plain string such as element text, data-testid or some other safe element attributes.

    In some cases when you're testing a legacy app and can't make additions to html (to add test specific identifiers like data-testid), css selectors may be the most viable selector option. We'd still advice against using a 'tool' as they may generate brittle css selectors that could depend on random class names or multi level child selectors. Instead, learn to write css selectors and write your own. We suggest you learn from this mdn article on css selectors. While you learn, use chrome/firefox developer tool's console tab to test your selectors (such as using document.querySelectorAll('YOUR_CSS_SELECTOR'))

  • If you really have to use DOM dependent css selectors, try to keep them short and less brittle.#

    In some cases, it may be unavoidable to write css selectors. If you write them, try making them less brittle. Try not to use class names and don't make too much use of multi level child selectors or sibling selectors.

    Always test your css selectors before using them in code. Use chrome/firefox developer tool's console tab and evaluate what your selector yields. For example, document.querySelectorAll('YOUR_CSS_SELECTOR') selects all nodes that matches specified selector. Confirm that your selectors are optimized and less prone to breakage before using them in tests.

  • Always narrow down the scope of your searches wherever possible#

    When you build a selector and invoke one of the function to get the required element, webdriver searches that element in the DOM before returning it. If you don't specify a parent element to start from, webdriver starts from the root of DOM consuming more time to search. To speed up your tests, always use a parent element if you've one. You should always do this when you need to search multiple elements within the same parent element. To do so use functions such as findElementFromElement(elemId, using, by), get the parent element once and use it in further searches.

Later chapters will discuss all available selectors in greater detail.