0

So i've ran into what is probably a common issue when designing a page object model based framework. For this specific example I am using Cypress (So JavaScript) but the problem exists for all PoM based setups.

Currently the way I have designed the framework is by breaking large pieces of the page into "Page Components". These are children of the PageObject class (but just smaller). Typically they make up obvious chunks of the page (IE: A Login form would be it's own component, a Header would be a "Common" component", etc...)

Typically these components ONLY model the UI. So their methods are aimed at returning elements OR filling in inputs. IE: fillInEmail getLoginButton etc...

The actual PageObject for that specific page pulls in all the "Components" for that page and created public methods that the test scripts will use. For example Login() will use the LoginFormComponent fillInEmail/fillInPassword/getSubmitButton.click()/etc... methods. This abstracts the test scripts into readable/obvious functions. It also helps break up larger more complex pages.

The issue I have ran into is with large forms with multiple inputs. I see a few options of how I can handle it:

  1. Create a method that takes an object (or fixture) in the Page Component for that Form (IE: the form itself is a component). The Page Component handles inputting everything and doing all the dirty work (IE: filling in/selecting/clicking based on the fixture/object passed). (This is what I am currently doing)

  2. The Page Component simply returns all the inputs as different methods (Keep in mind sometimes it can be 6-7+ inputs) and the Page Object handles all the dirty work within a public method (IE: filling in inputs/selecting/clicking based off the methods the Component passes to the page object). This would require sending an object/fixture in the test script itself for that method.

  3. Break the Form inputs into individual discrete methods (IE: one input is one page component). I think this is probably going to far and doesn't really do anything to abstract the page personally.

All of these feel not great. And despite me currently going with option #1...I don't like that either (As I am going against my own setup by handling larger behavior in the "PageComponents" which are really meant to just model the page.

It's also hard to figure out how to pass the data for the large amounts of inputs. I've been using an object but passing an object into the test script feels ugly. Any advice on handling this?

1

You can further break down your components into other components, by passing the baseLocator as you do with the components.

MainPage {

  baseLocator = driver.find("body")
]
  MainPage() {
     BigForm = BigForm(baseLocator.find("#myBigForm"))
  }

}

BigForm {

  BigForm(baseLocator) {
    Section1 = Section1(baseLocator.find(#section1)
    Section2 = Section1(baseLocator.find(#section2)
    Section3 = Section1(baseLocator.find(#section3)
  }
}

Section1 {

   Section1(baseLocator) {
      this.checkbox = Checkbox(baseLocator.find(".checkbox"))
      this.username = Input(baseLocator.find(".username"))
      this.amount = Input(baseLocator.find(".amount"))
   }
}

This, as you said, will model the HTML format. What you need is to model the data as on it is on the form.

  "form": {
      "section1": {
         "checkbox": "checked",
         "username": "Bla bla bla",
         "amount": "35.14"
      },
      "section2": {...},
      "section3": {...}
   }

This data can be parsed into an object tree, called e.g. FormData, which is composed of Section1, Section2, Section3 objects.

Section1 object has a string called checkbox, a string called username, a number called amount.

Section2 and Section3 objects will reflect the data specifically necessary for those sections.

This way, each object from the HTML representation and data modeling can be independently modified, to keep in sync with the application.

Any behavior, starting from the MainPage object will be a matter of passing the data model further down the HTML model tree until you reach a point where you have simple data, like a String or a Number, and a simple HTML, like input or a checkbox.

MainPage::fillBigForm(FormData formData) { BigForm.fill(formData }

BigForm::fill(FormData formData) { 
  Section1.fill(formData.section1Data); 
  Section2.fill(formData.section2Data); 
  Section3.fill(formData.section3Data); 
}

Section1::fill(Section1Data data) {
   checkbox.changeCheckState(data.checkbox);
   username.fill(data.username;
   amount.fill(data.amount);
}

Checkbox::changeCheckState(CheckboxState state) {
   ... //Only here you need to do web driver manipulation, with simple data
}

Any modification in a specific point of the Big Form requires changes at the node where the changes happened or further down, never on its siblings or parents: I.e., as isolated as it gets.

| improve this answer | |
  • So I did consider this. But the main issue I see with it is it creates a lot of overhead. The way I have it setup is my Components are classes. This would require multiple classes being initialized which would contain just one input potentially. I think in the end passing down a fixture potentially and breaking it up as you've done above is ideal though (IE: fixture.input1data, fixture.input2data). I think probably breaking out the inputs individually in the component class is probably a good idea though regardless. – Mercfh Feb 12 at 18:37

Not the answer you're looking for? Browse other questions tagged or ask your own question.