Many years ago, when I first started with web automation, I used a Ruby web framework called Watir. A lot has changed since then and I’ve used many different automation frameworks, including Golem, Nightwatch and others.
Recently I’ve been going back to direct calls to Selenium, rather than using a framework. Calling Selenium directly is easier when working with Mobile web automation, with a Appium.
Many frameworks built over Selenium will allow users to re-reference web elements. It’s trivial to access a field, send some text in, attempt a submit and try it again.
However, straight Selenium will not operate like that and it’s a good thing.
Instead of having code that runs procedurally:
There’s a better way to handle this, and in fact Selenium will require it. Selenium will complain if you call an element that’s already been defined.
Imagine we have a field that should take a 6 digit pin. 7+ digits will reject as will 5 or less. So we want to test a case were we try 5 digits and then 7 digits. Then we try to pass in a 6 digit pin, which should update.
If we write this procedurally, Selenium will complain when we reuse the defined elements:
# Defined Elements elem_call_in_pin = driver.find_element_by_css('#pin') elem_submit = driver.find_element_by_css('#submit') # Actions elem_call_in_pin.clear() elem_call_in_pin.send_keys("1234") elem_submit.click() elem_call_in_pin.clear() elem_call_in_pin.send_keys("1234567") elem_submit.click()
Reusing the field for “elem_call_in_pin” will throw an error in Selenium:
Element not found in the cache
The reason for the error is that the elements were defined. Unless you reinitialise them, you can’t reuse them. Besides, constantly calling the same field over and over is hard to read.
If we throw the code into a method, and simplify it, we can call the method repeatedly. Each time it initializes the elements on page, so they don’t go stale.
update_pin(driver, "12345") update_pin(driver, "1234567") update_pin(driver, "123456") def update_pin(driver, pin): # Defined Elements elem_call_in_pin = driver.find_element_by_css('#pin') elem_submit = driver.find_element_by_css('#submit') # Actions elem_call_in_pin.clear() elem_call_in_pin.send_keys(pin) elem_submit.click() src = driver.page_source pin_fail = bool(re.search(r'%s' % ("Please correct the following error highlighted below."), src)) pin_pass = re.search(r'%s' % ("Your account information has been updated successfully."), src) if len(pin) > 6 or len(pin) < 6 and pin_fail: print("PASS: Invalid PIN is not allowed") else: print("FAIL: Invalid PIN was accepted.") if pin_pass: print("PASS: Valid PIN was updated")
The above code calls the update_pin method, passing in a string. The first two strings are outside the boundary of the acceptable value. This is checked using a regular expression that looks for pass/fail text on the page.
An if statement is used to check for a condition where the pin string has a length too short, or too long and the fail text is present. If that’s right, then we output a pass.
By invoking the method each time (to reuse elements for testing), we create new objects for the fields and buttons. It’s also cleaner (less distracting elements in the code.)