DEV Community

Daniel Reade
Daniel Reade

Posted on

The Page Object Model, And When To Skip It

A flaky UI suite usually does not fail because the browser is hard. It fails because the test code slowly turns into a second product with its own architecture, naming debates, and maintenance debt. The Page Object Model grew popular for a reason: once a suite passes a few dozen browser tests, selectors spread everywhere, login flows get copied, and one small button rename can break six files at once.

Why Page Objects Became the Default

Page Object Model, usually shortened to POM, solves a plain problem: too many tests know too much about the page. When a checkout test directly references #email, .submit-btn, and a coupon modal selector inside the same spec file, the test becomes brittle in a very practical way. Change the checkout form structure, and every test that touches it needs edits.

That is why teams building browser suites with tools like why Selenium is used for browser automation and how POM relates adopted page classes so widely. A CheckoutPage object can hold selectors, helper methods, and workflow actions in one place. A team with 40 regression cases across sign-in, cart, and payment flows often sees this pay off fast. Rename one selector in the page object, and half the suite survives untouched.

There is also a design argument behind it. POM borrows from the broader idea that code should hide implementation details behind stable interfaces, a point covered in principles for choosing reusable software design patterns. If the test says checkout.applyCoupon() instead of clicking through four nested elements, the intent is easier to read. The benefit is real, especially when the suite is maintained by several engineers over months rather than one person over a sprint.

Where POM Starts to Hurt

The trouble begins when page objects become a reflex instead of a choice. A modern front end rarely behaves like a neat stack of pages. It has drawers, embedded widgets, reusable cards, multi-step forms, and state that appears only after an API response. Stuffing all of that into giant HomePage, DashboardPage, and SettingsPage classes can create an awkward abstraction layer that mirrors file names more than user behavior.

Picture a test suite with 25 Playwright specs and 18 page object files. The DashboardPage class has methods for opening filters, checking metrics, dismissing banners, exporting reports, and editing profile settings because those controls all exist somewhere on the same route. Now the object is not protecting complexity. It is collecting it. New contributors have to jump between spec and object just to learn where a single click lives.

This is where the core purpose of how test automation works and when to automate UI tests matters. UI tests already cost more than service-level checks. If the abstraction layer adds another maintenance surface, the suite gets slower to change without becoming more reliable. The hard question is not whether POM is good or bad. The hard question is whether the page object removes duplication that actually exists, or whether it creates a framework before the suite has earned one.

What Modern Tools Changed

Playwright and similar frameworks shifted the tradeoff because they already ship with stronger primitives than older browser tools. Locators are more expressive, waiting behavior is built in, and test fixtures can manage setup without as much handwritten plumbing. A short test can stay readable even when it talks to the page directly.

That is why you now see arguments like discussion arguing Playwright often eliminates the need for Page Object classes. The point is not that structure no longer matters. The point is that a lot of historical POM boilerplate existed to compensate for weak ergonomics in older tooling. When a spec can say page.getByRole('button', { name: 'Save' }) and rely on sane waiting, a thin helper may be enough.

Take a common case: a login helper, an authenticated fixture, and two small component helpers for a date picker and a modal. That may cover most reuse in a medium suite. Building full page classes for every route can feel tidy at first, but it often buries intent under ceremony. The better abstraction is the one that makes the next failing test easier to fix.

When Skipping POM Is the Smarter Call

Skipping POM makes sense when tests are short, the application uses stable semantic selectors, and most reuse happens around state setup rather than page interaction. In many teams, the bigger source of repetition is not clicking the same button. It is creating the same account, seeding the same cart, or forcing the same permission state before the browser even opens.

That is the idea behind conversations like thread on whether API-based state setup makes Page Object abstractions unnecessary. Imagine a suite for a subscription app. Instead of navigating through signup, email confirmation, plan selection, and onboarding in every test, a fixture creates a user through an API call and stores auth state once. Each spec can land directly on the scenario it cares about. In that setup, a heavy SignupPage object adds little value because the flow rarely appears in tests.

Skipping POM also helps when the UI is highly component-driven. A DateRangePicker helper used in six screens is often more useful than six page classes that each wrap the same widget differently. Direct locators plus a few focused helpers keep the structure close to the real source of reuse. That makes failures easier to trace when the product team ships fast.

A Better Rule Than “Always Use POM”

The useful rule is simpler: abstract around volatility and repetition, not around every route in the app. If one modal appears in eight flows, extract it. If an address form has the same fields across admin and checkout screens, give it a helper. If a page is touched by one test and has five selectors, leave it inline until duplication shows up twice and starts to bite.

This keeps architecture proportional to the suite. A 12-spec smoke pack does not need the same shape as a 400-spec cross-browser regression suite. One team may do well with classic page objects because its product is route-heavy and its engineers rotate often. Another team may prefer fixtures, component helpers, and direct locators because most tests are narrow and setup lives in APIs. The right choice depends on where maintenance time is actually going each week.

Conclusion

The Page Object Model is still useful, but it is no longer the automatic answer many teams inherited from older browser stacks. Good test architecture should reduce the cost of change. That cost shows up in very ordinary places: finding the right selector, updating setup logic, tracing a flaky assertion, or onboarding the next engineer who has to debug a failure at 8 a.m.

If page objects make those jobs easier, use them. If they turn a small suite into a mini framework with classes nobody enjoys touching, skip them and keep the code closer to the test. Modern automation tools reward smaller abstractions, especially when fixtures and API setup remove most of the repetitive work before the UI even loads. The strongest teams are usually the ones willing to prune patterns that once made sense, then stopped paying rent.

Top comments (0)