Test Automation Architecture

A test automation architecture is the technical design behind your automated tests: which parts exist, what each one is responsible for, and how they connect to each other and to the system under test (SUT). The ISTQB Advanced Level Test Automation Engineering syllabus (CTAL-TAE) separates the architecture (the design) from the test automation framework (the code that implements it: a test runner, libraries, test scripts and suites) and from the test automation solution (the framework plus the tools and adapters it needs for one SUT).

A good design shows in three everyday answers. The sign-in page changes: how many files do you edit? One. The suite must run against staging: what changes? One setting. A test fails at night: does the log say why? Yes, without a re-run. This lesson zooms out from the page objects of the previous lesson to the whole framework.

Why it matters

Without a design, a growing suite copies the same locators, URLs, waits and sign-in steps into every file, so every product change is multiplied by the number of copies. Maintenance eats the time meant for new tests, the suite falls behind the product, and the team stops trusting it.

For a tester, the architecture decides whether you can add a test in a few readable lines, switch environments without editing code, and understand a red result. You may not design the framework alone, but you will extend it, review it and live with its trade-offs.

Three layers and one rule

CTAL-TAE describes a framework in three layers, each with its own reason to change, and advises keeping the number of layers low: a layer for every purpose makes the design harder to follow.

One rule holds them together: calls go downwards only. The syllabus states that test scripts should not call the core libraries directly, so a test never drives the browser, builds a URL or reads an environment variable itself. Below the core, the chosen tool, such as Playwright, Selenium WebDriver or an HTTP library, connects to the SUT; CTAL-TAE calls this test adaptation.

  • Test scripts: test cases and suites in business terms, such as checkout.payWithSavedCard() followed by an assertion on the total. They change when a requirement changes.
  • Business logic: everything that knows this SUT, such as page objects, flows, API clients, test data builders and keywords. A changed screen or endpoint is fixed here, once.
  • Core libraries: everything independent of the SUT, such as browser and HTTP set-up, the configuration loader, the logger and retry helpers. Any project on the same technology stack can reuse them; they change when the tools change.
A test automation framework in three layers over the tool and the SUT: calls go downwards only, so a changed screen is fixed in the business logic and a tool upgrade in the core libraries.

Calls go downwards only

  1. 1Test scriptstests/Test cases and suites in business terms, with their assertions

    Changes when a requirement changes

  2. 2Business logicbusiness/Page objects, flows, API clients, data builders and keywords: everything that knows this SUT

    Changes when a screen or an endpoint changes

  3. 3Core librariescore/Configuration loader, logger, browser and HTTP set-up, retries: nothing specific to the SUT

    Changes when the tools change; reusable on the same stack

  4. 4Tool and SUTPlaywright, Selenium WebDriver or an HTTP library drives the SUT in one environment (test adaptation)

From Test scripts to Core libraries:not allowed: a test calling the driver, logger or configuration directly

Layer names from the ISTQB CTAL-TAE syllabus, which advises keeping the number of layers low. The core reads the files in config/; secrets come from the CI secret store.

shop-e2e/
  tests/                    test scripts: business terms and assertions
    delivery-fee.spec.ts
    refunds.spec.ts
  business/                 knows this SUT; calls core/, never tests/
    pages/                  page and component objects
    flows/                  user tasks across pages: signIn, placeOrder
    api/                    API clients for set-up and clean-up
    data/                   test data builders
  core/                     knows no SUT; reusable on the same stack
    config.ts               loads and validates configuration
    logger.ts               levels, redaction, run header
    browser.ts  http.ts     tool set-up, retries
  config/                   one file per environment, no secrets
    test.json  staging.json

Reusable components, and when not to share

CTAL-TAE calls tests built from a library of reusable steps and user flows structured scripting. Extract a component when the same steps appear in several tests and change for the same reason:

  • Flows: one user task across several pages, such as signIn(user) or placeOrder(cart). The syllabus calls this the flow model: a second facade (a simpler interface that hides the details) over the page objects, so steps are reused across many tests.
  • API set-up helpers and test data builders that create the customer or product a UI test needs, faster and more reliably than clicking through screens.
  • Not worth building: a helper with ten optional flags, a base class with hundreds of methods that every page inherits, or a thick wrapper that hides the tool and makes its documentation useless. Extract on the second or third copy, not before the first.

Configuration and logging

Configuration is whatever differs between runs but not between tests: base URLs, browser, timeouts, test accounts, feature toggles. Keep one file per environment, selected by one variable such as TEST_ENV=staging, and take secrets from the CI secret store, which usually passes them to the run as environment variables, never from a committed file. Validate the configuration once at start-up in a core library, so a missing value stops the run with one clear message instead of failing 200 tests one by one.

Logging is how a failed run explains itself. CTAL-TAE describes six levels: fatal for errors that can abort the run, error when a condition or interaction fails and so does the test, warn for something unexpected that does not stop the test, info for what the test is doing, debug for details that help investigate a failure, and trace for even more detail. The syllabus also lists what the framework should record; in practice that means:

  • A run header: environment, SUT build, browser and the effective configuration with secrets masked.
  • Per test: name, start and end time, the main steps at info level, and the status: passed, failed, or a failure of the automation itself rather than of the SUT.
  • For reproduction: ids of the records the test created and any random value or seed; request and response bodies at debug level.
  • Redaction: tokens, passwords and personal data masked, for example by a pattern applied before each line is written.
// config/staging.json: committed, contains no secrets
{
  "baseUrl": "https://staging.shop.example",
  "browser": "chromium",
  "timeoutMs": 10000
}
// SHOP_API_TOKEN comes from the CI secret store; TEST_ENV=staging selects this file

// test log, info level
03:00:04 INFO  run env=staging build=4.18.2 browser=chromium token=***
03:02:11 INFO  [delivery-fee] orders of 300.00 SAR ship free: start
03:02:12 INFO  created customer C-58213 and product P-7781 via API
03:02:15 INFO  step: add P-7781 to cart, open checkout
03:02:18 ERROR delivery fee: expected 0.00, actual 7.50
03:02:18 INFO  saved screenshot and trace in results/delivery-fee/
03:02:18 INFO  [delivery-fee] end: failed after 7 s

Try it: JSON Validator Try it: JSON Schema Validator Try it: Regex Tester

Scripting approaches and their maintenance cost

CTAL-TAE lists several ways to write the tests themselves, with their advantages and drawbacks; the five below run roughly from the least up-front investment to the most. The syllabus also covers test-driven and behaviour-driven development; BDD has its own lesson later in this module.

  • Capture/playback: a tool records manual actions on the SUT and generates a script. Quick to start, but nothing is reused, a recording depends on the SUT version it was captured from, and a changed screen means recording again. It fits a small scope on a rarely changing SUT.
  • Linear scripting: one script per test, steps from top to bottom, no shared libraries. Easier to edit than a recording, but every change is repeated in every copy. It fits a spike or a handful of tests.
  • Structured scripting: tests call reusable steps and flows from the business logic layer. It needs programming skills and an initial investment; afterwards a change is fixed in one place.
  • Data-driven: structured scripts fed by rows from CSV, spreadsheet or JSON files. A new case costs one row and test analysts can add cases without code; the cost moves to managing the data.
  • Keyword-driven: a test is a table of keywords named from the user's point of view, such as Add Product To Cart, with their data. It is often built on data-driven testing, and ISO/IEC/IEEE 29119-5 is devoted to it. People who do not program can compose tests, but the keyword library is demanding to design and maintain as scope grows, and a large effort for a small system.
Five scripting approaches side by side: what a test consists of, what a product change costs to maintain, and when each approach fits.
ApproachA test isWhen the product changesFits when
Capture/playbackA recording of manual actionsRecord the affected tests againSmall scope on a rarely changing SUT
Linear scriptingA script from top to bottom, no shared codeEdit every copy of the changed stepsA spike or a handful of tests
Structured scriptingCalls to reusable steps and flowsEdit one componentA suite meant to last, maintained by programmers
Data-drivenA structured script plus rows of dataEdit one component; a new case is one rowThe same steps with many input combinations
Keyword-drivenA table of keywords and their dataEdit the keyword; the keyword library needs an ownerMany non-programmers write tests in a stable domain

Based on the advantages and drawbacks in the ISTQB CTAL-TAE syllabus. Real suites mix approaches, for example keyword-driven tests over structured, data-driven code.

Try it: CSV Dataset Generator

Worked example: a keyword-driven checkout suite

A retailer has two automation engineers and five testers who know the business rules but do not write code. The delivery fee rules change every quarter and the testers must add cases themselves, so the team chooses a keyword-driven framework. Robot Framework, an open-source framework written in Python, is one example of the approach; nothing here depends on it.

The testers write tables like the one below, with the boundary values of the rule "orders of 300.00 SAR or more ship free". The engineers own the keywords, which are business logic built on an API client and page objects. It fits because many people who do not program write tests in a stable domain vocabulary, and an engineer is assigned to the keywords. For two developers who already write tests in code, keywords would only add a layer to read when a test fails.

# tests/delivery_fee.robot, written by the testers
*** Settings ***
Resource    ../keywords/checkout.resource

*** Test Cases ***
Orders of 300.00 SAR ship free
    Sign In As New Customer
    Add Test Product To Cart    price=300.00
    Delivery Fee Should Be    0.00

Orders of 299.99 SAR pay 7.50 SAR
    Sign In As New Customer
    Add Test Product To Cart    price=299.99
    Delivery Fee Should Be    7.50

# keywords/checkout.resource (excerpt), maintained by the automation engineers
*** Keywords ***
Add Test Product To Cart
    [Arguments]    ${price}    ${quantity}=1
    ${sku}=    Create Product Via API    price=${price}
    Open Product Page    ${sku}
    Add To Cart    quantity=${quantity}

Common mistakes

  • Letting tests call the driver, build URLs or read environment variables, so one change means editing every test.
  • Building layers, wrappers and keywords before the first ten tests show what really repeats.
  • Wrapping the tool so thickly that its documentation no longer applies.
  • Hard-coding environment values and committing secrets to the repository.
  • Logs too thin to diagnose a failure, so noisy at info level that the cause is buried, or containing tokens and personal data.
  • Choosing keyword-driven testing with nobody assigned to design and maintain the keywords.

The QA perspective

Treat the framework as a product whose users are the people who add tests. On a new team, read its structure before you write anything: where tests, page objects and flows live, how the environment is chosen, and what a failure log looks like. In code review, ask the architecture questions: does this test reach below the business layer? Does this helper duplicate an existing one? Does this value belong in configuration?

Write the decisions down where the team will find them, such as a short README in the framework repository: the layers, naming rules, how to add an environment and which scripting approach is used. Track where test repairs land: the same edit in many test files points to a missing component.

Practice exercise

Design the architecture for a linear suite

Your task

A team asks you to review the UI suite of its hotel booking site, described below. Propose a target architecture: what goes into which layer, how configuration and logging should work, and which scripting approach fits each need. For each decision, name the maintenance cost it removes.

Material
Suite: 85 linear UI tests, one file per test, no shared code.
Every test begins with these lines:

  await page.goto('https://test.booking.example/login');
  await page.fill('#email', 'qa.team@booking.example');
  await page.fill('#password', 'Booking-2026');
  await page.click('#login-btn');
  await page.waitForTimeout(3000);

Facts:
- 60 tests repeat the same 6 search lines; 25 of them then pay by test card.
- The suite must run nightly on "test" and on "staging" before each release.
- CI failures show only "Timeout 30000ms exceeded" and a stack trace.
- Two business analysts want to add booking-price cases without writing code.
Steps
  1. Place each duplicated step in a layer.
  2. Decide what moves into configuration and where secrets come from.
  3. List what the log must contain to diagnose the next CI failure without a re-run.
  4. Choose a scripting approach for the existing tests and for the analysts' cases.
Show the model answer
Model answer

Layers: tests/ keeps the 85 tests, each reduced to its own steps and assertions. business/ gets page objects for the login, search, results and payment pages, the flows signIn, searchHotel and payByTestCard, and an API client that creates a fresh test account per run. core/ gets the configuration loader, the logger and the browser set-up. A sign-in change drops from 85 edits to one, a search change from 60 to one.

Configuration: base URL, browser and timeouts in config/test.json and config/staging.json, selected by TEST_ENV; the hard-coded email and password leave the code, the account comes from the API client or the CI secret store, and a missing value stops the run. Running on staging then costs one variable instead of 85 edited URLs. The fixed three-second wait goes: flows wait for conditions, as in the lesson on waits and synchronization.

Logging: a run header with secrets masked; per test the name, times, status, current step and the ids of created records; on failure the URL, a screenshot and a trace. The bare timeout then reads step: pay by test card; Pay button not visible on /checkout/payment after 30 s, and the cause is found without a re-run.

Approach: structured scripting for the existing suite. For the analysts, a data-driven CSV of nights, guests, season and expected price feeding one structured test is the cheaper first step, as each new price case costs one row; keywords are worth adding only if they must compose new flows themselves and an engineer will maintain them.

Frequently asked questions

Is Playwright or Selenium a test automation framework?

In CTAL-TAE terms they are tools and libraries that a framework builds on. Your framework is the code your team writes around them: the test scripts, the business logic and the core libraries, with the runner and its configuration. Runners such as Playwright Test already provide part of the core, including fixtures, configuration files and reporters, which is a good reason not to rebuild those parts.

Does a small project need all three layers?

It needs the separation, not necessarily three folders. With twenty tests, a few page objects, one configuration file and the runner's built-in features may be enough. Add structure when duplication or confusion appears; the syllabus itself advises keeping the number of layers low.

Is keyword-driven testing the same as BDD?

No, although both let people who do not program read tests. Keyword-driven tests are tables of actions with data. BDD scenarios are Given/When/Then sentences agreed by business representatives, developers and testers, and BDD is a collaborative development practice as much as a test format. The lesson on BDD and Gherkin covers it.

What is the difference between a log and a test report?

A log is the detailed, time-ordered record of what the framework and the SUT did during a run, written for diagnosing failures. A report summarises the results for the people who decide something: what passed, what failed and the trend. A good report links each failed test to its log and artefacts; the lesson on automation reporting and debugging covers reports.

References

Practise with Mutqan tools

Validate JSON syntax and get precise error locations with helpful fix suggestions.

JSON & Data

Validate a JSON document against a JSON Schema (draft-07 / 2019-09 / 2020-12 core keywords).

JSON & Data

Test regular expressions live with match highlighting, capture groups and replace preview.

Utilities

Generate CSV files with custom columns, data types, row counts and delimiters.

Test Data

Go deeper

Your progress on this topic

Part of the QA Roadmap