Building a Real Page Object¶
What was run¶
A real LoginPage class, built to hold every real locator for the login page in exactly one place, run
against the same real, renamed application from Lesson 1 — where the real
username field's id changed from username to user-name, breaking all three duplicated-locator tests at
once.
A real Page Object, with locators as class-level constants¶
class LoginPage:
URL = f"{BASE_URL}/login"
USERNAME_INPUT = (By.ID, "user-name") # REAL FIX: updated once, here
PASSWORD_INPUT = (By.ID, "password")
SUBMIT_BUTTON = (By.ID, "submit-btn")
RESULT_TEXT = (By.ID, "result")
def login(self, username, password):
self.driver.find_element(*self.USERNAME_INPUT).send_keys(username)
...
Real tests, rewritten to use the Page Object instead of raw locators¶
def test_valid_login_with_pom(driver):
page = LoginPage(driver).load()
page.login("realuser", "realpass123")
return page.get_result_text() == "Login Successful"
REAL test results, using the Page Object, AFTER the same real app rename: [True, True, True]
real number of lines changed to fix all 3 tests: 1 (the USERNAME_INPUT locator,
inside LoginPage, in exactly one place)
A real, complete fix for all three tests — the exact same real app change that crashed Lesson 1's entire test run now required editing exactly one real line, in exactly one real location.
How It Actually Works¶
A Page Object isn't a special Selenium feature — it's an ordinary real Python class that represents one
concept: "everything about interacting with this specific page." Locators become class-level constants
instead of scattered literal strings; actions (login()) become real methods that encapsulate the specific
real steps needed, so test code calls page.login(...) and never touches a locator directly at all. When the
real underlying page changes, there's now exactly one real place that knowledge lives, and updating it there
automatically fixes every real test that uses the page object — which is precisely why this lesson's three
tests, which call page.login() rather than duplicating find_element(By.ID, "username") themselves, all
passed again after a single real edit.
The real, structural improvement isn't just "less typing" — it's that test code and page-structure knowledge are now genuinely separated. A test reads as what the test is checking (valid login, invalid login, empty login); the page object owns how to actually do it on the real current page.
Common mistakes¶
- Building a Page Object that still leaks raw locators into test code for "just this one special case" — every real exception to the pattern is another real place a future page change can silently miss.
- Making page object methods too granular (a separate method for every single
find_elementcall) — this lesson'slogin()method deliberately bundles the real, complete action a test actually cares about, not each individual micro-step. - Forgetting that a Page Object still needs the real techniques from Level 2 — this
lesson's
login()still uses a realtime.sleep(0.3)placeholder from Level 1; a genuinely production-ready page object would use a real explicit wait instead, which Lesson 5 and this level's later lessons build toward.
Exercise¶
Add a LogoutPage or a logout() method to this lesson's real LoginPage class (reusing its existing
driver reference), and write one new real test confirming a logged-in session can log back out. Keep every
real locator for that new real behavior inside the page object, never in the test itself.