OzFramework / oz

Oz is a behavioral web-ui testing framework developed to reduce test maintenance by using a predictive model rather than a scriptive model when writing tests.
Apache License 2.0
23 stars 7 forks source link

[Feature Evaluation] Wrapping defined driver elements #141

Open Castone22 opened 5 years ago

Castone22 commented 5 years ago

Okay, as per the discussion @greenarrowdb and I had in discord the other day, i'm going to document my thoughts here. Pardon the conscious stream of thought, it's just how things tend to live in my head.

I'm finding the hard ban on watir elements in the page models to be a lot more limiting than i originally thought and i'm finding that it causes me to have to.. A. Reinvent the wheel B. Write somewhat confusing code

Some examples might be a bit more telling here... Say i wanted to define a navigation bar for github. I'm not going to use the best selectors here because they wouldn't be available in the model i'm testing against. If i wanted to get a given nav link in watir

class Github
  class BasePage
    @header_bar = @browser.a(class: 'header-logo-invertocat').parent.parent.parent.parent
    @nav_links = @header_bar.links

    def visit(option)
      nav_link(option).click
    end

    def nav_link(option)
      @nav_links.find{|it| it.text =~ /option/i}
    end
  end
end

Oz's paradigm for this gets a lot less readable really quickly, one reason being that i'd have to use an xpath to find that header bar, another being that i end up with a lot of replication, and i have to statically define each element, since post processing availability is currently very limited being that the page models don't allow me to easily request a sub element... anonymously for lack of a better word.

class Github
  class BasePage < CorePage

    def create_common_elements
      @header_bar = add_element(:header_bar, element_type: :header, CoreElement, xpath: '//a[@class="header-logo-invertocat"]/../../../header') #this may not be exactly correct
      @header_nav_links = []
      @header_nav_links << add_link(:header_bar_pull_requests, parent: @header_bar, xpath: '//a[1]')
      @header_nav_links << add_link(:header_bar_issues, parent: @header_bar, xpath: '//a[2]')
      @header_nav_links << add_link(:header_bar_marketplace, parent: @header_bar, xpath: '//a[3]')
      @header_nav_links << add_link(:header_bar_explore, parent: @header_bar, xpath: '//a[4]')
    end

    def visit(option)
      click_on nav_link(option)
    end

    def nav_link(option)
      @header_nav_links.find{|it| it[:name].to_s =~ /option.gsub('_','')/i}
    end
  end
end

Granted, I understand that visit as a method isn't something that's typically used on the external page api in this case (i'm using it to ease defining a route, which i think should fit within the expected behavior). The Oz way just feels... really clunky to me as is.

With all of this background i can finally get to my point i think. I don't necessarily fully believe that driver elements don't have their place in a given page model. They would allow for much easier definition of structural objects on the page (things that are only really used to navigate the dom and find other elements more easily) and let us get to the stuff we actually care about validating much more easily. Something like the below example would feel pretty nice to me.

class Github
  class BasePage < CorePage
    add_route('Github::PullRequests', [:click_on, :pull_request_link])

    def create_elements
      @header_bar = browser.a(class: 'header-logo-invertocat').parent.parent.parent.parent
      add_element(:header_bar, driver_element: @header_bar)
      create_nav_links
    end

    def create_nav_links
      @header_bar.links.each do |link_element|
        add_link(link_element.text.to_field_name, driver_element: link_element)
      end
    end
  end
end

This has a few benefits, two big ones being

Some of the immediate cons I can think of though.

Castone22 commented 5 years ago

After talking to uriah, i had a few more ideas how this might work

add_from_watir_element(:name, options) do
  browser.div(id: id).parent.select_list
end

Sometimes this just gets way cleaner than an xpath