2015-08-25 30 views
4

我使用SitePrism來測試我的Web應用程序。我有許多延伸SitePrism::Page類和延伸SitePrism::Section如何動態添加節到SitePrism頁面對象?

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 

一些經常使用的HTML片段通過匹配類表示的問題是,我正在應用程序是基於CMS,在通過根據預定義的內容選擇模板,然後將任意數量的可用組件拖放到頁面上來組裝頁面。

最初的開發者創建了一個頁面對象來鏡像每個可用的模板。只要測試次數少,沒有太多的頁面變體,我們不得不在我們的功能文件中測試,這樣就沒有問題。

隨着多個測試用例的添加,頁面對象開始以驚人的速度增長。

雖然我們可以很容易地通過定義在CMS提供的每個組件和重用他們跨越頁面對象減輕代碼重複,但只是很多的屬性很少習慣。

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辦法一節動態地添加到頁面對象的實例,而不是整個班級?

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選擇器泄漏到步驟定義,通常是一個不好的做法。

解決此問題的另一種方法是針對與多功能模板相反的頁面的特定示例構建頁面對象。該模板頁面對象可以只含有任何真實烤成的模板和其他頁擴展對象是反映特定頁面,採取的不同治療。這聽起來像一個更簡潔的方法,所以我可能會這樣

寫我的測試反正,這個問題的技術部分代表。不管這個想法有多好或多壞,我怎樣才能動態地擴展一個頁面對象的額外部分?我只是好奇。

回答

4

我不得不在一個點想做的事你談論的幾乎相同的原因是什麼。我們有可能有新內容的頁面 - 部分被拖入其中;使他們非常有活力。我嘗試了一些方法來做到這一點,從未發現任何我特別喜歡的東西。

像站點棱鏡中的elementsections這樣的方法每個都定義了許多類的方法。您可以在測試中調用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 

然後你就可以使用這些作爲父母。

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 

另手真的可能更容易只需要創建一個頁面對象的幾個不同的版本在測試中使用。 (你真的要測試那麼多的變化嗎?也許...可能不會。)

+0

正如問題本身所述。我更傾向於遵循每個page_模式的_page對象,而不是每個template_的_page對象。我只是將模板作爲基類或mixin離開。我只是好奇如何在Ruby中進行元編程。 – toniedzwiedz

+1

事實證明,重新訪問這個問題很有意思,所以我很高興你問了這個問題:-)我可能會在我的叉子上包含類似這樣的東西。 – tgf

1

您可以通過修改其單例類來爲頁面對象實例添加節。

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) 
+0

整潔,明天就試試看。 – toniedzwiedz

+0

賈斯汀,你在我還在打字的時候發帖,但是看起來我們在這裏有非常相似的想法:-) – tgf