What Is Test Automation?

Test automation is the use of software to perform or support test activities, as the ISTQB glossary defines it. On most teams it means automated test execution: code that sets up a situation, drives the product through a function, an API or a browser, compares the actual result with an expected result written in advance, and reports pass or fail with nobody watching. The manual regression case "a registered user can sign in" becomes a script that runs on every commit.

The lesson on tool support for testing listed the tool categories and the ISTQB benefits and risks. This lesson looks inside: what one automated test is made of, what automation gives a team in practice, what goes wrong once scripts exist, and why those scripts are software that needs maintenance.

Why it matters

Every release adds regression checks, and re-running them all by hand takes longer each time, so teams either slow down or stop re-checking, and regressions reach users. Automation keeps yesterday's behaviour confirmed while the team builds today's. Done badly, it is worse than none: a suite that is always red gets ignored, and a suite that is always green because it checks nothing gives confidence the product has not earned.

Even if you never build a framework, automation changes your daily work: you read run results, decide whether a red test is a product defect, a test defect or an environment problem, and review the scripts your colleagues write.

What an automated test is made of

An automated test has the parts of a good manual test case, written as code. It arranges the preconditions, acts by performing the steps, asserts by comparing actual with expected results, and cleans up so the next test starts from a known state. The assertion is the heart of it: without a precise expected result, a script proves only that nothing crashed. Some practitioners call an automated test a "check" to stress that it verifies only what someone decided in advance.

Below, the sign-in case as a browser test written with Playwright; Selenium WebDriver and Cypress tests have the same shape. You do not need every keyword yet: read the comments, the steps and the assertion. A line such as page.getByLabel('Email') is a locator, which tells the test how to find an element on the page, here by its visible label. The script is only one part of what the ISTQB Advanced Level Test Automation Engineering syllabus calls a test automation solution: the scripts plus everything needed to run them and report the results. Every part can break:

  • Test scripts: one behaviour per test, named after the rule it checks.
  • A framework and runner that discovers the tests, runs them and records each result.
  • The interface the test drives: a function (unit level), an HTTP API or a user interface. Lower levels are faster and break less often, as the lesson on the test automation pyramid shows.
  • Test data and an environment: the accounts, products and settings the test needs, and the system it runs against.
  • A trigger and a report: usually a CI pipeline that runs the suite on every change, and a result that someone reads.
import { test, expect } from '@playwright/test';
import { testUser } from './test-data';

test('registered user can sign in', async ({ page }) => {
  // Arrange: start on the sign-in page with a known test account
  await page.goto('/login'); // resolved against baseURL in playwright.config.ts

  // Act: the steps of the manual test case
  await page.getByLabel('Email').fill(testUser.email);
  await page.getByLabel('Password').fill(testUser.password);
  await page.getByRole('button', { name: 'Sign in' }).click();

  // Assert: the expected result, decided before the test ran
  await expect(page.getByRole('heading', { name: 'My account' })).toBeVisible();

  // Clean up: nothing to undo, signing in changes no data
});

Try it: REST API Tester Try it: REST Request Generator

What automation gives a team

On a working team the benefits look like this. Each one holds only if the expected results are right, because a script repeats a wrong expected result as faithfully as a right one.

  • Fast regression feedback: the suite runs on every change, so a regression is reported while the developer still remembers the code.
  • Exact, repeatable comparison: every field of a response and every total to the last halala, on every run, where a person repeating a case for the twentieth time starts to skim.
  • Scale: the same steps over hundreds of data rows, in several browsers, every night.
  • A quality gate: an automated check in the pipeline can stop a broken build on every change, before it reaches staging, without waiting for a person.
  • Living documentation: a test named "orders of 300.00 SAR or more ship free" states a rule and shows on every run that it still holds.
  • Tester time: hours once spent re-executing scripted cases go to exploratory testing and to designing tests for new features.

Try it: Regression Scenario Generator Try it: Test Case Generator

What goes wrong once the scripts exist

The risks in the lesson on tool support for testing concern the decision to automate: unrealistic expectations, underestimated effort, the wrong tool. Once a suite exists, a second group appears, and most of it comes from the scripts themselves:

  • False confidence: assertions too weak to fail. A test that checks only that the cart page loaded stays green while the total on it is wrong.
  • Flaky tests: tests that pass and fail with no change to the product or to the test, usually because of timing, shared test data or an unstable environment. After a few false alarms the team stops reading red results, and real regressions slip through with them.
  • Brittle scripts: tests tied to details that change often, such as a CSS class or the position of a button, break on every redesign although the behaviour is the same.
  • Slow suites: when a run takes longer than people are willing to wait, they merge without it and the fast feedback is gone.
  • Worn-out tests: a suite re-checks only what someone anticipated, so it finds regressions but rarely new defects, and it does not replace exploratory testing.
  • A single owner: if only one person understands the framework, maintenance stops when that person moves on.
Each benefit of test automation has a matching risk: a suite maintained as software gives fast, exact regression feedback, while a neglected one gives noise or false confidence.

Benefit: suite maintained as software

Versioned, reviewed, every failure triaged

Feedback:
A regression reported while the code is still fresh
Comparison:
Exact checks, the same way on every run
A green run:
The stated checks passed
A red run:
Triaged: product defect, test defect or environment
A product change:
One edit in a shared helper
Tester time:
Freed for exploratory testing and new tests

Risk: suite left to decay

Recorded once, never reviewed, red results ignored

Feedback:
So slow that people merge without it
Comparison:
A wrong expected result, repeated just as faithfully
A green run:
Weak assertions: green while the total is wrong
A red run:
Flaky failures teach the team to ignore red
A product change:
The same fix in dozens of copied scripts
Tester time:
Spent re-running and repairing scripts

Automation code is software and needs maintenance

A test suite is a program that tests another program. It has requirements (the test cases it implements), a design, dependencies and defects of its own, such as a wrong expected result, a missing wait or a test that passes only after another test has run. It lives as long as the product and changes whenever the product does.

Maintenance comes from four directions: the product changes (a renamed button, a new mandatory field, a changed API response), the tools change (browser, framework and runtime upgrades), the data and environments change, and the tests' own defects surface. Teams whose suites stay useful treat test code like product code:

  • Version control next to the product code; a behaviour change and its test change go in together.
  • Code review for tests too: names, assertions, duplication, fixed waits.
  • Each step written once, in a shared helper (later in this module, a page object), so one UI change means one edit.
  • Data, URLs and secrets kept in configuration; never a real password in a script.
  • Every failure triaged and recorded as a product defect, a test defect or an environment problem, never re-run until green.
  • Maintenance time planned in every sprint, and tests that no longer protect anything deleted.

Worked example: one redesign, 37 red tests

A team has 120 browser tests. Designers split the sign-in page into two steps, the email and a "Continue" button first, then the password, and rename the button "Sign in" to "Log in". The nightly run shows 37 failures. The tester triages before anyone re-runs anything: the failures are exactly the 37 tests that sign in, all stop at the sign-in step, and each of those test files has its own copy of the sign-in lines. The product works; the tests are out of date.

Patching 37 files would take most of a day and would recur at the next redesign, so the team moves the steps into one helper, changed and reviewed once. After the fix, 36 tests pass. One still fails further on: "Remember me" no longer keeps the user signed in after a browser restart. The tester reproduces it by hand and reports a product defect, which is exactly what the suite is for.

// Before: these three lines were copied into 37 test files
//   await page.getByLabel('Email').fill(testUser.email);
//   await page.getByLabel('Password').fill(testUser.password);
//   await page.getByRole('button', { name: 'Sign in' }).click();

// After: one helper (sign-in.ts) that every test calls; the next redesign means one edit
import type { Page } from '@playwright/test';
import type { TestUser } from './test-data';

export async function signIn(page: Page, user: TestUser) {
  // Step 1: email, then "Continue". Step 2: password, then the renamed button.
  await page.getByLabel('Email').fill(user.email);
  await page.getByRole('button', { name: 'Continue' }).click();
  await page.getByLabel('Password').fill(user.password);
  await page.getByRole('button', { name: 'Log in' }).click();
}

// In each test: await signIn(page, testUser);

Common mistakes

  • Treating automation as a project that ends: recording scripts once and budgeting no time to maintain them.
  • Automating a manual case whose expected result is vague, which produces a check that cannot fail.
  • Trusting a green run without reading what its assertions actually check.
  • Re-running failed tests until they pass instead of triaging each failure.
  • Keeping test code outside version control and review, with hard-coded URLs, waits and passwords.
  • Measuring success by the number of automated tests instead of by the regressions they catch and the effort they cost.

The QA perspective

When a story is refined, ask which of its checks will be automated, at which level, and who will maintain them; a precise expected result in the test case is your contribution even when a developer writes the script. Review test code in the same pull requests as the product code it covers.

When a run is red, triage before anyone re-runs it, and record each failure as a product defect, a test defect or an environment problem. Track flaky tests and the hours spent on maintenance; those records are what the team needs when it decides what to automate next. When you report "automated regression passed", say what the suite covers and what it does not.

Practice exercise

Review an automated test as code

Your task

A colleague automated the manual case below. Review the script as you would review product code: list every problem you find, say why it matters and propose a fix.

Then classify three failures of the improved test as a product defect, a test defect or an environment problem.

Material
Manual case TC-CART-01: Add a product to the cart
Preconditions: signed in as a test customer; product 1042 in stock; cart empty
Steps: open product 1042; click "Add to cart"
Expected: cart badge shows 1; the cart holds product 1042, quantity 1, at 49.00 SAR

test('test1', async ({ page }) => {
  await page.goto('https://staging.shop.example/login');
  await page.fill('#email', 'sara.qa@example.com');
  await page.fill('#pwd', 'secret123');
  await page.click('text=Sign in');
  await page.waitForTimeout(5000);
  await page.click('#product-1042');
  await page.click('.btn-primary');
  await expect(page.locator('.cart-count')).toBeVisible();
});

Failures of the improved test:
A. Fails only during the Monday 08:00 staging redeploy: connection refused.
B. After a redesign the button reads "Add to basket"; the product works correctly.
C. Badge shows 2 after adding one item to an empty cart; reproducible by hand.
Steps
  1. Compare the script with the manual case: which preconditions and expected results did it keep, and which did it drop?
  2. Check the name, the data, the waits and the locators.
  3. Check whether the test can run twice in a row, and on another machine.
  4. Classify the three failures and say what you would do next for each.
Show the model answer
Model answer

Name: test1 says nothing. Name the rule, for example "adding an in-stock product to an empty cart shows it once at its price".

Data and secrets: the staging URL, a real-looking personal account and its password are hard-coded. Use the configured base URL, a test customer created by the setup, and a secret from the environment.

Wait: waitForTimeout(5000) always costs five seconds and still fails when staging is slower. Wait for a condition instead; the lesson on waits and synchronization shows how.

Locators: #pwd, #product-1042 and .btn-primary depend on implementation details. Prefer labels, roles and visible names, and move the sign-in lines into the shared helper.

Assertion: "badge visible" checks none of the expected results exactly: it passes when the badge shows 0 or 2, or when the wrong product is in the cart. Assert that the badge shows 1 and that the cart holds product 1042, quantity 1, at 49.00 SAR.

Preconditions: the script signs in but drops the other two. Nothing empties the cart, so a second run starts with an item already in it, and nothing makes sure product 1042 is in stock. Create a fresh test customer or empty the cart in setup, prepare the stock in the test data, and clean up afterwards.

Failures: A is an environment problem; record it and schedule runs outside the redeploy window. B is a test defect, because the test is out of date: confirm the new label was intended, then update the locator in one place. C is a product defect that the stronger assertion caught; report it with the steps, the expected count 1 and the actual count 2.

Frequently asked questions

Do I need to be a programmer to automate tests?

You need to read and write code at a basic level to automate in a way that can be maintained. Record-and-playback and low-code tools reduce typing, not the need to design, review and maintain tests. The lesson on programming basics for test automation covers them; until then, precise test cases and careful triage are already part of automation work.

Is test automation the same as automated testing?

Teams usually use both terms for automated test execution. The ISTQB definition of test automation is wider: any use of software to perform or support test activities, such as generating test data, preparing environments or producing reports.

Does automation find more defects than manual testing?

It finds a different kind. An automated suite re-checks known behaviour quickly and finds regressions; new defects come mostly from new test design and exploratory testing. Its main value is fast, repeated confirmation, not discovery.

How much maintenance should we expect?

There is no universal figure: it depends on how often the product changes and how the suite is designed. Measure it instead: record the hours spent repairing tests each sprint and why. The next lesson uses those numbers in an honest ROI calculation.

References

Practise with Mutqan tools

Test REST endpoints with full control over method, headers and JSON body, then assert on status, headers and JSON paths.

API Testing

Generate ready-to-run REST API request code in cURL, JavaScript fetch, axios, Python requests, Java, C#, Go and PHP.

Web

Generate structured test cases (id, title, preconditions, steps, expected result, priority) from a feature description and field list.

Test Design

Go deeper

Your progress on this topic

Part of the QA Roadmap