zabil 4 hours ago

In my experience, Page Objects sound neat in theory but end up as a leaky abstraction: they mix UI details with test logic, duplicate flows, and make even trivial UI changes ripple through dozens of files. What I’ve seen is indirection that hides test intent and bloats maintenance.

I also find them very developer-centric — testers get forced into upfront design work that doesn’t fit how they naturally test, and many struggle with it. I’ve had better results by expressing behavior directly and keeping UI concerns thin, instead of using a wrapper around page structure.

  • epolanski 9 minutes ago

    > In my experience, Page Objects sound neat in theory but end up as a leaky abstraction: they mix UI details with test logic, duplicate flows,

    I have literally the opposite experience.

    1. Page Object Model should not contain any test-related stuff. It contains stuff such as

      registerUser({ name: string, familyName: string, age: number })
    
      findProduct(name: string)
    
      addProductToCart(id: string, quantity: number)
    
    etc

    > and make even trivial UI changes ripple through dozens of files.

    2. Exactly the opposite. If you update the user form logic, or add a field, all you need to do is update `fillUserForm`. Whereas without POM you would be updating your tests in countless places.

  • adityaathalye 2 hours ago

    OP here. Ordinarily, I would agree with you, because PageObjects themselves are not composable, in languages belonging to the "Kingdom Of Nouns".

    However, the following design, thanks to Clojure's language design, helped address a rather nasty situation.

    A tightly scoped Domain Specific Language, over some Types of PageObjects, all of which compose arbitrarily (without breaking value semantics). So, if you wanted the `value` of a Modal box having all sorts of switches, form fields etc., you'd call `value` on it, and it would call `value` on all of its constituents, and return the immutable hash-map snapshot of whatever state it found.

      Cross-cutting concerns
    
      | v PageObject / DSL -> | open | close | open? | select | deselect | ... |
      |-----------------------+------+-------+-------+--------+----------+-----|
      | Dropdown              |      |       |       |        |          |     |
      | Checkbox              |      |       |       |        |          |     |
      | Switch                |      |       |       |        |          |     |
      | Modal                 |      |       |       |        |          |     |
      | SearchList            |      |       |       |        |          |     |
      | ...                   |      |       |       |        |          |     |
    
    Concrete example (in the deck and demo linked below):

      (defprotocol IPageObject
        "Each PageObject MUST implement the IPageObject protocol."
        (page-object [this])
        (exists? [this])
        (visible? [this]))
    
    And then an implementation like this:

      (defrecord Checkbox [target-css]
        IPageObject
        (page-object [this]
          this)
        (exists? [this]
          ;; webdriver check if target-css exists
          )
        (visible? [this]
          ;; webdriver check if target-css is visible
          )
    
        Selectable
        (select [this]
          ;; webdriver select the target
          )
    
        (deselect [this]
          ;; webdriver undo selection
          )
      
        (selected? [this]
          ;; webdriver return true if target selected
          )  
    
        Value
        (get-value [this]
          ;; webdriver return current selection state
          (selected? this)))
    
    Deck: https://github.com/adityaathalye/slideware/blob/master/desig...

    Talk + Demo: https://www.youtube.com/watch?v=hwoLON80ZzA&list=PLG4-zNACPC...

    I also commented about it here: https://news.ycombinator.com/item?id=45161410 (Clojure's solution to the Expression problem).

    That said, UI testing is a hot mess in general (especially for SPAs). I prefer to avoid automating anything but the "happy paths". IME, exploratory testing is better at sussing out corner cases, and "emergent" misbehaviours. So I do that, in addition to the "happy path" suites. Cue: James Bach et. al. https://www.satisfice.com/

    Also I am warming up to server-generated HTML, because I can unit-test that, if I can use `hiccup` syntax + HTMX. In that case, I just make all request handlers spit out HTML fragments as Clojure data, and test those responses in my test suite.

searls 2 hours ago

I was doing a ton of agile, testing, and QA consulting in this era, and I feel like page objects worked well only in very narrow circumstances. Namely, QA departments where quality engineers were tasked with building very large and comprehensive automated UI tests (usually a bad idea) that exercised the same pages many, many times (also a bad idea, b/c extremely slow).

But anyway if you were in that situation, duplication of selectors and labels and other implementation details of a page that were liable to change across a bunch of tests was an _absolute nightmare_ to deal with when you'd get a massive report of failures with bad error messages and often incorrect stack traces. Being able to fix all those errors by changing a selector in a single page object governing all of them was indeed better than the alternative.

In the era of Cucumber, page objects were yet another level of indirection around what was a far-too-indirect process to provide the kind of feedback loop you'd need for it to be valuable. Especially as part of a development workflow.

emmanueloga_ 8 hours ago

Page Object Models trade off clarity for encapsulation. Concrete example [1]. They can make tests look "cleaner" but often obscure what's actually happening. For example:

    await page.getStarted(); // what does this actually do?
vs

    await page.locator('a', { hasText: 'Get started' }).first().click();
    await expect(page.locator('h1', { hasText: 'Installation' })).toBeVisible();
The second version is explicit and self-documenting. Tests don't always benefit from aggressive DRY, but I've seen teams adopt POMs to coordinate between SDETs and SWEs.

--

1: https://playwright.dev/docs/pom

  • littlecranky67 2 hours ago

    I assume you do not use TypeScript but JavaScript? With TS your POs are basically discoverable with source-code completion and statically typed.

       await page.getStartedLink.click()
       expect(page.installationHeadline).toBeVisible()
    
    Is much more readable, and on typing "page." you will see what props on your PO are available.

    Another note on your specific example: You are probably in the US and only have a single-language project. I am a Frontend Contractor in Europe and for the past 10 years didn't have a single project that was single language, hence the "hasText" selector would always be off-limits. Instead, very often we used the react-intl identificator as text content for code execution - which would make the above example look much more unreadable without POs, while with POs the code looks the same (just your PO looks different).

  • zikani_03 7 hours ago

    I am working on a tool[1] to try to make working with playwright a bit more ergonomic and approachable. Sorry to shamelessly plug but I'd love feedback on if it is even a good idea/direction.

    Hadn't considered the Page Object Model and will definitely have to consider how to incorporate that for those who want to do things that way.

    ---

    1: https://github.com/zikani03/basi

  • sa46 5 hours ago

    > Page Object Models trade off clarity for encapsulation [and] obscure what's actually happening.

    This argument also applies to using a function for abstraction.

    I've just written a few dozen e2e tests with Playwright. The code looks like:

        await invoiceDetailPage.selectCustomer(page, 'Acme');
        await invoiceDetailPage.selectCustomerPoNumber(page, '1234567890');
        await invoiceDetailPage.setCustomerReleaseNumber(page, '987654321');
        ...10-15 more lines
    
    Each of those lines is 3 to 20 lines of Playwright code. Aggressive DRY is bad, but Page Object Models are usually worth it to reduce duplication and limit churn from UI changes.
  • kleyd 5 hours ago

    It's not a trade-off of clarity just to save developers some extra typing. It's actually improving the clarity by bringing the thing you care about to the foreground: the getting started page having a table of contents with specific items.

  • pintxo 6 hours ago

    Well, if you have tests with 100+ lines of such explicitness, it becomes really hard to see the high level picture of „what is tested here“.

    As usually, there is a balance to be found.

  • rullopat 6 hours ago

    So if you have a page that is common between, lets say, 30-40 tests, you prefer to copy/paste the new selectors everywhere?

jojohohanon an hour ago

I’m confused and think I don’t get it.

If a webpage is an iceberg, the buttons and menus and dropdowns are the visible part. If I understand this page object, that’s what he proposes testing.

But the bit I care about is the bit underneath that will compose REST calls from those UI elements and sometimes make subsequent REST calls from the result of the previous ones.

That is the tricky bit to test, and the bit where we *still* fall back to manual testing and recorded demos for qa.

I was hoping this was a suggestion for a better selenium.

movpasd 5 hours ago

I think this comes back to the idea of having a "UX model" that underlies the user interface, laying out its affordances clearly in code. In a modern application you're going to have complex UX logic and state that's distinct from the domain model as such and that deserves representation in the code.

In an MVC conception, the UX model becomes a top layer of abstraction of the domain model. It's a natural place to be because for modern apps, users expect "more than forms", i.e.: different ways of cutting up the domain data, presented in different ways, ...

This is something that component-based frontend frameworks struggle with a bit: the hierarchical layout of the DOM doesn't always reflect the interrelations in data between parts of a user experience. Prop drilling is just a reflection of this fact and perhaps it's why we're seeing a rise in the use of state stores. It's not really about state, that's just the technical symptom, it's really about providing a way of defining a (in-browser) data model based on the user experience itself rather than the particularities of the UI substrate.

a_t48 8 hours ago

I'm not really a UI guy, but isn't this MVC (or some subset)?

  • JimDabell 3 hours ago

    This seems like it’s reversing the M->V generation. You don’t want to write your tests explicitly for the minutiae of the markup you are generating, but you don’t want to bypass the view layer entirely and go straight to the model because then you aren’t testing what you intend to. So you build a new model from the view layer, and then test the model. If the markup changes enough that tests start failing, then you update the model instead of all the failing tests.

    I’m not certain that it buys enough to justify an extra layer of indirection in every front-end test though. Having a collection of selectors with meaningful names seems to get you about half the value without that extra layer.

  • vasusen 8 hours ago

    It is quite popular in testing circles to write e2e tests that are easier to maintain. However, in practice I have found it to be quite useless due to the time it takes to write good page objects. QA teams usually rely on a complete POM before writing tests on it. I used to joke that by the time my team was done shipping a page object model, our product team would have changed the entire product again.

  • serial_dev 8 hours ago

    Page object is a useful model for writing maintainable tests

oweiler 8 hours ago

This is honestly the main reason I prefer Playwright to Cypress. Playwright leans heavily into using POs, while for some reason Cypress doesn't.

So in almost every project the Cypress tests are a procedural mess, while the Playwright tests are mostly well structured.

I know that Cypress has other patterns for dealing with this but they never seem to get applied.

  • littlecranky67 2 hours ago

    I remember, for a while Cypress had in its documentations actually a warning about POs and recommended not to use them. I got curious as to why (because I am a big fan of POs) and dug deeper - I think at some point on a sales slide I found the reason "because POs have internal state". I started then to write my POs without any internal state (which you don't need with Typescripts property getters) and have luckily scaled my app with dozens os POs and hundred of tests. Later they removed that recommendation and seem to accept that people like POs.

fergie 4 hours ago

I'm confused about why this is on the front page of HN?

  • kitd 2 hours ago

    From the Guidelines [1]:

    What to Submit On-Topic: Anything that good hackers would find interesting. That includes more than hacking and startups. If you had to reduce it to a sentence, the answer might be: anything that gratifies one's intellectual curiosity.

    Off-Topic: Most stories about politics, or crime, or sports, or celebrities, unless they're evidence of some interesting new phenomenon. Videos of pratfalls or disasters, or cute animal pictures. If they'd cover it on TV news, it's probably off-topic.

    This one seems reasonable.

    [1] https://news.ycombinator.com/newsguidelines.html

boxed 5 hours ago

I think the implementation details matter a ton here. I bet you can make this thing work and be useful, but you can also follow this blindly and have just an absolute mess.

I worked at a place where a well meaning QA tech rewrote the test suite to use page objects. It was a total mess and I undid most of that work in the end. It just moved a bunch of xpath expressions a long way from the rest of the test code, and it was all single use anyway.