Top Banner
Three simple chords of Alternative “PageObjects” and Hardcore of LoadableComponents Iakiv Kramarenko
79

Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

May 06, 2015

Download

Education

***VIDEO***: view in SD at http://youtu.be/HPHKeBakulQ or download in HD at http://bit.ly/1nyvA67

Often we have lack of automation resources. If we just would involve less experienced juniors to implement test model and even Manual QA to write DSL like tests…

In this talk I want to present the simplified approach to write PageObjects for your test model as it would be like playing “three chords” song on a guitar. And also share the experience of pacifying the LoadableComponent pattern, rather hard in implementation but making your tests much more DRY and easy to use in context of loading pages.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Three simple chords of Alternative “PageObjects” and Hardcore of

LoadableComponents

Iakiv Kramarenko

Page 2: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Conventions :)

● Sympathy colors***:

Green =

Orange =

Red =

*** often are subjective and applied to specific context ;)

Page 3: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

● At project with no Web UI automation, no unit testing

● With 4(5) Manual QA

– Using checklists + long detailed End to End test cases

● With 1 QA Automation found

Page 4: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Testing single page web app● Ajax

● only <div>, <a>, <input>

– Inconsistent implementation

● No confident urls

● One current Frame/Page with content per user step

– Deep structure: ● Authorization > Menus > SubMenus > Tabs > Extra > Extra

– Modal dialogs

Page 5: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Met Requirements● Automate high level scenarios

– use AC from stories

– existed Manual Scenarios

● Use 3rd party solutions

– Java Desired

● Involve Manual QA

– provide easy to use solution

– BDD Desired

● In Tough Deadlines :)

Page 6: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Dreaming of framework...● Fast in development

– Using instruments quite agile to adapt to project specifics

● Extremely easy to use and learn

● With DRY and handy

page loading

● Simple DSL for tests

● BDD – light and DRY as much as possible

Page 7: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Choosing Programming ParadigmFor WebUI Automation

Based on https://bitbucket.org/yashaka/oopbucket/src

Page 8: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

OOP or not to OOPThat is the question

Page 9: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

OOP can impose you to● Learn much

– Concept itself

– Design Patterns

● Have bulky structured implementation

– Coupled via inheritance

– Having too many layers of abstractions

● Work harder to implement DSL

Do you need this?

Page 10: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

OOP can give you● Batch common operations on pages/steps ***

E.g.– Reporting per steps

– abstract open() per page

– abstract getExpectedElements() per page

● Obligations over conventions

● Certainty in future refactoring

Do you need this?

Page 11: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Sometimes...● Batch/common operations may be redundant for pages/steps

– Sufficient reporting can be implemented in low-level libraries

– “batch” open() may be called on LoadableComponent separately

– IHaveExpectedElements may give no advantages for smoke testing in your project

● And still can be implemented separately in e.g. LoadableComponent

● Or via Reflection

– Some “common” implementation can be moved from pages to “widgets” and still be implemented with OOP

Page 12: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Sometimes...● Conventions can be very easy

● No severe refactoring is coming

– Test Automation Project is not a NASA Space Shuttle ;).

Page 13: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

So Think Always!

Page 14: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Procedural

Functional

OOP

And Balance!

Page 15: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Classic PageObject Pattern

Page 16: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

public class LoginClassicPageObject extends BasePage {

@FindBy(css = "#login-form") private WebElement container;

@FindBy(name = "username") private WebElement usernameField;

@FindBy(name = "password") private WebElement passwordField;

@FindBy(css = ".ui-button[value='Log in']") private WebElement loginButton;

public void WebElement getContainer(){ return container; } @Override public void open(String baseurl) { driver.get(baseurl); }

public void doLogin(String login, String pass){

usernameField.sendKeys(login);

passwordField.sendKeys(pass);

loginButton.click();

}

public LoginClassicPageObject(

WebDriver driver, String baseurl) {

PageFactory.initElements(driver, this);

this.driver = driver;

this.baseurl = baseurl;

}

private String baseurl;

private WebDriver driver;

}

Page 17: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Involving

Page 18: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

public class LoginSelenidePageObject extends BasePage {

private final String container = "#login-form";

private final By username = By.name("username");

private final By password = By.name("password");

private final String loginButton = ".ui-button[value='Log in']";

public void SelenideElement getContainer(){

return $(container);

}

@Override

public void open(String baseurl) {

open(baseurl);

}

public void doLogin(String login, String password){

$(username).setValue(login);

$(password).sendKeys(password);

$(loginButton).click();

}

public LoginSelenidePageObject(String baseurl) {

this.baseurl = baseurl;

}

private String baseurl;

}

Page 19: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

public class LoginSelenidePageObject2 extends BasePage {

public void SelenideElement сontainer(){ return $("#login-form");}

public void SelenideElement usernameField(){ return $(By.name("username"));}

public void SelenideElement passwordField(){ return $(By.name("password"));}

public void SelenideElement loginButton(){ return $(".ui-button[value='Log in']");}

@Override

public void open(String baseurl) {

open(baseurl);

}

public void doLogin(String login, String password){

usernameField().setValue(login);

passwordField().setValue(password);

loginButton.click();

}

public LoginSelenidePageObject(String baseurl) {

this.baseurl = baseurl;

}

private String baseurl;

}

Page 20: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

“Procedural” approach to implement “PageObjects”

Page 21: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

PageUtilspublic class Login{

public static void open(String baseurl) { Selenide.open(baseurl); }

public static SelenideElement container() { return $("#login-form");}

public static SelenideElement usernameField(){ return $(By.name("username"));} public static SelenideElement passwordField(){ return $(By.name("password"));} public static SelenideElement loginButton(){ return $(".ui-button[value='Log in']");}

public static void doLogin(String login, String password){ usernameField().setValue(login); passwordField().setValue(password); loginButton().click(); }}

AlternatIvE

Page 22: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Or...public class Login{

public static void open(String baseurl) { Selenide.open(baseurl); }

public static final String container = "#login-form"; public static final By username = By.name("username"); public static final By password = By.name("password"); public static final String loginButton = ".ui-button[value='Log in']";

public static void doLogin(String login, String password){ $(username).setValue(login); $(password).sendKeys(password); $(loginButton).click(); }}

AlternatIvE

Page 23: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

PageUtils usageLogin.open(baseurl);

Login.doLogin(username, password);

Home.addProduct("Product_1");UserPanel.doLogout();

Login.container().shouldBe(visible);

PageObjects usageloginPage = new LoginPage(baseurl);loginPage.open();loginPage.doLogin(username, password);

homePage = new HomePage();homePage.addProduct("Product_1");homePage.doLogout();loginPage.getContainer().shouldBe(visible);

Compare

Page 24: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Three Chords of “Procedural” PageUtils :)

Page 25: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

1. Abstraction

Factor out implementation details into helper methods

doLogin(username, password);

public static void doLogin(String login, String password){ usernameField().setValue(login); passwordField().setValue(password); loginButton().click();}

public static SelenideElement usernameField(){ return $(By.name("username"));}

Page 26: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

2. Modularity

Collect your helpers in classes of correspondent context

Page 27: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

3. Try to Be “Functional”

– write functions returning result only based on passed parameters

– write smaller functions and use them in a 'chain':

select(dropdownIn(userPanel()), “En”)

– Instead of

selectLanguageDropdownInUserPanel(“En”)

– Use Composition over Inheritance

Page 28: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

P.S. Be smart ;)

– You can't use inheritance.● If you have any conventions you need to remember to

follow them

Page 29: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

When Use?

● Need to involve and teach Manual/Junior Automation QA

● Need a fast solution

● Language support Functional Paradigm

– At least first-class functions

● You know what you do:)

Page 30: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

When maybe not use?

● All committers to test framework are either Senior QA Automation or Developers

● No need to teach Manual QA/Juniors

● No tough deadlines

● Java (only)

Page 31: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

When not use?

● Your are Junior/Middle

– And/Or Manager/Lead/Dev says: OOP or DIE! :)

● You can't predict what features your framework may need in future

Page 32: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

This is how your test

model may look

Page 33: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

“What are those classes

in pagegetters package?”

:)

Page 34: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Here comeLoadable Components...

public abstract class SimpleLoadableComponent { public void get() { try { isLoaded(); } catch (Error e) { load(); isLoaded(); } }

protected abstract void load();

protected abstract void isLoaded() throws Error;}

Page 35: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

What's the point?O_o

Page 36: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

From :(

Login.open(baseurl);

Home.open(username, password);

Home.ensureHasProduct("Product_1");

Product.open("Product_1");

ProductTestTables.open();

ProductTestTables.addCategoryButton().shouldBe(visible);

Page 37: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Technically To

(new ProductTestTablesPage(

new ProductPage(

new HomePage(

new LoginPage(baseurl), username, password),

"Product_1"))).get();

Page 38: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Actually To :)

ProductTestTables.page("Product_1").get();

Page 39: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Selenium LoadableComponentpublic abstract class LoadableComponent<T extends LoadableComponent<T>> {

@SuppressWarnings("unchecked")

public T get() {

try {

isLoaded();

return (T) this;

} catch (Error e) {

load();

}

isLoaded();

return (T) this;

}

protected abstract void load();

protected abstract void isLoaded() throws Error;

}

Page 40: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Ajax? > Selenium SlowLoadableComponent

Calm down, no code, just link:)

● (c) A LoadableComponent which might not have finished loading when load() returns. After a call to load(), the isLoaded() method should continue to fail until the component has fully loaded.

Page 41: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Once you need some abstract classes to DRY your code...

public abstract class

AbstractPage<T extends SlowLoadableComponent<T>> extends SlowLoadableComponent<T>{

O_O

Page 42: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Typical isLoaded() Implementations

Page 43: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

'url-based' isLoaded implementation

protected void isLoaded() throws Error {

String url = driver.getCurrentUrl();

assertTrue(url.contains(pageUrl));

}

Page 44: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

If you want to identify pages by actual content

protected void isLoaded() throws Error {

try {

WebElement div = driver.findElement(By.id("login-select"));

} catch (NoSuchElementException e) {

fail("Cannot locate user name link");

}

}

Page 45: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Once you use @FindBy

public void isLoaded() throws Error {

if (loginButton != null) {

assertElementIsDisplayed(loginButton);

} else {

fail("Login button is not loaded");

}

}

Page 46: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Typical Selenide isLoaded() implementation

public void isLoaded(){

Login.container().shouldBe(visible);

}

Page 47: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Selenide LoadableComponent

public abstract class SelenideLoadableComponent {

public void get() {

long originalTimeout = Configuration.timeout; try {

Configuration.timeout = 0; isLoaded(); Configuration.timeout = originalTimeout; } catch (Error e) {

Configuration.timeout = originalTimeout; load(); isLoaded(); } } protected abstract void load(); protected abstract void isLoaded();}

“slow”,ajax-friendly by default

“no” <T extends Madness>

Page 48: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

If you wish...

public abstract class AbstractPage extends

SelenideLoadableComponent {

public abstract void isLoaded();

}

Home.page().get();

doCrazyStuff();

Home.page().isLoaded() // still?

Page 49: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Implementation Example

Page 50: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Initialize

public class ProductPage extends SelenideLoadablePage{

private HomePage parent;

protected String productName;

public ProductPage(HomePage parent, String productName){

this.parent = parent;

this.productName = productName;

}

….

Page 51: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Load

protected void load() {

parent.get();

Home.ensureHasProduct(productName);

Product.open(productName);

}

Page 52: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

isLoaded()

public void isLoaded() {

Breadcrumb.productLink(productName).shouldBe(visible);

Product.testTableItem().shouldBe(visible);

}

Page 53: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Factory

public class Product {

public static ProductPage page(String productName){

return new ProductPage(Home.page(), productName);

}

…}

Page 54: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

If it would be so “simple”...

Page 55: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

But it would not :ppublic class AuthorizedPage extends AbstractPage {

protected AbstractPage parent;

private String username; private String password;

public AuthorizedPage( LoginPage parent, String username, String password) {

this.parent = parent; this.username = username; this.password = password; }

public AuthorizedPage(AuthorizedPage parent){

this.parent = parent; } ...

Page 56: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Initializepublic class ProductPage extends AuthorizedPage{

protected String productName;

public ProductPage(HomePage parent, String productName){

super(parent);

this.productName = productName;

}

public ProductPage(ProductPage parent){ //It's possible to “load” page from itself

super(parent);

this.productName = parent.getProductName();

}

….

Page 57: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Load protected void load() {

parent.get();

if (parent instanceof ProductPage) {

Breadcrumb.productLink(((ProductPage) parent).getProductName()).click();

} else { //parent instanceof HomePage

Home.ensureHasProduct(productName);

Product.open(productName);

}

}

Page 58: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Though...The beast is not so scary after you write up to 10 first Lions Components :)

Page 59: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

AndYou still can live only with PageUtils and keep LoadableComponents as options to be implemented by crazy devs:)

QA Dev

Page 60: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

scenario "Surf Pages", { where "Page is #page", { page = [ Login.page(), Home.page(), Settings.page(), Login.page(Authorized.page()), Product.page(TEST_PRODUCT), Login.page(Authorized.page()), Settings.page(), Product.page(Home.page(Settings.page()), "Product_1"), Product.page(Product.page(TEST_PRODUCT)), ProductTestTables.page(TEST_PRODUCT), Login.page(Authorized.page())] } then "go to #page", { page.get() }}

Bonus :)

Page 61: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

When Maybe Use?

● No confident urls

● Complex “deep” page hierarchy

● Authorization > Menus > SubMenus > Tabs > Extra > Extra...

Page 62: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

When Use?

● Desired dependent End to End scenarios with “walking through pages” feature

– emulating real user experience

– big amount of such test cases

Page 63: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

When maybe not use?

● Too many ways to load the same page

● Though you still can implement LC for 1 way, if you need to use it often.

● Too many pages, especially “visible” at the same time

Page 64: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

When not use?

● URL-based loading is enough

– Or work around via custom JS load helpers is enough● what is true for most cases...

● Have no “deep” pages

Page 65: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

All Together

Page 66: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

● PageUtils:

class Login

– Page smart loader:

Login.page().get()

– Page root html element:

Login.container()

– Method to open Page once preconditions satisfied:

Login.open()

– Page elements:

Login.signInButton()

– Page business steps:

Login.doLogin(“guest”, “1234”)

● LoadableComponent:

class LoginPage

isLoaded()

load()

Conventions

Page 67: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Ideas to think about

Page 68: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

LoadableComponent

● Is not PageObject

– Though you can integrate it into PageObject, violating Single Responsibility principle

● It's an object incapsulating page loading logic.

– Initializing the “loading way” through LC constructor● It's possible also to move logic into separate loadable

components fro each “way”, though this may lead to overcomplicated hierarchy

– choosing the “way” in load() implementation

– And then just get() your page

Page 69: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

LoadableComponent Integration

● PageUtils + LoadableComponent

– Two classes instead of one

● PageObject + LoadableComponent

– May be harder to achieve friendly tests code

● PageObject extends LoadableComponent

– Bulky

– Harder to explain to juniors/interns

– Violates Single Responsibility Principle

Page 70: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

LoadableComponent Factory

Too more calls to page() ?

Use Singleton Pattern

Page 71: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

PageUtils

Page elements as functions public static SelenideElement usernameField(){

return $(By.name("username"));

}

Login.usernameField().setVal("guest");

Page elements as locators public static final By usernameField = By.name("username");

$(Login.usernameField).setVal("guest");

Compare

Page 72: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Functional “Scales”

● Main cons of Procedural approach is that it may be not DRY

● In most cases you can fix this with high-order functions in much more concise way than with OOP

– Though less obvious for non-FP user

Page 73: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Ideas for improvements● Use Groovy as main language

– in order to simplify implementation.

– Finally Java is the OOP language● and not adapted for both procedural and functional styles.

– In Groovy OOP may be not “bulky”

● and with some functional & metaprogramming features you can achieve the same level of simplicity still powerful

– and easy to explain to juniors “how to use” (though not “how to understand details”)

Page 74: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Did it work for Manual QA?

Page 75: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Demo

Page 76: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Q&A

Page 77: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Resources, Links

● Src of example test framework:

https://github.com/yashaka/gribletest

● Programming Paradigms Comparison: https://bitbucket.com/yashaka/oopbucket/src

● Functional Thinking articles: http://www.ibm.com/developerworks/views/java/libraryview.jsp?search_by=functional+thinking

● Application under test used in easyb examples: http://grible.org/download.php

● Instruments

– http://selenide.org/

– http://code.google.com/p/selenium/wiki/LoadableComponent

Page 78: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

● To Artem Chernysh for implementation of main base part of the test framework for this presentation

– https://github.com/elaides/gribletest● To Maksym Barvinskyi for application under test

– http://grible.org/

Page 79: Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

Contacts

[email protected]

● skype: yashaolin

● twitter: @yashaka

● http://www.linkedin.com/in/iakivkramarenko