awta2009 semantic graffitti
DESCRIPTION
AWTA 2009 Presentation on Watirloo, a Watir Framework. Page Adapters and UseCase scenario driven runners. Modeling Customer Language in Acceptance TestsTRANSCRIPT
Semantic Watirloo Graffitti
My Battles with WatirHow I got startedWhere I went wrong.How I ducktaped my way out of it, sort of...
and built Watirloo Framework
marekj | testr.us | for ATWA2009
Huh? The What?
towards the UseCase driven Domain Object Test Modeling, Page Object Human Readable Machine Executable OO Test Notation with a touch of mint.
let's get started at the beginning
require 'watir'ie = Watir::IE.start 'http://bigblabla.com/'ie.text_field(:name, 'ye_id').set 'BubbaJones74'ie.text_field(:name, 'ye_pass').set 'pigscanfly42'ie.button(:value, 'open ye').click
wrap it with meaning
def load_login_page ie.goto 'http://bigblabla.com/'enddef enter_login_info ie.text_field(:name, 'ye_id').set 'BubbaJones74' ie.text_field(:name, 'ye_pass').set 'pigscanfly42'enddef click_login ie.button(:value, 'open ye').clickend#steps to executeload_login_pageenter_login_infoclick_login
and put it in a test harness
require 'test/spec'describe 'user logging to BlaBla awesome app' do it 'presents user with a page' do load_page; ie.title.should == 'BlaBla Login' end it 'lets user enter id and password' do ie.text_field(:name, 'ye_id').exists?.should.be true ie.text_field(:name, 'ye_pass').exists?.should.be true enter_login_info end it 'loads home page when you click login' do click_login; ie.title.should == 'BlaBla HomePage' endend
TaDa! Building a Trap
After 100 tests I realized I have built a trap.I am now concerned with 4 things- managing Browser, Pages and Elements- sequence of events, scenarios - and data sets.- test execution, harness, runtime etc...all of in the same place
Separate Concerns
Pages = Adapter for Watir. Domain Vocabulary for things of concern on the page.
UseCase = Sequence of Events, scenarios, actors, Domain Vocabulary
Scenario Data = Realization of UseCasesTestCases = use the existing frameworks for
execution
Page Adapters
Declare interface as semantic intent mapping to Document Object Model implementation
class Page def initialize(browser) @browser = browser end def semantic_page_object browser.control(:what, how) endendpage = Page.new(ie)page.semantic_page_object.action some_data
Let's try an example
@author = {:first => 'Kurt', :last => 'Vonnegut', :dob => '11/11/1918', :books => ['Slaugherhouse 5', 'Hokus Pocus', 'Mother Night']}
@page = SearchAuthorPage.new do first.set @author[:first] last.set @author[:last] dob.set @author[:dob]end
@page.search
# on a Author search page enter first name, last name and date of birth and click search.
Good Quotes Inspire
"The fundamental principle of Object Oriented programming is the unification of methods and data. Splitting this up inappropriately gets you right back to procedural programming."
Dave Thomashttp://pragprog.com/articles/tell-dont-ask
(there is a chance I misread it)
unify by convention
make hash keys match page objects and set values
@author = {:first => 'Kurt', :last => 'Vonnegut', :dob => '11/11/1918', :books => ['Slaugherhouse 5', 'Hokus Pocus', 'Mother Night']}
@page = [email protected] @author # set the page with data [email protected]
# by convention maps hash keys to methodsdef spray(hashmap) hashmap.each_pair do |page_object, value| page_object.set value endend
Ducktape Watir
I needed RadioGroup, radios sharing the same name
# individual radiosie.radio(:name, 'ze_razio', 'value1')ie.radio(:name, 'ze_razio', 'value2')ie.radio(:name, 'ze_razio', 'value3')
# as a radio_grouprg = ie.radio_group(:name, 'ze_razio')rg.values # => ['value1, 'value2', 'value3']rb.set 2 # => sets second radio in a group
Ducktape Watir
I needed CheckboxGroup, checkboxes that share the same name.
# individual checkboxesie.checkbox(:name, 'ze_shekbochs', 'value1')ie.checkbox(:name, 'ze_shekbochs', 'value2')ie.checkbox(:name, 'ze_shekbochs', 'value3')
# as a checkbox_grouprg = ie.checkbox_group(:name, 'ze_shekbochs')rg.values # => ['value1, 'value2', 'value3']rb.set 2 # => sets second checkbox in a group
Ducktape Watir
RadioGroup is like a Single SelectListCheckboxGroup is like a Multi SelectList
unify Interface to Selectable Control
[SelectList,RadioGroup, CheckboxGroup].each do |control| # set control by item, value, position # query control about item(s), value(s), position(s)end
Moving from Page to Page
That's it with Page ObjectsNow I need to model actor, her behaviour and
value creation in context of a goal
I reach for UseCase object. - provides sequence, recipe, scenarioand default data
UseCase Scenarios
class UseCase attr_accessor :actor, :scenario, :page, :dataset include UseCaseSteps #decorate your usecase def initialize @dataset = some_data_call_to_populate_run @scenario = [:do_that, do_this] end def run @scenario.each {|task| self.send task} endend
usecase = UseCase.newusecase.dataset.update :key => 'value', :bla => 'blabla'usecase.run
UseCases use UseCases
UseCase glues data to pages. Acts like a Gluegun (act_as_gluegun)UseCase is a wrapper for page interactions.The intent is Actor executing a recipe to create
value.UseCase is a TestModel AbstractionUseCase is a holder of needed Domain Objects
holding values that have corresponding Page Object representations. (confused?)
UseCase example
class MarekSchedulesDentistVisit < UseCase attr_accessor :request #my dataset @@scenario = [
:load_dentist_calendar, :find_date_and_time,
:select_date_and_time,:enter_your_personal_info,
:submit_request]end
request={:date=>today,:name=>'marekj',:phone=>testdata[:phone]}usecase = MarekSchedulesDentistVisit.newusecase.request = requestusecase.run
Stack of Abstractions
UseCases can be at different level of abstractionsclass DoTripToTheMoon < UseCase# has method :launch_rocket
class LaunchTheRocket < UseCase# has method :fire_engines
UseCases calling UseCases
def load_dentist_calendar usecase = LoadCalendar.new usecase.runend
def find_date_and_time usecase = FindDateAndTime.new usecase.runend
usecase = MarekSchedulesDentistVisit.newusecase.request = requestusecase.load_dentist_calendarusecase.find_date_and_time# or usecase.run
Maybe Attach Test Framework
You can put UseCase execution in Test Frameworktest/unit, rspec, test/spec, cucumber
require 'test/spec' describe 'requesting dentist appointment' do it 'does not allow to occur in the past' do @usecase = RequestDentistAppointment.new @usecase.request.update :date => yesterday @usecase.run # assert error should be here on submit @usecase.page.title.should == 'I can not has travel machine' endend
About Watirloo
Attempt to make Acceptance Testing written in the language of the Customer's domain propped by UseCase ideas and PageObjects Adapters
Extracted from real world watir frameworks I've implemented for clients: Health Insurance company and Airplane Parts management company.
Thank You
Questions? Tomatos?
http://github.com/marekj/watirloo