1 Simple Way Developers Can Improve Testability
Mar 12, 2024
Sappo
It does not matter what automation testing tool or framework you use. If you want to deliver a quality product to your users, you should also want to give a testable product to your testers.
IDs
IDs are great; you probably use IDs for several things already. You might have anchor points through a page to give users a nice place to jump to. You might have them on your forms or the fields inside. You might use IDs for A/B testing your call to action or applying styling. This is the problem with IDs; there are too many purposes.
Test IDs
Adding Test IDs is the single best way to massively improve your testability, regardless of your automation test tool or framework.
What is a Test ID?
A Test ID is an HTML attribute (commonly data-test-id
but there are many aliases) added to core elements to speed up and simplify testing.
<button data-test-id="login-button" type="submit">Login</button>
Test IDs can be added to any element, but if you're getting started, I would suggest the following:
Buttons
Links
Forms & Inputs
User Feedback (alerts, modals, form validation, loading spinners, etc.)
Menus
Lists
Checkout Summaries
How do Test IDs save engineering time?
Let's say a developer adds a "forgotten password" button to a login form; the resulting code might look like this:
<form class="login" action="/login" method="post">
<h1 class="h3 mb-3 font-weight-normal">Please Login</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control mb-2" placeholder="Email address" required autofocus />
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control mb-2" placeholder="Password" required />
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
<p class="mt-5 mb-3 text-muted">
<a href="/forgotten-password">Forgot password?</a>
</p>
</form>
An automation tester will look at this and have to create a selector. They might use a combination of tags and classes for the element and its parents. Nothing here is too tricky, but producing a unique selector on a large page often takes several minutes.
Now let's look again with Test IDs added:
<form data-test-id="login-form" class="login" action="/login" method="post">
<h1 class="h3 mb-3 font-weight-normal">Please Login</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input data-test-id="login-username" type="email" id="inputEmail" class="form-control mb-2" placeholder="Email address" required autofocus />
<label for="inputPassword" class="sr-only">Password</label>
<input data-test-id="login-password" type="password" id="inputPassword" class="form-control mb-2" placeholder="Password" required />
<button data-test-id="login-button" class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
<p class="mt-5 mb-3 text-muted">
<a data-test-id="login-forgotten-password" href="/forgotten-password">Forgot password?</a>
</p>
</form>
These Test IDs will only take a developer seconds to add and a tester seconds to use. So, this has already saved your engineering team, as a whole, several minutes per selector. But the real benefits are still to come.
How Test IDs improve test quality?
The quality of the selector is far better; here's the above comparison as an example:
[data-test-id="login-forgotten-password"] // <-- Robust Test ID
Side Node: DoesQA has a specific selector type for Test IDs 😉
form.login > p.text-muted > a // <-- Flaky CSS Selector
Both of the selectors above were unique at the time they were created. But whereas the Test ID should permanently be unique, the other will stop being unique if another link is added to the login form; here's an example:
<form class="login" action="/login" method="post">
<h1 class="h3 mb-3 font-weight-normal">Please Login</h1>
<!-- ... -->
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
<p class="mt-5 mb-3 text-muted">
<a href="/forgotten-password">Forgot password?</a>
<a href="/register">Create Account</a>
</p>
</form>
With the addition of a new link, previous tests will start failing. Revisiting this test to improve the selector is wasted time that could be used to expand test coverage elsewhere.
How Test IDs make testing more efficient?
We have already examined how Test IDs can save some time; with some imagination, we can extrapolate this to substantial efficiency gains in the long term. But there's a much more direct way to show that Test IDs will bring massive time savings.
What happens when the login form gets a face-lift? As it's such an integral area of your service, you have hundreds of tests following through it. Your functional regression tests shouldn't care if your form inputs now look slightly different (that's the job of your Visual Regression tests).
The simple solution is that your existing Test IDs can be reused. While development is ongoing, you could even have the same test pack running on different environments, one with the new form and another with the old. Here's a new form reusing the Test IDs:
<form data-test-id="login-form" class="max-w-md mx-auto mt-8 bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/login" method="post">
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2" for="username">Username</label>
<input data-test-id="login-username" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="text" placeholder="Username" required />
</div>
<div class="mb-6">
<label class="block text-gray-700 text-sm font-bold mb-2" for="password">Password</label>
<input data-test-id="login-password" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" id="password" type="password" placeholder="Password" required />
</div>
<div class="flex items-center justify-between">
<button data-test-id="login-button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="submit">Sign In</button>
<a data-test-id="login-forgotten-password" class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" href="/forgotten-password">Forgot Password?</a>
</div>
</form>
More Testing, Less Coding
Automation Testing is the overlapping area on a Vent Diagram between Coding and Testing. You want to save manual testing time by running high-quality automation tests with broad coverage, but traditionally, you've needed coders to write these tests while wanting them designed with a tester's mind.
Times are changing, and creating a high-quality test pack without code is both possible and desirable. However, building page objects/elements required some knowledge of CSS or XPath. These coding skills are entirely unnecessary and result in slower and less reliable tests than using the Test IDs inserted by developers.
Note to Devs
Howdy 👋, adding Test IDs to your department's code standards would be fantastic. This will add minimal time to your development work, and the automation test team will repay you with faster feedback, broader test coverage, and generally high confidence in your work.
Thank you,
What is the best way to get started using Test IDs?
While writing this, I added 97 Test IDs to the DoesQA app by first adding the Test Selectors ESLint Plugin, running the linting, and working through the warnings. It only took 20 minutes to do everything, and now I can change the rule from warnings to errors to ensure every new commit adheres to this rule as part of our in-house code standards.
I recommend this approach.
It does not matter what automation testing tool or framework you use. If you want to deliver a quality product to your users, you should also want to give a testable product to your testers.
IDs
IDs are great; you probably use IDs for several things already. You might have anchor points through a page to give users a nice place to jump to. You might have them on your forms or the fields inside. You might use IDs for A/B testing your call to action or applying styling. This is the problem with IDs; there are too many purposes.
Test IDs
Adding Test IDs is the single best way to massively improve your testability, regardless of your automation test tool or framework.
What is a Test ID?
A Test ID is an HTML attribute (commonly data-test-id
but there are many aliases) added to core elements to speed up and simplify testing.
<button data-test-id="login-button" type="submit">Login</button>
Test IDs can be added to any element, but if you're getting started, I would suggest the following:
Buttons
Links
Forms & Inputs
User Feedback (alerts, modals, form validation, loading spinners, etc.)
Menus
Lists
Checkout Summaries
How do Test IDs save engineering time?
Let's say a developer adds a "forgotten password" button to a login form; the resulting code might look like this:
<form class="login" action="/login" method="post">
<h1 class="h3 mb-3 font-weight-normal">Please Login</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control mb-2" placeholder="Email address" required autofocus />
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control mb-2" placeholder="Password" required />
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
<p class="mt-5 mb-3 text-muted">
<a href="/forgotten-password">Forgot password?</a>
</p>
</form>
An automation tester will look at this and have to create a selector. They might use a combination of tags and classes for the element and its parents. Nothing here is too tricky, but producing a unique selector on a large page often takes several minutes.
Now let's look again with Test IDs added:
<form data-test-id="login-form" class="login" action="/login" method="post">
<h1 class="h3 mb-3 font-weight-normal">Please Login</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input data-test-id="login-username" type="email" id="inputEmail" class="form-control mb-2" placeholder="Email address" required autofocus />
<label for="inputPassword" class="sr-only">Password</label>
<input data-test-id="login-password" type="password" id="inputPassword" class="form-control mb-2" placeholder="Password" required />
<button data-test-id="login-button" class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
<p class="mt-5 mb-3 text-muted">
<a data-test-id="login-forgotten-password" href="/forgotten-password">Forgot password?</a>
</p>
</form>
These Test IDs will only take a developer seconds to add and a tester seconds to use. So, this has already saved your engineering team, as a whole, several minutes per selector. But the real benefits are still to come.
How Test IDs improve test quality?
The quality of the selector is far better; here's the above comparison as an example:
[data-test-id="login-forgotten-password"] // <-- Robust Test ID
Side Node: DoesQA has a specific selector type for Test IDs 😉
form.login > p.text-muted > a // <-- Flaky CSS Selector
Both of the selectors above were unique at the time they were created. But whereas the Test ID should permanently be unique, the other will stop being unique if another link is added to the login form; here's an example:
<form class="login" action="/login" method="post">
<h1 class="h3 mb-3 font-weight-normal">Please Login</h1>
<!-- ... -->
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
<p class="mt-5 mb-3 text-muted">
<a href="/forgotten-password">Forgot password?</a>
<a href="/register">Create Account</a>
</p>
</form>
With the addition of a new link, previous tests will start failing. Revisiting this test to improve the selector is wasted time that could be used to expand test coverage elsewhere.
How Test IDs make testing more efficient?
We have already examined how Test IDs can save some time; with some imagination, we can extrapolate this to substantial efficiency gains in the long term. But there's a much more direct way to show that Test IDs will bring massive time savings.
What happens when the login form gets a face-lift? As it's such an integral area of your service, you have hundreds of tests following through it. Your functional regression tests shouldn't care if your form inputs now look slightly different (that's the job of your Visual Regression tests).
The simple solution is that your existing Test IDs can be reused. While development is ongoing, you could even have the same test pack running on different environments, one with the new form and another with the old. Here's a new form reusing the Test IDs:
<form data-test-id="login-form" class="max-w-md mx-auto mt-8 bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/login" method="post">
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2" for="username">Username</label>
<input data-test-id="login-username" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="text" placeholder="Username" required />
</div>
<div class="mb-6">
<label class="block text-gray-700 text-sm font-bold mb-2" for="password">Password</label>
<input data-test-id="login-password" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" id="password" type="password" placeholder="Password" required />
</div>
<div class="flex items-center justify-between">
<button data-test-id="login-button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="submit">Sign In</button>
<a data-test-id="login-forgotten-password" class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" href="/forgotten-password">Forgot Password?</a>
</div>
</form>
More Testing, Less Coding
Automation Testing is the overlapping area on a Vent Diagram between Coding and Testing. You want to save manual testing time by running high-quality automation tests with broad coverage, but traditionally, you've needed coders to write these tests while wanting them designed with a tester's mind.
Times are changing, and creating a high-quality test pack without code is both possible and desirable. However, building page objects/elements required some knowledge of CSS or XPath. These coding skills are entirely unnecessary and result in slower and less reliable tests than using the Test IDs inserted by developers.
Note to Devs
Howdy 👋, adding Test IDs to your department's code standards would be fantastic. This will add minimal time to your development work, and the automation test team will repay you with faster feedback, broader test coverage, and generally high confidence in your work.
Thank you,
What is the best way to get started using Test IDs?
While writing this, I added 97 Test IDs to the DoesQA app by first adding the Test Selectors ESLint Plugin, running the linting, and working through the warnings. It only took 20 minutes to do everything, and now I can change the rule from warnings to errors to ensure every new commit adheres to this rule as part of our in-house code standards.
I recommend this approach.
It does not matter what automation testing tool or framework you use. If you want to deliver a quality product to your users, you should also want to give a testable product to your testers.
IDs
IDs are great; you probably use IDs for several things already. You might have anchor points through a page to give users a nice place to jump to. You might have them on your forms or the fields inside. You might use IDs for A/B testing your call to action or applying styling. This is the problem with IDs; there are too many purposes.
Test IDs
Adding Test IDs is the single best way to massively improve your testability, regardless of your automation test tool or framework.
What is a Test ID?
A Test ID is an HTML attribute (commonly data-test-id
but there are many aliases) added to core elements to speed up and simplify testing.
<button data-test-id="login-button" type="submit">Login</button>
Test IDs can be added to any element, but if you're getting started, I would suggest the following:
Buttons
Links
Forms & Inputs
User Feedback (alerts, modals, form validation, loading spinners, etc.)
Menus
Lists
Checkout Summaries
How do Test IDs save engineering time?
Let's say a developer adds a "forgotten password" button to a login form; the resulting code might look like this:
<form class="login" action="/login" method="post">
<h1 class="h3 mb-3 font-weight-normal">Please Login</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control mb-2" placeholder="Email address" required autofocus />
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control mb-2" placeholder="Password" required />
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
<p class="mt-5 mb-3 text-muted">
<a href="/forgotten-password">Forgot password?</a>
</p>
</form>
An automation tester will look at this and have to create a selector. They might use a combination of tags and classes for the element and its parents. Nothing here is too tricky, but producing a unique selector on a large page often takes several minutes.
Now let's look again with Test IDs added:
<form data-test-id="login-form" class="login" action="/login" method="post">
<h1 class="h3 mb-3 font-weight-normal">Please Login</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input data-test-id="login-username" type="email" id="inputEmail" class="form-control mb-2" placeholder="Email address" required autofocus />
<label for="inputPassword" class="sr-only">Password</label>
<input data-test-id="login-password" type="password" id="inputPassword" class="form-control mb-2" placeholder="Password" required />
<button data-test-id="login-button" class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
<p class="mt-5 mb-3 text-muted">
<a data-test-id="login-forgotten-password" href="/forgotten-password">Forgot password?</a>
</p>
</form>
These Test IDs will only take a developer seconds to add and a tester seconds to use. So, this has already saved your engineering team, as a whole, several minutes per selector. But the real benefits are still to come.
How Test IDs improve test quality?
The quality of the selector is far better; here's the above comparison as an example:
[data-test-id="login-forgotten-password"] // <-- Robust Test ID
Side Node: DoesQA has a specific selector type for Test IDs 😉
form.login > p.text-muted > a // <-- Flaky CSS Selector
Both of the selectors above were unique at the time they were created. But whereas the Test ID should permanently be unique, the other will stop being unique if another link is added to the login form; here's an example:
<form class="login" action="/login" method="post">
<h1 class="h3 mb-3 font-weight-normal">Please Login</h1>
<!-- ... -->
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
<p class="mt-5 mb-3 text-muted">
<a href="/forgotten-password">Forgot password?</a>
<a href="/register">Create Account</a>
</p>
</form>
With the addition of a new link, previous tests will start failing. Revisiting this test to improve the selector is wasted time that could be used to expand test coverage elsewhere.
How Test IDs make testing more efficient?
We have already examined how Test IDs can save some time; with some imagination, we can extrapolate this to substantial efficiency gains in the long term. But there's a much more direct way to show that Test IDs will bring massive time savings.
What happens when the login form gets a face-lift? As it's such an integral area of your service, you have hundreds of tests following through it. Your functional regression tests shouldn't care if your form inputs now look slightly different (that's the job of your Visual Regression tests).
The simple solution is that your existing Test IDs can be reused. While development is ongoing, you could even have the same test pack running on different environments, one with the new form and another with the old. Here's a new form reusing the Test IDs:
<form data-test-id="login-form" class="max-w-md mx-auto mt-8 bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" action="/login" method="post">
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2" for="username">Username</label>
<input data-test-id="login-username" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="text" placeholder="Username" required />
</div>
<div class="mb-6">
<label class="block text-gray-700 text-sm font-bold mb-2" for="password">Password</label>
<input data-test-id="login-password" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" id="password" type="password" placeholder="Password" required />
</div>
<div class="flex items-center justify-between">
<button data-test-id="login-button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="submit">Sign In</button>
<a data-test-id="login-forgotten-password" class="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" href="/forgotten-password">Forgot Password?</a>
</div>
</form>
More Testing, Less Coding
Automation Testing is the overlapping area on a Vent Diagram between Coding and Testing. You want to save manual testing time by running high-quality automation tests with broad coverage, but traditionally, you've needed coders to write these tests while wanting them designed with a tester's mind.
Times are changing, and creating a high-quality test pack without code is both possible and desirable. However, building page objects/elements required some knowledge of CSS or XPath. These coding skills are entirely unnecessary and result in slower and less reliable tests than using the Test IDs inserted by developers.
Note to Devs
Howdy 👋, adding Test IDs to your department's code standards would be fantastic. This will add minimal time to your development work, and the automation test team will repay you with faster feedback, broader test coverage, and generally high confidence in your work.
Thank you,
What is the best way to get started using Test IDs?
While writing this, I added 97 Test IDs to the DoesQA app by first adding the Test Selectors ESLint Plugin, running the linting, and working through the warnings. It only took 20 minutes to do everything, and now I can change the rule from warnings to errors to ensure every new commit adheres to this rule as part of our in-house code standards.
I recommend this approach.