Playwright — Encapsulation in Page Object Model (POM)
Encapsulation in Page Object Model (POM) is all about hiding the internal implementation logic of the page exposing only the necessary actions that can be performed on that page. By following this principle we make sure that internal of page interactions are kept separate from test scripts.
Key steps for Encapsulation
- Private variables for locators: Define the locators or selectors as private. This ensures that they are hidden to the test files. Test should be able to interact only with methods provided by the page object.
- Public methods for interactions: Provide the public methods that interacts with the page elements (such as filling forms, clicking a button, etc.). This hides the implementation details of how these actions are performed by the page object.
- Constructor of page initiation: Initialize the
page
property with this parameterized constructor. Thispage
property will be then used by locators and public methods.
Example
Let’s say you are testing a login page that has two input fields: ‘username’ and ‘password’ and a ‘login’ button. We’ll encapsulate this page’s internal details using Page Object Model (POM).
// login.ts
import { Page } from '@playwright/test';
export class LoginPage {
// Step 1: Define private fields for locators (encapsulated)
private page: Page;
private readonly usernameInput = this.page.getByTestId('username-input'); // private
private readonly passwordInput = this.page.getByTestId('password-input'); // private
private readonly loginButton = this.page.getByRole('button'); // private
// Step 2: Constructor to initialize the Page instance
constructor(page: Page) {
this.page = page;
}
// Step 3: Public method to perform login (abstracts the interaction)
public async login(username: string, password: string): Promise<void> {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
// Step 4: You can expose other useful methods if needed
public async getLoginErrorMessage(): Promise<string> {
return await this.page.locator('.error-message').innerText();
}
}
In this example:
- Locators:
usernameInput
,passwordInput
, andloginButton
are encapsulated as private fields. They cannot be accessed directly by test scripts. - Methods:
login()
andgetLoginErrorMessage()
are made public and it abstracts away how the login and get error message actions are performed. Tests can directly call these methods without bothering about the internal logic.
The test file will use the page methods like this:
// loginTest.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from './loginPage';
test('User should be able to login successfully', async ({ page }) => {
const loginPage = new LoginPage(page);
await page.goto('<https://example.com/login>');
// Encapsulated login method
await loginPage.login('user1', 'password1');
// Add assertions after login if needed
await expect(page).toHaveTitle('Dashboard');
});
In this test:
- We create an instance of the
LoginPage
class and invoke thelogin()
method. The test doesn't need to know how the login happens, it just knows it can use theloginPage.login()
method to perform the action.
Why Encapsulation is important in POM?
- Maintainability: If in future, any page is getting updated, then we just need to update the relevant locator and page methods. No need to interfere the test script.
- Reusability: Page methods can be reused across multiple tests.
- Readability: Test scripts becomes more readable and cleaner, focused on the “what” and not the “how”.
- Abstraction: The internal implementation details (e.g., selectors, clicking, filling inputs) are hidden from the test.
Best Practices:
- Keep locators as private and expose only the necessary methods.
- Group common actions like form filling, button clicking, or navigation into well-named methods.
- Ensure that page objects do not directly expose elements to the test code.
Shiv Jirwankar
SDET