Three simple chords of Alternative
“PageObjects” and Hardcore of
LoadableComponents
Iakiv Kramarenko
Conventions :)
● Sympathy colors***:
Green =
Orange =
Red =
*** often are subjective and applied to specific context ;)
● 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
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
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 :)
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
Choosing Programming Paradigm
For WebUI Automation
Based on https://bitbucket.org/yashaka/oopbucket/src
OOP or not to OOP
That is the question
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?
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?
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
Sometimes...
● Conventions can be very easy
● No severe refactoring is coming
– Test Automation Project is not a NASA Space Shuttle ;).
So Think Always!
Procedural
Functional
OOP
And
Balance!
Classic PageObject Pattern
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;
}
Involving
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;
}
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;
}
“Procedural” approach to
implement “PageObjects”
PageUtils
public 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();
}
}
A
l
t
e
r
n
a
t
I
v
E
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();
}
}
A
l
t
e
r
n
a
t
I
v
E
PageUtils usage
Login.open(baseurl);
Login.doLogin(username, password);
Home.addProduct("Product_1");
UserPanel.doLogout();
Login.container().shouldBe(visible);
PageObjects usage
loginPage = new LoginPage(baseurl);
loginPage.open();
loginPage.doLogin(username, password);
homePage = new HomePage();
homePage.addProduct("Product_1");
homePage.doLogout();
loginPage.getContainer().shouldBe(visible);
C
o
m
p
a
r
e
Three Chords of “Procedural”
PageUtils :)
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"));
}
2. Modularity
Collect your helpers in classes of correspondent context
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
P.S. Be smart ;)
– You can't use inheritance.
● If you have any conventions you need to remember to
follow them
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:)
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)
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
This is how
your test
model may
look
“What are those classes
in pagegetters
package?”
:)
Here come
Loadable 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;
}
What's the point?
O_o
From :(
Login.open(baseurl);
Home.open(username, password);
Home.ensureHasProduct("Product_1");
Product.open("Product_1");
ProductTestTables.open();
ProductTestTables.addCategoryButton().shouldBe
(visible);
Technically To
(new ProductTestTablesPage(
new ProductPage(
new HomePage(
new LoginPage(baseurl),
username, password),
"Product_1"))).get();
Actually To :)
ProductTestTables.page("Product_1").get();
Selenium LoadableComponent
public 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;
}
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.
Once you need some abstract
classes to DRY your code...
public abstract class
AbstractPage<T extends SlowLoadableComponent<T>>
extends SlowLoadableComponent<T>{
O_O
Typical isLoaded()
Implementations
'url-based' isLoaded implementation
protected void isLoaded() throws Error {
String url = driver.getCurrentUrl();
assertTrue(url.contains(pageUrl));
}
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");
}
}
Once you use @FindBy
public void isLoaded() throws Error {
if (loginButton != null) {
assertElementIsDisplayed(loginButton);
} else {
fail("Login button is not loaded");
}
}
Typical Selenide isLoaded()
implementation
public void isLoaded(){
Login.container().shouldBe(visible);
}
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>
If you wish...
public abstract class AbstractPage extends
SelenideLoadableComponent {
public abstract void isLoaded();
}
Home.page().get();
doCrazyStuff();
Home.page().isLoaded() // still?
Implementation Example
Initialize
public class ProductPage extends SelenideLoadablePage{
private HomePage parent;
protected String productName;
public ProductPage(HomePage parent, String productName){
this.parent = parent;
this.productName = productName;
}
….
Load
protected void load() {
parent.get();
Home.ensureHasProduct(productName);
Product.open(productName);
}
isLoaded()
public void isLoaded() {
Breadcrumb.productLink(productName).shouldBe(visible);
Product.testTableItem().shouldBe(visible);
}
Factory
public class Product {
public static ProductPage page(String productName){
return new ProductPage(Home.page(), productName);
}
…
}
If it would be so
“simple”...
But it would not :p
public 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;
}
...
Initialize
public 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();
}
….
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);
}
}
Though...
The beast is not so scary after you write up to 10 first Lions
Components :)
And
You still can live only with PageUtils and keep LoadableComponents
as options to be implemented by crazy devs:)
QA Dev
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 :)
When Maybe Use?
● No confident urls
● Complex “deep” page hierarchy
● Authorization > Menus > SubMenus > Tabs > Extra >
Extra...
When Use?
● Desired dependent End to End scenarios with “walking
through pages” feature
– emulating real user experience
– big amount of such test cases
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
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
All Together
● 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
Ideas to think about
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
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
LoadableComponent Factory
Too more calls to page() ?
Use Singleton Pattern
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");
C
o
m
p
a
r
e
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
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”)
Did it work for Manual QA?
Demo
Q&A
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=fun
● Application under test used in easyb examples:
http://grible.org/download.php
● Instruments
– http://selenide.org/
– http://code.google.com/p/selenium/wiki/LoadableComponent
● 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/
Contacts
● yashaka@gmail.com
● skype: yashaolin
● twitter: @yashaka
● http://www.linkedin.com/in/iakivkramarenko

Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

  • 1.
    Three simple chordsof Alternative “PageObjects” and Hardcore of LoadableComponents Iakiv Kramarenko
  • 2.
    Conventions :) ● Sympathycolors***: Green = Orange = Red = *** often are subjective and applied to specific context ;)
  • 3.
    ● At projectwith 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
  • 4.
    Testing single pageweb 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
  • 5.
    Met Requirements ● Automatehigh 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 :)
  • 6.
    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
  • 7.
    Choosing Programming Paradigm ForWebUI Automation Based on https://bitbucket.org/yashaka/oopbucket/src
  • 8.
    OOP or notto OOP That is the question
  • 9.
    OOP can imposeyou 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?
  • 10.
    OOP can giveyou ● 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?
  • 11.
    Sometimes... ● Batch/common operationsmay 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
  • 12.
    Sometimes... ● Conventions canbe very easy ● No severe refactoring is coming – Test Automation Project is not a NASA Space Shuttle ;).
  • 13.
  • 14.
  • 15.
  • 16.
    public class LoginClassicPageObjectextends 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; }
  • 17.
  • 18.
    public class LoginSelenidePageObjectextends 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; }
  • 19.
    public class LoginSelenidePageObject2extends 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; }
  • 20.
  • 21.
    PageUtils public class Login{ publicstatic 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(); } } A l t e r n a t I v E
  • 22.
    Or... public class Login{ publicstatic 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(); } } A l t e r n a t I v E
  • 23.
    PageUtils usage Login.open(baseurl); Login.doLogin(username, password); Home.addProduct("Product_1"); UserPanel.doLogout(); Login.container().shouldBe(visible); PageObjectsusage loginPage = new LoginPage(baseurl); loginPage.open(); loginPage.doLogin(username, password); homePage = new HomePage(); homePage.addProduct("Product_1"); homePage.doLogout(); loginPage.getContainer().shouldBe(visible); C o m p a r e
  • 24.
    Three Chords of“Procedural” PageUtils :)
  • 25.
    1. Abstraction Factor outimplementation 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")); }
  • 26.
    2. Modularity Collect yourhelpers in classes of correspondent context
  • 27.
    3. Try toBe “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
  • 28.
    P.S. Be smart;) – You can't use inheritance. ● If you have any conventions you need to remember to follow them
  • 29.
    When Use? ● Needto 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:)
  • 30.
    When maybe notuse? ● All committers to test framework are either Senior QA Automation or Developers ● No need to teach Manual QA/Juniors ● No tough deadlines ● Java (only)
  • 31.
    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
  • 32.
    This is how yourtest model may look
  • 33.
    “What are thoseclasses in pagegetters package?” :)
  • 34.
    Here come Loadable Components... publicabstract class SimpleLoadableComponent { public void get() { try { isLoaded(); } catch (Error e) { load(); isLoaded(); } } protected abstract void load(); protected abstract void isLoaded() throws Error; }
  • 35.
  • 36.
  • 37.
    Technically To (new ProductTestTablesPage( newProductPage( new HomePage( new LoginPage(baseurl), username, password), "Product_1"))).get();
  • 38.
  • 39.
    Selenium LoadableComponent public abstractclass 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; }
  • 40.
    Ajax? > Selenium SlowLoadableComponent Calmdown, 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.
  • 41.
    Once you needsome abstract classes to DRY your code... public abstract class AbstractPage<T extends SlowLoadableComponent<T>> extends SlowLoadableComponent<T>{ O_O
  • 42.
  • 43.
    'url-based' isLoaded implementation protectedvoid isLoaded() throws Error { String url = driver.getCurrentUrl(); assertTrue(url.contains(pageUrl)); }
  • 44.
    If you wantto 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"); } }
  • 45.
    Once you use@FindBy public void isLoaded() throws Error { if (loginButton != null) { assertElementIsDisplayed(loginButton); } else { fail("Login button is not loaded"); } }
  • 46.
    Typical Selenide isLoaded() implementation publicvoid isLoaded(){ Login.container().shouldBe(visible); }
  • 47.
    Selenide LoadableComponent public abstractclass 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>
  • 48.
    If you wish... publicabstract class AbstractPage extends SelenideLoadableComponent { public abstract void isLoaded(); } Home.page().get(); doCrazyStuff(); Home.page().isLoaded() // still?
  • 49.
  • 50.
    Initialize public class ProductPageextends SelenideLoadablePage{ private HomePage parent; protected String productName; public ProductPage(HomePage parent, String productName){ this.parent = parent; this.productName = productName; } ….
  • 51.
    Load protected void load(){ parent.get(); Home.ensureHasProduct(productName); Product.open(productName); }
  • 52.
    isLoaded() public void isLoaded(){ Breadcrumb.productLink(productName).shouldBe(visible); Product.testTableItem().shouldBe(visible); }
  • 53.
    Factory public class Product{ public static ProductPage page(String productName){ return new ProductPage(Home.page(), productName); } … }
  • 54.
    If it wouldbe so “simple”...
  • 55.
    But it wouldnot :p public 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; } ...
  • 56.
    Initialize public class ProductPageextends 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(); } ….
  • 57.
    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); } }
  • 58.
    Though... The beast isnot so scary after you write up to 10 first Lions Components :)
  • 59.
    And You still canlive only with PageUtils and keep LoadableComponents as options to be implemented by crazy devs:) QA Dev
  • 60.
    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 :)
  • 61.
    When Maybe Use? ●No confident urls ● Complex “deep” page hierarchy ● Authorization > Menus > SubMenus > Tabs > Extra > Extra...
  • 62.
    When Use? ● Desireddependent End to End scenarios with “walking through pages” feature – emulating real user experience – big amount of such test cases
  • 63.
    When maybe notuse? ● 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
  • 64.
    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
  • 65.
  • 66.
    ● 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
  • 67.
  • 68.
    LoadableComponent ● Is notPageObject – 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
  • 69.
    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
  • 70.
    LoadableComponent Factory Too morecalls to page() ? Use Singleton Pattern
  • 71.
    PageUtils Page elements asfunctions 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"); C o m p a r e
  • 72.
    Functional “Scales” ● Maincons 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
  • 73.
    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”)
  • 74.
    Did it workfor Manual QA?
  • 75.
  • 76.
  • 77.
    Resources, Links ● Srcof 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=fun ● Application under test used in easyb examples: http://grible.org/download.php ● Instruments – http://selenide.org/ – http://code.google.com/p/selenium/wiki/LoadableComponent
  • 78.
    ● To ArtemChernysh 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/
  • 79.
    Contacts ● [email protected] ● skype:yashaolin ● twitter: @yashaka ● http://www.linkedin.com/in/iakivkramarenko