Implementing Page Object Model (POM) in Protractor for Angular E2E Tests
Learn how to apply the Page Object Model in Protractor to centralize UI locators, improve test readability, and reduce maintenance effort for Angular end‑to‑end tests.
24 Aug 2025, 06:41 UTC

The Problem: Locator Fragility in E2E Suites
In large-scale Angular applications, UI changes are frequent. When element locators (like CSS selectors or ng-model attributes) are hardcoded directly into test scripts, a single change to a button's ID can break dozens of tests. This creates a maintenance bottleneck where engineers spend more time updating selectors than writing new test cases.
The Page Object Model (POM) solves this by decoupling the UI structure from the test logic. Instead of the test script knowing how to find a login button, it asks a Page Object to perform the login action. If the UI changes, you update the locator in one class, and every test using that page is automatically fixed.
Prerequisites
- Node.js installed and a Protractor project initialized.
- An Angular application running locally or in a staging environment.
- Knowledge of WebDriverJS basics.
Structuring the Page Object
A Page Object should act as a service layer for a specific page or component. It defines the elements it manages and the actions a user can take on those elements. To maintain a clean separation of concerns, never place assertions (expect statements) inside the Page Object; keep assertions in the spec file to ensure the Page Object remains a reusable representation of the UI.
// login.page.ts
export class LoginPage {
// Define locators as properties
get usernameInput() { return element(by.model('username')); }
get passwordInput() { return element(by.model('password')); }
get submitButton() { return element(by.css('.btn-primary')); }
// Define user interactions as methods
async login(user, pass) {
await this.usernameInput.sendKeys(user);
await this.passwordInput.sendKeys(pass);
await this.submitButton.click();
// Return the next page object to allow chaining
return new DashboardPage();
}
}
Implementing the Test Spec
The spec file should read like a business requirement document. It handles the navigation and the validation, while the Page Object handles the technical implementation.
// login.spec.ts
import { LoginPage } from './pages/login.page';
describe('Authentication Flow', () => {
let loginPage;
beforeEach(async () => {
loginPage = new LoginPage();
await browser.get('http://localhost:4200/login');
});
it('should redirect to dashboard on valid credentials', async () => {
const dashboardPage = await loginPage.login('test_user', 'secure_password');
expect(await dashboardPage.welcomeMessage.getText()).toContain('Welcome');
});
});
Handling Synchronization with ExpectedConditions
Avoid browser.sleep(), as it introduces arbitrary delays that lead to flaky tests. Instead, use ExpectedConditions (EC) to wait for the UI to reach a specific state before interacting with it.
Run these commands in your terminal with the necessary project permissions to execute the suite:
# Run tests via the Protractor CLI
./node_modules/.bin/protractor conf.js
To implement a robust wait within a Page Object method, use the browser.wait function:
import { ExpectedConditions as EC } from 'protractor/angular';
async waitForLoginButton() {
await browser.wait(EC.elementToBeClickable(this.submitButton), 5000, 'Login button not clickable');
}
Verification and Maintenance
| Check | Expected Result | Failure Indicator |
|---|---|---|
| Locator Update | Changing one selector in the Page Object fixes all related tests. | Multiple spec files require manual updates. |
| Spec Readability | Spec files contain business actions (e.g., .login()). |
Spec files contain by.css() or by.model(). |
| Execution Stability | Tests pass consistently across environments. | Intermittent NoSuchElementError (indicates missing EC). |
Limitations and State Recovery
Limitations: Protractor is officially deprecated by the Angular team. While POM is a universal pattern, for new projects consider migrating these Page Objects to Playwright or Cypress, as the logic of separating locators from tests remains identical.
Rollback: Since this is a structural pattern change rather than a database migration, the only state change is the file organization. To revert, move locators back into the .spec.ts files and delete the /pages directory.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.