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.
Calls go downwards only
- 1Test scriptsTest cases and suites in business terms, with their assertions
Changes when a requirement changes
- 2Business logicPage objects, flows, API clients, data builders and keywords: everything that knows this SUT
Changes when a screen or an endpoint changes
- 3Core librariesConfiguration loader, logger, browser and HTTP set-up, retries: nothing specific to the SUT
Changes when the tools change; reusable on the same stack
- 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.jsonReusable 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)orplaceOrder(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 sTry 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.
| Approach | A test is | When the product changes | Fits when |
|---|---|---|---|
| Capture/playback | A recording of manual actions | Record the affected tests again | Small scope on a rarely changing SUT |
| Linear scripting | A script from top to bottom, no shared code | Edit every copy of the changed steps | A spike or a handful of tests |
| Structured scripting | Calls to reusable steps and flows | Edit one component | A suite meant to last, maintained by programmers |
| Data-driven | A structured script plus rows of data | Edit one component; a new case is one row | The same steps with many input combinations |
| Keyword-driven | A table of keywords and their data | Edit the keyword; the keyword library needs an owner | Many 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.
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
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.
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.- Place each duplicated step in a layer.
- Decide what moves into configuration and where secrets come from.
- List what the log must contain to diagnose the next CI failure without a re-run.
- Choose a scripting approach for the existing tests and for the analysts' cases.
Show the 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
- Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0ISTQB's advanced syllabus on designing, implementing and maintaining test automation solutions and their architecture.
- ISTQB GlossaryThe standard definitions of testing terms — test case, regression testing, severity, priority, boundary value analysis and the rest.
- Playwright DocumentationOfficial documentation for Playwright, an end-to-end test framework that drives Chromium, WebKit and Firefox.
Practise with Mutqan tools
Validate JSON syntax and get precise error locations with helpful fix suggestions.
Validate a JSON document against a JSON Schema (draft-07 / 2019-09 / 2020-12 core keywords).
Test regular expressions live with match highlighting, capture groups and replace preview.
Generate CSV files with custom columns, data types, row counts and delimiters.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.