A day at the spa. What could be better: a massage, maybe some sauna time, or a mud bath. Wow, that sounds totally relaxing. Unfortunately, that kind of spa is a little different than a SPA, i.e., a single-page application. The latter can be the bane of an automator’s existence and hardly relaxing.
So, what exactly is a SPA? A SPA is exactly what the name indicates, i.e. a web app that runs on a single page. SPAs give a slick look-and-feel to the GUI; the transitions between states aren’t encumbered by the delay of a page transition. That slick look-and-feel, unfortunately, can make it hard to create consistent automated test scripts.
What’s the challenge of automation for SPAs? Mainly, the transient states of the HTML elements that that make the application look slick. Some of the culprits are that the elements:
- Can appear and disappear on the same web page.
- Often are added on-the-fly to the DOM, effectively changing the DOM.
- Often always exist but are only made visible or non-visible.
The direct result of these challenges is automated test scripts needing to wait for elements to both appear and disappear.
In non-SPA websites, we can often rely on page transitions to know when to start looking for other GUI elements. This “wait for transition” is often implicitly handled in the tool, stack, or framework that’s being used. Since SPAs don’t always or ever have a page transition between states, it’s more difficult to determine when to start to look for new GUI elements.
Waiting for GUI elements to exist and be visible are the bread-and-butter of traditional GUI test automation. Considering HTML, there are numerous approaches to handle waiting for an HTML element to appear. The easiest, most over-used, and most problematic is the hard wait. A hard wait is a programming approach that tells the test script to do just that: wait, unconditionally, for a specified duration. If the hard wait says to wait for 30 seconds but the HTML element with which we want to interact generally appears after two seconds, the script usually wastes about 28 seconds for this wait. This doesn’t seem like such a big waste but imagine 10 hard waits in one test script. Then, imagine, 200 test scripts with an average of 10 hard waits per script. Lots of math; lots of wasted waiting time just to handle those cases when the specific element needs the extra 28 seconds to appear.
A more refined approach is the polling wait. A polling wait is, as the name suggests, just that: waiting up to a specified duration but checking, or polling, along the way to see if the condition we expect is satisfied. If the condition is satisfied, execution can continue; if not, wait a little longer and poll again. As previously indicated, a hard wait waits for the specified duration with no regard for the potentially changed environment of the system under test. A polling wait polls for the condition to be met or for the specified timeout to be reached. If the timeout is reached before the expected condition is satisfied, the behavior is effectively the same as a hard wait: a timeout. If, however, the condition is satisfied before the wait timeout expires, execution can resume sooner than when using a hard wait, thus reducing execution time of a specific script.
For example, let’s say that most of the time a particular GUI widget will appear approximately two seconds after a specific button click. Because, however, computers will compute and networks will network, sometimes that GUI widget takes eleven or twelve seconds to appear. Setting a hard wait to handle this situation would require unconditionally waiting for at least 12 seconds, or maybe 13 seconds to be extra safe. Using a polling wait in this situation is preferable; it allows us to set the timeout as high as is reasonable, say 15 seconds, but in most cases, the test script will only wait for two seconds because that’s how the timing typically happens.
That explains waiting for elements to appear, but what about waiting for them to disappear? On a SPA, clicking a button often causes data to be fetched from a remote data source such as a database. While waiting for the call to this data source to return, the page can’t be updated with the returned data because it’s, you know, waiting for that data. This interim state is often handled by displaying a visual indicator on the webpage such as one of those spinning wheels; that wheel is, of course, an HTML element. Similarly, when clicking a button, a “slide-out” or “in-page popup” form is often displayed which is also an HTML element. As automators, we are used to making our test scripts wait for things to appear, but we’re not always used to waiting for things to disappear.
To demonstrate, consider this pseudo-code for a possible implementation of an automated script for the “slide-out” form described above:
Browser.AddUserForm.EmailAddress = “email@example.com”
Browser.AddUserForm.Password = “mypassword”
Well, we want to log out after we click the submit button. We could just click the logout button after we click the submit button, but there’s a race condition here: we may try to click the logout button before the form for adding a user disappears. This will cause the button click to fail in one way or another because the logout button is not yet clickable; it’s being shaded by the add user form.
We could wait until the logout button exists, is visible, or is clickable. The challenge with this approach is that depending on our automation tool’s implementation and our application’s implementation, any of those checks might evaluate to true even though the “slide-out” is still obscuring the button so the button is not actually clickable. It’s important to note that whether these checks succeed or fail is context-dependent, meaning that the checks may be appropriate for some tools and some SPA implementations, but not for others. Waiting for the “slide-out” to disappear is a more consistent way to decide when to proceed to the next step in an automated test script.
There is another problem, a different kind of problem. As automators, we’re very familiar with building our automation and associated scripts to handle the automation being faster than the GUI. To be fair, this makes a lot of sense because our automation needs to start waiting for some GUI update before that update happens. There are, however, cases where the anticipated update happens before the automation begins waiting for it. Consider the case of the “spinning wheel” described above. If the data retrieval happens fast enough, it’s possible that the spinning wheel appears and then disappears before our automation starts to check for it. In this situation, our automation will fail because the spinning wheel we were waiting to find never appears.
One solution to this problem is to wait for the spinning wheel to appear, but not to fail if it does not appear. After that wait, we then wait for the spinning wheel to disappear. Here some possible scenarios to illustrate this approach:
- Automation starts waiting before the spinning wheel appears or while it is visible; this is our usual case. The “wait for spinning wheel to appear” will succeed, then the “wait for spinning wheel to disappear” will wait until the element has disappeared.
- Automation starts waiting after the spinning wheel has already disappeared. The “wait for spinning wheel to appear” will fail but not cause an assertion failure to the automated test script, then the “wait for spinning wheel to disappear” will succeed since the spinning wheel is not visible.
- Automation executes both “wait for spinning wheel to appear” and “wait for spinning wheel to disappear” before the spinning wheel ever appears. Uh oh, how do we know if we arrived too early or if the spinning wheel never appeared at all? Clearly, this case is problematic.
Regarding the last bullet above, there are a few ways to handle it. One way to handle this is to lengthen the time the “wait for spinning wheel to appear” waits before moving on to the subsequent check; the longer timeout causes the statement to wait longer, thus allowing additional time for the element to appear. An alternate approach is to partner with the development team to devise a solution: perhaps, the spinning wheel can be displayed earlier in GUI’s execution flow so that it is always visible when the automation starts looking for it.
Wouldn’t it be better to just add some hard waits and call it a day? Would it be easier? Yes! Would it be better, i.e. more valuable? Not usually, but sometimes. But, to quote Alton Brown, as I like to do, “that’s another show”.
Creating automation for a SPA is never going to be a day at the beach. If, however, we keep the aforementioned considerations and approaches in mind, we can all relax just a little.
Like this? Catch me at an upcoming event!