如何动态地将部分添加到SitePrism页面对象?

时间:2015-08-25 15:08:33

标签: ruby cucumber capybara automated-tests site-prism

我使用SitePrism测试我的网络应用程序。我有许多扩展SitePrism::Page的类,许多经常使用的HTML代码段由扩展SitePrism::Section的匹配类表示

class Login < SitePrism::Section
  element :username, "#username"
  element :password, "#password"
  element :sign_in, "button"
end

class Home < SitePrism::Page
  section :login, Login, "div.login"
end

问题是,我正在处理的应用程序基于CMS,其中可以通过基于预定义的内容选择模板来组装页面,然后拖动 - 将任意数量的可用组件丢弃到页面上。

初始开发人员创建了一个页面对象来镜像每个可用的模板。只要测试数量很少,并且我们不得不在我们的功能文件中测试过多的页面变体,这样就可以了。

随着多个测试用例的增加,页面对象开始以惊人的速度增长。

虽然我们可以通过为CMS中可用的每个组件定义 Sections 并在页面对象中重复使用它们来轻松减轻代码重复,但这只是很多很少使用的属性。

class BlogPost < SitePrism::Page

    section :logo, MySite::Components::Logo, '.logo'    
    section :navigation, MySite::Components::Navigation, '.primary-navigation'
    section :header, MySite::Components::BlogHeader, '.header'
    section :introduction, MySite::Components::Text, '.text .intro'
    # and so on, a lot of dynamic staff that could potentially be dropped onto the page
    # but does not neccessarily be there, going in dozens of lines
end

SitePrism中是否有一种方法可以动态地将一个部分添加到 Page Object 的实例而不是整个类?

Then(/^Some step$/) do
    @blog = PageObjects::BlogPost.new()
    @blog.load("some url")
    @blog.somehow_add_a_section_here_dynamically
    expect (@blog.some_added_section).to be_visible
end

它还让我担心做这样的事情可能会导致CSS选择器泄漏到步骤定义中,这通常是一种不好的做法。

解决此问题的另一种方法是为特定的页面示例构建页面对象,而不是通用模板。 模板页面对象可以包含模板中的任何内容,并由镜像特定页面的其他页面对象进行扩展,从而处理差异。这听起来像一个更清洁的方法,所以我可能会以这种方式编写我的测试

无论如何,问题的技术部分代表。无论一个想法有多好或多坏,我怎样才能动态扩展一个带有附加部分的页面对象?我只是好奇。

3 个答案:

答案 0 :(得分:5)

我曾经一度想要按照同样的理由去做你正在谈论的事情。我们的页面可能会有新的内容部分被拖入其中;使他们非常有活力。我尝试了这样做的方法,从未发现任何我特别喜欢的方式。

site-prism中的elementsections等方法各自为该类定义了许多方法。您可以在测试中调用MyPage.section或添加一个调用self.class.section的方法,并使用该方法添加新的部分。但那些页面的所有实例都会存在;可能不是你想要的。

你也可以通过singleton_class来解决它们:

my_page = MyPage.new
my_page.singleton_class.section(:new_section, NewSection, '#foo')

但是,这会让你的测试变得有点难看,对吗?

我一直认为Sections应该有一个default_locator(但很难接受补丁) 有了这个,我们可以概括一点:

class DynamicSection < SitePrism::Section
  def self.set_default_locator(locator)
    @default_locator = locator
  end
  def self.default_locator
    @default_locator
  end
end      

class DynamicPage < SitePrism::Page
  # add sections (and related methods) to this instance of the page
  def include_sections(*syms)
    syms.each do |sym|
      klass = sym.to_s.camelize.constantize
      self.singleton_class.section(sym, klass, klass.default_locator)
    end
  end
end

然后你可以用这些作为父母。

class FooSection < DynamicSection
  set_default_locator '#foo'
  element :username, "#username"
end

class BlogPostPage < DynamicPage
  # elements that exist on every BlogPost
end

在测试中:

@page = BlogPostPage.new
@page.include_sections(:foo_section, :bar_section)
expect(@page.foo_section).to be_visible

另一方面,为了在测试中使用,创建页面对象的一些不同变体可能更容易。 (你真的要测试那么多变化吗?也许......不可能。)

答案 1 :(得分:2)

您可以通过修改单个类来为页面对象实例添加一个部分。

Then(/^Some step$/) do
  @blog = PageObjects::BlogPost.new
  @blog.load("some url")

  # You can see that @blog does not have the logo section
  expect(@blog).not_to respond_to(:logo)

  # Add a section to just the one instance of BlogPost
  class << @blog
    section(:logo, MySite::Components::Logo, '.logo')
  end

  # You can now see that #blog has the logo section
  expect(@blog).to respond_to(:logo)
end

这可能会导致多个步骤中的部分定义重复。要解决此问题,您可以在BlogPost中创建一个方法来动态添加指定的部分。

在以下BlogPost类中,创建了可用组件的字典。该类有一个方法,可以根据字典定义添加组件。

class BlogPost < SitePrism::Page
  COMPONENT_DICTIONARY = {
    logo: {class: MySite::Components::Logo, selector: '.logo'},
    navigation: {class: MySite::Components::Navigation, selector: '.primary-navigation'},
    header: {class: MySite::Components::BlogHeader, selector: '.header'}
  }

  def add_components(*components)
    Array(components).each do |component|
      metaclass = class << self; self; end
      metaclass.section(component, COMPONENT_DICTIONARY[component][:class], COMPONENT_DICTIONARY[component][:selector])
    end
  end
end

作为用法的一个例子:

# Create a blog post that just has the logo section
@blog = BlogPost.new
@blog.add_components(:logo)

# Create a blog post that has the navigation and header section
@blog2 = BlogPost.new
@blog2.add_components(:navigation, :header)

# Notice that each blog only has the added components
expect(@blog).to respond_to(:logo)
expect(@blog).not_to respond_to(:navigation)
expect(@blog).not_to respond_to(:header)

expect(@blog2).not_to respond_to(:logo)
expect(@blog2).to respond_to(:navigation)
expect(@blog2).to respond_to(:header)

答案 2 :(得分:0)

为此目的使用page.find

class MyPage < SitePrism::Page
  element :static_selector_element, "#some-static-id"
  def dynamic_element(id)
    find "label[for=\"dynamic-value-#{id}\"]"
  end
end

在测试中:

RSpec.feature 'My Feature' do
  scenario 'Success' do
    p = MyPage.new 
    p.visit '/'
    p.static_selector_element.click
    p.dynamic_element(SomeObject.fist.id).click
  end
end