What Is the Page Object Model?
Learn the Page Object Model in Selenium and Playwright, then grow it into a test automation framework that runs in parallel on a cloud grid.
What you will learn
- What Is the Page Object Model?
- Your First Page Object in Selenium
- Page Object Model in Playwright
- Components and Fluent Page Objects
- Page Factory vs Plain By Locators
- Test Data, Config and Environments
- Test Automation Framework Structure
- Run the Framework in Parallel on a Grid
- POM Anti-Patterns and When to Skip POM
- Knowledge check
On this page
Last reviewed November 2026 against Selenium 4.52.0, @playwright/test 1.65.0, TestNG 7.12.0 and pytest 9.2.0.
The Page Object Model (POM) is a design pattern for UI tests: every screen, or meaningful part of a screen, gets a class that knows how to find its elements and how to perform the things a user does there. Tests talk to those classes in the language of the application ("log in as student", "open the Practice menu") and never touch a raw selector.
That is the whole idea. The rest of this course is about doing it well, because the pattern is easy to get roughly right and surprisingly easy to get wrong at scale.
The problem it solves#
Here is a perfectly working Selenium test in Python. It logs in to the public practice site we use throughout this course, practicetestautomation.com, and checks the result:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
def test_login():
driver = webdriver.Chrome()
driver.get("https://practicetestautomation.com/practice-test-login/")
driver.find_element(By.ID, "username").send_keys("student")
driver.find_element(By.ID, "password").send_keys("Password123")
driver.find_element(By.ID, "submit").click()
WebDriverWait(driver, 10).until(EC.url_contains("logged-in-successfully"))
assert driver.find_element(By.TAG_NAME, "h1").text == "Logged In Successfully"
driver.quit()
def test_wrong_password():
driver = webdriver.Chrome()
driver.get("https://practicetestautomation.com/practice-test-login/")
driver.find_element(By.ID, "username").send_keys("student")
driver.find_element(By.ID, "password").send_keys("wrong")
driver.find_element(By.ID, "submit").click()
error = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "error")))
assert error.text == "Your password is invalid!"
driver.quit()
Two tests, and already the same five lines appear twice. Now imagine the real suite: 300 tests, 80 of which log in first. Then a developer renames #submit to #login-button. You are about to edit 80 files, and the diff for that change will hide whatever the tests were actually checking.
There are three separate problems tangled together in that code:
- Duplication of mechanics. How to log in is written out in every test that needs it.
-
Locators in the wrong place.
By.ID, "submit"is knowledge about the page, not about the behaviour under test. -
Intent is buried. To understand what
test_wrong_passwordchecks, you have to read and mentally execute the steps.
Notice also that the second test leaks a browser when its assertion fails, because driver.quit() never runs. That is a framework problem rather than a page object problem, and we fix it properly in chapter 01.
The same tests with a page object#
def test_login(driver, base_url):
home = LoginPage(driver, base_url).open().login_as("student", "Password123")
assert home.heading == "Logged In Successfully"
def test_wrong_password(driver, base_url):
login = LoginPage(driver, base_url).open().login_expecting_error("student", "wrong")
assert login.error_message == "Your password is invalid!"
The locators and waits moved into a LoginPage class. When #submit changes, you edit one line in one file. The tests now read like the test cases a product owner would write, and the assertions are the only thing left in them.
That is the payoff Martin Fowler describes in his original PageObject article: a page object "wraps an HTML page, or fragment, with an application-specific API", so tests manipulate the page "without digging around in the HTML". Two details from that article are easy to miss, and both shape this course:
- Page objects are not strictly one per page. Fowler recommends them "for the significant elements on a page": a header, a results list, a single result row. Chapter 03 builds these as components.
- He favours no assertions in page objects. The Selenium documentation says the same thing more strongly. Playwright's own POM guide does put an
expectinside a page object method. Chapter 08 takes that debate apart.
Page object vs. framework#
A page object is one class. A test automation framework is everything around it: how drivers are created and torn down, where the base URL and credentials come from, how test data is built, how failures are reported, how the suite runs in parallel locally and on a remote grid. Most suites that rot do so because of the framework layer, not the page objects. That is why this course spends its second half there.
What you'll build / prerequisites#
One small suite, evolved chapter by chapter against the same login page, its "Logged In Successfully" page and the site navigation:
- Chapters 01-02: your first page objects in Selenium (Java and Python side by side) and in Playwright with TypeScript, using fixtures to inject them.
-
Chapters 03-04: components, fluent page objects that return the next page, and an honest look at Selenium's
PageFactory. - Chapters 05-06: a config and test data layer, then the framework layout, reporting and retries (including the case against retries).
- Chapter 07: the same suite running in parallel, locally and on a remote Selenium Grid or Playwright endpoint, with thread-safe drivers.
- Chapter 08: anti-patterns, and when POM is the wrong tool.
You should be comfortable writing a basic Selenium or Playwright test in at least one of Java, Python or TypeScript. You need Java 17+ with Maven, Python 3.11+, or Node.js 22+ installed. Every local code sample in this course was run against the live practice site before publishing.
If you test mobile apps rather than websites, the same pattern applies; see our article on the Page Object Model for Appium and the WebdriverIO page objects chapter of our Appium course.