Hero Background

Next-Gen App & Browser Testing Cloud

Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

Next-Gen App & Browser Testing Cloud
  • Home
  • /
  • Blog
  • /
  • What Is the Single Responsibility Principle (SRP)
AutomationTutorial

What Is the Single Responsibility Principle (SRP)

Learn how to use the Single Responsibility Principle for reducing code complexity and easy maintainability.

Author

Sri Priya

January 11, 2026

Single Responsibility Principle (SRP) is one of the SOLID design principles you must follow while designing, developing, and maintaining an automation framework. The SOLID design Principles help us create a maintainable, reusable, and flexible automation framework that helps update the code and deliver with less time.

In test automation projects with several WebElements and nested pages, updating and testing class files can be cumbersome if the framework isn’t well-developed with proper design principles. This is where the Single Responsibility Principle helps keep the code maintainable and easy to update, regardless of the programming language.

What are SOLID Design Principles?

In the Object-Oriented Programming (OOP) world, many patterns, principles, and framework designs exist. In all those, five principles or patterns are called SOLID principles. In simple words, design principles help us to create more efficient, effective, maintainable, flexible, and understandable code. If you’re preparing for a technical interview, understanding SOLID principles is crucial, and practicing oops interview questions can greatly enhance your readiness.

The main goal of the SOLID principles is to reduce dependencies to change a part of code without impacting the other part. Additionally, they’re intended to make designs easier to understand and maintain.

SOLID principles resemble the microservices. They are loosely coupled, and each service handles a dedicated function within a large-scale application. For example, the shopping cart, billing, and user profile will be individual microservices. Updating the code in the shopping cart will not impact the other two, which helps in easy maintenance.

Now, let us see what the five principles are, which are abbreviated for SOLID.

SSingle Responsibility Principle
OOpen-Closed Principle
LLiskov Substitution Principle
IInterface Segregation Principle
DDependency Inversion Principle

The SOLID principles are the best practices used to reduce dependencies. The purpose of SOLID principles is to allow developers to build better solutions. Maintaining and updating the code effectively in case of any requirement changes will be easier. Additionally, SOLID principles help us to make designs easier to understand, maintain, and extend. Each of these principles describes a specific design pattern.

When we use all the SOLID principles in a combined way, we start writing the code effectively and efficiently. Using these design principles makes it easier to avoid issues and to build adaptively, & effectively.

Here are some of the benefits of using SOLID principles:

  • The code written using SOLID principles will help to extend the code in an easy, sustainable, manageable, and efficient way.
  • // Without SRP
    class Order {
        void calculateTotal() {
            // code to Calculate total price
        }
       
        void placeOrder(){
            //code to place order
        }
        void sendConfirmationEmail() {
            // code to Send confirmation email
        }
    }
    
    
    // Using SRP
    class Order {
        void calculateTotal() {
            // code to Calculate total price
        }
    }
    
    
    class PlaceOrder{
    
    
        void placeOrder(){
            //code to place order
        }
    }
    
    
    class EmailService {
        void sendConfirmationEmail(Order order) {
            // code to Send confirmation email
        }
    }
    
  • Using SOLID principles, classes can be extended easily without modifying the existing code base.
  • // without Using  OCP
    class Vehicle {
        void drive() {
           
        }
    }
    
    
    // Using OCP
    abstract class Vehicle {
        abstract void drive();
    }
    
    
    class Car extends Vehicle {
        void drive() {
           
        }
    }
    
    
    class Bike extends Vehicle {
        void drive() {
           
        }
    }
    
  • SOLID principles help us reduce complex modules into smaller ones by reducing coupling and improving modularity.

What is the Single Responsibility Principle?

The Single Responsibility Principle states, “A class should have only responsibility or reason to change.” Each class should have a single responsibility or functionality and should not be affected by changes in other parts of the framework of the project.

It is one of the design patterns that states that every module or class should have only one responsibility or reason to change. A class or a module should have a single, well-defined purpose or responsibility. Applying the Single Responsibility Principle to the automation testing framework can help ensure the test code is maintainable and easy to understand.

For example, suppose you have a class that handles both login functionality and searching the product. In that case, you are violating the Single Responsibility Principle, as any change in login functionality will require modifying the same class.

The best approach would be to separate these two methods into different classes, each with its responsibility.

Benefits of the Single Responsibility Principle

In the Single Responsibility Principle, classes are highly cohesive and loosely coupled, which helps ease maintenance and makes them more flexible. When a class is highly coupled with more responsibilities, any slight change could easily lead to many changes that are more challenging to maintain. The Single Responsibility Principle helps to write decoupled code, where each class has its job and encapsulates responsibilities to other classes. If the specification of one class changes, we need to change and test only that particular class.

Below are some benefits of the Single Responsibility Principle:

  • When there are multiple classes, each of them following this principle improves maintainability. It is easier to understand and make changes without difficulty in case of any requirement change.
  • Classes with lesser responsibility are smaller, and it is easier to understand the code, which makes it easier to fix the defects.
  • Classes with fewer responsibilities are often more reusable because they are loosely coupled to other classes and have fewer dependencies.
  • Whenever any issue arises, it’s easier to identify the code causing problems when classes have a single responsibility. This helps in smoother debugging and troubleshooting.

Let us understand how to apply the Single Responsibility Principle in the test automation framework.

Applying the Single Responsibility Principle

Applying the Single Responsibility Principle can help us ensure that the test code is maintainable and easy to understand for modification.

We can apply SRP to test the automation framework by below methods:

  • Separate test action and validation methods
    We need to create separate classes for each module and each test method. This will make the test code easier to read and understand. It helps to reuse the methods in other tests.
  • Separate test action and validation methods
  • Test data handling
    Encapsulating the data in separate data objects or files helps it be read and understood easily. It will help to update and maintain the test data easily. For example, a test data provider can be encapsulated for easy readability.
  • Create modular test execution classes
    Break the test logic into smaller and modular methods, each having a single responsibility. This will make code easier to understand, maintain, and reuse the test logic in other tests.
  • Interaction with Application Under Test (AUT)
    Each page class should have a defined class for interacting with the application. This ensures that any UI changes are confined to a specific class.
  • Utility functions
    Utility modules or classes for common functions reused across the framework should be class-specific. It helps to update easily. For example, Wait conditions and Garbage collection methods.

By following the Single Responsibility Principle, you can create a modular and maintainable test automation framework in which each class will have a single responsibility. It makes developing, maintaining, and updating automated tests in any programming language easier. This principle helps in front-end testing by fostering cohesion and less coupling.

This principle fosters cohesion, implying that a component should be responsible for a single part of the functionality provided by the software, and its services should be narrowly aligned with that responsibility.

...

Code Demonstration Without Using SRP

Let us understand with an example code. Below is the project without using the Single Responsibility Principle in test automation.

Project Structure:

 project without using the Single Responsibility Principle in test automation

Implementation:

Below is the code snippet for the test class, which has the following test flow:

  • Launch the test URL.
  • Navigate to the login page.
  • Enter the valid credentials and login.
  • Search.

Here, the LoginPage class has multiple responsibilities. It represents WebElements, methods for interaction, and handling navigation. This class has various responsibilities and hence multiple reasons to change. If the URL of the application changes, the login method needs to be updated. If login credentials change, we need to update and test the entire class, which is tedious.

As the LoginPage class has many responsibilities, we have created a more difficult class to understand and maintain because of tight coupling. To follow the Single Responsibility Principle, we should separate the responsibilities into separate classes and methods.

package com.lambdatest.test;


import com.lambdatest.base.BaseTest;
import com.lambdatest.pages.HomePage;
import com.lambdatest.pages.LoginPage;
import org.testng.annotations.Test;


public class TestCase extends BaseTest {


    LoginPage loginPage;
    HomePage homePage;


    @Test
    public void loginTest(){
        driver.get("https://ecommerce-playground.lambdatest.io/");
        loginPage=new LoginPage(driver);
        homePage=new HomePage(driver);
        homePage.clickOnAccount();
        loginPage.enterEmailId("[email protected]");
        loginPage.enterPassword("123456");
        loginPage.clickOnLogin();
    }


}
package com.lambdatest.pages;


import org.openqa.selenium.WebDriver;
import org.openqa.selenium.By;


public class LoginPage{


   WebDriver driver;


    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }


    By enterEmailId = By.xpath("//input[@id='input-email']");
    By enterPassword = By.xpath("//input[@id='input-password']");
    By clickOnLogin = By.xpath("//input[@value='Login']");


    public void enterEmailId(String email) {
        driver.findElement(enterEmailId).sendKeys(email);
    }


    public void enterPassword(String password) {
        driver.findElement(enterPassword).sendKeys(password);
    }


    public void clickOnLogin() {
        driver.findElement(clickOnLogin).click();
    }


    public void goToURL(){
        driver.get("https://ecommerce-playground.lambdatest.io/");
    }


}
github

The class above violates the Single Responsibility Principle.

Code Walkthrough:

This LoginPage class has four responsibilities – enterEmailId, enterPassword, clickOnLogin, and goToURL. The code above will work perfectly but will lead to some challenges. We cannot make this code reusable for other classes or objects. The class has a lot of interconnected logic, so we would have difficulty fixing errors. And as the codebase grows, so does the logic, making it even harder to understand.

Imagine a new developer joining a team with this logic and a codebase of about two thousand lines of code all congested into one class; it will lead to a lot of confusion about where to update the changes, if any.

Code Demonstration Using SRP

Let us understand with an example code. Below is the project using the Single Responsibility Principle in test automation.

Project Structure:

Code Demonstration Using SRPproject using the Single Responsibility Principle in test automation

We have separated the responsibilities of the page object class and the test class into separate classes.

Here, the TestClass is the class that has two test methods. TestMu AIHomePage is the main page class that gives navigation to other page component classes. The NavigationBar class consists of code related only to the navigation bar on the home page. MyAccountsPage class only contains the account login code. The SearchProduct class has code for search functionality, and the SpecialLinkBar class has code related to the special link bar module.

If we want to update the login credentials, we can just update the MyAccountsPage class and test only that flow. There is no need to test any other classes as we are not updating anything in other classes. The MyAccountsPage class has only one responsibility, which is account login.

Implementation:

Below is the code snippet for the test class, which has the following test scenario:

Test Scenario 1:

  • Launch the test URL.
  • Search for a product without login.

Test Scenario 2:

  • Launch the test URL.
  • Navigate to the login page.
  • Enter the valid credentials.
  • Search for a product.

Test Scenario 1 uses the NavigationBar class only for interaction, whereas Test Scenario 2 uses both the NavigationBar and MyAccountsPage classes. Using the Single Responsibility Principle, we can also achieve loose coupling and reusability.

package com.lambdatest.srp;


import com.lambdatest.base.BaseTest;
import com.lambdatest.mainpagecomponents.LambdaTestHomePage;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;


public class TestClass extends BaseTest {


    private LambdaTestHomePage lambdatestHomePage;


    @BeforeTest
    public void setupPages() {
        this.lambdatestHomePage = new LambdaTestHomePage(driver);
    }


    @Test
    public void Test1(){
        lambdatestHomePage.goTo();
        lambdatestHomePage.getNavigationBar().goToSearchField("Canon EOS 5D");
        lambdatestHomePage.getNavigationBar().clickOnSearchButton();
    }


    @Test
    public void Test2() throws InterruptedException {
        lambdatestHomePage.goTo();
        lambdatestHomePage.getNavigationBar().MyAccountLink();
        lambdatestHomePage.myAccountPage().loginfunctionality();
        //search the product
        lambdatestHomePage.getNavigationBar().goToSearchField("iPod Nano");
        lambdatestHomePage.getNavigationBar().clickOnSearchButton();
    }
}

package com.lambdatest.mainpagecomponents;


import com.lambdatest.pagecomponents.MyAccountPage;
import com.lambdatest.pagecomponents.NavigationBar;
import com.lambdatest.pagecomponents.SearchProduct;
import com.lambdatest.pagecomponents.SpecialLinkBar;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;


public class LambdaTestHomePage {


    private WebDriver driver;
    private SearchProduct searchProduct;
    private SpecialLinkBar specialLinkBar;
    private NavigationBar navigationBar;
    private MyAccountPage myAccountPage;




    public LambdaTestHomePage(final WebDriver driver){


        this.driver=driver;
        this.searchProduct= PageFactory.initElements(driver,SearchProduct.class);
        this.specialLinkBar= PageFactory.initElements(driver,SpecialLinkBar.class);
        this.navigationBar= PageFactory.initElements(driver,NavigationBar.class);
        this.myAccountPage=PageFactory.initElements(driver,MyAccountPage.class);




    }


    public void goTo() {
        this.driver.get("https://ecommerce-playground.lambdatest.io/");
    }


    public SearchProduct getSearchProduct() {
        return searchProduct;
    }


    public NavigationBar getNavigationBar() {
        return navigationBar;
    }


    public SpecialLinkBar getSpecialLinkBar() {
        return specialLinkBar;
    }


    public MyAccountPage myAccountPage(){
        return myAccountPage;
    }




}
Github

Code Walkthrough:

The lambdaTestHomePage class has navigation to all the other page component classes, such as SearchProduct, NavigationBar, SpecialLinkBar, and MyAccountPage classes. Each page component class has a single responsibility and reason to change. It is essential to consider the responsibilities of page object classes and ensure they are easily maintained.

Here, we will be running the tests on the TestMu AI platform. It is an AI-powered testing platform that offers a reliable and scalable infrastructure, supporting over 3,000 browsers and operating systems. This way, you can perform automation testing across different browsers and operating systems to ensure your websites and web apps work as expected.

Learning about the Single Responsibility Principle is key for creating strong and easy-to-maintain software. If you’re getting ready for an interview or want to know more, our detailed guide on Operating System Interview Questions can help you get better at it.

Before the tests run, set the environment variables LT_USERNAME & LT_ACCESS_KEY from the terminal. The account details are available on your TestMu AI Password & Security page.

For Windows:

set LT_USERNAME=LT_USERNAME set LT_ACCESS_KEY=LT_ACCESS_KEY

For macOS:

export LT_USERNAME=LT_USERNAME export LT_ACCESS_KEY=LT_ACCESS_KEY

For Linux:

export LT_USERNAME=LT_USERNAME export LT_ACCESS_KEY=LT_ACCESS_KEY

To demonstrate, this is the project which I have created.

The demonstrate project created
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.lambdatest</groupId>
    <artifactId>Single-Responsibility-Principle-In-Java</artifactId>
    <version>0.0.1-SNAPSHOT</version>


    <properties>
        <java.version>8</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.11.0</version>
        </dependency>


        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.7.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Step 1: Import the packages of JavaScriptExecutor and other methods.

packages of JavaScriptExecutor and other methods

Step 2: Create the RemoteWebDriver reference. RemoteWebDriver is a class that implements the WebDriver interface to execute the tests on the RemoteWebDriver server on a remote machine. It is implemented under the package below:

Create the RemoteWebDriver reference

Step 3: The method implemented in @BeforeSuite annotation in TestNG sets the browser’s capabilities. A RemoteWebDriver instance is created with the desired browser capabilities, with the Selenium Grid URL set to the cloud-based Selenium Grid on TestMu AI [@hub.lambdatest.com/wd/hub].

method implemented in @BeforeSuite annotation in TestNG

Step 4: All the test navigation steps are implemented under the @Test annotation. There are two tests – Test 1 and Test 2. In @BeforeTest annotation, we are creating an object of the TestMu AIHomePage class, as it is the main page component class with navigation to all other page component classes.

navigation steps are implemented under the @Test annotation

Step 5: All the WebElements and methods related to the Navigation Bar module are in NavigationBar classes. The @FindBy annotation is used in page objects to specify the object location strategy for a WebElement. Using the PageFactory, these WebElements are initialized when a page object is created.

WebElements and methods related to the Navigation Bar

Step 6: The isDisplayed() method checks whether the WebElement is displayed or not using lambda expression for wait.

isDisplayed() method checks whether the WebElement

Step 7: Close the driver instance using the quit() method.

Test Execution:

The test was successfully run, and the TestMu AI Web Automation Dashboard indicates the status of the test execution.

LambdaTest Web Automation Dashboard indicates the status of the test execution

Other SOLID Design Principles

We have already learned about the Single Responsibility Principle and its practical implementation through code. However, SRP is just the beginning. There are additional SOLID design principles that play a crucial role in creating robust software applications.

These principles collectively aim to enhance the efficiency, effectiveness, maintainability, flexibility, and understandability of our code. By adhering to these principles, developers can ensure that their software is easier to manage and scale, reducing the likelihood of encountering issues as the project evolves.

Let’s delve into these other design principles to understand how they contribute to creating high-quality, resilient software.

Open-Closed Principle

The Open-Closed Principle states, “Software entities like classes, modules, and functions should be open for extension but closed for modification.” Any entity should be able to add new features or methods without changing the existing code.

For example, you must calculate the total amount while placing the order. You will have a method to calculate the total amount paid based on the delivery location and charge. A class that calculates the total amount should not be modified every time you change the delivery location.

The best approach would be to use polymorphism or inheritance to create a subclass for the delivery location code and override each subclass’s total amount calculation method.

Liskov Substitution Principle

The Liskov Substitution Principle states, “Objects of a subclass should be able to replace objects of a superclass without breaking the code of functionality methods of the framework.” A class should be designed in such a way that it adheres to the contract or specification of the base class.

You have a base class, “Page Object,” representing a web page. It also has a load method that loads the page and returns the driver instance. You have two other subclasses: “Login Page” and “Search Product Page”. The LoginPage class returns the driver instance to the Search Product Page.

Using the Liskov Substitution Principle, you can ensure that classes are easily maintainable and can be extended and reused.

Interface Segregation Principle

The Interface Segregation Principle states, “Clients should not be forced to depend upon interfaces they do not use.” In the automation framework, we can apply the Interface Segregation Principle to break the larger interfaces into smaller ones by making them more specific as per the needs of individual clients.

We have an interface, IWebElement, which represents a web element. The IWebElement interface has methods click(), getAttribute(), findElement(), sendKeys(), clear(), and submit().

For example, a class submit represents a Submit button element on the web page. The Submit button class implements the IWebElement interface, but it should implement only the submit() method; there is no need to implement any other methods.

According to the Interface Segregation Principle, clients should not be forced to depend on methods they are not using. The Submit button clients only need to call submit() and do not need to depend on other methods.

Dependency Inversion Principle

The Dependency Inversion Principle states, “High-level modules should not depend upon low-level modules; they should depend on abstractions. Secondly, abstractions should not depend upon details; details should depend upon abstractions.” In the automation framework, the Dependency Inversion Principle can be applied using interfaces or abstract classes to define the dependencies between modules.

In the automation framework, we use the WebDriver interface variable driver instance whenever we want to interact with a web browser by creating a method. We can use this same method to interact with any browser, such as FirefoxDriver, ChromeDriver, and InternetExplorerDriver, which implements the WeDriver interface using abstract classes.

Final Thoughts

The Single Responsibility Principle is just a recipe for great and maintainable code. Even though the Single Responsibility Principle can be easily understood, we must apply the design principle correctly.

With the above example, we have now understood how to apply the Single Responsibility Principle. The Single Responsibility Principle helps us reduce complexity and makes it easier to maintain the code.

The Single Responsibility Principle is important and useful because it is easier to maintain, enhances flexibility, increases modularity, and reduces complexity. Following the Single Responsibility Principle, we can create easier code to understand, maintain, and update with minimal time.

I walked you through the Single Responsibility Principle in Selenium WebDriver with Java in this blog.

I hope you found this blog very useful and informative.

Please feel free to share this blog with your friends and colleagues.

Happy Testing…!!!

Author

An ISTQB certified tester with primary focus on Software Quality and making sure that Software is Bug free. She has a strong background in Testing Tools, API testing , Linux OS , UI and Backend Automation testing. I will enjoy to work in team and learning from others, across all areas of business and technologies. I love to share my knowledge and write about latest technology stacks.

Close

Summarize with AI

ChatGPT IconPerplexity IconClaude AI IconGrok IconGoogle AI Icon

Frequently asked questions

Did you find this page helpful?

More Related Hubs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests