Page Objects vs. Functional Helpers
A while ago, I published Functional Programming Test Patterns with Cypress, where I went in-depth on...
For further actions, you may consider blocking this person and/or reporting abuse
Nice write-up.
I still prefer POMs - I like having 1-1 abstractions between the component and a testing flow. Each component gets a POM, and subcomponents are instantiated as POM properties. It makes the mental model easier for me to reason about as things scale. With functional approaches, I find people tend to be unsure which files to put functions, and in a team environment it can get fractured and confusing.
I use TypeScript so the constructor property setting gets rid of the extra assignment work, I still use Playwright's API directly, and the benefit of the defined 1-1 rule outweighs the extra bloat that a class structure may create.
Nonetheless appreciate the well-articulated perspective!
The TS constructor shortcut is a good point, I always use it as well.
One thing I identified from your post, and correlated with the information I get from other QA is that, the term "Component" is not used in the same context as a React/Angular/Vue as developers think.
For frontend devs, a component is a reusable UI unit with state, props, and lifecycle hooks.
For test automation, a component is often just a logical grouping of elements & interactions within a larger page.
• Example: A checkout form component in testing might represent the entire checkout UI, while in React, it might be broken into FormField, Button, AddressInput, etc.
• In POM, this means subcomponents are typically instantiated as properties within a page object, mirroring how a UI is structured for interaction rather than strict reusability.
While I understand the reasoning behind this, I don’t fully agree with the terminology.
I personally advocate for UI-component-driven testing, as I discuss in my bookhttps://muratkerem.gitbook.io/cctdd .
With modern tools like Playwright Component Testing, Cypress Component Testing, and Vitest with UI, we can now test at a lower level, making full-page interactions less necessary.
It is an alien approach for QA at the moment, but this shift indirectly solves many POM complexities:
We only move up the pyramid when there are things we cannot fully test at components, like routing, user flows, backend calls.
I've started started diving into the vue test tools and I struggle to understand how to use it effectively.
I try to look at tests ensuring code can be refractored. And vue is very declarative which leeds to tests that look to verify the code generator which I see as mostly wasted.
When devs write vue components what are their mistakes and refactoring that happens?
A good point view! I'd like to share my point too. I have a nice example of avoiding POM approach and just focusing on functions in e2e scenarios. Sooner or later the code becomes unbearable stack of lines and you need to handle it somehow so it stays readable (it all depends on dev's experience, anyway, or could use test blocks, steps, comments etc.). As for me POM has not only inheritance, but also a lexical context, as it was mentioned in previous comments. Like, you create on one page, then view on another and delete on the third. All these actions will be faceless until you start to wrap and name it like, createAItem, createBItem, createCItem, the same for view and delete actions, what seems like easier to be done in terms of different classes. Also don't forget about Playwright's fixture approach that ease the process class instantiation. So, as for me function helpers are suitable for a one functionality test cases or something like this. For complex or integration test cases POM is fitting better, I assume. Any thoughts on that case?
I see your point.
Ironically one way to get around that is using fixtures without classes. Take a look at this repo github.com/muratkeremozcan/cy-vs-p... .
Here is something you can improve POM with youtube.com/watch?v=of1v9cycTdQ
Decorators in POM methods.
Again, ironically, the same tweak can be used with the functional approach to solve the problem you identified.
Cool-cool! Thanks for examples!
I don't think this addresses real issues with POM. There are a few challenges that you'd want to tackle.
POM recommendation included hiding framework interaction, in the context of selenium your test shouldn't touch IWebElement. I disagree with this approach for abstraction.
But you have replaced classes with modules, which is fine, but not a real difference.
Objects still provide guidance, where the login operation can return the welcome page where available locators are defined. If all you are doing with objects is filling out boilerplate then sure there is no value.
The one I like is click delete it returns an object representing the confirm dialog.
Appreciate your perspective. This discussion has been happening across many responses, and I covered my stance in the main post. The shift I’m advocating is moving beyond manually organizing locators—leveraging AI-assisted debugging and ARIA selectors as the new standard.
Hi Murat,
Thanks for sharing this post, I'm a big fan of both your QA articles and courses on Udemy. 🤠
I've a question regarding using Functional Programming instead of POM, if we have to automate a flow scenario, for example checkout a product in an E-commerce web app, and we need to navigate between pages, in POM I had to create classes with Page Objects then create instances of these classes and use them into the test file, if I will use the functional helpers pattern, how can I achieve this?
Simple: just compose functions—no need for classes!
🔥 TL;DR
• If a flow is one-off? Just write UI actions directly.
• If reused? Use functional helpers—not Page Objects.
❌ Unnecessary classes
❌ Extra abstraction
❌ Harder debugging
This is what I prefer, if the code isn't repeated anywhere else
✅ No class instantiation
✅ No unnecessary helpers
✅ Just readable, direct UI actions
✅ If this flow is repeated, use functional helpers instead
Web elements which are needed to interact with in automated tests should have reliable and stable selectors. That just needs to be requested from frontend developers and they will implement it. No AI is necessary in that area if you work in that way together.
The page objects are a relatively logical way to avoid code duplication and to structure test automation code in a relatively deterministic and less arbitrary way. Yes, they don't write themselves, humans have to write them. When there are many pages which are very similar and have very few web elements, then page objects do not seem to fit I noticed. The idea of a page object method is to return the next page after the action, but sometimes the next page can be one of many, depending on the context, which you often realize only later and then have to change the page objects which has a ripple effect on the tests which use them.
End-to-end testing is black box, you do not know and should not need to know how the web application is designed internally. All what you see as a tester and user are pages or screens with content which can be interacted with. And so are your automated end-to-end tests, black box, because you have that perspective. Otherwise they would not be black box tests and we would be talking about frontend integration tests and not end-to-end tests.
If you use functional helpers you should still avoid code duplication regarding locators and other things, otherwise it makes not sense to me. I am not sure how to apply the functional helpers in a programming language like Java where functional programming is possible, but maybe not so essential like in JavaScript, since Java was designed to be an object-oriented programming language.
These are just my first thoughts, I need to investigate the pros and cons and if that makes only sense for JavaScript (if at all) to use functional helpers.
Good points! I agree that stable selectors should be a collaboration with frontend devs, but the discussion has moved beyond manually managing locators in a POM. With AI-assisted debugging and ARIA snapshots, we no longer need an abstraction layer just to store and organize selectors—the source code and ARIA selector tree become the source of truth.
POM is useful when managing state (constructors), inheritance, or encapsulation, but none of those apply to our use case. Functional helpers handle reusable code without unnecessary boilerplate, making them a more cost-effective and maintainable alternative.
For Java and C#, I acknowledge that everything must be inside a class—you don’t have the option to avoid structured objects like you do in JavaScript. But even in those cases, the key principle remains:
• Avoid deep hierarchies, unnecessary inheritance, and over-engineered page classes.
• Keep interactions lightweight, close to the test itself, and structured for readability—not forced abstraction.
At this point, the question isn’t POM vs. functional helpers—it’s whether POM is even necessary when AI and modern test tools can directly interpret and interact with the DOM more effectively.
P.S. If you’re testing a UI application, why use Java or C# (or even Python)? Use the language of the source code for the tests. Non-JS/TS languages have their place outside UI testing (like API E2E testing), but POM isn’t relevant there anyway.
Hi Murat,
Thank you for sharing your opinion.
I really like the idea of the functional helpers as I am moving towards Playwright from Java/Selenium for my new projects. It does make sense that POM is not entirely necessary in such new tools. However I find it hard for the new tools to maintain locators as in your examples, you might need to repeat the locator strategy in many functions (e.g: Input text in textbox, verify text in textbox). If the UI changes, then we need to find all functions to update instead updating 1 line of code in POM.
Can you help me share the way we can deal with this problem if we implement functional helpers?
Thank you again!
Hi Duy,
What I recommend for locators is adding selectors in the source code, or using testing-library-like semantic selectors in PW (which come built-in).
Take if you check out my Udemy courses, this is the way I did it there udemy.com/user/murat-ozcan-14/?srs...
Here is the repo you can take a look at github.com/muratkeremozcan/pact-js.... Look for data-cy selectors. You will also find testing-library-like selectors where data-cy isn't used.
Cheers,
Murat
Technically Pom can have composition too and still be class based
If we need another one method to enter username alone for sake of field validation we need to paste along with locator for Functional helper whereas for POM locator will be in one place. Always POM
You don’t need POM to centralize locators—just use a locators module instead.
If you must do this (and I'm not a fan) you can apply something like the below.
This looks great