Ruby PageObject Design for Similar Page Sections

547 Views Asked by At

I'm using the Cheezy Page Object gem (which also means I'm using Watir, which also means I'm using Selenium). I also have the watir gem explicitly loaded.

Anyway I have a site I am modeling with the UI written in angular where there is 1 page whose contents change based on dropdown selection. The page has several sections but it is visibly the same for each dropdown choice. The only difference is the xpath locators I am using to get there (there's no unique ID on the sections).

So for example I have an xpath like html/body/div[1]/div/div[1]/div/**green**/div/div[1]

and another like

html/body/div[1]/div/div[1]/div/**red**/div/div[1]

The elements on the sections strangely all have the same ID attribute and same class name. So I've been using xpath for the elements since that appears to make it a unique locator.

Problem is there are currently seven dropdown choices each with several sections like this. And they have visibly same elements and structure (from end user perspective) but when you look at html the only difference is the locator so like this for the elements:

html/body/div[1]/div/div[1]/div/green/div/div[1]/**<element>**

and another like

html/body/div[1]/div/div[1]/div/red/div/div[1]/**<element>**

In my current design I have created one page and created page sections for each section on a page. Multiply the number of page sections with number of dropdown choices and you see it is alot. Some of the choices do generate extra elements but there are still common elements between all sections. I also have to duplicate all of these elements across the seven different pages because the xpath is different. Is there some way for me to pass some initializer to the PageObject page_section like the type-a or type-b string and then based on that I can also choose correct xpath for all elements?

So like if I have text field like so in like a base page object page_section:

text_field(:team, xpath: "...#{type_variable}")

Can I do something like section = SomePageObject.page_section_name(type_variable)?

EDIT: Adding Page Object code per request

class BasePO
  include PageObject

  #Option S1 Cards
  page_section(:options_red_card, OptionRedCard, xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/red/div[2]/div[2]/div/div/div/div")
  page_section(:options_green_card, OptionGreenCard, xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/green/div[2]/div[2]/div/div/div/div")
  page_section(:options_yellow_card, OptionYellowCard, xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/yellow/div[2]/div[2]/div/div/div/div")

  #Detail S2 Cards
  page_section(:detail_red_card, DetailRedCard, xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/red/div[1]/div/div/div")
  page_section(:detail_green_card, DetailGreenCard, xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/green/div[1]/div/div/div")
  page_section(:detail_yellow_card, DetailYellowCard, xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/yellow/div[1]/div/div/div")

end

EDIT2: Adding page_section content per request. All Option Cards share these elements at a minimum. Different elements in the Detail Cards but same structure as Option Cards.

class OptionRedCard
  include PageObject

  def field1_limit
    text_field_element(xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/red-unit/div[2]/div[2]/div/div/div/red/form/div/div/div/table/tbody/tr[2]/td[2]/div/div[1]/div/currency/div/input")
  end

  def field1_agg
    text_field_element(xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/red-unit/div[2]/div[2]/div/div/div/red/form/div/div/div/table/tbody/tr[2]/td[2]/div/div[2]/div/currency/div/input")
  end

  def field2_limit
    text_field_element(xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/red-unit/div[2]/div[2]/div/div/div/red/form/div/div/div/table/tbody/tr[3]/td[2]/div/div[1]/div/currency/div/input")
  end

  def field2_agg
    text_field_element(xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/red-unit/div[2]/div[2]/div/div/div/red/form/div/div/div/table/tbody/tr[3]/td[2]/div/div[2]/div/currency/div/input")
  end

  def field3_limit
    text_field_element(xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/red-unit/div[2]/div[2]/div/div/div/red/form/div/div/div/table/tbody/tr[4]/td[2]/div/div[1]/div/currency/div/input")
  end

  def field3_agg
    text_field_element(xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/red-unit/div[2]/div[2]/div/div/div/red/form/div/div/div/table/tbody/tr[4]/td[2]/div/div[2]/div/currency/div/input")
  end

  def field1_agg_value
    field1_agg.attribute_value('data-value')
  end

  def field2_agg_value
    field2_agg.attribute_value('data-value')
  end

  def field3_agg_value
    field3_agg.attribute_value('data-value')
  end

end
1

There are 1 best solutions below

0
Justin Ko On

I think the short answer to your question, is no, there is no built-in support for passing a value to the page sections. However, here are some alternatives I can think of.

Option 1 - Use initialize_accessors

Usually the accessors are executed at compile time. However, you could use the #initialize_accessors method to defer the execution until the initialization of the page object (or section). This would let you define your accessors in a base class that, at initialization, inserts color type into the paths:

class BaseCard
  include PageObject

  def initialize_accessors
    # Accessors defined with placeholder for the color type
    self.class.text_field(:field1_limit, xpath: "/html/body/some/path/#{color_type}/more/path/input")      
  end
end

# Each card class would define its color for substitution into the accessors
class OptionRedCard < BaseCard
  def color_type
    'red'
  end
end

class OptionGreenCard < BaseCard
  def color_type
    'green'
  end
end

class BasePO
  include PageObject

  page_section(:options_red_card, OptionRedCard, xpath: '/html/body/path')
  page_section(:options_green_card, OptionGreenCard, xpath: '/html/body/path')
end

Option 2 - Using relative paths

My suggested approach would be to use relative paths such that the color can be removed from the path of the page section. From the objects provided, you might be able to do something like:

class OptionCard
  include PageObject

  element(:unit) { following_sibling(tag_name: "#{root.tag_name}-unit") }
  div(:field1_limit) { unit_element.tr(index: 1).text_field(index: 0) }
  div(:field1_agg) { unit_element.tr(index: 1).text_field(index: 1) }
  div(:field2_limit) { unit_element.tr(index: 2).text_field(index: 0) }
  div(:field2_agg) { unit_element.tr(index: 2).text_field(index: 1) }
end

class BasePO
  include PageObject

  # Page sections only defined to the top most element of the section (the color element)
  page_section(:options_red_card, OptionCard, xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/red")
  page_section(:options_green_card, OptionCard, xpath: "/html/body/app-component/app-page/div[2]/div/div/div[1]/div/div/div/ngb-tabset/div/div/green")
end