# Implement Page Object Model

> Implement Page Object Model (POM) pattern for maintainable UI test automation. Use when: creating page objects for web UI tests, encapsulating page elements and interactions, reducing test code duplication, improving test maintainability.

- Skill: `bavithiranhardy14/implement-page-object-model` (Agent Skill)
- Install (CLI): `npx skillmds@latest add bavithiranhardy14/implement-page-object-model`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bavithiranhardy14/implement-page-object-model/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: bavithiranhardy14 (https://skillmd.com/u/bavithiranhardy14)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/bavithiranhardy14/implement-page-object-model

---


# Implement Page Object Model (POM)

## When to Use

- Building web UI test automation
- Reducing locator hardcoding in test files
- Improving test maintainability and reusability
- Centralizing page element definitions
- Enabling cross-browser compatibility

## Procedure

### 1. Create Base Page Class

**pages/base_page.py**

```python
from utils.logger import get_logger

logger = get_logger(__name__)

class PlaywrightBasePage:
    """Base page class with common Playwright methods for all pages."""
    
    def __init__(self, page, timeout=10000):
        self.page = page
        self.timeout = timeout
    
    def goto(self, url):
        self.page.goto(url)
        logger.info(f"Navigated to: {url}")
    
    def click(self, selector):
        self.page.click(selector)
        logger.info(f"Clicked: {selector}")
    
    def fill(self, selector, text):
        self.page.fill(selector, text)
    
    def get_text(self, selector):
        return self.page.text_content(selector)
    
    def is_visible(self, selector):
        return self.page.is_visible(selector)
    
    def is_enabled(self, selector):
        return self.page.is_enabled(selector)
    
    def wait_for_selector(self, selector):
        self.page.wait_for_selector(selector)
    
    def wait_for_load_state(self, state="networkidle"):
        self.page.wait_for_load_state(state)
    
    def select_option(self, selector, value):
        self.page.select_option(selector, value)
    
    def check_checkbox(self, selector):
        self.page.check(selector)
    
    def screenshot(self, filename):
        self.page.screenshot(path=f"reports/screenshots/{filename}.png")
    
    def get_page_title(self):
        return self.page.title()
    
    def get_current_url(self):
        return self.page.url
```

### 2. Create Specific Page Classes

**pages/login_page.py**

```python
from pages.base_page import PlaywrightBasePage
from utils.logger import get_logger

logger = get_logger(__name__)

class LoginPage(PlaywrightBasePage):
    """Login page object."""
    
    USERNAME_INPUT   = "#username"
    PASSWORD_INPUT   = "#password"
    LOGIN_BUTTON     = "#loginBtn"
    ERROR_MESSAGE    = ".error-message"
    FORGOT_PWD_LINK  = "text=Forgot Password?"
    
    def enter_username(self, username):
        self.fill(self.USERNAME_INPUT, username)
    
    def enter_password(self, password):
        self.fill(self.PASSWORD_INPUT, password)
    
    def click_login(self):
        self.click(self.LOGIN_BUTTON)
    
    def login(self, username, password):
        logger.info(f"Logging in as: {username}")
        self.enter_username(username)
        self.enter_password(password)
        self.click_login()
        self.wait_for_load_state("networkidle")
    
    def get_error_message(self):
        if self.is_visible(self.ERROR_MESSAGE):
            return self.get_text(self.ERROR_MESSAGE)
        return ""
    
    def is_error_displayed(self):
        return self.is_visible(self.ERROR_MESSAGE)
    
    def click_forgot_password(self):
        self.click(self.FORGOT_PWD_LINK)
```

**pages/dashboard_page.py**

```python
from pages.base_page import PlaywrightBasePage
from utils.logger import get_logger

logger = get_logger(__name__)

class DashboardPage(PlaywrightBasePage):
    """Dashboard page object."""
    
    WELCOME_MESSAGE = "h1"
    USER_PROFILE    = "#userProfile"
    LOGOUT_BUTTON   = "#logoutBtn"
    NAV_MENU        = "#navMenu"
    
    def get_welcome_message(self):
        return self.get_text(self.WELCOME_MESSAGE)
    
    def is_user_logged_in(self):
        return self.is_visible(self.USER_PROFILE)
    
    def click_logout(self):
        self.click(self.LOGOUT_BUTTON)
        logger.info("Logout clicked")
    
    def navigate_to(self, menu_item):
        self.click(f"nav >> text={menu_item}")
        logger.info(f"Navigated to: {menu_item}")
```

### 3. Using POM in Tests

**tests/ui/test_login.py**

```python
import pytest
from pages.login_page import LoginPage
from pages.dashboard_page import DashboardPage

class TestLogin:
    
    @pytest.fixture(autouse=True)
    def setup(self, page):
        self.page = page.page
        self.page.goto("https://app.example.com/login")
    
    @pytest.mark.smoke
    def test_successful_login(self):
        login = LoginPage(self.page)
        login.login("user@example.com", "password123")
        
        dashboard = DashboardPage(self.page)
        assert dashboard.is_user_logged_in()
    
    @pytest.mark.regression
    def test_invalid_credentials(self):
        login = LoginPage(self.page)
        login.login("user@example.com", "wrongpass")
        assert login.is_error_displayed()
    
    def test_forgot_password_link(self):
        login = LoginPage(self.page)
        login.click_forgot_password()
        assert "forgot" in login.get_current_url()
```

## Best Practices

- All selectors live in ONE class — single place to update
- Pages expose **actions**, not raw selectors
- Tests never use raw locators directly
- Base page holds shared Playwright methods
- One class per page

