Hiprup

What are the four pillars of OOP in Java, and how do you apply them in your automation framework?

The four pillars of OOP in Java are encapsulation, inheritance, polymorphism, and abstraction. A Selenium/TestNG framework built on the Page Object Model is essentially all four pillars working together, so interviewers expect you to map each pillar to a concrete framework element.

  • Encapsulation — each page class keeps its locators as private fields and exposes only public actions such as login(user, pass); tests can never touch a locator directly, so a UI change is fixed in one place.

  • Inheritance — common code (driver setup, explicit waits, screenshots, logging) lives in BasePage and BaseTest; every page object and TestNG class extends them instead of duplicating logic.

  • PolymorphismWebDriver driver = new ChromeDriver(); is runtime polymorphism (upcasting plus method overriding), letting one suite run on any browser; overloaded helpers like click(By) and click(WebElement) show compile-time polymorphism.

  • Abstraction — interfaces and abstract classes expose what without how: WebDriver itself is an interface, and an abstract BasePage can force each page to implement isLoaded().

  • Combined payoff — the result is a framework that is reusable, maintainable, and browser-independent, which is the real reason POM is the industry standard.

Key terms: encapsulation, inheritance, polymorphism, abstraction, Page Object Model, method overriding, method overloading, upcasting, WebDriver interface, BasePage

// Mini page-object framework showing all four OOP pillars (runs without Selenium)
abstract class BasePage {                       // Abstraction: common page contract
    private final String url;                   // Encapsulation: private state

    BasePage(String url) { this.url = url; }

    String getUrl() { return url; }             // controlled access, no direct field exposure

    void open() { System.out.println("Opening " + getUrl()); }

    abstract boolean isLoaded();                // every page MUST say how it verifies itself
}

class LoginPage extends BasePage {              // Inheritance: reuses open() and getUrl()
    LoginPage() { super("https://app.example.com/login"); }

    @Override
    boolean isLoaded() { return true; }         // Overriding: runtime polymorphism

    void login(String user, String pass) {      // Overloading: compile-time polymorphism
        System.out.println("Logging in as " + user);
    }

    void login() { login("guest", "guest"); }   // default-credentials variant
}

public class OopPillarsDemo {
    public static void main(String[] args) {
        BasePage page = new LoginPage();        // upcasting, like WebDriver d = new ChromeDriver()
        page.open();                            // Output: Opening https://app.example.com/login
        System.out.println(page.isLoaded());    // Output: true  (LoginPage's version runs)
        ((LoginPage) page).login();             // Output: Logging in as guest
    }
}

The snippet compresses a Page Object Model skeleton into plain Java: BasePage is abstract and hides its url field (abstraction plus encapsulation), while LoginPage extends it to reuse open() (inheritance). Assigning a LoginPage to a BasePage reference mirrors WebDriver driver = new ChromeDriver(), and the call to isLoaded() dispatches to LoginPage's override at runtime — that dynamic dispatch is runtime polymorphism.

The two login(...) methods share a name with different parameter lists, demonstrating compile-time polymorphism (overloading).

Never stop at textbook definitions — the differentiator is mapping each pillar to a specific artifact in your own framework, e.g. "my locators are private (encapsulation), my tests extend BaseTest (inheritance)".

Expect follow-ups like "what is the difference between abstraction and encapsulation" and "why is WebDriver driver = new ChromeDriver() polymorphism", so be ready to explain upcasting and overriding vs overloading. A classic mistake is calling method overloading runtime polymorphism — overloading is resolved at compile time.