学学 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
参考: