Optimize Selenium WebDriver Automated Test Scripts: Readability
Simple techniques to make automated tests readable for the whole team
Working automated test scripts is only the first test step to successful test automation. As automated tests are executed often and the application changes frequently, it is important that test scripts need to be
• Fast
• Easy to maintain
• Easy to read
Programmers (with rare exceptions) usually write poor automated test scripts. The main reason is that they forgot that, different from unit/integration tests, the audience of functional tests is the whole team, including business analysts, manual testers, and customers.
In this article, I will show you some simple and practical tips to enhance the readability of automated functional tests. Please note, we must not go for readability at the price of maintainability. If the automated test scripts are not maintainable, test automation simply fails (unless for fake demos).
Gherkin syntax, e.g. Cucumber or SpecFlow, is bad, as it is almost impossible to maintain. Please read my other article, “Why Gherkin (Cucumber, SpecFlow,…) Always Failed with UI Test Automation?” Even for readability, Gherkin is not good as many thought. Gherkin tests may be only readable to the person who wrote them. The simple reason is that the Gherkin steps (like English) are detached from what a user does on the page.
More often than not, maintainability and readability should go hand in hand.
1. Use DSL (Domain Specific Language) via reusable functions and page object models
No matter how good an automation framework’s syntax is, the raw test step syntax will not be very readable.
Selenium WebDriver:
driver.find_element(id: 'username').send_keys 'agileway'
driver.find_element(id: 'password').send_keys 'testwise'
driver.find_element(:xpath, "//input[@value='Sign In']").click
Watir (known as its readability):
browser.text_field(id: 'username').set 'agileway'
browser.text_field(id: 'password').set 'testwise'
The solution is to follow a Maintainable Test Design. With helper functions and page object models, we can achieve much more readable syntax. Some even give this a fancy name: DSL (Domain Specific Language).
sign_in('agileway', 'testwise') # helper function
home_page = HomePage.new(driver) # page object
home_page.click_new_applicationnew_application_page = NewApplicationPage(driver)
# ...
2. User Personas instead of test user accounts
The most common test data in web application testing is to test user accounts. Once, a new programmer (not hired by me, but was allocated to us from the architecture team for free) changed our test scripts from personas (explanations later) like below:
sign_in(UserLookUp.getManagerUser())
He spent about an hour on ‘find-n-replace’, which was an acceptable mistake as he was new to automation. What annoyed the team was the email he wrote: “The test scripts with hard-coded user logins, I spent 1 hour on ‘correcting’ it, and we should have hired primary students to fix this”. I was not in the office when it occurred. There had been several compliant emails from the team, especially the manual testers.
I immediately reverted the changes and apologized to the team for the confusion caused. Then I told the programmer: “You must follow the test syntax that of the manual testers. Otherwise, there is no position for you in this team.”
I understand that his motive was based on a programming anti-pattern: “hard-code strings”. However, this rule does not always apply to automated test scripts. For example, when a new business analyst views or runs this script, he may wonder:
Which user account did the test script use?
How do I change to a different user?
In this case, the test scripts are actually harder to maintain. For a change to
Test Script => UserLookup
class => Manager.properites
file
If Manager.properites
was modified by another user, the behaviour of the next test execution would change. As you can imagine, this will cause a lot of confusion.
The simple solution is to use Personas.
A persona, in user-centered design and marketing is a fictional character created to represent a user type that might use a site, brand, or product in a similar way. — Wikipedia
Better:
sign_in("john")
# or
sign_in("manager01")
2. Remove the use of parentheses
Programmers are used to parentheses. However, this might not be the case for business analysts, customers and manual testers.
driver.find_element("text").send_keys("ABC")
A better version:
driver.find_element("text").send_keys "ABC"
The above are raw Selenium WebDriver. The same principle applies to the helper/page-object functions as well.
login_page.enter_user "tester"
login_page.enter_password "wise1234"
login_page.click_sign_in
Tests in JavaScript used parentheses excessively, which is wrong. Below is an example of the Playright documentation.
(async () => {
const browser = await firefox.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://www.example.com/');
const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio
}
});
console.log(dimensions);
await browser.close();
})();
This is one of the reasons that I don’t use JavaScript for real test automation (and I have never seen even one successful test automation that used JS). Check out my article: “Why JavaScript Is Not a Suitable Language for Real Web Test Automation?”
3. With question marks (?) in function names
Ruby language allows the question mark ?
in function names. This actually increases the readability.
if is_uat_server
driver.find_element(:id, "no-confirm").click
end
A better version:
driver.find_element(:id, "no-confirm").click if is_uat_server?
3. Using a good test data library
Test data is an essential part of test scripts. Every test automation engineer, including me, has created some utilities to generate test data. However, we don’t have to reinvent the wheel most of the time. Using a good test Data library (such as Faker) will do the trick. Generally speaking, the test syntax of Faker alike for generating test data will be very readable after many years of refinement.
Faker examples:
`Faker::Number.number(digits: 5)` # => 38943
`Faker::Number.between(from: 4000, to: 4999)` #=> QLD post code
`Faker::Name.name` #=> “Christophe Bartell”
With the ActiveSupport library, we can achieve
3.days.ago
Date.today.advance(weeks: 3)
Date.today.next_month.beginning_of_week
Pretty cool, right?
4. Repeats
driver.find_element("button").send_keys(:tab)
driver.find_element("button").send_keys(:tab)
driver.find_element("button").send_keys(:tab)
A better version:
3.times { driver.find_element("button").send_keys(:tab) }
This makes the repeat count more obvious.
5. Naming conventions
I recommend writing test statements consistently with:
Active tone, and
Convention on standard user operations
Let’s look at some examples:
click a link, button, or radio button
e.g.click_reset_password
check/uncheck a checkbox
e.g.check_accept_terms
select a dropdown option
e.g.select_role('admin')
enter text field, text area
e.g.enter_user_name('wisetester')
There are many benefits of following an intuitive convention. I will highlight one here: Script Completion. But first, let me illustrate it with an example.
I am about to test the steps that use a new page object created by my colleagues. The first operation I want to perform is to enter a first name. So I typed .enter
(after the page object), the functional testing IDE shows all the matching functions. This will greatly increase efficiency.
6. Name Page Class consistently and use “Introduce Page refactoring”
With a large test suite such as WhenWise (500+ Selenium WebDriver user-story-level regression tests), there will be many page objects (corresponding to the actual web pages in the app).
+------------+---------+---------+---------+--------+
| TEST | LINES | SUITES | CASES | LOC |
| | 23571 | 307 | 513 | 18545 |
+------------+---------+---------+---------+--------+
| PAGE | LINES | CLASSES | METHODS | LOC |
| | 9042 | 159 | 1484 | 6867 |
+------------+---------+---------+---------+--------+
| HELPER | LINES | COUNT | METHODS | LOC |
| | 809 | 5 | 61 | 627 |
+------------+---------+---------+---------+--------+
| TOTAL | 33422 | | | 26039 |
+------------+---------+---------+---------+--------+
Therefore, we must have a convention for naming Page Classes, too. My recommendation is to use the heading text on the page. Here are some examples:
NewClientPage
PasswordResetPage
NewClientModalPage
(popup style)
With the convention in place, it will be easier for others to discover and use the page classes (avoiding duplicates). In addition, TestWise IDE supports “Introduce Page Object” refactoring, which will list matching page classes for selection.
Related articles:
Functional Test Refactoring
Test refactorings make creating well-designed test scripts efficientlyAgileWay Test Automation Formula
A shortcut to test automation success