·5 min read
The Modern Page Object Model: Less Shared Code, Easier Changes
What a page object model is, five outdated habits, and a simpler Playwright pattern with less shared code.

Published: · 4 min read
Should page objects contain assertions? A practical rule: business checks live in tests, technical guards live in page objects. With Playwright code.
On this page
Should a page object contain assertions? Here is the short answer: business assertions belong in the test. Technical guards belong in the page object. A test must show what "correct" means for its scenario. A page object must only promise that the page is ready to use. Mixing the two is why suites become hard to read and hard to trust.
Last week I published the modern page object model. One sign of legacy design was "page objects that assert." A senior QA engineer pushed back in the comments. His position: business checks go in the test layer, but technical checks, did the page reach the right state?, can hide inside the class.
It was a fair challenge. This post is the full answer.
Every check in a UI test is one of two kinds.
Business checks answer: did the product do the right thing? The order total is $41.97. The welcome message names the user. The discount applied.
Technical guards answer: is the page ready? The form finished loading. The spinner went away. The URL changed.
They look similar in code. They serve different readers. A business check speaks to the person deciding "is this feature broken?" A technical guard speaks to the machine deciding "can I click now?"
Put business checks in the test, always:
test('applies the discount', async ({ checkoutPage }) => {
await checkoutPage.applyCode('SAVE10');
await expect(checkoutPage.total).toHaveText('$35.97');
});
The expected value sits in the test file. When this fails at 2 a.m., the reader sees what "correct" was supposed to be. No file jumping.
Handle technical guards inside the page object, but prefer waiting over asserting:
export class CheckoutPage {
readonly total: Locator;
async applyCode(code: string) {
await this.codeInput.fill(code);
await this.applyButton.click();
await this.priceUpdate.waitFor({ state: 'visible' });
}
}
The page object does not judge the total. It makes one promise: when applyCode returns, the page finished reacting. That is a technical guard, and note it is a wait, not an assert. Playwright's web-first assertions and auto-waiting handle most of these guards for free.
Three costs show up at scale.
The expected value disappears. checkoutPage.verifyTotal() hides $35.97 in another file. The failing test cannot tell you what it believed.
The page object takes sides. Fifty tests share that class. One scenario needs a different expected total, and the shared method becomes a maze of parameters.
Failures point at the wrong layer. When an assert fires inside a page object, the stack trace blames plumbing. The reader has to dig to find which business rule broke.
My commenter's position (hidden technical assertions are fine) is workable. Plenty of strong suites do it. My preference is stricter for one reason: an assert stops the test with a verdict; a wait just holds the door. Verdicts belong to tests. But if your team hides technical guards as asserts and everyone can read the failures, that is a style choice, not a defect.
What is a defect: expected business values living anywhere except the test body.
Same as the POM modernization: no big rewrite. When a change touches a page object that asserts, move the business expectation up into the tests that call it, and convert the technical remainder into a wait. Each class takes minutes.
Run this to find your candidates:
grep -rn "expect(" src/pages/
Every hit is either a business check to promote or a guard to convert.
Anton Gulin is the AI QA Architect, the first person to claim this title on LinkedIn. He builds AI-powered test automation systems where AI agents and human engineers collaborate on quality. Former Apple SDET (Apple.com / Apple Card pre-release testing). Find him at anton.qa or on LinkedIn.
Get notified when I publish something new, and unsubscribe at any time.
·5 min read
What a page object model is, five outdated habits, and a simpler Playwright pattern with less shared code.

·5 min read
An AI model can change or vanish under your test suite overnight. The three-rule discipline — pin the version, keep a validated fallback, re-run the same suite on both — explained with the 19-day Fable 5 outage as the case study.

·7 min read
Where AI helps in QA, where it lies, and the one rule that keeps AI tests trustworthy — the AI does the work, a fixed check decides. With a working example.
