I’m constantly needing to fill out forms when unit testing, so I need to make this process as simple as possible. Today I’ll cover a simple method that allows you to fill out all text inputs on a page just by sending a Python dictionary. This is an expansion on my last post, but today I’ll be including the needed import statements and the class this method is contained in, as well as a method to start the Chrome driver with options specified by the user.
Lots to cover, so let’s get to it! Here’s the full code we’ll be going over today:
from selenium import webdriver from selenium.common.exceptions import * class SeleniumHelper(object): """ Method to start the Chrome driver with specified helpful options """ def start_chrome_driver(self): self.options = webdriver.ChromeOptions() self.options.add_argument("start-maximized") self.options.add_argument("disable-infobars") self.options.add_argument("--disable-extensions") self.driver = webdriver.Chrome( options=self.options, executable_path=CHROME_PATH ) self.driver.implicitly_wait(1) """ Method accepts a dictionary of any size with keys equal to partial but still unique IDs or an xpath and values equal to desired text to be inserted in the specified input and textbox elements """ def set_input_elements(self, data): for key, value in data.items(): # Select each input and textarea element try: # partial but unique ids are easiest element = self.driver.find_element_by_xpath( f"//*[contains(@id, '{key}')]" ) element.send_keys(value) except NoSuchElementException: try: # xpaths are allowed for inputs without id attributes element = self.driver.find_element_by_xpath(key) element.send_keys(value) except NoSuchElementException: print(f"Element matching {key} can't be found!") def click_button(self, identifier): See code here
Starting the Web Driver
Before we can do anything, we need to start a web driver that Selenium can control. I prefer the ChromeDriver and have it downloaded locally. A variable called CHROME_PATH
points to its local path, which for me is a directory below the one where my script is called:
import os CHROME_PATH = os.getcwd() + '\\..\\chromedriver_win32\\chromedriver.exe'
os.getcwd()
is a neat little function that returns a string of the path of your current working directory.
Here’s the method itself:
""" Method to start the Chrome driver with specified helpful options """ def start_chrome_driver(self): self.options = webdriver.ChromeOptions() self.options.add_argument("start-maximized") self.options.add_argument("--disable-extensions") self.driver = webdriver.Chrome( options=self.options, executable_path=CHROME_PATH ) self.driver.implicitly_wait(1)
Before we start the driver, we first instantiate an object from the webdriver.ChromeOptions
class, where we add two arguments:
"start-maximized"
: This starts the driver with a maximized window, which would otherwise not be the case."--disable-extensions"
: This disables extensions the user may have installed on their version of Chrome, which could interfere with the testing process.
Once these arguments are added, we open the web driver with the specified options.
self.driver = webdriver.Chrome( options=self.options, executable_path=CHROME_PATH )
Finally, we add a self.driver.implicitly_wait(1)
. This tells the driver to wait for one second before throwing an error. The amount of time can be increased, but in my testing, most of the time an error cannot be avoided by waiting for more time – and when it does happen, an explicit wait is simple enough to add. If an error is going to be thrown, I generally want to know about it pretty quickly.
Filling Out Text Input Elements
This is a fairly simple method that uses a lot of the same techniques covered in the click_button()
method from my last post.
""" Method accepts a dictionary of any size with keys equal to partial but still unique IDs or an xpath and values equal to desired text to be inserted in the specified input and textbox elements """ def set_input_elements(self, data): for key, value in data.items(): # Select each input and textarea element try: # partial but unique ids are easiest element = self.driver.find_element_by_xpath( f"//*[contains(@id, '{key}')]" ) element.send_keys(value) except NoSuchElementException: try: # xpaths are allowed for inputs without id attributes element = self.driver.find_element_by_xpath(key) element.send_keys(value) except NoSuchElementException: print(f"Element matching {key} can't be found!")
It accepts one argument, data
, which must be a dictionary, as the first thing we are doing is iterating through the keys and values of each of the items in the dictionary:
def set_input_elements(self, data): for key, value in data.items():
Like last time, we’re looking for an input element by a partial but still unique ID:
try: # partial but unique ids are easiest element = self.driver.find_element_by_xpath( f"//*[contains(@id, '{key}')]" ) element.send_keys(value)
If that’s unsuccessful, we can try by XPath. I’ve found that most web forms have good IDs to target, but sometimes XPath is still necessary. I haven’t found CSS selectors uniquely useful to be necessary for this method (yet), so I haven’t added them here, although one certainly could.
except NoSuchElementException: try: # xpaths are allowed for inputs without id attributes element = self.driver.find_element_by_xpath(key) element.send_keys(value)
Each of these try
clauses ends with element.send_keys(value)
. Don’t make the mistake I did of adding this just once outside of these clauses. This can have the unintended consequence of appending text to a previous input element when the next input element can’t be found by its identifier.
If an input element cannot be found by either partial but unique ID or XPath, a message is printed to let the user know: print(f"Element matching {key} can't be found!")
. This does not break the loop, however, allowing one to include items in the dictionary that may or may not appear on the form in question.
Simple Example Usage
Save this script in a file named selenium_helper.py
, then in a Python shell enter the following:
from selenium_helper import SeleniumHelper() sh = SeleniumHelper() sh.start_chrome_driver()
This pulls up a blank Chrome shell that looks like this:
Next, we enter:
sh.driver.get("http://www.google.com")
Which pulls up a familiar looking website:
Inspecting the source code of Google’s home page, I’m actually a little surprised there is no id
attribute for its searchbox:
That means we’ll need to send a valid XPath, which shouldn’t be too hard. Let’s type the following into our Python shell:
sh.set_input_elements({"//input[@name='q']" : "hello world"})
Here, we’re calling our set_input_elements
with a dictionary targeting an input element that has a name attribute equal to “q”, then sending the text “hello world” to that element. When we press enter in our shell, like magic, that exact thing happens!
Finally, we just need to click the search button using the method we defined last time. We’ll likewise be using an XPath here, as Google hasn’t provided an id attribute to use:
sh.click_button("//input[@value='Google Search']")
And we have our results! We’ll continue expanding this class with more methods in the future.