How to fix Frontend Tests as a lazy developer
Ingo Steinke, web developer
Posted on December 14, 2022
Following up on my first steps of test automation using CodeceptJS and Testomat.io, let's assume you're a lazy / pragmatic programmer with no priority to perfect your rudimental front-end tests.
This is fine! you're following the YAGNI principle ("You Aren't Gonna Need It") or rather that's not fine and you're lazily doing error-driven test development instead of test-driven development (TDD).
Pragmatic Approach
Okay, I actually met some people who do test driven development, and they love it. On the other end of the spectrum, there are fellow DEVelopers wondering if "writing tests [is] still a thing?"
Why 100% Test Coverage might not be enough
Viewing myself somewhere in between, I don't even strive to write clean code and achieve 100% test coverage, although I have learned, at least in theory, that 100% test coverage might not even be enough.
Takeaways from a Testing and Refactoring Conference (apart from the T-Shirt)
Ingo Steinke, web developer ・ Mar 31 '22
Exercise No. 1
100% test coverage is not enough, because ___________________.
(Please print this post and fill in your answers and submit them to your teacher by the end of the weekend.)
Extending existing frontend tests
Now back to business! Let's get our hands dirty, at least literally, by extending existing frontend tests.
It does not really matter much what kind of testing framework or language you use. Unlike unit testing, frontend (or behavioral) testing is quite similar across different frameworks.
Your automated tests will act on behalf of a real user, open a real web browser (typically Chromium, Chrome, Firefox) using one an existing technology like Webdriver and add some syntactical sugar on top.
You can then try to identify and interact with elements of web pages using more or less sophisticated selectors, including visible text, CSS class names, or XPath if you want to feel like a real hacker, crafting arcane code that is at least a little bit more legible than a regular expression.
Dark mode is not enough if we can use XPath!
I.click('.wc-block-mini-cart__button'); // click mini cart button (top right)
I.waitForElement('//h2[starts-with(text(),"Your Cart")]', 120); // verify that the heading is visible
I.waitForElement('a.wc-block-mini-cart__footer-checkout', 120);
I.click('a.wc-block-mini-cart__footer-checkout'); // click "checkout"
I.waitForElement('//*[contains(@class,"breadcrumb_last") and contains(text(),"Checkout")]', 120);
I.amOnPage('https://www.example.com/checkout/');
I.scrollPageToBottom();
I.scrollTo('.wc-block-components-checkout-return-to-cart-button',0,-500);
I.waitForElement('//*[contains(@class, "wc-block-components-checkout-step__title") and contains(text(),"Payment options")]', 120);
Now if that's not the anti-pattern of clean code, I don't know what it is.
Fragile Software Development
Testing real-world front-end code is always quite fragile. Ever wondered why so few people actually use screenshot tests?
The example code quoted above has been used for testing an actual web shop built using WooCommerce, an extension to the infamous WordPress content management system dreaded by developers but still very popular among small business site owners.
Using WordPress or any other extensible community software with an ecosystem of more than 50,0000 plugins you can hardly rely on well-formed accessible markup.
Instead, we have to rely on button text, which might change depending on our default language, rebranding, or an updated translation file, or CSS selectors nested in a most complicated way, about to change when the next update of the built-in editor or one of its plugins will introduce the next breaking change.
Pragmatic Approach: test the "happy path"!
As a rule of thumb, let's start by testing the happy path, adding some relevant edge cases based on
- errors that happen frequently
- critical errors that might compromise security or prevent our customers from earning money.
In an e-commerce (web shop) scenario, this usually means to prove that we are able to place an order.
Keeping our tests as generic as possible, we don't want to rely on a specific product being available, but rather open the home page, pick the first product that we see, put it in the cart and try to buy it.
Well, there might be reasons not to actually complete a transaction (place an order). Some shops aren't able to tell fake / test orders from real ones, and it might come costly to cancel or refund test orders on a regular basis.
At least we can prove that we enter the checkout, see at least one available payment option, and an active checkout button that is not disabled.
Viewing and selecting elements
Defining a "happy path" scenario might work like this:
I.click('.wc-block-mini-cart__button');
This is a simple selector following the Document.querySelector() syntax.
I won't dive into details about complex XPath variations, but rather discuss some problems that might not seem intuitive at first sight.
Before you start, have a look at your testing framework's documentation and find out if you are expected to specify explicitly if and how long you want to wait for something, or if the test commands imply asynchronous execution by default.
If not, we will have to use commands like
I.waitForElement('.wc-block-mini-cart__button', 120);
and make arbitrary assumptions about how long to wait for an element to become visible before giving up.
Visibility detection
Another common misconception or uncertainty is, does an element have to be visible inside our browser's viewport to be clickable or count as "existing"?
If unsure, we might have to explicitly scroll up or down or perform some other action to create a situation where our element should absolutely be visible and accessible, like scrolling back to the top of the document to make sure we see some specific element, like the "mini cart" icon in the header.
I.scrollPageToTop();
I.click('.wc-block-mini-cart__button');
Last, but not least, some elements require some trial and error to "catch" them with an automated test.
"This is fine!"
One of my customers used a cookie consent banner that lacked any semantic markup. An abomination of DIV elements with inline style attributes, the "button" to accept the cookie policy was neither a <button>
nor a link (<a>
) but rather a <span>
with a click handler that my testing framework failed to recognize.
I doubt that cookie consent plugin is in any way accessible, and I don't want to be the user with visual impairments listening to their screen reader trying to get past the cookie consent, but (pragmatically and unfortunately) not our top priority that day.
"This is fine!"
For some reason, using XPath did do the job this time:
// I.click('Accept all'); // does not work
I.click('//span[contains(.,"Accept all")]'); // works
Conclusion
Rudimentary, fragile, and incomplete as they may be: any test is better than no test at all!
I hope this anecdotal examples might help some of you to either fix some problem you have been stuck with, or else just feel better about your own code having fun a funny DEV blog to procrastinate before finishing your own test definitions.
Exercise No. 2
Finish your own test definitions: ___________________.
Good luck and happy coding!
Posted on December 14, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.