avatarZhimin Zhan

Summary

This article provides tips for improving the readability of automated functional tests, including using DSL, personas, and removing parentheses.

Abstract

The article discusses the importance of readability in automated functional tests, which are executed frequently and need to be understood by the whole team. The author suggests using DSL (Domain Specific Language) via reusable functions and page object models to improve readability. The article also recommends using personas instead of test user accounts and removing uses of parentheses. The author provides examples of how to implement these tips using Ruby and JavaScript.

Opinions

  • The author believes that programmers often write poor automated test scripts because they forget that the audience is the whole team, including business analysts, manual testers, and customers.
  • The author argues that maintainability and readability should go hand in hand and that Gherkin syntax is bad for readability.
  • The author suggests using a good test data library, such as Faker, to generate test data and improve readability.
  • The author recommends using a consistent naming convention for page classes and using "Introduce Page refactoring" to avoid duplicates.
  • The author provides examples of how to implement these tips using Ruby and JavaScript, but does not provide examples for other programming languages.
  • The author does not discuss the trade-offs between readability and other factors, such as performance or maintainability.
  • The author does not provide empirical evidence to support their claims about the benefits of these tips for improving readability.

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

FastEasy to maintainEasy 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_application
new_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 uses 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. The 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, have created some utilities to generate test data. However, we don’t have to re-invent 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, 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 the efficiency.

page functions completion in TestWise IDE

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.

Introduce Page Object Refactoring in TestWise IDE

Related articles:

Automated Testing
Test Automation
Selenium
Agile
DevOps
Recommended from ReadMedium