Test Automation Framework: Types, Layers and How to Choose
What a test automation framework is, the six framework types, the layers every framework needs, code examples, and how to choose the right one for your team.
- Written by
- Gautam Agarwal
- Read time
- 8 min read
- Published
.png)
A test automation framework is the set of conventions, libraries and tooling that defines how a team writes, organises, runs and reports automated tests, so every test follows the same structure. The framework sits around the test tool, much as an architecture sits around a programming language.
Teams often confuse the two. Playwright, Selenium and pytest are tools; a framework is the structure you build with them: where test data lives, how pages are modelled, how tests run in CI and how results reach the team. Two teams using the same tool can end up with very different maintenance costs because of that structure.
This guide covers the six common framework types, the layers every framework needs, code for the two most useful patterns, and a practical way to choose.
Key Takeaways
- A framework is the structure around a test tool, covering test data, page models, execution, reporting and CI.
- The six common framework types are linear, modular, data-driven, keyword-driven, behaviour-driven and hybrid.
- pytest's
@pytest.mark.parametrizeruns one test function once per data row, the core of a data-driven framework. - Playwright documents page object models as a way to keep selectors in one place and reuse code across tests.
- Most mature teams land on a hybrid: modular page objects, data-driven inputs and CI-first execution.
The Six Types of Test Automation Frameworks
| Type | How tests are built | Strength | Weakness |
|---|---|---|---|
| Linear (record and playback) | Each test is a straight script of steps | Fastest to start | Duplicated steps everywhere |
| Modular | Shared functions or page objects reused across tests | One fix updates many tests | Needs design rigour |
| Data-driven | One test runs against many rows of input data | Broad coverage from little code | Data files need upkeep |
| Keyword-driven | Tests are tables of action keywords | Readable by people who write little code | A keyword library to build and maintain |
| Behaviour-driven (BDD) | Tests are plain-language scenarios mapped to code | Shared language with product owners | Step definitions sprawl over time |
| Hybrid | A deliberate mix of the above | Fits real teams and real apps | Requires clear conventions |
Linear frameworks suit a proof of concept, then become expensive: when a login button changes, every script that clicks it breaks. Modular frameworks fix that by moving each repeated action into one place. Data-driven frameworks separate inputs from logic, so adding a test case means adding a row. Keyword-driven frameworks raise the abstraction further, with steps such as OpenPage, EnterText and Click that team members who write little code can arrange. Behaviour-driven frameworks express tests as Given, When, Then scenarios; our guide to behaviour-driven development covers how to keep those scenarios useful, and our Gherkin syntax patterns show how to write steps that stay reusable.
The Layers Every Framework Needs
Whatever the type, a durable framework separates five concerns:
| Layer | Responsibility | Example choices |
|---|---|---|
| Test runner | Finds, runs and parallelises tests | pytest, JUnit, Playwright Test, Jest |
| Interaction layer | Talks to the application | Playwright, Selenium, Appium, an HTTP client |
| Abstraction layer | Page objects, API clients, reusable flows | Page object classes, helper modules |
| Test data layer | Inputs, fixtures, seeded accounts | Parametrised data, factories, seed scripts |
| Reporting and CI | Results, artifacts, pipeline gates | HTML reports, screenshots, CI checks |
Keeping these layers apart is what lets a framework survive change. Swap the interaction layer from one browser tool to another, and only the abstraction layer needs rewriting. Update a selector, and one page object absorbs the change. Teams that blur the layers end up with the maintenance burden described in our guide to regression suite maintenance.
Pattern 1: Data-Driven Tests With pytest
The pytest parametrize documentation describes the built-in @pytest.mark.parametrize decorator, which runs one test function once for each set of arguments. That is a data-driven framework in a few lines:
# test_discount.py
import pytest
from pricing import apply_discount
@pytest.mark.parametrize(
"price, percent, expected",
[
(200.0, 10, 180.0),
(200.0, 0, 200.0),
(99.99, 50, 49.995),
],
)
def test_apply_discount(price, percent, expected):
assert apply_discount(price, percent=percent) == pytest.approx(expected)
Each row becomes its own reported test. When product adds a new discount rule, a tester adds a row rather than a function. For larger data sets, the same decorator can read rows from a CSV or JSON file at collection time.
Pattern 2: Page Objects With Playwright
The Playwright page object model guide describes page objects as a way to create a higher-level API for your application, capturing element selectors in one place and avoiding repetition. Here is a login page object and a test that uses it:
// pages/login-page.ts
import { type Page, type Locator } from '@playwright/test';
export class LoginPage {
readonly email: Locator;
readonly password: Locator;
readonly submit: Locator;
constructor(readonly page: Page) {
this.email = page.getByLabel('Email');
this.password = page.getByLabel('Password');
this.submit = page.getByRole('button', { name: 'Sign in' });
}
async signIn(email: string, password: string) {
await this.page.goto('/login');
await this.email.fill(email);
await this.password.fill(password);
await this.submit.click();
}
}
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login-page';
test('registered user reaches the dashboard', async ({ page }) => {
await new LoginPage(page).signIn('ada@example.com', 'correct-horse-42');
await expect(page).toHaveURL(/dashboard/);
});
If the sign-in button label changes, one line in LoginPage changes and every test that signs in keeps working. For the tool itself, our guide to Playwright testing covers setup and configuration.
A Folder Structure That Scales
A framework becomes real once its layers map to folders. A hybrid web framework in TypeScript often looks like this:
e2e/
pages/ # page objects, one class per screen
flows/ # multi-page journeys built from page objects
data/ # parametrised inputs and seed fixtures
tests/ # specs grouped by feature
support/ # shared setup, auth state, API helpers
playwright.config.ts # browsers, retries, reporters, base URL
Tests import from pages and flows, and only page objects touch selectors. That single rule keeps refactors local, because a changed button or field means one edit in one file.
Framework Pitfalls to Watch
- Selectors in test files. Every selector belongs in a page object, so a UI change touches one file.
- Logging in through the UI before every test. Create a session once through the API and reuse the stored state.
- Shared mutable test data. Give each test its own records, so tests can run in parallel and in any order.
- Retries that hide real defects. Track every retried pass as a signal, since a test that needs retries is telling you something.
- Reports that stay on the CI server. Attach screenshots and traces to failures so the person fixing the test sees the evidence first.
How to Choose a Framework
Start from your team and your application, then pick the structure:
- Who writes the tests? Developers suit code-first modular or data-driven frameworks. Mixed teams with product owners often suit BDD. Our comparison of codeless, low-code and full-code testing explains the trade-offs by team type.
- What does the app look like? Form-heavy apps with many input combinations reward data-driven design. Apps with many shared screens reward page objects.
- Which platforms ship? A web-only product can standardise on one browser tool. A product that ships on iOS, Android and web needs coverage on each surface, and our platform page shows how one flow can run across iOS, Android, the web, Chrome extensions and App Clips.
- How often do you release? Daily releases demand fast parallel runs and clear CI gates from day one.
- Prove it small first. Build a proof of concept with five real tests, run it in CI for two weeks, and measure flake rate and time to fix before scaling.
Conclusion
A test automation framework decides whether a growing suite stays cheap or becomes a second product to maintain. The six framework types describe how tests are built, while the five layers describe how a framework stays changeable.
For most teams, the right answer is a hybrid: page objects for structure, parametrised data for coverage, a runner that parallelises, and CI that reports every failure with evidence. Start with five tests on that structure, prove it in CI, then scale.
FAQs
What is a test automation framework?
It is the set of conventions, libraries and tooling that defines how a team writes and runs automated tests.
What are the main types of test automation frameworks?
Linear, modular, data-driven, keyword-driven, behaviour-driven and hybrid frameworks are the six common types.
Is Selenium a test automation framework?
Selenium is a browser automation tool; a framework is the structure a team builds around it.
What is the best test automation framework?
For most teams, a hybrid combining page objects, data-driven inputs and CI-first execution works best.
What is a data driven framework?
A data-driven framework runs one test against many rows of input data, separating test data from test logic.
What is the Page Object Model?
The Page Object Model wraps each page's selectors and actions in a class, so tests reuse them.
.png)