When learning Selenium, we start with tutorials which use a Login page example. It demonstrates the capabilities and show how to use them. When working on a project, it takes a while to figure out the structure so as to keep it manageable. Complexity increases as timelines get strict. To catch up with changes, teams devise different approaches. Here, I will describe one such approach. The focus is on Separation of Concerns and DRY.

Selenium supports many languages. All examples here use Java and TestNG. I assume you have gone through the basics.

It pays to start with an automation framework that covers common interactions in the application adhering to the DRY principle. Adding new tests becomes easier and faster. This is different from testing frameworks (JUnit/TestNG). Automation framework focuses on the tasks not verification.

If individual tests are hard to refactor then rot creeps in. It is important how we organize the test code. One useful pattern for Selenium is PageObject . It helps to separate automation code from tests.

Tests should be readable. Using the language of the domain helps. I am not talking about DSLs here. For example, the intent of a method called addItemToCart is clearer than clickButton So, it pays to keep the test methods short and clear.

Coming back to the Login page tutorial. At the end of it you have a single class with tests and automation methods. Let us split it and organize the code.

1. First, create a PageObject and move all the interactions in it. Define web elements (buttons, checkboxes etc) and locators (XPaths, CSS) here. Consider a shopping cart. The test should not bother about how to add items to the cart. Its purpose is to verify. So, the test should have access to a method like addItemToCart implemented by the PageObject. Suppose if the location of the button is changed, then tests need not be changed. Only addItemToCart is changed.

2. Suppose, you want to run loginTest with multiple combinations of usernames and passwords. For tests to be parameterized, create Data Providers. You will put data combinations here. Now, you can add test cases without touching the test method. Just append items to this data provider.

3. Create test suites which contain JUnit/TestNG classes. Now is a good time to think about a BaseTest class. Future tests can be derived from it. For example, if each test must login, then instead of repeating the @BeforeClass method, place it in BaseTest.

We have the following project structure:


  • Page Objects
  • Data Providers


  • Test Suites

So that should be good for a start. I’ll elaborate on the code with example in another post.