OOP in Automation Framework
"How did you use OOP concepts in your framework?" is asked in almost every automation interview. A strong answer names each concept, points to the exact place it appears in your framework, and explains why it helps. This guide maps the four pillars to real framework code, includes a small Java model you can run to see all four working together, and gives interview lines for each.
The Four Pillars at a Glance
| Concept | Meaning | Where it lives in a framework | Why it helps |
|---|---|---|---|
| Encapsulation | Hide data, expose behavior | Page objects: private locators, public actions | Locator changes stay inside one class |
| Inheritance | Reuse a parent's code | BaseTest for setup/teardown; BasePage for waits and helpers |
No duplicated setup code |
| Polymorphism | One reference, many forms | WebDriver driver = new ChromeDriver();; overloaded helpers |
Same tests run on every browser |
| Abstraction | Show what, hide how | Interfaces (WebDriver) and abstract classes (BasePage) | Tests read like user actions, not Selenium calls |
A Runnable Model of the Framework
This simplified model replaces Selenium with fake drivers so you can compile and run it with nothing but Java — and see all four pillars together:
import java.util.*;
public class OopFramework {
// Abstraction: the contract (like Selenium's WebDriver interface)
interface Driver {
void open(String url);
String find(String locator);
void quit();
}
// Polymorphism: different implementations behind one interface
static class ChromeDriver implements Driver {
public void open(String url) { System.out.println("[chrome] open " + url); }
public String find(String locator) { return "chrome-element(" + locator + ")"; }
public void quit() { System.out.println("[chrome] quit"); }
}
static class FirefoxDriver implements Driver {
public void open(String url) { System.out.println("[firefox] open " + url); }
public String find(String locator) { return "firefox-element(" + locator + ")"; }
public void quit() { System.out.println("[firefox] quit"); }
}
static Driver create(String browser) { // a simple factory
return switch (browser) { case "firefox" -> new FirefoxDriver(); default -> new ChromeDriver(); };
}
// Abstraction + inheritance: an abstract base page
static abstract class BasePage {
protected final Driver driver;
protected BasePage(Driver driver) { this.driver = driver; }
protected abstract String path(); // every page must say where it lives
public void load() { driver.open("https://shop.example.com" + path()); }
// Overloading: same name, different parameters (compile-time polymorphism)
protected void click(String locator) { System.out.println("click " + driver.find(locator)); }
protected void click(String locator, int retries) {
for (int i = 1; i <= retries; i++) System.out.println("click attempt " + i + " on " + driver.find(locator));
}
}
// Encapsulation: private locators and state, public actions
static class LoginPage extends BasePage {
private final String username = "#user-name";
private final String password = "#password";
private final String loginButton = "#login-button";
private int attempts;
LoginPage(Driver driver) { super(driver); }
@Override protected String path() { return "/login"; } // overriding (runtime polymorphism)
public void login(String user, String pass) {
attempts++;
System.out.println("type " + user + " into " + driver.find(username));
System.out.println("type ***** into " + driver.find(password));
click(loginButton);
}
public int getAttempts() { return attempts; } // read-only access to internal state
}
public static void main(String[] args) {
for (String browser : List.of("chrome", "firefox")) {
Driver driver = create(browser); // like: WebDriver driver = new ChromeDriver();
LoginPage login = new LoginPage(driver);
login.load();
login.login("standard_user", "secret");
System.out.println("attempts = " + login.getAttempts());
driver.quit();
}
}
}
Output (first browser):
[chrome] open https://shop.example.com/login
type standard_user into chrome-element(#user-name)
type ***** into chrome-element(#password)
click chrome-element(#login-button)
attempts = 1
[chrome] quit
The same test code then runs unchanged against the Firefox implementation — which is exactly what WebDriver gives a real framework. Step through it line by line in the Java Code Visualizer.
Encapsulation → Page Objects
Locators are private; tests can only call public methods like login(). If the login button's ID changes, you fix one line in LoginPage and every test keeps working. State that tests need to read (here, attempts) is exposed through a getter, not a public field.
public class LoginPage extends BasePage {
private final By username = By.id("user-name"); // hidden from tests
private final By password = By.id("password");
private final By loginBtn = By.id("login-button");
public void login(String user, String pass) { … } // the only way in
}
Interview line: "I used encapsulation through the Page Object Model: locators are private inside each page class and tests only call public methods such as login(), so UI changes are fixed in one place." More: Page Object Model.
Inheritance → BaseTest and BasePage
BaseTestholds@BeforeMethod/@AfterMethod: create the driver, open the URL, take a screenshot on failure, quit. Every test classextends BaseTest.BasePageholds the driver, explicit-wait helpers (click,type,getText) and common components. Every pageextends BasePageand callssuper(driver).
Interview line: "I used inheritance with a BaseTest for setup and teardown, and a BasePage for shared wait helpers, so test and page classes contain only their own logic."
Prefer composition where a class merely uses something: a page has a WaitHelper or a HeaderComponent rather than inheriting from them — deep inheritance chains become hard to change.
Polymorphism → WebDriver, Overriding and Overloading
| Type | Resolved | Framework example |
|---|---|---|
| Runtime — method overriding | When the program runs | WebDriver driver = new ChromeDriver() or new FirefoxDriver(); each page overriding an abstract method from BasePage |
| Compile time — method overloading | When the code compiles | click(By) and click(By, int retries); Selenium's own sendKeys(CharSequence...), Actions.moveToElement(el) vs moveToElement(el, x, y) |
Interview line: "Runtime polymorphism comes from the WebDriver interface — the same reference drives Chrome, Firefox or Edge depending on configuration. I also used overloading for helpers, like a click method with and without a retry count."
Abstraction → Interfaces and Abstract Classes
- Interfaces: Selenium's
WebDriver,WebElement,TakesScreenshotandJavascriptExecutor; TestNG'sITestListenerandIRetryAnalyzer, which your framework implements. - Abstract classes: a
BasePagewith anabstractmethod every page must implement (such as its URL or anisLoaded()check). - Page methods themselves: a test says
checkout.placeOrder()without knowing the clicks, waits and locators behind it.
Interview line: "Abstraction appears in the WebDriver interface, in my abstract BasePage, and in page methods that hide Selenium details — tests describe what the user does, not how."
Interface vs abstract class — the follow-up question
An interface defines a contract a class can implement alongside others (a class can implement many); an abstract class can hold state, constructors and shared code, but a class extends only one. In a framework: interfaces for capabilities (driver, listener), abstract classes for shared base behavior (BasePage).
Designing a framework from scratch? Explore how layers, patterns and responsibilities fit together in the Test Architecture Design tool, and see the patterns in action in the Design Patterns Visualizer.
The One-Minute Answer
"In my framework, encapsulation comes from the Page Object Model — private locators, public actions. Inheritance comes from a BaseTest for driver setup and teardown and a BasePage for shared wait helpers. Polymorphism comes from the WebDriver interface, so the same tests run on any browser, plus overloaded helper methods. And abstraction comes from interfaces like WebDriver and ITestListener and an abstract BasePage, so tests describe user actions without Selenium details."
From Real Projects
Java has been the language of every automation framework I've worked with — Selenium, TestNG, POM classes and Apache POI for Excel data. Interviews for automation roles regularly include basic programs like this one, often followed by a question on OOP concepts. Practice writing it by hand and explaining how you'd test it. On Canolog, the many forms and screens across sales, inventory, finance and service are where keeping page details in POM classes and reusable steps in a business library paid off. Point to where each OOP concept lives in your own framework.
📚 Official documentation: Selenium documentation · dev.java: Learn Java (Oracle)
FAQs
How do you use OOP concepts in your automation framework?
Encapsulation in page objects, inheritance through BaseTest and BasePage, polymorphism through the WebDriver interface and overloaded helpers, and abstraction through interfaces and abstract base classes.
Where is encapsulation used?
In page classes: locators are private and only action methods such as login() are public.
Where is inheritance used?
Test classes extend BaseTest for setup and teardown; page classes extend BasePage for the driver and shared helpers.
How is polymorphism used?
WebDriver driver = new ChromeDriver() (runtime), and overloaded methods such as click(By) and click(By, int) (compile time).
How is abstraction used?
Through interfaces such as WebDriver and ITestListener, abstract classes such as BasePage, and page methods that hide Selenium details from tests.
Is WebDriver a class or an interface?
An interface. ChromeDriver, FirefoxDriver and EdgeDriver are classes that implement it (through RemoteWebDriver).
Related
- Java OOP & Keywords
- How to Explain Your Automation Framework
- Collections in Your Framework
- The Complete Java for Testers Guide