Danny Brown

A Blog on Code and Occasionally Other Things

Writing an Element Clicker with Python and Selenium That Takes ID, CSS Selector, or XPath

Danny BrownSeptember 13, 2019

When unit testing using Python and Selenium, it’s usually easiest to click a button with an element’s ID, but I’ve found a more flexible solution that doesn’t require multiple different function calls to be best.

To that end, here’s a method I’ve written that one can use to click a button using one of three identifiers for an element:

  1. A partial but still unique ID. (I test a site with long, dynamically generated IDs, where usually only the ending portion of the ID is relevant.)
  2. A CSS selector.
  3. An XPath.

Depending on a website’s HTML structure and the specific actions one is trying to take, sometimes only an XPath or CSS selector will really do the trick, but it’s nice to be able to just copy some or all of an ID when available.

For context, this is part of a class called SeleniumHelper, which has a Selenium WebDriver instance stored in self.driver. Here’s the full method to start; we’ll go through it in detail below.

def click_button(self, identifier, dbl_click=False):
    attempts = 0
    while True:
        try:
            element = self.driver.find_element_by_xpath(
                f"//*[contains(@id, '{identifier}')]"
            )
        except NoSuchElementException:
            try: # try to find the element by CSS selector
                element = self.driver.find_element_by_css_selector(
                    f"{identifier}"
                )
            except NoSuchElementException:
                try: # try to find element by xpath
                    element = self.driver.find_element_by_xpath(
                        f"{identifier}"
                    )
                except NoSuchElementException:
                    print(
                        f"Cannot find element by ID, CSS selector, or "
                        f"xpath:\n\n\t{identifier}\n\nPlease send a valid "
                        f"selection string of one of these types."
                    )
                    attempts += 1
                    if attempts > 5:
                        break
        if dbl_click:
            action = ActionChains(self.driver)
            action.double_click(element).perform()
        else:
            action = ActionChains(self.driver)
            action.move_to_element(element).click(element).perform()
        break

Selecting the Element

The bulk of this method is pretty much just working through the selection of an element as flexibly as possible.

First, we check for a partial but unique ID attribute:

        try: 
            element = self.driver.find_element_by_xpath(
                f"//*[contains(@id, '{identifier}')]" 
            )

If that fails, we’re looking for an element by CSS selector:

        except NoSuchElementException:
            try: # try to find the element by CSS selector
                element = self.driver.find_element_by_css_selector(
                    f"{identifier}"
                )

As a last resort, we look for the element by XPath:

            except NoSuchElementException:
                try: # try to find element by xpath
                    element = self.driver.find_element_by_xpath(
                        f"{identifier}"
                    )

Giving Elements a Chance to Load

The problem I’ve found with using Selenium’s WebDriverWait class within this function is that it will wait for the ID check, then the CSS selector, then the XPath, really slowing up the testing process. Instead, I’ve wrapped these selectors in a while statement that counts the number of attempts through the process. I have it set to five attempts here, but that can be increased or decreased depending on a site’s load speed or other factors:

def click_button(self, identifier, dbl_click=False):
    attempts = 0
    while True:
        try:
            # selections all happen here
            # ... etc. etc. etc.
            # if not found by any of the three selection types:
        except NoSuchElementException:
            attempts += 1
            if attempts > 5:
                print(
                    f"Cannot find element by ID, CSS selector, or "
                    f"xpath:\n\n\t{identifier}\n\nPlease send a "
                    f"valid selection string of one of these types."
                )
                break

This isn’t a perfect solution, as sometimes elements will take longer to load than the number of attempts specified will take to perform. The speed gains, in my opinion, are worth the inconvenience of having to code waits outside of this function call.

In truth, this portion of the function could probably be handled more elegantly and reliably, but it has saved me from a few errors without slowing down testing significantly. If you’re positive an element will appear, you could simply have the loop continue until the element is found, not counting attempts at all. The problem with this approach is that if something goes wrong with the webpage, a test can be caught in an endless loop until the user notices.

Clicking the Button

You probably noticed the dbl_click=False in the function definition:

def click_button(self, identifier, dbl_click=False):

By default, calling click_button(identifier) will single-click the element. By instead specifying in the function call that dbl_click=True (i.e., click_button(identifier, dbl_click=True)), the element will instead be double-clicked. This isn’t often necessary on webpages, but it’s nice to be able to easily call it with an optional argument.

        if dbl_click:
            action = ActionChains(self.driver)
            action.double_click(element).perform()
        else:
            action = ActionChains(self.driver)
            action.move_to_element(element).click(element).perform()
        break

Don’t forget to break out of the while statement, or Selenium will try to click the same button forever.

Posted In code | Python | Selenium
Tagged unit testing

Post navigation

PreviousA 2019 of Live Performances, Q2
NextFilling Out Input Elements on a Web Form with One Method Call Using Python and Selenium

Danny Brown

A Dev Blog with Some Tangents

About

Categories

  • code
    • APIs
    • Bash
    • CSS
    • Django
    • HTML
    • JavaScript
    • Python
    • S3
    • Selenium
    • Serverless
    • TypeScript
  • games
  • music
    • concert reviews
    • synthesizers
  • opinion
  • sports
  • tech
    • Bitbucket
    • Git
    • GitHub
    • MS Teams
    • WordPress
  • theater

Recent Posts

  • Open Pull Requests from the Terminal (One of My Favorite Dotfiles Scripts)
  • Dotfiles Script for a New TypeScript/Node Project
  • So I Told You to Go See a Broadway Play? Tips for Theater in New York
  • Build a Simple Microsoft Teams Bot Easily, No SDK Required
  • Creating a GUI for Conway’s Game of Life Using Pygame and Numpy

External Links

  • GitHub
  • LinkedIn

Recent Posts

  • Open Pull Requests from the Terminal (One of My Favorite Dotfiles Scripts)
  • Dotfiles Script for a New TypeScript/Node Project
  • So I Told You to Go See a Broadway Play? Tips for Theater in New York
  • Build a Simple Microsoft Teams Bot Easily, No SDK Required
  • Creating a GUI for Conway’s Game of Life Using Pygame and Numpy

Categories

  • code
    • APIs
    • Bash
    • CSS
    • Django
    • HTML
    • JavaScript
    • Python
    • S3
    • Selenium
    • Serverless
    • TypeScript
  • games
  • music
    • concert reviews
    • synthesizers
  • opinion
  • sports
  • tech
    • Bitbucket
    • Git
    • GitHub
    • MS Teams
    • WordPress
  • theater
Copyright © 2025. Danny Brown
Powered By WordPress and Meritorious