Representing Complex Objects for Page Object Model?

  • So this is sort of a follow up question to me previous question: Page Object Model: How much abstraction is necessary?

    In an effort to make my test scripts simpler and methods easier (since implementing a very discrete PoM didn't really reduce my test scripts) i've came into the problem of how to handle methods that handle complex forms for example.

    With a page object model something like a Login()method is pretty easy, it takes a username and password. So even if we have to pass parameters in the test script it's not a big deal.

    But for example I have a page that deals with creating a widget for example (That's this particular pages sole purpose). The form is a series of input text fields, dropdowns, etc... (This framework is written in Ruby by the way).

    How do I go about dealing with this with PoM?

    Currently i've made a method for each input (along with the "save" button)...but that doesn't really abstract away anything, and honestly is just as long in the test script as using pure selenium calls.

    I could make a method that handles all this (Which makes sense) but the number of parameters I would need to pass would be staggering. OR I could just use default parameters (perhaps using a gem such as faker to fill in inputs within the method). Or perhaps have another class (which seems overkill) or fixture to pass into the method.

    I've seen some guides that sort of have a default data of {} that uses default in the method by merging the defaults into a blank object (if nothing is otherwise passed). Which seems like an ok way of handling it.

    What's the best way to handle this?

  • A check usually follows the Setup-Act-Check pattern.

    In the setup, you put the environment in the minimal state relevant for your check, including your test data.

    Let's consider a form that have 3 sections and a few fields on each.

    Your test could be some of this sort:

    FormPage form = FormPage()
    FormData data = FormData("some_test_data_file.json")
    assert.equals(form.section1(), data.section1)
    assert.equals(form.section2(), data.section2)
    assert.equals(form.section3(), data.section3)

    Your page object would be also straightforward:

    fun fill(FormData data){
    fun fill(Section1Data data) {

    And the test data object is basically composition:

    Section1Data section1
    Section2Data section2
    String name
    String age
    Boolean isOverAge

    Every single method is follow the Clean Code recommendations:

    • Should be as small as possible -> One to three lines here
    • Should have an explicit intent, both in its name and code -> No loops or ifs here
    • Should do one thing only and do it well -> Delegate as much as possible to lower level functions

    The magic here is in creating almost a mirror of your test data structure and the page object, which allows you to:

    1 - Simply create both at test and ask them to deal with each other;

    2 - Know almost always the points on the test data and page object you have to change to adapt to a change in the entity the they represent - if you have a new section, just add something to FormPage.fill and a new attribute to FormData | if age is not necessary, remove the attribute and change Section1.fill;

    3 - Almost no change in the UI will make you have to change the test itself;

    With this basic setup, you can come up with optimizing ideas of DefaultFormData, RandomFormData, FormDataSection1 (which takes data from a file for section 1 and randomizes the rest), etc.

    Note: Even for the login example, I would argue for creating a LoginData object - username and password at this moment represent a single entity, and they don't make sense by themselves.

Suggested Topics

  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2