Using TypeScript for Test Automation

Getting Started — Published January 18, 2023

TypeScript is not only for developers anymore. If you are working as a tester in a web development team, chances are that you have heard about TypeScript. It’s been getting more and more attention over the past couple of years and has even surpassed JavaScript as the dominant language in a recent survey on state of software delivery made by CircleCI.

In my very un-scientific poll on LinkedIn, I asked fellow web application testers about the programming language of their choice. It seems that JavaScript is the most popular choice, winning over TypeScript and Java. But, in my opinion, testers should pay attention to the rising popularity of TypeScript and ideally start using it. In this article, I would like to take a closer look at many of TypeScript’s benefits for testing and automation.

What is TypeScript?

TypeScript is a programming language that is a superset of JavaScript. It adds many extra capabilities to JavaScript, improving the overall developer experience. As Basarat Ali Syed aptly puts it, TypeScript gives you the ability to use future JavaScript today. Besides that, TypeScript adds a type system into JavaScript, helping you write more stable and maintainable code. Let me give you an example of what that means.

Look at this plain JavaScript function:

const addition = (a, b) => {
 return a + b
}

This function takes two parameters and adds them up. It’s helpful if we need to sum two numbers. But what if we use this function in a way it was not intended? Or worse – what if we misunderstood how this function works?

addition(1, 2) // returns 3
addition('1', '2') // returns '12'

In the example above, our function will yield different results based on the type of input we provide it with. On the first line, we are passing two numbers, and our function will correctly add them up. On the second line, we are passing two strings, and since the input values are wrapped in quotation marks, the function will concatenate them instead of adding the numbers.

The function works as designed, but even if that’s the case, we may get into unexpected results. After all, this is why software testing is a thing.

But as developers work on more complex projects, on more complex problems, and with more complex data, risks increase. This is where TypeScript can become very helpful, since it can specify what kind of input the function expects. Let’s see how a similar function definition and function call would look like in TypeScript:

const addition = (a: number, b: number) => {
 return a + b
}


addition(1, 2) // returns 3
addition('1', '2') // shows an error

In the function definition, we specify the types for the parameters that this function expects. If we try to use our add() function incorrectly, the TypeScript compiler will complain about this and throw an error. So what is TypeScript compiler, you ask?

How TypeScript works

TypeScript cannot be read by the browser. In order to run TypeScript code, it needs to be compiled. In other words, everything you create using TypeScript will be converted to JavaScript at some point.

To run the compiler, we can open the terminal and run the following command:

tsc addition.ts

This command will point TypeScript compiler (tsc) to our addition.ts file. It will create a JavaScript file alongside the original TypeScript file. If there are any “type errors” in the file, we’ll get an error into our terminal.

But you don’t have to do this manually every time. There’s a good chance your code editor has a TypeScript compiler running in the background as you type your code. With VS Code, this functionality comes out of the box, which makes sense since both TypeScript and VS Code are developed and maintained by Microsoft. Having the compiler running in the background is incredibly useful, mostly because this allows us to immediately see errors such as the one shown in the last example. Whenever there is such an error, we get feedback and an explanation:

In this case, we are passing a string into a function that requires us to pass numbers. This information comes from a compiler that runs inside the editor (VS code in my case).

Additionally, some modern testing tools such as Playwright and Cypress run the compiler on the fly and convert your TypeScript code into browser-readable JavaScript for you.

How to use TypeScript as a tester

Now that you know what TypeScript is and how it works, it is time to answer the most important question: why? Is TypeScript even useful for someone who focuses on test automation?

My answer is yes, and I would like to demonstrate this in a few examples. I’ll also give you a couple of reasons why I think test automation engineers should start getting familiar with TypeScript.

Typed libraries

As test automation engineers, we often implement many different libraries for our work. There’s no tool that fits all the needs, and many times we deal with plugins, integrations, toolsets, extensions, and libraries. When you start, you need to get yourself familiar with the API of that tool, dig into the documentation, try it out, and understand different commands. It’s a process. And it can be exhausting to get through that first mile.

TypeScript can be helpful in speeding up that process. Libraries that contain type definitions can really get you up to speed with using them. When a library or a plugin contains type definitions, it means that functions in that library will have the same type of checking implemented as in the example I have given earlier.

This means that whenever you e.g. pass a wrong argument to a function, you will see an error in your editor. In the following example, I am passing a number into a cy.type() function that will only accept text:

Code autocompletion

Besides checking for correct arguments, TypeScript can help with giving autocomplete suggestions. This can act as a quick search tool, when you are looking for the right command or argument.

I have recently made a plugin for testing API with Cypress, and TypeScript autocompletion helps with passing different attributes such as url, method, or request body:

Handling imported data

Working with data can often get complicated. Especially when working with complex datasets in which you have to navigate through multiple levels of data structure. One mistake can make the whole test fail and cause a headache when trying to debug that problem.

When data is imported to a test, for example from a JSON file, the structure of that JSON file is imported to the test as well. This is something that the TypeScript compiler inside the editor does for us. Let’s take a look at a simple JSON file that will seed data into our test:

While creating our test, the editor will guide us through the fixture file and suggest possible keys that can be inferred from that file:

Notice how we not only get the key names, but also the type of the key. We can use this type inference for both seeding the data into our tests, as well as making assertions. The best part about this? The fixture file and test are now interconnected. Whenever we change our fixture file, the test file will be affected as well. If e.g. we decide to change the name property in our JSON file to title, TypeScript compiler will notice this error even before we decide to run our test.

Tightening source code with test code

Probably my strongest argument for using TypeScript is the connection between test code and source code. Being able to tie things together is a game changer. Whenever the source code changes, it can have a direct effect on tests, and with TypeScript, there’s a high chance it will show even before you run your tests on a pipeline.

Let’s say you have an API test. If your developers use TypeScript, chances are they have a TypeScript definition of the API structure. As a tester, you can import that structure into your test and use it e.g. for testing the API response:

import Board from "trelloapp/src/typings/board";


it('Returns proper response when creating new board', () => {
 cy.request<Board>('POST', '/api/boards', { name })
   .then(({ body }) => {
     expect(body.name).to.eq(name)
     expect(body.id).to.exist
   })
 })

Similarly to the previous example with fixture files, whenever something changes in our typings file, we will notice the change in the test file as well.

Checking errors on CLI

A really powerful feature of TypeScript is the ability to check all errors in the project. We can do this by typing following command in the terminal:

tsc --noEmit

The –noEmit flag means that the TypeScript compiler will not create JavaScript files, but it will check for any errors on our files. We can check all our files in the project, which means that even if we have worked on a single file, all the files will be checked for errors.

We can check the health of our tests even before they are run. Whenever we change files in our project, we can check if type checks are still passing. Even without opening any files.

This is sometimes referred to as “static testing”. It enables us to add an additional layer of checks that will help us make less mistakes in our code.

A great advantage of this is that it runs super fast, making it a good candidate for a pre-commit check.

In fact, setting up such a check is very easy and can be done in three simple steps:

First, install pre-commit package via npm using following command:

npm install pre-commit --save-dev

Then, create a lint script in package.json:

"scripts": {
 "lint": "tsc --noEmit"
}

As a final step, define lint as one of the pre-commit checks in package.json:

"pre-commit": [ "lint" ]

From now on, whenever we try to commit, our tsc –noEmit script will run. If it throws any errors, we will not be able to commit staged files.

Conclusion

TypeScript offers a variety of advantages. Static typing can improve the reliability and maintainability of the test code. It can help catch errors and issues earlier in the development process, saving time and effort spent on debugging. And there are many more that didn’t make it to this post.

Since TypeScript is built on top of JavaScript, test automation engineers who are familiar with JavaScript will be able to easily pick up TypeScript. The knowledge and skills they have developed in JavaScript will transfer over. If you need the initial push, you can check out my new course on TypeScript in Cypress, where I explain the basics of TypeScript within Cypress, but most of the knowledge can be transferred to other tools as well. The best part? It’s absolutely free on Test Automation University.

Are you ready?

Get started Schedule a demo