The Page Object Model with Vibium
Apply the Page Object Model in Vibium with Python — wrap pages in classes, centralize locators, and keep tests readable, DRY, and resilient to UI change.
The Page Object Model (POM) in Vibium wraps each screen or component in a Python class that owns its locators and exposes high-level methods, so your tests describe what a user does instead of how the DOM is queried. Instead of scattering vibe.find("css") calls across dozens of tests, you write a LoginPage class with a login(user, pw) method and a DashboardPage with a is_loaded() check. The browser handle is injected into each class, and every locator for that screen lives in exactly one file. When the UI changes — a renamed button, a moved field — you update one method, not fifty tests. Because Vibium's find() already auto-waits for actionability, your page methods stay short and free of manual waits. POM pairs naturally with Vibium's semantic selectors (role, label, testid), giving you locators that read like the page itself and survive cosmetic refactors.
What is the Page Object Model?
The Page Object Model is a pattern where each page in your app becomes a class, and each user-meaningful action becomes a method on that class. Tests never touch selectors directly — they call methods. This single rule is what makes a suite maintainable as it grows.
The benefits compound:
- One source of truth for locators — a changed selector is a one-line fix.
- Readable tests —
login_page.login("alice", "secret")beats fourfind()calls. - Reuse — the same
LoginPageserves every test that needs to authenticate.
How do I write a Vibium page object in Python?
Inject the launched browser into the class constructor, keep your locators as plain attributes, and expose methods named after user actions. Here is a LoginPage built on Vibium's verified sync API.
from vibium import browser_sync as browser
class LoginPage:
URL = "https://example.com/login"
def __init__(self, vibe):
self.vibe = vibe
def open(self):
self.vibe.go(self.URL)
return self
def login(self, username, password):
self.vibe.find(label="Username").type(username)
self.vibe.find(label="Password").type(password)
self.vibe.find(role="button", text="Sign in").click()
return DashboardPage(self.vibe)
class DashboardPage:
def __init__(self, vibe):
self.vibe = vibe
def welcome_text(self):
return self.vibe.find(".welcome").text()Notice that login() returns the next page object. This fluent chaining lets a test flow from screen to screen while each class still owns its own locators. See how to automate a login flow for the raw version this pattern wraps.
How does a test use the page object?
The test reads like a description of user behavior, with no selectors in sight. It launches a browser, hands it to the page object, and asserts on the high-level result.
from vibium import browser_sync as browser
vibe = browser.launch()
try:
dashboard = LoginPage(vibe).open().login("alice", "secret")
assert "Welcome, Alice" in dashboard.welcome_text()
finally:
vibe.quit()If the login button's label later changes, you edit one line in LoginPage and this test — along with every other test that logs in — keeps passing. That is the entire point of POM.
Which locators should page objects use?
Prefer Vibium's semantic selectors — role, label, text, placeholder, and testid — because they describe the element by what it is to a user, not by fragile CSS paths. A find(role="button", text="Sign in") keeps working when a class name changes, and a find(testid="cart-count") is immune to layout churn entirely.
# Resilient — describes intent
self.vibe.find(role="button", text="Sign in").click()
self.vibe.find(label="Email").type("alice@example.com")
self.vibe.find(testid="submit-order").click()
# Brittle — breaks on cosmetic changes
self.vibe.find("div.form > button.btn.btn-primary.mt-3").click()Keeping these locators inside the page class means a UI team can rename a CSS class without breaking a single test. For the full selector reference, see finding elements with find().
How do I model reusable components?
Not every page object is a full page. Headers, modals, and navigation bars appear everywhere, so model them as their own small classes and compose them. A HeaderComponent that exposes search(term) or open_cart() can be reused by every page that renders the header, avoiding copy-pasted locators across your suite.
class HeaderComponent:
def __init__(self, vibe):
self.vibe = vibe
def search(self, term):
self.vibe.find(placeholder="Search products").type(term)
self.vibe.find(role="button", text="Search").click()Component objects keep POM honest: each shared UI piece has exactly one class, so a redesign of the header is a single edit. This composition mindset is what separates a tidy suite from a tangle of duplicated selectors.
Why does POM matter more as the suite grows?
A five-line script does not need page objects, but a hundred-test suite lives or dies by them. Without POM, a single renamed field forces a find-and-replace across the whole repo and risks missing a case. With POM, the change is localized, reviewable, and safe. Combined with Vibium's auto-waiting find() — which removes the manual sleep() calls that bloat other frameworks' page objects — you get classes that are short, declarative, and durable. For the bigger picture on organizing the files these classes live in, read how to structure a Vibium test suite.
Next steps
Frequently asked questions
What is the Page Object Model in Vibium?
The Page Object Model is a design pattern where each screen or component is a Python class that owns its locators and exposes high-level methods. Tests call those methods instead of raw find() calls, so UI changes are fixed in one class rather than across every test.
Does Vibium need the Page Object Model?
No, Vibium works fine without it, but POM is the recommended way to organize anything beyond a quick script. It removes duplicated locators, names user actions in business terms, and keeps a growing suite maintainable as the application UI evolves over time.
How do I pass the Vibium browser into a page object?
Inject the launched browser handle into the page class constructor, typically as self.vibe. The page object then uses self.vibe.find() internally, which keeps every locator and action for that screen in one place and makes the class easy to reuse across many tests.
Vibium is created by Jason Huggins. This is an independent tutorial — see the official Vibium site and GitHub repo for canonical docs.
Related guides
Running Vibium in CI/CD with GitHub Actions
Run Vibium tests in CI/CD with GitHub Actions — install on a runner, run headless, and upload screenshots and traces as artifacts on failure.
4 min read→Best PracticesHow to Write Flake-Free Vibium Tests
Write flake-free Vibium tests by trusting auto-waiting, using semantic selectors, isolating state with browser contexts, and controlling the clock and network.
4 min read→Best PracticesHow to Parallelize Vibium Tests
Parallelize Vibium tests with pytest-xdist — give each test its own browser or context, run workers in parallel, and cut suite time without flaky cross-talk.
4 min read→Best PracticesSelector Best Practices in Vibium
Selector best practices in Vibium: prefer semantic finds (role, text, label, testid) over brittle CSS, scope your locators, and lean on auto-waiting.
4 min read→