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