page objects done right - selenium conference 2014

69
Page Objects Oren Rubin Testim.io

Upload: oren-rubin

Post on 28-Nov-2014

598 views

Category:

Software


3 download

DESCRIPTION

Presentation by Oren Rubin on Page Object presented at the seleniumconf2014

TRANSCRIPT

Page 1: Page Objects Done Right - selenium conference 2014

Page ObjectsOren Rubin Testim.io

Page 2: Page Objects Done Right - selenium conference 2014

About Me

Page 3: Page Objects Done Right - selenium conference 2014

Talk Topics

● Page Objects

○ Why?

○ How?

○ When?

● Synchronously

○ Integrate in Page Objects

○ Remove magic sleep

Page 4: Page Objects Done Right - selenium conference 2014

Talk Topics (we won't discuss)

● Locators - best practice

● Retrys

○ Locator retry (SPA)

○ Entire Test (stability)

Page 5: Page Objects Done Right - selenium conference 2014

Page Objects?

A Design Pattern.

Provides a programmatic API to drive and interrogate a UI

Page 6: Page Objects Done Right - selenium conference 2014

Naming things is hard

● Originally "Window Drivers" - Martin Fowler, 2004

● Only pages? what about:

○ Header/footer

○ Components/widgets

○ Simple HTML elements (e.g., Tables)

Page 7: Page Objects Done Right - selenium conference 2014

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

Page 8: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

Page 9: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

<form id="gaia_loginform">

<input id="email">

<input id="passwd">

<input id="signIn" type="submit">

</form>

Page 10: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

void testMyApp() {

// login

driver.findId("email").setText("[email protected]");

driver.findId("passwd").setText("12345");

driver.findId("signIn").click();;

sleep(3000); // wait till page loads

assert(...) // my assertions}

Page 11: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

void testMyApp() {

// login

driver.findId("email").setText("[email protected]");

driver.findId("passwd").setText("12345");

driver.findId("signIn").click();;

sleep(3000); // wait till page loads

assert(...) // my assertions}

Simon says no!

Page 12: Page Objects Done Right - selenium conference 2014

Test Automation

testMyApp() { account.login();

gallery.showImage()

}

Business LogictestMyApp() { account.login();

gallery.showImage()

}

the implementation of the automatic execution of some Business Logic

ImplementationClass LoginPage() { login() { // selenium code }}

Class GalleryPage() { showImage() {...}}

Page 13: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

loginPage = new LoginPage();

loginPage.login();

public class LoginPage {

public login();

}

Page 14: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

loginPage = new LoginPage();

loginPage.login();

// compilation error: missing return type

public class LoginPage {

public ? login();

}

Page 15: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

Option 1: void

public class LoginPage {

public void login();

}

Page 16: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

void testMyApp() {

// TODO move to @setup

loginPage = new LoginPage();

loginPage.login();

sleep(3000); // wait till page loads

assert(…)

}

Page 17: Page Objects Done Right - selenium conference 2014

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?

Page 18: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

Option 2 (improved): Return a page object

public class LoginPage {

public GalleryPage login() {{…

return new GalleryPage();}

}

Page 19: Page Objects Done Right - selenium conference 2014

Step 1 - Expose The Service

void testMyApp() {// TODO consider moving to @before

loginPage = new LoginPage();

galleryPage = loginPage.login();

sleep(3000);

galleryPage.showImageFullscreen();assert(…)

}

Page 20: Page Objects Done Right - selenium conference 2014

Q: What's the source of all evil?

"No more war - no more blood shed"

Page 21: Page Objects Done Right - selenium conference 2014

A: Random waits

"No more war - no more blood shed"

random sleep

Abie NathanThe voice of peace

Page 22: Page Objects Done Right - selenium conference 2014

Step 2 - Eliminate random sleep

void testMyApp() {

loginPage = new LoginPage();

galleryPage = loginPage.login();

sleep(3000); // should we move it?galleryPage.showImageFullscreen();

assert(…)}

Page 23: Page Objects Done Right - selenium conference 2014

Step 2 - option 2

public class LoginPage {

public GalleryPage login() {…

sleep(3000);

return new GalleryPage();}

}

Page 24: Page Objects Done Right - selenium conference 2014

Step 2 - option 2

void testMyApp() {

loginPage = new LoginPage();

// synchronous for testers

galleryPage = loginPage.login();

galleryPage.showImageFullscreen();

assert(…)}

Page 25: Page Objects Done Right - selenium conference 2014

Step 2 - back to option 1

public class LoginPage {void login() {…}

}

public class GalleryPage { void showImageFullscreen() {…}

static GalleryPage waitForPage() {…}}

Page 26: Page Objects Done Right - selenium conference 2014

Step 2 - back to option 1

void testMyApp() {

loginPage = new LoginPage()

loginPage.login(); // login() is void

galleryPage = GalleryPage.waitForPage();

galleryPage.showImageFullscreen();

assert(…)}

Page 27: Page Objects Done Right - selenium conference 2014

Step 2 - Combining options 1+2

public class LoginPage {

public GalleryPage login() {…

// return new GalleryPage();

return GalleryPage.waitForPage();}

}

Page 28: Page Objects Done Right - selenium conference 2014

Step 2 - Force API comformance

public class LoginPage {static LoginPage waitForPage() {…}GalleryPage login() {…}

}

public class GalleryPage {static GalleryPage waitForPage() {…}void showImageFullscreen() {…}

}

Page 29: Page Objects Done Right - selenium conference 2014

Step 2 - Basic code reuse

abstract class BasicPage {// force derived classes

public static BasicPage waitForPage();}

public class GalleryPage {public static BasicPage waitForPage() {…}

}

Page 30: Page Objects Done Right - selenium conference 2014

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!

Page 31: Page Objects Done Right - selenium conference 2014

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!

Page 32: Page Objects Done Right - selenium conference 2014

Step 2 - Basic code reuse

abstract class BasicPage {// force derived classes to implement

public BasicPage waitForPage();}

public class GalleryPage {public GalleryPage waitForPage() {…}

}

Page 33: Page Objects Done Right - selenium conference 2014

Step 2 - Basic code reuse

Another option is to use c'tor as wait

public class GalleryPage {GalleryPage() {

sleep(3000);

}

}

Page 34: Page Objects Done Right - selenium conference 2014

Step 2 - Basic code reuse

Tip!Add all common utilities to base class

abstract class BasicPage {public BasicPage waitForPage();

public void waitForSpinnerToFade();}

Page 35: Page Objects Done Right - selenium conference 2014

What's next?

Support Different Users

Page 36: Page Objects Done Right - selenium conference 2014

Step 3 - Params and overload

Sounds simple!

public class LoginPage {

public GalleryPage login(user, password);

}

Page 37: Page Objects Done Right - selenium conference 2014

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();

}

Page 38: Page Objects Done Right - selenium conference 2014

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

Page 39: Page Objects Done Right - selenium conference 2014

Step 3 - Params and overload

Compiles successfully

public class LoginPage {

public GalleryPage loginAsRegular(…); public OtherPage loginAsAdmin(…); public LoginPage loginAsBadCredintial(…);}

Page 40: Page Objects Done Right - selenium conference 2014

Step 3 - Overloading Philosophy

The LoginPage is used for:1. Setup

Drive the app to a specific stateNo one cares about the implementation

2. Test the Login page itselfTest the specific implementation

Page 41: Page Objects Done Right - selenium conference 2014

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

Page 42: Page Objects Done Right - selenium conference 2014

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

Page 43: Page Objects Done Right - selenium conference 2014

Step 3 - Overloading Philosophy

Should we put everything together?

class LoginPage {

login() {}

login(username, password){}

}

Page 44: Page Objects Done Right - selenium conference 2014

Step 3 - Overloading Philosophy

Do we want more abstraction

interface LoginPage { login();

LoginPageDriver getDriver();}

interface LoginPageDriver { login(username, password);

}

Page 45: Page Objects Done Right - selenium conference 2014

Step 3 - Overloading Philosophy

class LoginPageImpl implements LoginPage, LoginPageDriver { login() {}

login(username, password);

LoginPageDriver getDriver() {return this;

}

}

Page 46: Page Objects Done Right - selenium conference 2014

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);

}

Page 47: Page Objects Done Right - selenium conference 2014

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);

}

Page 48: Page Objects Done Right - selenium conference 2014

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

}

}

Page 49: Page Objects Done Right - selenium conference 2014

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)

Page 50: Page Objects Done Right - selenium conference 2014

Step 4 - Page Factory

Recommended way - in c'tor

public class LoginPage {

LoginPage() {

// wait till ready

sleep(3000);

// init

PageFactory.initElement(driver, this)

}

}

Page 51: Page Objects Done Right - selenium conference 2014

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

}

});

Page 52: Page Objects Done Right - selenium conference 2014

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

}

Page 53: Page Objects Done Right - selenium conference 2014

Step 4 - Page Factory

Annotations to the rescue!

public class GoogleLoginPage {

@FindBy(how = How.name, using = "q")private WebElement searchBox;

}

Page 54: Page Objects Done Right - selenium conference 2014

Step 4 - Page Factory

Shorthand FTW!

public class GoogleLoginPage {

@FindBy(name = "q")private WebElement searchBox;

}

Supports id, tagName and custom annotations!

Page 55: Page Objects Done Right - selenium conference 2014

Step 4 - Assertions

Two options● Separate from Page Objects

Community Recommends

● Inside Page ObjectMaybe inside the BasicPage class

Page 56: Page Objects Done Right - selenium conference 2014

Off topic - No more sleep

public class GalleryPage {

public void waitForPage() {sleep(3000);

}

}

Page 57: Page Objects Done Right - selenium conference 2014

Off topic - No more sleep

Some solutions1. 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

Page 58: Page Objects Done Right - selenium conference 2014

The Coordinator

login()waitForTestEvent('logged')

gallery.showImage()

// injected codesetInterval( function({ if (works) { // we're done callback(); }), 500ms)

Page 59: Page Objects Done Right - selenium conference 2014

The Coordinator

Option 1 - JavascriptThe API driver.executeAsyncScript("some js.. callback()");

Translation - browser runsfunction executeAsync(codeToEval, callback) {

// evaluated code has access to callbackeval(codeToEval);

}

Page 60: Page Objects Done Right - selenium conference 2014

The Coordinator

Option 1 - JavascriptThe API driver.executeAsyncScript("some js.. callback()");

Translation - browser runsfunction executeAsync(codeToEval, callback) {

// name 'callback' might change. last param guaranteed though

eval(codeToEval);}

Page 61: Page Objects Done Right - selenium conference 2014

The Coordinator

driver.executeAsyncScript("some js.. // callback()var lastIndex = arguments.length;var workingCallback = arguments[lastIndex]

workingCallback(); // nowsetTimeout(workingCallback, 5000); // later

");

Page 62: Page Objects Done Right - selenium conference 2014

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)");

}

Page 63: Page Objects Done Right - selenium conference 2014

The Coordinator - option 2

login()waitForTestEvent('logged')

gallery.showImage()

// load things…// readysendTestEvent('logged')

Page 64: Page Objects Done Right - selenium conference 2014

The Coordinator - option 2

Testers wait for a known event

public class LoginPage {public GalleryPage login() {

waitForTestEvent('gallery-ready')

return new GalleryPage();}

}

Page 65: Page Objects Done Right - selenium conference 2014

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>

Page 66: Page Objects Done Right - selenium conference 2014

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);

}

}

Page 67: Page Objects Done Right - selenium conference 2014

The Coordinator - option 2

Dev add html element in test mode

function loadGalleryPage() {

callServer(function() {

// page is loaded

testing.sendTestEvent('gallery-ready')

})

}

Page 68: Page Objects Done Right - selenium conference 2014

The Coordinator - option 2

Dev add html element in test modeclass Testing {

sendTestEvent: function(ev) {

if (!app.isInTest){

return;

$('#test').append('<div class="+ev+">')

})

}Illustration only, don't use jQuery. Use Angular.js / Ember.js

Page 69: Page Objects Done Right - selenium conference 2014

अंत!

Thank You!

Oren Rubin

Testim.io | shexman@gmail | @shexman | linkedin