Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Page Objects 
Oren Rubin Testim.io
About Me
Talk Topics 
● Page Objects 
○ Why? 
○ How? 
○ When? 
● Synchronously 
○ Integrate in Page Objects 
○ Remove magic sleep
Talk Topics (we won't discuss) 
● Locators - best practice 
● Retrys 
○ Locator retry (SPA) 
○ Entire Test (stability)
Page Objects? 
A Design Pattern. 
Provides a programmatic API to drive and 
interrogate a UI
Naming things is hard 
● Originally "Window Drivers" - Martin Fowler, 2004 
● Only pages? what about: 
○ Header/footer 
○ ...
Page Object Pattern 
Expose the service you're interacting with, not the 
implementation. -- Selenium Wiki 
If you have a ...
Step 1 - Expose The Service
Step 1 - Expose The Service 
<form id="gaia_loginform"> 
<input id="email"> 
<input id="passwd"> 
<input id="signIn" type=...
Step 1 - Expose The Service 
void testMyApp() { 
// login 
driver.findId("email").setText("u@app.com"); 
driver.findId("pa...
Step 1 - Expose The Service 
void testMyApp() { 
// login 
driver.findId("email").setText("u@app.com"); 
driver.findId("pa...
Test Automation 
the implementation of the automatic execution of some Business Logic 
Business Logic testMyApp() { 
testM...
Step 1 - Expose The Service 
loginPage = new LoginPage(); 
loginPage.login(); 
public class LoginPage { 
public login(); 
...
Step 1 - Expose The Service 
loginPage = new LoginPage(); 
loginPage.login(); 
// compilation error: missing return type 
...
Step 1 - Expose The Service 
Option 1: void 
public class LoginPage { 
public void login(); 
}
Step 1 - Expose The Service 
void testMyApp() { 
// TODO move to @setup 
loginPage = new LoginPage(); 
loginPage.login(); ...
Step 1 - Expose The Service 
Pro - Only deals with login page 
● Don't interleave code relevant to other pages in 
this cl...
Step 1 - Expose The Service 
Option 2 (improved): Return a page object 
public class LoginPage { 
public GalleryPage login...
Step 1 - Expose The Service 
void testMyApp() { 
// TODO consider moving to @before 
loginPage = new LoginPage(); 
gallery...
Q: What's the source of all evil? 
"No more war - no more blood shed"
A: Random waits 
random sleep 
"No more war - no more blood shed" 
Abie Nathan 
The voice of peace
Step 2 - Eliminate random sleep 
void testMyApp() { 
loginPage = new LoginPage(); 
galleryPage = loginPage.login(); 
sleep...
Step 2 - option 2 
public class LoginPage { 
public GalleryPage login() { 
… 
sleep(3000); 
return new GalleryPage(); 
} 
...
Step 2 - option 2 
void testMyApp() { 
loginPage = new LoginPage(); 
// synchronous for testers 
galleryPage = loginPage.l...
Step 2 - back to option 1 
public class LoginPage { 
void login() {…} 
} 
public class GalleryPage { 
void showImageFullsc...
Step 2 - back to option 1 
void testMyApp() { 
loginPage = new LoginPage() 
loginPage.login(); // login() is void 
gallery...
Step 2 - Combining options 1+2 
public class LoginPage { 
public GalleryPage login() { 
… 
// return new GalleryPage(); 
r...
Step 2 - Force API comformance 
public class LoginPage { 
static LoginPage waitForPage() {…} 
GalleryPage login() {…} 
} 
...
Step 2 - Basic code reuse 
abstract class BasicPage { 
// force derived classes 
public static BasicPage waitForPage(); 
}...
Step 2 - Basic code reuse 
abstract class BasicPage { 
// force derived classes 
public static BasicPage waitForPage(); 
}...
Step 2 - Basic code reuse 
abstract class BasicPage { 
// force derived classes to implement 
public BasicPage waitForPage...
Step 2 - Basic code reuse 
abstract class BasicPage { 
// force derived classes to implement 
public BasicPage waitForPage...
Step 2 - Basic code reuse 
Another option is to use c'tor as wait 
public class GalleryPage { 
GalleryPage() { 
sleep(3000...
Step 2 - Basic code reuse 
Tip! 
Add all common utilities to base class 
abstract class BasicPage { 
public BasicPage wait...
What's next? 
Support Different Users
Step 3 - Params and overload 
Sounds simple! 
public class LoginPage { 
public GalleryPage login(user, password); 
}
Step 3 - Params and overload 
void testMyApp() { 
// bad password 
loginPage = new LoginPage(); 
loginPage = loginPage.log...
Step 3 - Params and overload 
What about different roles? what about failures 
public class LoginPage { 
public GalleryPag...
Step 3 - Params and overload 
Compiles successfully 
public class LoginPage { 
public GalleryPage loginAsRegular(…); 
publ...
Step 3 - Overloading Philosophy 
The LoginPage is used for: 
1. Setup 
Drive the app to a specific state 
No one cares abo...
Step 3 - Overloading Philosophy 
1. Setup 
// look ma! no params! 
loginPage.login(); 
Implementation might be using 
● Us...
Step 3 - Overloading Philosophy 
2. Test the Login page itself 
○ Abstract everything 
login(username, password) 
○ Act on...
Step 3 - Overloading Philosophy 
Should we put everything together? 
class LoginPage { 
login() {} 
login(username, passwo...
Step 3 - Overloading Philosophy 
Do we want more abstraction 
interface LoginPage { 
login(); 
LoginPageDriver getDriver()...
Step 3 - Overloading Philosophy 
class LoginPageImpl 
implements LoginPage, LoginPageDriver { 
login() {} 
login(username,...
Step 4 - Page Factory 
public class LoginPage { 
private WebDriver driver; 
public LoginPage(driver) { 
this.driver = driv...
Step 4 - Page Factory 
public class LoginPage { 
private WebElement email; 
public LoginPage(driver) { 
email = driver.fin...
Step 4 - Page Factory 
public class LoginPage { 
private WebElement email; 
private WebElement password; 
public LoginPage...
Step 4 - Page Factory 
public class LoginPage { 
private WebElement email; 
private WebElement password; 
/* look ma.. no ...
Step 4 - Page Factory 
Recommended way - in c'tor 
public class LoginPage { 
LoginPage() { 
// wait till ready 
sleep(3000...
Step 4 - Page Factory Pseudo Code 
class PageFactory { 
public static initElements(driver, class){ 
obj = class.new(); 
ob...
Step 4 - Page Factory 
But this won't pass any code review 
public class GoogleLoginPage { 
private WebElement q; 
// Lint...
Step 4 - Page Factory 
Annotations to the rescue! 
public class GoogleLoginPage { 
@FindBy(how = How.name, using = "q") 
p...
Step 4 - Page Factory 
Shorthand FTW! 
public class GoogleLoginPage { 
@FindBy(name = "q") 
private WebElement searchBox; ...
Step 4 - Assertions 
Two options 
● Separate from Page Objects 
Community Recommends 
● Inside Page Object 
Maybe inside t...
Off topic - No more sleep 
public class GalleryPage { 
public void waitForPage() { 
sleep(3000); 
} 
}
Off topic - No more sleep 
Some solutions 
1. The "No smoke without fire" - 
Wait for another element we know that loads l...
The Coordinator 
login() 
waitForTestEvent('logged') 
gallery.showImage() 
// injected code 
setInterval( function({ 
if (...
The Coordinator 
Option 1 - Javascript 
The API 
driver.executeAsyncScript("some js.. callback()"); 
Translation - browser...
The Coordinator 
Option 1 - Javascript 
The API 
driver.executeAsyncScript("some js.. callback()"); 
Translation - browser...
The Coordinator 
driver.executeAsyncScript(" 
some js.. 
// callback() 
var lastIndex = arguments.length; 
var workingCall...
The Coordinator 
The API 
driver.executeAsyncScript("some js.. callback()"); 
Better implementation 
function executeAsync...
The Coordinator - option 2 
login() 
waitForTestEvent('logged') 
gallery.showImage() 
// load things 
… 
// ready 
sendTes...
The Coordinator - option 2 
Testers wait for a known event 
public class LoginPage { 
public GalleryPage login() { 
… 
wai...
The Coordinator - option 2 
Dev add html element in test mode 
<body> 
<div id="app"> app goes here </div> 
<div id="test"...
The Coordinator - option 2 
Imlement waitForTestEvent in base class 
abstract class BasicPage { 
void waitForTestEvent(eve...
The Coordinator - option 2 
Dev add html element in test mode 
function loadGalleryPage() { 
callServer(function() { 
// p...
The Coordinator - option 2 
Dev add html element in test mode 
class Testing { 
sendTestEvent: function(ev) { 
if (!app.is...
अंत! 
Thank You! 
Oren Rubin 
Testim.io | shexman@gmail | @shexman | linkedin
Upcoming SlideShare
Loading in …5
×

Page Objects Done Right - selenium conference 2014

11,502 views

Published on

Presentation by Oren Rubin on Page Object presented at the seleniumconf2014

Published in: Software

Page Objects Done Right - selenium conference 2014

  1. 1. Page Objects Oren Rubin Testim.io
  2. 2. About Me
  3. 3. Talk Topics ● Page Objects ○ Why? ○ How? ○ When? ● Synchronously ○ Integrate in Page Objects ○ Remove magic sleep
  4. 4. Talk Topics (we won't discuss) ● Locators - best practice ● Retrys ○ Locator retry (SPA) ○ Entire Test (stability)
  5. 5. Page Objects? A Design Pattern. Provides a programmatic API to drive and interrogate a UI
  6. 6. Naming things is hard ● Originally "Window Drivers" - Martin Fowler, 2004 ● Only pages? what about: ○ Header/footer ○ Components/widgets ○ Simple HTML elements (e.g., Tables)
  7. 7. Page Object Pattern Expose the service you're interacting with, not the implementation. -- Selenium Wiki If you have a WebDriver APIs in your test methods... You're doing it wrong. -- Simon Stewart
  8. 8. Step 1 - Expose The Service
  9. 9. Step 1 - Expose The Service <form id="gaia_loginform"> <input id="email"> <input id="passwd"> <input id="signIn" type="submit"> </form>
  10. 10. Step 1 - Expose The Service void testMyApp() { // login driver.findId("email").setText("u@app.com"); driver.findId("passwd").setText("12345"); driver.findId("signIn").click();; sleep(3000); // wait till page loads assert(...) // my assertions }
  11. 11. Step 1 - Expose The Service void testMyApp() { // login driver.findId("email").setText("u@app.com"); driver.findId("passwd").setText("12345"); driver.findId("signIn").click();; sleep(3000); // wait till page loads assert(...) // my assertions } Simon says no!
  12. 12. Test Automation the implementation of the automatic execution of some Business Logic Business Logic testMyApp() { testMyApp() { account.login(); gallery.showImage() } Implementation Class LoginPage() { login() { account.login(); // selenium code gallery.showImage() } } } Class GalleryPage() { showImage() {...} }
  13. 13. Step 1 - Expose The Service loginPage = new LoginPage(); loginPage.login(); public class LoginPage { public login(); }
  14. 14. Step 1 - Expose The Service loginPage = new LoginPage(); loginPage.login(); // compilation error: missing return type public class LoginPage { public ? login(); }
  15. 15. Step 1 - Expose The Service Option 1: void public class LoginPage { public void login(); }
  16. 16. Step 1 - Expose The Service void testMyApp() { // TODO move to @setup loginPage = new LoginPage(); loginPage.login(); sleep(3000); // wait till page loads assert(…) }
  17. 17. Step 1 - Expose The Service Pro - Only deals with login page ● Don't interleave code relevant to other pages in this class. Con - Only deals with login page ● Was the login successful? ● On which page should we be? ● Is the new page ready?
  18. 18. Step 1 - Expose The Service Option 2 (improved): Return a page object public class LoginPage { public GalleryPage login() {{ … return new GalleryPage(); } }
  19. 19. Step 1 - Expose The Service void testMyApp() { // TODO consider moving to @before loginPage = new LoginPage(); galleryPage = loginPage.login(); sleep(3000); galleryPage.showImageFullscreen(); assert(…) }
  20. 20. Q: What's the source of all evil? "No more war - no more blood shed"
  21. 21. A: Random waits random sleep "No more war - no more blood shed" Abie Nathan The voice of peace
  22. 22. Step 2 - Eliminate random sleep void testMyApp() { loginPage = new LoginPage(); galleryPage = loginPage.login(); sleep(3000); // should we move it? galleryPage.showImageFullscreen(); assert(…) }
  23. 23. Step 2 - option 2 public class LoginPage { public GalleryPage login() { … sleep(3000); return new GalleryPage(); } }
  24. 24. Step 2 - option 2 void testMyApp() { loginPage = new LoginPage(); // synchronous for testers galleryPage = loginPage.login(); galleryPage.showImageFullscreen(); assert(…) }
  25. 25. Step 2 - back to option 1 public class LoginPage { void login() {…} } public class GalleryPage { void showImageFullscreen() {…} static GalleryPage waitForPage() {…} }
  26. 26. Step 2 - back to option 1 void testMyApp() { loginPage = new LoginPage() loginPage.login(); // login() is void galleryPage = GalleryPage.waitForPage(); galleryPage.showImageFullscreen(); assert(…) }
  27. 27. Step 2 - Combining options 1+2 public class LoginPage { public GalleryPage login() { … // return new GalleryPage(); return GalleryPage.waitForPage(); } }
  28. 28. Step 2 - Force API comformance public class LoginPage { static LoginPage waitForPage() {…} GalleryPage login() {…} } public class GalleryPage { static GalleryPage waitForPage() {…} void showImageFullscreen() {…} }
  29. 29. Step 2 - Basic code reuse abstract class BasicPage { // force derived classes public static BasicPage waitForPage(); } public class GalleryPage { public static BasicPage waitForPage() {…} }
  30. 30. Step 2 - Basic code reuse abstract class BasicPage { // force derived classes public static BasicPage waitForPage(); } public class GalleryPage { public static BasicPage waitForPage() {…} } Computer says no! Cannot override static methods!
  31. 31. Step 2 - Basic code reuse abstract class BasicPage { // force derived classes to implement public BasicPage waitForPage(); } public class GalleryPage { public BasicPage waitForPage() {…} } Computer says ok! But could be improved!
  32. 32. Step 2 - Basic code reuse abstract class BasicPage { // force derived classes to implement public BasicPage waitForPage(); } public class GalleryPage { public GalleryPage waitForPage() {…} }
  33. 33. Step 2 - Basic code reuse Another option is to use c'tor as wait public class GalleryPage { GalleryPage() { sleep(3000); } }
  34. 34. Step 2 - Basic code reuse Tip! Add all common utilities to base class abstract class BasicPage { public BasicPage waitForPage(); public void waitForSpinnerToFade(); }
  35. 35. What's next? Support Different Users
  36. 36. Step 3 - Params and overload Sounds simple! public class LoginPage { public GalleryPage login(user, password); }
  37. 37. Step 3 - Params and overload void testMyApp() { // bad password loginPage = new LoginPage(); loginPage = loginPage.login('a', 'wrong'); // good password galleryPage = loginPage.login('a', 'correct'); galleryPage.showImageFullscreen(); }
  38. 38. Step 3 - Params and overload What about different roles? what about failures public class LoginPage { public GalleryPage login(user, password); public OtherPage login(user, password); public LoginPage login(user, password); } // compilation error: can't distinguish overload by return type
  39. 39. Step 3 - Params and overload Compiles successfully public class LoginPage { public GalleryPage loginAsRegular(…); public OtherPage loginAsAdmin(…); public LoginPage loginAsBadCredintial(…); }
  40. 40. Step 3 - Overloading Philosophy The LoginPage is used for: 1. Setup Drive the app to a specific state No one cares about the implementation 2. Test the Login page itself Test the specific implementation
  41. 41. Step 3 - Overloading Philosophy 1. Setup // look ma! no params! loginPage.login(); Implementation might be using ● Username/password ● Cookies ● Google Account Might be hardcoded, or using config files
  42. 42. Step 3 - Overloading Philosophy 2. Test the Login page itself ○ Abstract everything login(username, password) ○ Act on element wrappers (get/set kind) Definition: InputDriver getPasswordField() Usage: loginPage.getPasswordField.set('12345') * less recommended
  43. 43. Step 3 - Overloading Philosophy Should we put everything together? class LoginPage { login() {} login(username, password){} }
  44. 44. Step 3 - Overloading Philosophy Do we want more abstraction interface LoginPage { login(); LoginPageDriver getDriver(); } interface LoginPageDriver { login(username, password); }
  45. 45. Step 3 - Overloading Philosophy class LoginPageImpl implements LoginPage, LoginPageDriver { login() {} login(username, password); LoginPageDriver getDriver() { return this; } }
  46. 46. Step 4 - Page Factory public class LoginPage { private WebDriver driver; public LoginPage(driver) { this.driver = driver; } public GalleryPage login(username, password) { driver.findId("email").setText(username); … }
  47. 47. Step 4 - Page Factory public class LoginPage { private WebElement email; public LoginPage(driver) { email = driver.findById("email"); } public GalleryPage login(username, password) { email.setText(username); … }
  48. 48. Step 4 - Page Factory public class LoginPage { private WebElement email; private WebElement password; public LoginPage(driver) { email = driver.findById("email"); password = driver.findById("password"); // Linting error! too much glue code } }
  49. 49. Step 4 - Page Factory public class LoginPage { private WebElement email; private WebElement password; /* look ma.. no ctor! */ } // loginPage = new LoginPage(); loginPage = PageFactory.initElement(driver, LoginPage.class)
  50. 50. Step 4 - Page Factory Recommended way - in c'tor public class LoginPage { LoginPage() { // wait till ready sleep(3000); // init PageFactory.initElement(driver, this) } }
  51. 51. Step 4 - Page Factory Pseudo Code class PageFactory { public static initElements(driver, class){ obj = class.new(); obj.properties.forEach(function(property) { if (property.type === WebElement.class) { byId = driver.findById(property.name); byName = driver.findByName(property.name); obj[property.name] = byId || byName } });
  52. 52. Step 4 - Page Factory But this won't pass any code review public class GoogleLoginPage { private WebElement q; // Linting error! name too short and non descriptive }
  53. 53. Step 4 - Page Factory Annotations to the rescue! public class GoogleLoginPage { @FindBy(how = How.name, using = "q") private WebElement searchBox; }
  54. 54. Step 4 - Page Factory Shorthand FTW! public class GoogleLoginPage { @FindBy(name = "q") private WebElement searchBox; } Supports id, tagName and custom annotations!
  55. 55. Step 4 - Assertions Two options ● Separate from Page Objects Community Recommends ● Inside Page Object Maybe inside the BasicPage class
  56. 56. Off topic - No more sleep public class GalleryPage { public void waitForPage() { sleep(3000); } }
  57. 57. Off topic - No more sleep Some solutions 1. The "No smoke without fire" - Wait for another element we know that loads last 2. The "Take the time" Wait till state is what you expect (element exists, row count,..). Selenium's implicit wait helps. 3. The "Coordinator" - Recommended! Wait for a sign from AUT
  58. 58. The Coordinator login() waitForTestEvent('logged') gallery.showImage() // injected code setInterval( function({ if (works) { // we're done callback(); } ), 500ms)
  59. 59. The Coordinator Option 1 - Javascript The API driver.executeAsyncScript("some js.. callback()"); Translation - browser runs function executeAsync(codeToEval, callback) { // evaluated code has access to callback eval(codeToEval); }
  60. 60. The Coordinator Option 1 - Javascript The API driver.executeAsyncScript("some js.. callback()"); Translation - browser runs function executeAsync(codeToEval, callback) { // name 'callback' might change. last param guaranteed though eval(codeToEval); }
  61. 61. The Coordinator driver.executeAsyncScript(" some js.. // callback() var lastIndex = arguments.length; var workingCallback = arguments[lastIndex] workingCallback(); // now setTimeout(workingCallback, 5000); // later ");
  62. 62. The Coordinator The API driver.executeAsyncScript("some js.. callback()"); Better implementation function executeAsync(codeToEval, callback) { // evaluated code has access to callback var lastArgument = "arguments[arguments.legnth - 1]" eval(" ( function(callback){" +codeToEval+" } )(lastArgument)"); }
  63. 63. The Coordinator - option 2 login() waitForTestEvent('logged') gallery.showImage() // load things … // ready sendTestEvent('logged')
  64. 64. The Coordinator - option 2 Testers wait for a known event public class LoginPage { public GalleryPage login() { … waitForTestEvent('gallery-ready') return new GalleryPage(); } }
  65. 65. The Coordinator - option 2 Dev add html element in test mode <body> <div id="app"> app goes here </div> <div id="test"> test events go here </div> </body>
  66. 66. The Coordinator - option 2 Imlement waitForTestEvent in base class abstract class BasicPage { void waitForTestEvent(eventName) { By selector = By.CSS("#test ." + eventName) driver.waitForElement(selector); WebElement element = driver.find(selector); driver.removeElement(element); } }
  67. 67. The Coordinator - option 2 Dev add html element in test mode function loadGalleryPage() { callServer(function() { // page is loaded testing.sendTestEvent('gallery-ready') }) }
  68. 68. The Coordinator - option 2 Dev add html element in test mode class Testing { sendTestEvent: function(ev) { if (!app.isInTest){ return; $('#test').append('<div class="+ev+">') }) } Illustration only, don't use jQuery. Use Angular.js / Ember.js
  69. 69. अंत! Thank You! Oren Rubin Testim.io | shexman@gmail | @shexman | linkedin

×