Mastering Page Object Model for Selenium test automation
As someone who has worked for over a decade in test automation across diverse industries, I have witnessed firsthand the many false starts, pain points and lessons learned when implementing sustainable test automation.
Browser testing poses unique challenges given the constantly evolving nature of web applications. Just when you think your test suites are solid, some UI tweaks break a bunch of tests and trigger a round of maintenance across hundreds of specs to get back to green.
This resource covers comprehensive guidance for overcoming these headaches by mastering page object model for Selenium test automation. I aim to share techniques refined over thousands of test automation initiatives to help you conquer some common pitfalls.
My background
I lead test automation initiatives for top enterprises across banking, retail, healthcare and technology verticals. Over my 15 years in software quality and testing, I have strategized and executed test automation for over 50 companies.
The techniques covered here represent the best practices gleaned from both successes and failures in sustainably automating tests for web, mobile, API and desktop apps. This deep expertise with a wide variety of languages, tools and frameworks led me to become an independent consultant.
Now I leverage cross-industry knowledge to offer tailored coaching and mentoring on automation approach, architecture, implementation and maintenance.
The case for Selenium browser automation
As web applications continue to grow more complex, comprehensive test coverage is impossible through manual testing alone. The costs and effort needed grows exponentially across different browsers, devices and operating systems.
Selenium emerged as the leading open source solution for automating browsers over a decade ago. Some key advantages:
Cross-browser, cross-platform support
- Forks like Selenium Grid enable distributed testing across 1500+ browser environments
Multiple language bindings
- Supports test scripting in Java, C#, Python, JavaScript, Ruby and more
Active open source community
- 3+ million downloads and counting driven by contributors and commercial vendors
For these reasons, Selenium remains the gold standard for functional testing of web UIs and flows.
Why use JavaScript for browser test automation
Selenium supports authoring test scripts in a variety of programming languages. Why choose JavaScript?
Full-stack JavaScript
- Integrates seamlessly with Node.js ecosystem for dependency management
Easy for web developers
- Reuses concepts around dynamic typing and callbacks
Lightweight syntax
- Minimizes coding overhead compared to more verbose languages
Active ecosystem
- Takes advantage of many supportive NPM packages/modules
Expressive
- Supports both object-oriented and procedural styles
Let‘s look at how to build on these strengths.
Introducing page object model
As Selenium tests grow to thousands of lines across hundreds of files, sustaining test suites becomes challenging:
- Fragile tests prone to breaking with UI changes
- Duplicated code across tests testing same flows
- Poor abstractions tightly couples test and page internals
Page object model provides software engineering principles to address these issues through improved separation of concerns, encapsulation and reuse across pages.
Conceptual overview
At a high-level, page object model aims to isolate UI test logic by mapping key aspects to relevant components:

This offers many advantages:
- Promotes loose coupling between tests and UI
- Enforces standard conventions per page
- Centralizes points affected by UI changes
- Shields tests from underlying implementation
Now let‘s walk through hands-on setup and implementation.
Setting up local test environment
The first step is configuring a development environment for creating Selenium test scripts using page objects with JavaScript.
Install Node.js runtime
Download and install the latest Long Term Support version of Node.js. This bundle includes the node package manager (npm).
Verify successful installation:
node --version
# v16.14.2 - example
npm --version
# 8.5.0 - example
Install Selenium Webdriver
The WebDriver JavaScript bindings enable programmatically driving the browser.
npm install selenium-webdriver
Acquire Browser Drivers
Need browser specifics – let‘s grab ChromeDriver:
npm install chromedriver
Can download WebDriver implementations for Safari, Edge, Firefox etc.
Scaffold new project
Initialize npm project manifest so we can add dependencies:
npm init
Follow prompts to create package.json manifest file.
{
"name": "selenium-webdriver-demo",
"version": "1.0.0",
"description": "Selenium Webdriver Demo",
"main": "index.js",
"scripts": {
"test": "mocha --timeout 10000"
},
"author": "John Doe",
"license": "MIT"
}
This npm project structure sets the foundation for constructing page objects!
Authoring reusable base page
The base page acts as parent superclass containing commonly-used logic. This promotes reuse across child page objects.
Responsibilities
Typical contents:
- Test fixture setup/teardown
- Browser actions: navigate, click, set values
- Synchronization helpers
- Custom assertions
- DOM measurements for validation
Sample base page implementation
base-page.js
const {Builder, By, Key} = require("selenium-webdriver");
class BasePage {
// Initialize test context
constructor() {
this.driver = new Builder().forBrowser(‘chrome‘).build();
}
// Browser navigation
async navigateTo(url) {
return this.driver.get(url);
}
// Common helpers
async enterText(locator, text) {
return this.driver.findElement(By.css(locator)).sendKeys(text);
}
// Teardown cleanup
async close() {
return this.driver.quit();
}
}
module.exports = BasePage;
This lays groundwork all page objects can build on!
Building page object classes
With shared base page established, we construct page-specific objects:

For example:
login-page.js
const BasePage = require(‘./base-page‘);
class LoginPage extends BasePage {
async loginAs(username, password) {
// Page-specific logic
await this.enterText(‘#username‘, username);
await this.enterText(‘#password‘, password);
await this.click(‘#submit-button‘);
}
}
module.exports = new LoginPage();
And home-page.js:
const BasePage = require(‘./base-page‘);
class HomePage extends BasePage {
async searchFor(searchTerm) {
await this.enterText(‘#search‘, searchTerm);
await this.click(‘#search-button‘);
}
}
module.exports = new HomePage();
This isolates unique page characteristics into cohesive page objects. Tests then sequence the pages to build test flows.
Executing test suites
With the framework in place, we can implement Mocha test suites:
const LoginPage = require(‘../pages/login-page‘);
const HomePage = require(‘../pages/home-page‘);
describe(‘Login Tests‘, function() {
it(‘can login and search‘, async function() {
await LoginPage.loginAs(‘[email protected]‘, ‘123456‘);
await HomePage.searchFor(‘automation‘);
// Assertions
});
});
Now we separate test code from underlying UI details through page object abstractions!
This improves maintainability and reduces duplication across tests.
Adapting to UI changes
When the application changes, we localize updates to pages instead of mass test edits.
For example, the home page search box ID is updated:
home-page.js
- await this.enterText(‘#search-box‘, searchTerm);
+ await this.enterText(‘#search‘, searchTerm);
No test changes needed! This demonstrates improved resilience through page objects.
Conclusion
Some key takeaways from this comprehensive guide:
- Page object model brings order to browser test automation
- Enforces standards around page interactions
- Shields tests from underlying UI changes
- Promotes reuse across pages to reduce duplication
- Centralizes points of potential maintenance
The techniques here represent real-world best practices refined over thousands of test automation initiatives. Page object model remains essential for scaling and sustaining test suites.
For personalized guidance on implementing browser test automation using Selenium, please contact me!