Test Automation Frameworks and Its Types
An automation framework is a collection of multiple tools and libraries that are structured together to support automated testing of an application.
Advantages of an automation framwework
- Less manual work, saves time, validates 100s of test cases in a shorter span
- Early bug detection
- Regression and sanity testing
- Improved test coverage
- Minimal manual intervention
- Ability to perform parallel testing
- Shift left testing
- Lower maintenance costs.
- Reusability
- Minimize cost, Maximize ROI
Framework design should be like building a product, you need to think through how it maps with your application under test, identify generic ways of handling the tools and libraries.
To be able to do that it's important to understand the different types of automation frameworks. This will help decide the basis of building the automation framework.
Linear Automation Framework
This is the simplest form of any automation framework that involves record and playback. There is no modularity involved, and you would simply write the test code as you would record and keep it going.
Sample use case
Consider the following scenario
- Login to https://www.saucedemo.com/
- Add 6 items to cart
- Validate the number of items in the cart
- Complete checkout and validate the total amount.
Automation code
For this example, I've used playwright to record the entire use case. The code is written in the same flow as the use case would be tested manually. But when there are multiple use cases, the same steps will be added for different cases, code maintenance will be a challenge.
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.goto('https://www.saucedemo.com/');
await page.locator('[data-test="username"]').click();
await page.locator('[data-test="username"]').fill('standard_user');
await page.locator('[data-test="password"]').click();
await page.locator('[data-test="password"]').fill('secret_sauce');
await page.locator('[data-test="login-button"]').click();
await page.locator('[data-test="add-to-cart-sauce-labs-backpack"]').click();
await page.locator('[data-test="add-to-cart-sauce-labs-bike-light"]').click();
await page.locator('[data-test="add-to-cart-sauce-labs-bolt-t-shirt"]').click();
await page.locator('[data-test="add-to-cart-sauce-labs-fleece-jacket"]').click();
await page.locator('[data-test="add-to-cart-sauce-labs-onesie"]').click();
await page.locator('[data-test="add-to-cart-test\\.allthethings\\(\\)-t-shirt-\\(red\\)"]').click();
await page.locator('[data-test="shopping-cart-link"]').click();
await expect(page.locator('[data-test="shopping-cart-badge"]')).toContainText('6');
await page.locator('[data-test="checkout"]').click();
await page.locator('[data-test="firstName"]').click();
await page.locator('[data-test="firstName"]').fill('Test');
await page.locator('[data-test="lastName"]').click();
await page.locator('[data-test="lastName"]').fill('Test');
await page.locator('[data-test="postalCode"]').click();
await page.locator('[data-test="postalCode"]').fill('600017');
await page.locator('[data-test="continue"]').click();
await expect(page.locator('[data-test="total-label"]')).toContainText('Total: $140.34');
await page.locator('[data-test="finish"]').click();
await expect(page.locator('[data-test="complete-header"]')).toContainText('Thank you for your order!');
});
Advantages
- Simple and straightforward
- Works well for basic applications
- Minimal time to design and build the framework
Disadvantages
- Cannot solve complex use cases
- Not modular
- Error prone
- Test code maintenance is challenging
Modular Driven Framework
This is an enhancement to the linear framework, where in test steps repeated across multiple use cases will be extracted into multiple modules. In essence, you would be breaking down the interactions with the application into smaller modules.
Sample use case
Consider the following scenario
- Login to https://www.saucedemo.com/
- Add 6 items to cart
- Validate the number of items in the cart
- Complete checkout and validate the total amount.
Automation code
The same code from Linear Automation Framework, will be transformed into smaller modules.
- Login
- Add item to cart
- Checkout
- Validate
Actions
import { Page, expect } from '@playwright/test';
import selectors from './selectors';
export async function login(page: Page, username: string, password: string) {
await page.goto('https://www.saucedemo.com/');
await page.locator(selectors.username).fill(username);
await page.locator(selectors.password).fill(password);
await page.locator(selectors.loginButton).click();
}
export async function addItemToCart(page: Page, itemSelector: string) {
await page.locator(itemSelector).click();
}
export async function checkout(page: Page, firstName: string, lastName: string, postalCode: string) {
await page.locator(selectors.cartLink).click();
await page.locator(selectors.checkoutButton).click();
await page.locator(selectors.firstName).fill(firstName);
await page.locator(selectors.lastName).fill(lastName);
await page.locator(selectors.postalCode).fill(postalCode);
await page.locator(selectors.continueButton).click();
}
export async function verifyOrder(page: Page) {
await expect(page.locator(selectors.totalLabel)).toContainText('Total: $140.34');
await page.locator(selectors.finishButton).click();
await expect(page.locator(selectors.completeHeader)).toContainText('Thank you for your order!');
}
Selectors
Contains all the selectors in a single file for easy maintenance.
const selectors = {
username: '[data-test="username"]',
password: '[data-test="password"]',
loginButton: '[data-test="login-button"]',
addToCartBackpack: '[data-test="add-to-cart-sauce-labs-backpack"]',
addToCartBikeLight: '[data-test="add-to-cart-sauce-labs-bike-light"]',
addToCartBoltTshirt: '[data-test="add-to-cart-sauce-labs-bolt-t-shirt"]',
addToCartFleeceJacket: '[data-test="add-to-cart-sauce-labs-fleece-jacket"]',
addToCartOnesie: '[data-test="add-to-cart-sauce-labs-onesie"]',
addToCartTestAllTheThingsTshirt: '[data-test="add-to-cart-test\\.allthethings\\(\\)-t-shirt-\\(red\\)"]',
cartLink: '[data-test="shopping-cart-link"]',
cartBadge: '[data-test="shopping-cart-badge"]',
checkoutButton: '[data-test="checkout"]',
firstName: '[data-test="firstName"]',
lastName: '[data-test="lastName"]',
postalCode: '[data-test="postalCode"]',
continueButton: '[data-test="continue"]',
totalLabel: '[data-test="total-label"]',
finishButton: '[data-test="finish"]',
completeHeader: '[data-test="complete-header"]',
};
export default selectors;
Test Steps
import { test, expect } from '@playwright/test';
import { login, addItemToCart, checkout, verifyOrder } from './actions';
import selectors from './selectors';
test('test', async ({ page }) => {
await login(page, 'standard_user', 'secret_sauce');
await addItemToCart(page, selectors.addToCartBackpack);
await addItemToCart(page, selectors.addToCartBikeLight);
await addItemToCart(page, selectors.addToCartBoltTshirt);
await addItemToCart(page, selectors.addToCartFleeceJacket);
await addItemToCart(page, selectors.addToCartOnesie);
await addItemToCart(page, selectors.addToCartTestAllTheThingsTshirt);
await page.locator(selectors.cartLink).click();
await expect(page.locator(selectors.cartBadge)).toContainText('6');
await checkout(page, 'Test', 'Test', '600017');
await verifyOrder(page);
});
Advantages
- Reusability
- When a test step changes, multiple tests will not be impacted.
- Ease of test maintenance