qa docs.
QA Automation
Master building robust frameworks from scratch.
Introduction to the Stack
Maven (Builder), Selenium (Driver), TestNG (Brain).
Manual Project Setup
Create the strict Maven directory structure.
The POM.xml
Pull down required automation dependencies.
Writing the Java Code
Write your first script using @BeforeMethod & @Test.
Compiling and Running
Execute via terminal and understand reports.
Q&A: mvn test vs clean test
Understanding Maven lifecycles and the target cache.
Page Object Model (POM)
Organizing code to represent pages instead of messy scripts.
Step-by-Step E2E Test
Write a complete end-to-end shopping cart checkout flow.
1. Introduction to the Stack
Building an automation framework from scratch is the best way to deeply understand how the tools interact. We will use three primary tools:
Maven
The builder. It handles downloading all external libraries automatically so you don't have to manually manage .jar files.
Selenium
The driver. It provides the commands (like click, type, find) to physically interact with the web browser.
TestNG
The brain. It organizes the code into tests, provides assertions (Pass/Fail), and generates reports.
2. Manual Project Setup
To use Maven, we must adhere to its strict folder structure. This separates application source code from testing source code.
Open your terminal in an empty folder (e.g., qa-framework) and run the following commands to build the skeleton:
mkdir -p src/main/java
mkdir -p src/test/java
Why this structure?
src/main/javais where developers put the actual app code (we will ignore this, since we are only writing tests).src/test/javais where QA engineers put all automation scripts.
3. The POM.xml
For Maven to know what to download, it looks for a file named pom.xml (Project Object Model) in the root of your folder.
Create a file named pom.xml in your root folder, and paste this in:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qastore</groupId>
<artifactId>automation-tests</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<!-- 1. Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.23.0</version>
</dependency>
<!-- 2. TestNG -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.2</version>
<scope>test</scope>
</dependency>
<!-- 3. WebDriverManager -->
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.9.1</version>
</dependency>
</dependencies>
</project>
Deep Understanding
We include WebDriverManager because otherwise, you would have to manually download a chromedriver.exe file every time Google Chrome updates. It manages the binary compatibility for you seamlessly!
4. Writing the Java Code
Now, let's write the actual Java code. In src/test/java, create a file named BasicTest.java:
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class BasicTest {
WebDriver driver;
// @BeforeMethod runs automatically BEFORE every single @Test
@BeforeMethod
public void setup() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
}
// @Test is the actual script. TestNG looks for this tag to execute it.
@Test
public void myFirstTest() {
// Selenium command
driver.get("https://qa.randomly.online/");
String pageTitle = driver.getTitle();
// TestNG command
Assert.assertTrue(pageTitle.contains("QA Store"), "The title did not match!");
}
// @AfterMethod runs automatically AFTER every single @Test, even if it fails
@AfterMethod
public void teardown() {
if(driver != null) {
driver.quit();
}
}
}
Deep Dive on Annotations
Notice how we separate logic using Annotations (@BeforeMethod, @Test, @AfterMethod). If a test fails halfway through, normal Java code would just crash and leave a ghost Chrome window open forever in your RAM. By using TestNG's @AfterMethod, we guarantee that driver.quit() is executed no matter what happens.
5. Compiling and Running
Now that you have your code, open your terminal in the root folder (where pom.xml is) and run:
mvn test
What happens when you press enter?
Maven reads your pom.xml.
It downloads dependencies from the internet (takes a minute on the first run).
It compiles BasicTest.java into computer-readable bytecode.
It tells TestNG to look for any method with a @Test annotation.
TestNG runs @BeforeMethod, then @Test, then @AfterMethod.
TestNG prints a summary showing Pass, Fail, or Skip.
6. Q&A: mvn test vs clean test
When executing your automation scripts, you will frequently use Maven commands in the terminal. The two most common are mvn test and mvn clean test. Here is the deep technical difference between them.
The target/ Directory
Before understanding the commands, you must understand the target/ directory. When Maven compiles your Java code (from .java to computer-readable .class files), it places them in a hidden folder named target/.
# The target folder structure
qa-framework/
├── pom.xml
├── src/
└── target/ <-- Maven generates this automatically
├── classes/ <-- Compiled app code
├── test-classes/ <-- Compiled QA scripts (BasicTest.class)
└── surefire-reports/ <-- Test results (HTML/XML)
Command 1: mvn test
This command tells Maven to compile your code and run the tests. However, it is "lazy" to save time.
- It looks at your
.javafiles and compares them to the.classfiles intarget/. - If it thinks the file hasn't changed, it skips compilation and uses the old version.
- The Danger: Sometimes Maven gets confused. You might fix a bug in your code, run
mvn test, and it still fails because Maven used the old cached version!
Command 2: mvn clean test
This chains two Maven phases together: clean and then test.
- Phase 1 (clean): It physically deletes the entire
target/folder. All old compiled code and reports are destroyed. - Phase 2 (test): It rebuilds the
target/folder completely from scratch and runs the tests. - The Benefit: It guarantees you are running the absolute latest version of your code. You never have to worry about "ghosts" in the cache.
The Golden Rule
Always use mvn clean test if you are running into unexplainable bugs, or right before you push your code to GitHub. It takes a few extra seconds, but guarantees a fresh execution!
7. Page Object Model (POM)
In the real world, tests get huge. If you put all your button clicks inside your test script, it becomes impossible to read and maintain. Instead, we use the Page Object Model.
The Rule: For every physical page in your web app, you create one Java class to represent it.
Structure
Inside src/test/java/com/qastore, create a folder named pages. This keeps your test scripts separated from your page blueprints.
package com.qastore.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage {
private WebDriver driver;
// 1. Locators (How to find elements on the page)
private By usernameInput = By.cssSelector("input[type='email']");
private By passwordInput = By.cssSelector("input[type='password']");
private By loginButton = By.xpath("//button[text()='Sign In']");
// 2. Constructor (Hooking up the driver)
public LoginPage(WebDriver driver) {
this.driver = driver;
}
// 3. Actions (What can a user do on this page?)
public void enterUsername(String username) {
driver.findElement(usernameInput).sendKeys(username);
}
public void enterPassword(String password) {
driver.findElement(passwordInput).sendKeys(password);
}
public void clickLogin() {
driver.findElement(loginButton).click();
}
}
Why do this?
If the developers change the login button from Sign In to Login Now, you only have to update one line in the LoginPage.java class, and all 50 tests that use it will instantly be fixed!
8. Step-by-Step E2E Test
Let's write a complete End-to-End (E2E) test. This simulates a real user going to the store, logging in, and checking out.
The Test Script
Create a file named CheckoutE2ETest.java inside src/test/java/com/qastore/.
package com.qastore;
import com.qastore.pages.LoginPage;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class CheckoutE2ETest {
private WebDriver driver;
private LoginPage loginPage;
@BeforeMethod
public void setup() {
driver = new ChromeDriver();
driver.manage().window().maximize();
// Initialize the page objects
loginPage = new LoginPage(driver);
}
@Test
public void testFullCheckoutFlow() throws InterruptedException {
// Step 1: Go to the Login Page
driver.get("https://qa.randomly.online/login");
// Step 2: Use the Page Object to interact
loginPage.enterUsername("test@example.com");
loginPage.enterPassword("password123");
loginPage.clickLogin();
// Pause so we can visually see the login happen (Not recommended for real frameworks!)
Thread.sleep(3000);
// In a complete framework, you would now have a HomePage object to click "Add to Cart",
// and a CartPage object to click "Checkout".
System.out.println("E2E Test executed successfully!");
}
@AfterMethod
public void teardown() {
if(driver != null) {
driver.quit();
}
}
}
You're an Automation Engineer!
You've officially created a professional Maven architecture, implemented the Page Object Model, and written an E2E test. Next time you run mvn test, watch the magic unfold!