如何动态地向 SitePrism 页面对象添加一个部分?
How to add a section to a SitePrism page object dynamically?
我正在使用 SitePrism 测试我的网络应用程序。我有一些扩展 SitePrism::Page
的 classes 和一些经常使用的 HTML 片段由扩展 SitePrism::Section
[=15 的匹配 classes 表示=]
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 的,其中可以通过选择基于预定义内容的 Template 来组装页面,并且然后将任意数量的可用组件拖放到页面上。
最初的开发人员创建了一个 页面对象 来镜像每个可用的 模板 。这很好,只要测试数量很少,并且我们必须在功能文件中测试的页面变体不太多。
随着多个测试用例的添加,页面对象开始以惊人的速度增长。
虽然我们可以通过为 CMS 中可用的每个组件定义 Sections 并跨 Page Objects 重用它们来轻松减少代码重复,但是只是很多很少使用的属性。
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 中是否有一种方法可以动态地将部分添加到 页面对象 的实例而不是整个 class?
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 选择器泄漏到步骤定义中,这通常是一种不好的做法。
另一种解决此问题的方法是为页面的特定示例构建 页面对象 ,而不是通用模板。 模板页面对象可以只包含模板中的任何内容,并由镜像特定页面的其他页面对象扩展,处理差异。这听起来像是一种更简洁的方法,所以我可能会以这种方式编写我的测试
无论如何,问题的技术部分成立。不管这个想法是好是坏,我如何才能动态地扩展页面对象并增加一个部分?我只是好奇。
您可以通过修改其单例将部分添加到页面对象实例 class。
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 中,创建了可用组件的字典。 class 有一个方法可以根据字典定义添加组件。
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)
出于几乎相同的原因,我曾一度想做你所说的事情。我们的页面可以将新 content-sections 拖入其中;使它们非常有活力。我尝试了多种方法来做到这一点,但从未发现任何我特别喜欢的方法。
site-prism 中的 element
和 sections
等方法分别为 class 定义了多个方法。您可以在测试中调用 MyPage.section
或添加调用 self.class.section
的方法并使用它来添加新部分。但是那些将存在于该页面的所有实例;可能不是你想要的。
您也可以通过 singleton_class:
my_page = MyPage.new
my_page.singleton_class.section(:new_section, NewSection, '#foo')
但是把它扔进你的测试中有点难看,对吧?
我一直认为章节应该有一个 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
然后您可以将它们用作 parents。
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
在 other-hand 上,创建一些 page-object 的不同变体以用于测试确实可能更容易。 (你真的要测试那么多变体吗?也许..也许不会。)
为此目的使用 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
我正在使用 SitePrism 测试我的网络应用程序。我有一些扩展 SitePrism::Page
的 classes 和一些经常使用的 HTML 片段由扩展 SitePrism::Section
[=15 的匹配 classes 表示=]
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 的,其中可以通过选择基于预定义内容的 Template 来组装页面,并且然后将任意数量的可用组件拖放到页面上。
最初的开发人员创建了一个 页面对象 来镜像每个可用的 模板 。这很好,只要测试数量很少,并且我们必须在功能文件中测试的页面变体不太多。
随着多个测试用例的添加,页面对象开始以惊人的速度增长。
虽然我们可以通过为 CMS 中可用的每个组件定义 Sections 并跨 Page Objects 重用它们来轻松减少代码重复,但是只是很多很少使用的属性。
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 中是否有一种方法可以动态地将部分添加到 页面对象 的实例而不是整个 class?
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 选择器泄漏到步骤定义中,这通常是一种不好的做法。
另一种解决此问题的方法是为页面的特定示例构建 页面对象 ,而不是通用模板。 模板页面对象可以只包含模板中的任何内容,并由镜像特定页面的其他页面对象扩展,处理差异。这听起来像是一种更简洁的方法,所以我可能会以这种方式编写我的测试
无论如何,问题的技术部分成立。不管这个想法是好是坏,我如何才能动态地扩展页面对象并增加一个部分?我只是好奇。
您可以通过修改其单例将部分添加到页面对象实例 class。
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 中,创建了可用组件的字典。 class 有一个方法可以根据字典定义添加组件。
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)
出于几乎相同的原因,我曾一度想做你所说的事情。我们的页面可以将新 content-sections 拖入其中;使它们非常有活力。我尝试了多种方法来做到这一点,但从未发现任何我特别喜欢的方法。
site-prism 中的 element
和 sections
等方法分别为 class 定义了多个方法。您可以在测试中调用 MyPage.section
或添加调用 self.class.section
的方法并使用它来添加新部分。但是那些将存在于该页面的所有实例;可能不是你想要的。
您也可以通过 singleton_class:
my_page = MyPage.new
my_page.singleton_class.section(:new_section, NewSection, '#foo')
但是把它扔进你的测试中有点难看,对吧?
我一直认为章节应该有一个 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
然后您可以将它们用作 parents。
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
在 other-hand 上,创建一些 page-object 的不同变体以用于测试确实可能更容易。 (你真的要测试那么多变体吗?也许..也许不会。)
为此目的使用 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