Implementing the Page Object Model (POM) for every single automated test scenario often feels like a chore, but not everything needs to be wrapped in a class. If you go about it the wrong way, you end up building a "second framework" that just becomes a headache to maintain.
The real strength of Playwright is testing things that are hard to replicate with mocking: using actual cookies for login, navigating between pages, or handling third-party redirects. This is where POM helps reduce code repetition. But if you overdo it, your page objects become a burden rather than a tool.
My rule of thumb is simple: keep page objects thin. Their only job is to give a
name to what the user sees—like a login page or settings—and provide basic
actions. For example, just create a submitValidCredentials() function; don't
let getByTestId('btn-primary-2') leak all over your test files. One crucial
thing: don't put assertions inside your objects. Let the object know how to
click a button, but let the test file decide what the expected outcome is.
Use POM when you have repeating flows, like selecting an organization or opening a project across multiple test files. It’s also useful as a single point of control if a UI label suddenly changes. However, if a flow only happens once and isn't likely to change, just write it directly in the test file. Don't force it into an object if it just ends up bloating your code.
A common mistake I see is creating massive "god objects" with dozens of methods. That’s no longer a helper; it’s a standalone framework that’s prone to breaking if you don't maintain it perfectly.
Regarding locators, I prefer using role and name, only falling back to
test-id as a last resort. Relying purely on test-id can be a sign that your
product's accessibility isn't actually that great.
It’s also important to distinguish between global elements—like navigation bars
or toast messages—and page-specific elements. For things that appear everywhere,
a small, single helper is enough. One more tip: don't hide "waiting" logic
inside your objects. Using sleep or manual retries inside a page object will
only create flaky tests that are a pain to debug.
The bottom line: don't over-mock in your E2E tests. If you stub out every single network connection, you’re basically just running integration tests on a slow browser. Save the mocking for unit tests and let Playwright test what actually happens in the real world.