msmith1114
msmith1114

Reputation: 3231

Page Object Model, how discrete are the methods?

So i've been implementing PoM on a new project, it'll be my first time doing so. Im using Capybara with Rspec (Selenium) to write my framework.

One thing I keep running into is how "discrete" I should make the methods in my Page Object Classes. I see two trains of thought when it comes to this:

Lets take a page that makes a widget:

Option 1 (Generalized, more focused on what a user would do)

class WidgetPage

   def click_widgets_tab
        click_on('Widgets')
    end

    def create_widget_button
        click_on('Add Widget')
    end

    def enter_widget_name(name)
        fill_in 'Widget Name', with: name
    end

    def enter_widget_type(type)
        fill_in 'Widget Type', with: type
    end

    def widget_success?
        expect(page).to have_content('.alert', text: 'Widget Successfully created!')
    end
end

(The "Fill in" methods could probably be even combined in the above case

or option 2:

class WidgetPage

    def click_widgets_tab
        widget_tab_link.click
    end

    def create_widget_button
        widget_add_element.click
    end

    def enter_widget_name(name)
        widget_form_name.fill_in(name)
    end

    def enter_widget_type(type)
        widget_form_type.fill_in(type)
    end

    def widget_success?
        widget_success_alert.has_text? 'Widget successfully created!'
    end


    private

    def widget_add_element
        find_button('Widget')
    end

    def widget_form_name
        find_field('Widget Name')
    end

    def widget_form_type
        find_field('Widget Type')
    end

    def widget_tab_link
        find_link('Widgets')
    end

    def widget_success_alert
        find(.alert, text: "Widget successfully created!")
    end

end

I feel like I see most Page Object tutorials use option 2...but it seems like a lot of extra code for little return on investment. Having methods to return elements makes sense to me if they are being used in multiple methods...but not for every method.

Also as far as assertions go, option 1 makes more sense. But i've also heard that you shouldn't have assertions in a page object. So maybe it makes more sense to just have a method that returns whether an alert for example is visible or not? Still not sure the best way to handle that.

Upvotes: 1

Views: 166

Answers (1)

Nathaniel C
Nathaniel C

Reputation: 564

I think this is a fair question, a lot of it does come down to preference, but there are some pretty real pitfalls you can get into if you choose the wrong "preference" for your situation, so I think it's worth talking about.

  • Partly opinion: If your app is flaky/volatile/prone to needing workarounds, consider more granular methods in the page objects, and less Capybara in the step definitions. It's very annoying to have dozens of page.element.click() calls in the step defs that all need to be updated because the app changes to require a hover before every click. If that action is wrapped in the page object as page.clickElement() then you have a single place to make that update (or implement the workaround).
  • Mostly opinion: If you wrap absolutely everything that Capybara can do for every element, you'll end up with a bloated page object that's difficult to use. If there's a set of actions that step defs will be using 90% of the time, I'll expose those as explicit methods, and let the remaining 10% edge-case type actions use the elements directly.
  • Partly opinion: Be aware that capybara's click_on/fill_in methods restrict you to using their concept of a locator (name, id, or label). I always found this to be so restrictive, compared to arbitrary CSS, that I avoided using them.
  • Less opinion: Avoid putting assertions in page objects! Page objects are an abstraction for the page, not a test of the business rule. For your example above, both options will fail with a flat "could not find element" or "false != true", which isn't very helpful. Consider instead returning the text (whatever it is) from the page object, and asserting that in the step def.

Something like

def get_widget_alert
    find(.alert).get_text()
end

Called from the step def as

expect(widget_page.get_widget_alert).to eq('Widget Successfully created!')

Will fail with a helpful message if someone decides to change the punctuation or case of the message down the road.

Upvotes: 1

Related Questions