🗺️ Your First Steps in TypeScript: A Practical Roadmap for Automation QA

Ivan DavidovIvan Davidov
5 min read

Ever had a test fail at a critical moment due to a simple typo in a variable or an unexpected API response? We’ve all been there. These runtime errors are frustrating and time-consuming. This is where TypeScript comes in.

For QA Testers, TypeScript is more than just a programming language - it’s a safety net. It helps you catch bugs before you run your tests, not during. This article is your starting point. We'll cover the essentials you need to start writing more robust and reliable automated tests today.


✅ Prerequisites

While experience with JavaScript, Playwright, or Cypress is beneficial, it’s not required. We’ll explain everything in a simple way, making this a great starting point even for beginners.


🤔 The “Why”: Catching Bugs Before They Happen

Imagine you're testing an API. You expect a response body with a user's id and username. In JavaScript, you might write a test like this:

const userId = response.body.id;
const userPassword = response.body.password; // Oops!

Your test would run, and only then would it fail because response.body.password is undefined. It’s a bug in your code, but you only found it at runtime.

With TypeScript, you would first define the shape of your expected data. If you tried to access .password—a key that doesn't exist in your defined shape—TypeScript would show an error right in your code editor. You fix the bug before the test even runs. This is the core power of TypeScript: adding confidence and preventing errors.


🧱 The Building Blocks: Core Types

Image description

At its heart, TypeScript is about adding type annotations to your variables. Think of it as giving every variable a specific job description.

A string is used for text data, like URLs, locators, or messages.

const blogURL: string = 'https://idavidov.eu/';

In testing, you'll use strings constantly for base URLs, selectors, and test data. Typing them ensures you don't accidentally try to perform a mathematical operation on a URL!

A number is for, well, numbers! This includes integers and decimals.

const expectedItemCount: number = 13;

This is perfect for asserting an expected element count, checking API status codes (200, 404), or verifying prices.

A boolean can only be true or false. It’s the ultimate "yes or no."

const isButtonEnabled: boolean = true;

Booleans are essential for checking the state of UI elements. Is a checkbox ticked? Is a button visible? Is a toggle on? A boolean gives you a clear answer.


✍️ Building Dynamic Strings with Template Literals

Hardcoding strings is rarely practical. You often need to build them dynamically. Template literals make this clean and easy. You use backticks instead of quotes and embed variables with ${...}.

const blogURL: string = 'https://idavidov.eu/';

// Dynamically create a new URL
const newsletterURL: string = `${baseURL}newsletter`;

console.log(newsletterURL); // Outputs: 'https://idavidov.eu/newsletter'

This is incredibly useful for QA work, such as building API endpoints with dynamic IDs or creating detailed assertion messages that include variable outputs.

✨ The Special Trio: void, any, and unknown

Beyond the basics, TypeScript has special types for unique situations.

void: For Actions, Not Data

What about functions that do something but don't return a value? Think of a function that clicks an element. Its job is to perform an action, not to give you data back. This is what void is for.

// This helper performs a click but doesn't return anything.
function clickElement(selector: string): void {
  // In a real Playwright/Cypress test, you'd have:
  // await page.click(selector);
  console.log(`Successfully clicked on ${selector}!`);
}

Using void makes your code clearer. It tells other developers (and your future self) that the function's purpose is its side effect, not its return value.

any: The Double-Edged Sword 🗡️

Sometimes, you might be working with legacy JavaScript code or a poorly documented library. In these cases, you can use any as an "escape hatch." It effectively tells TypeScript, "Don't type-check this variable."

// Use with extreme caution!
let legacyData: any = { "user-id": 123, details: "some-info" };

// TypeScript won't complain about this, even if it's wrong.
console.log(legacyData.nonExistentProperty); // Returns 'undefined' at runtime

The Risk: Using any completely defeats the purpose of TypeScript. It hides potential bugs and should be used as a last resort. Always aim to replace any with a proper type as soon as you can.

unknown: The Safe Alternative 🛡️

So what if you truly don't know the type of data you're getting, like from an external API? Use unknown. It's the type-safe version of any.

unknown forces you to check the type of the data before you're allowed to do anything with it.

async function fetchUserData(userId: number): Promise<void> {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const data: unknown = await response.json();

  // We MUST check the type before using it
  if (
    typeof data === 'object' &&
    data !== null &&
    'name' in data &&
    typeof data.name === 'string'
  ) {
    // Only inside this block does TypeScript know data has a 'name' property
    console.log(`User's name is ${data.name.toUpperCase()}`);
  } else {
    console.error("API response is not in the expected format.");
  }
}

unknown is perfect for QA because it forces us to write defensive code that can safely handle unexpected API responses without crashing.

🚀 Your Next Step

We've covered the absolute essentials for getting started with TypeScript in a QA role. From basic types that clarify your intent to special types that help you handle tricky situations safely.

The single most important message is this: TypeScript isn't about adding complexity; it's about adding confidence. You can trust your tests more because an entire category of bugs is eliminated before you ever run them.

Your immediate next step? Go and convert one of your small JavaScript test files to TypeScript. Add types to your variables (.ts file extension) and see what potential issues you uncover. You might be surprised by what you find!


🙏🏻 Thank you for reading! Building robust, scalable automation frameworks is a journey best taken together. If you found this article helpful, consider joining a growing community of QA professionals 🚀 who are passionate about mastering modern testing.

Join the community and get the latest articles and tips by signing up for the newsletter.

0
Subscribe to my newsletter

Read articles from Ivan Davidov directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ivan Davidov
Ivan Davidov

Automation QA Engineer, ISTQB CTFL, PSM I, helping teams improve the quality of the product they deliver to their customers. • Led the development of end-to-end (E2E) and API testing frameworks from scratch using Playwright and TypeScript, ensuring robust and scalable test automation solutions. • Integrated automated tests into CI/CD pipelines to enhance continuous integration and delivery. • Created comprehensive test strategies and plans to improve test coverage and effectiveness. • Designed performance testing frameworks using k6 to optimize system scalability and reliability. • Provided accurate project estimations for QA activities, aiding effective project planning. • Worked with development and product teams to align testing efforts with business and technical requirements. • Improved QA processes, tools, and methodologies for increased testing efficiency. • Domain experience: banking, pharmaceutical and civil engineering. Bringing over 3 year of experience in Software Engineering, 7 years of experience in Civil engineering project management, team leadership and project design, to the table, I champion a disciplined, results-driven approach, boasting a record of more than 35 successful projects.