1. A Cross platform mobile automation
framework
Created & Presented by,
Priti Biyani & Aroj George
ONE PAGE TO TEST THEM
ALL
2. U.S. AIRLINE MOBILE APP
๏ iOS, Android, iPad, Blackberry, Windows and Mobile Web
๏ Apple Appstore featured app (Travel)
2
3. CALATRAVA
Custom open source cross platform javascript framework - Calatrava.
https://github.com/calatrava/calatrava
‣ Pure native screens
‣ Pure HTML
‣ HTML + Native widgets
‣ Native + HTML Webviews
3
5. PROBLEM STATEMENT
5
Create a generic automation framework which could support each of the
three UI automation tools and work seamlessly across both native and
hybrid UI screens
6. REQUIREMENTS
๏ Should make it easy to add automation. It should make life of QA easy!
๏ Allow reuse of code and help avoid duplication
๏ Promote higher level business/domain oriented thinking instead of low level UI
interactions
๏ Make changes in only one place.
๏ Once automation is done for a feature on one platform, adding the same for other
platforms should be trivial.
๏ Keep it simple, just use basic OO concepts.
6
7. SOLUTION ANALYSIS
๏The Page Object Model Pattern
๏Requirements satisfied
✓Domain oriented thinking
✓Code reuse/no duplication
✓ Changes in only one place.
✓ Easy to add automation.
7
8. PAGE OBJECT MODEL SECOND THOUGHTS
๏ ~ 60 screens on iOS and Android
๏ ~ 30 screens on Mobile Web
๏ 60 x 2 platforms + 30 = 150 page object classes!
๏ Screens have similar behavior and expose same services
‣Requirements check
✓Allows reuse of code and helps avoid duplication.
✓Changes are required to be done in only one place.
✓Makes it easy to add automation.
✓Promotes higher level business/domain oriented thinking
➡ So clearly this approach is not scalable.
8
9. COMMON PAGE OBJECTS PER SCREEN
๏ page objects per screen will solve the class explosion problem.
๏ Requirements satisfied
✓ Should make it easy to add automation. It should make life of QA easy!
✓ Allow reuse of code and help avoid duplication!
✓ Promote higher level business/domain oriented thinking instead of low level UI interactions!
✓ Make change in only one place.
✓ Once automation is done for a feature on one platform, adding the same for other
platforms should be trivial.
9
10. COMMON PAGE OBJECTS - CONCERNS?
๏ Different automation tool APIs
Element query syntax (xpath/css vs custom calabash syntax)
๏ Different UI actions
click/tap on web/mobile
๏ Different platform implementations for same screen
locator values can vary
๏ UX interactions patterns
for e.g Nav Drawer in Android, the Tab Bar in iOS and the Nav bar in web.
10
11. COMMON PAGE OBJECTS - MORE CONCERNS ?
๏ No online example of this approach.
๏ Calabash reference uses a different Page Object class for iOS and Android.
11
➡ So is it really possible to have common page objects?
12. OBJECT MODELING
Lets try and do a Object Modeling exercise!
Single page object class
➡ What are the key fields?
➡ What’s the key behavior?
12
14. PAGE IDENTIFIER
14
Page
Name : String
Id : Map
Identify each page uniquely on the
device
{
:web => "//div[@id='payment_info']",
:ios => "all webView css:'#payment_info'",
:droid => "all webView css:'#payment_info'"
}
15. PAGE ID CLASS
15
PageId
Id : Map
exists? ()
Check if locator specified by id exists on the UI
Calabash Android and IOS:
not query(locator).empty?
Mobile Web (Watir):
Browser.element(:css => locator).present?
16. ID EXISTS CHECK
16
def exists?
case platform
when ANDROID
query(ui_query).empty?
when IOS
query(ui_query).empty?
when WEB
Browser.element(:css => locator).present?
end
end
17. DRIVER ABSTRACTION
17
Pag
e
Name : String
Id : PageId
PageId
Field : Map
exists? ()
PageId
Driver
Platform : droid
exists? ()
Driver
Platform : ios
exists? ()
Driver
Platform : web
exists? ()
def exists?(id_map)
locator = id_map[:droid]
begin
opts.merge!(:screenshot_on_error => false)
wait_for_elements_exist([locator],opts)
rescue WaitError
false
end
end
def exists?(id_map)
locator = id_map[:ios]
begin
opts.merge!(:screenshot_on_error => false)
wait_for_elements_exist([locator], opts)
element_exists locator
rescue WaitError
false
end
end
def exists?(id_map, wait=true)
locator = id_map[:web]
begin
Browser.element(:css => locator).wait_until_present if wait
Browser.element(:css => locator).present?
rescue TimeoutError
puts "exists error #{locator}"
false
end
end
18. ELEMENTS
18
Pag
e
Name : String
Id : PageId
Elements
Element.new({
:web => ‘#save_and_continue_button’,
:ios => "navigationButton marked:'DONE'",
:droid => "* marked:'Done'"
}
Element
Id: Map
click ()
def click
case platform
when ANDROID
touch(query(locator))
when IOS
touch(query(locator))
when WEB
Browser.element(:css => locator).click()
end
end
19. ELEMENTS DELEGATE TO DRIVER
19
Pag
e
Name : String
Id : Map
Elements
Element
Id : Map
Driver : Driver
click()
Driver
Platform : droid
exists?
click()
Driver
Platform : ios
exists?
click()
Driver
Platform : web
exists?
click()
def click(id_map)
locator = id_map[:droid]
begin
scroll_to locator
rescue RuntimeError
end
touch(query(locator))
wait_for_loader_to_disappear
end
def click(id_map)
locator = id_map[:ios]
begin
scroll_to locator
rescue RuntimeError
end
touch(query(locator))
wait_for_loader_to_disappear
end
def click(id_map)
locator = id_map[:web]
B.element(:css => locator).wait_until_present
B.element(:css => locator).click
wait_for_loader_to_disappear
end
20. MORE ELEMENTS
20
Pag
e
Name : String
Id : PageId
Element 1: Element
Element 2: Element
….
Element
Driver
Platform : droid
exists?
click()
setText()
getText()
checked()
Textbox
setText()
getText()
id : map
Checkbox
checked?()
check(item)
id : map
Dropdown
select(item)
id : map
Id : Map
Driver : Driver
Driver
Platform : ios
exists?
click()
setText()
getText()
checked()
Driver
Platform : web
exists?
click()
setText()
getText()
checked()
exists? ()
21. PAGE TRANSITION
21
Login Home Page
def login
click_login_button
wait_for_home_page_to_load
return HomePage.new
end
[ success ]
def <action>
click_button
wait_for_<next_page>_to_load
return <NextPage.new>
end
22. TRANSITION MODELING
1. driver.click
✓ Driver should be responsible only for UI interaction.
✓ Driver should not know about higher level abstraction Page.
2. element.click
✓ It will not be applicable to all elements.
3. Transition Aware Element
➡ An element that understands page transitions.
22
23. TRANSITION ELEMENT
23
Pag
e
Name : String
Id : PageId
Elements
Element
Driver
Platform : droid
exists?
click()
setText()
getText()
checked()
Textbox
setText()
getText()
id : map
Checkbox
checked?()
check(item)
id : map
Dropdown
select(item)
id : map
Id : Map
Driver : Driver
Driver
Platform : ios
exists?
click()
setText()
getText()
checked()
Driver
Platform : web
exists?
click()
setText()
getText()
checked()
exists? ()
TransitionElement
click(item)
id : map
24. TRANSITION ELEMENT
24
@login_button = TransitionElement.new(
{
:web => '#continue_button',
:ios => "* marked:'Login'",
:droid => "* marked:'Login'"
},
{
:to => HomePage,
}
)
Next Page Class
25. TRANSITION ELEMENT - MULTIPLE TRANSITION
25
Login
Admin
Home Page
Member
Home Page
userType?
[success]
29. MULTIPLE TRANSITIONS - WRONG TRANSITION
‣ Scenario: If there is a bug in code, app transitions to wrong page from
list of multiple transitions.
‣ Should transition element detect this bug?
✓ but this is application logic
✓ will increase complexity
➡ Tests should take care for assertions of correct page, not the
framework.
30. TRANSITION ELEMENT
30
def wait_till_next_page_loads(next_pages, error_page)
has_error, found_next_page = false, false
begin
wait_for_element(timeout: 30) do
found_next_page = next_pages.any? { |page| page.current_page?}
has_error = error_page.has_error? if error_page
found_next_page or has_error
end
has_error ? error_page : next_pages.find { |page| page.current_page?}
rescue WaitTimeoutError
raise WaitTimeoutError, "None of the next page transitions were found. Checked for: =>
#{next_page_transitions.join(' ,')}"
end
end
31. PAGE OBJECT EXAMPLE - SEAT MAP
31
Native Implementation
Common code across platform
Iphone Mobile Web Android
32. SEAT MAP FEATURE STEP
32
And I select following seat
| origin | destination | flight_number | pax_name | seat_number |
| ORD | JFK | DL3723 | Rami Ron | 9C |
| ORD | JFK | DL3723 | John Black | 10C |
| JFK | ATL | DL3725 | Rami Ron | 12C |
| JFK | ATL | DL3725 | John Black | 13C |
43. MENU ITEM IMPLEMENTATION
43
class MenuItem
PRIMARY = 1
SECONDARY = 2
attr_accessor :name, :page, :type
def initialize(name, page, type = PRIMARY)
@name = name
@page = page
@type = type
@menu_button = Field.transition_element(
{
:web => "//li[@class='#{@name.downcase}']",
:ios => "label marked:'#{@name}'",
:droid => "WhitneyDefaultTextView id:'drawer_item_text' text:'#{@name}'"
},
{
:to => @page
})
end
def click
@menu_button.click
end
def is_secondary?
@type[Driver.platform] == SECONDARY
end
end
#
MenuItem.new(FLIGHT_STATUS, FlightStatus, ios: MenuItem::SECONDARY)
44. COMMON PAGE OBJECTS - SOLUTION
✓ Different automation tool APIs
➡ Driver abstraction
✓ Different UI actions
➡ Driver abstraction
✓ Different platform implementations for same screen
➡ Element locator map.
✓ UX interactions patterns (Nav Drawer / Tab bar /Menu)
➡ Menu and Menu Item Abstraction
44
45. IMPLEMENTATION NOTES
‣ We "require" specific driver class during test execution.
‣ Page registry, can be queried for page object instances.
‣ PageRegistry.get "Login Page”
‣ Rspec Unit Tests
‣ Rake task to create new page classes.
45
46. FUTURE DIRECTION
‣ Debug Mode
‣ Log debug info like element id, clicks etc…
‣ Slow element locator finder
46