有关Howitzer中的几个概念

学学 Howitzer。

写在前面

howitzer是一个基于ruby脚本的web验收测试框架, 初学时,对这个新伙伴是不甚了解,按照老规矩,看doc,看API。看完后,结合自己的理解,简单过一下里面几个重要的概念。

基本思路:

  • 是什么?
  • 怎么用?
  • 用的时候要注意什么?

正文

  • Driver

    是一个针对多个浏览器进行测试的通用界面,所有的Driver可以分成两类:

    • Headless testing: 没有用户操作界面的模拟浏览器
    • real browser testing: 通过扩展程序或者插件来与真实的浏览器进行集成

    Howitzer使用Capybara来对driver进行管理和设置,可以在config/default.yml中进行设置

  • Pages

    介绍pages之前,介绍一下Page Object Model。

    Page Object Model是一个自动化测试模版,创建用于测试的用户界面的抽象实体。可以将网站的每个page看作是一个类,然后通过调用这些类的实例来进行测试,而每个页面的元素可以看作是一个page的method,通过调用这些method来对该页面的元素进行调用。

    这样,page就很好理解了。

    Page是用于描述真实页面的类,比如在web/pages目录下,建立主页的文件,可以这样写:

    class Homepage < Howitzer::Web::Page
    path '/'
    end
    HomePage.open #打开主页
    

    当然,这里的前提是你在config/default.yml中已经设置好了app_host

    再比如:

    class ProductPage < Howitzer::Web::Page
      path '/products{/id}'
    end
    ProductPage.open(id: 1) #=> 访问 /products/1
    

    如果要指定特定的host,可以这样:

    class AuthPage < Howitzer::Web::Page
        site 'https://example.com'
        path '/auth'
    end
    

    Pages的验证:

    其实就是用于区分每个page的anchor,有三种不同的类型:

    URL,title,element

    比如:

    class HomePage < Howitzer::Web::Page
      path '/'
      validate :url, /\A(?:.*?:\/\/)?[^\/]*\/?\z/
    end
    

    使用.displayed?可以去判断一个页面的所有验证是否都通过。

    用这个可以检查特定的页面成功打开,比如:

    class AccountPage < Howitzer::Web::Page
      path '/accounts/{id}'
     end
     AccountPage.open(id: 22)
     expect(AccountPage).to be_displayed
    
  • Elements

    页面的元素,可以是单个元素,比如搜索栏,也可以是元素集合,比如菜单中的选项集。

    比如:

    class HomePage < Howitzer::Web::Page
      element :test_name1, '.foo'                         #css locator, default
      element :test_name2, :css, '.foo'                   #css locator
      element :test_name3, :xpath, '//div[@value="bar"]'  #xpath locator
    
      element :test_link1, :link, 'Foo'                   #link locator by 'Foo' text
      element :test_link2, :link, 'bar'                   #link locator by 'bar' id
    
      element :test_field1, :fillable_field, 'Foo'        #field locator by 'Foo' text
      element :test_field2, :fillable_field, 'bar'        #field locator by 'bar' id
      element :test_field3, :fillable_field, 'baz'        #field locator by 'baz' name
    end
    

    这里要注意,不能通过page类直接调用element,需要在page类中定义一个method来调用element。

    比如:

    class HomePage < Howitzer::Web::Page
      element :new_button, :xpath, ".//*[@name='New']"
    
      def start_new_project
        new_button_element.click
      end
    end
    
    HomePage.on { new_button_element.click } # 不正确的用法
    HomePage.on { start_new_project } # 正确的打开方式
    
  • Sections

    在howitzer中,有一个Howitzer::Web::Section类。

    section的使用场景:当多个页面中出现相同的部分时,或者某部分在一个页面中频繁出现,类似rails中的公用表单。

    使用<section_name>_section来生成一个section的实例, 这里使用一个特别的me方法,通过参数指定section,比如:

    class MenuSection < Howitzer::Web::Section
      me "#gbx3" #section的父元素
    end
    

    使用的时候,这么用:

    # 定义section:
    
    class MenuSection < Howitzer::Web::Section
      me "#gbx3"
    end
    
    # 包含menusection的页面:
    
    class HomePage < Howitzer::Web::Page
      section :menu
    end
    
    # 页面调用section:
    
    HomePage.on { menu_section }
    

    当两个页面有相同的section,但是跟节点不同时,可以这么用:

    # 定义section
    
    class MenuSection < Howitzer::Web::Section
      me '#gbx3'
    end
    
    # 定义两个page类,都包含有menu这个section
    
    class HomePage < Howitzer::Web::Page
      section :menu # 使用默认的selector
    end
    
    class SearchResultsPage < Howitzer::Web::Page
      section :menu, "#gbx48" # 覆盖了默认的selector
    end
    

    测试section是否存在某个页面:

    class MenuSection < Howitzer::Web::Section
      me '#gbx3'
      element :search, "a.search"
    end
    
    class HomePage < Howitzer::Web::Page
      section :menu
    end
    
    HomePage.on { has_menu_section? } #=> returns true or false
    

    多层级section的使用,看一个登陆的例子,结合了cucumber 的step定义:

    # define a page that contains an area that contains a section for both logging in and registration, then modeling each of the sub sections separately
    
    class LoginSection < Howitzer::Web::Section
      me "div.login-area"
      element :username, "#username"
      element :password, "#password"
      element :sign_in, "button"
    
      def login(username, password)
        username_element.set username
        password_element.set password
        sign_in_element.click
      end  
    end
    
    class RegistrationSection < Howitzer::Web::Section
      me "div.reg-area"
      element :first_name, "#first_name"
      element :last_name, "#last_name"
      element :next_step, "button.next-reg-step"
    
      def sign_up(first_name, last_name)
        first_name_element.set first_name
        first_name_element.set last_name
        next_step_element.click
      end
    end
    
    class LoginRegistrationFormSection < Howitzer::Web::Section
      me "div.login-registration"
      section :login
      section :registration  
    end
    
    class HomePage < Howitzer::Web::Page
      section :login_and_registration
    end
    
    # how to login (fatuous, but demonstrates the point):
    Then /^I sign in$/ do
      HomePage.open
      HomePage.on do
        is_expected.to have_login_and_registration_section
        login_and_registration_section.login_section.login('bob', 'p4ssw0rd')
      end  
    end
    
    # how to sign up:
    
    When /^I enter my name into the home page's registration form$/ do
      HomePage.open
      HomePage.on do
        expect(login_and_registration_section.registration_section).to have_first_name
        expect(login_and_registration_section.registration_section).to have_last_name
        login_and_registration_section.registration_section.signup('Bob', 'Arum')
      end  
    end
    
  • IFrames

    IFrames 在howitzer中被声明为一个 Howitzer::Web::Page类,通过包含该frame 的section或者page来引用,比如:

    创建一个frame实例,在DashboardPage中引用它,测试该frame是否在DashboardPage中存在。

    class FbPage < Howitzer::Web::Page
      element :some_text_field, "input.username"
    end
    
    class DashboardPage < Howitzer::Web::Page
      iframe :fb, "#fb"
    end
    
    DashboardPage.open
    DashboardPage.on { is_expected.to have_fb_iframe }
    

    与iframe中的元素进行交互, 这里用了cucumber login的step definition来说明:

    # Howitzer::Web::Page representing the iframe
    class LoginPage < Howitzer::Web::Page
      element :username, "input.username"
      element :password, "input.password"
    
      def login(username, password)
        username_element.set username
        password_element.set password
      end  
    end
    
    # Howitzer::Web::Page representing the page that contains the iframe
    class HomePage < Howitzer::Web::Page
      iframe :login, "#login_and_registration"
    end
    
    # cucumber step that performs login
    When /^I log in$/ do
      HomePage.open
      HomePage.on do
        login_iframe do |frame|
          #`frame` is an instance of the `LoginPage` class
          frame.login('admin', 'p4ssword')
        end
      end  
    end
    

The End

参考:

howitzer doc