Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

2,983 views

Published on

***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.

Published in: Education

Three Simple Chords of Alternative PageObjects and Hardcore of LoadableComponents

  1. 1. Three simple chords of Alternative “PageObjects” and Hardcore of LoadableComponents Iakiv Kramarenko
  2. 2. Conventions :) ● Sympathy colors***: Green = Orange = Red = *** often are subjective and applied to specific context ;)
  3. 3. ● 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
  4. 4. 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
  5. 5. 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 :)
  6. 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. 7. Choosing Programming Paradigm For WebUI Automation Based on https://bitbucket.org/yashaka/oopbucket/src
  8. 8. OOP or not to OOP That is the question
  9. 9. 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?
  10. 10. 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?
  11. 11. 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
  12. 12. Sometimes... ● Conventions can be very easy ● No severe refactoring is coming – Test Automation Project is not a NASA Space Shuttle ;).
  13. 13. So Think Always!
  14. 14. Procedural Functional OOP And Balance!
  15. 15. Classic PageObject Pattern
  16. 16. 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; }
  17. 17. Involving
  18. 18. 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; }
  19. 19. 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; }
  20. 20. “Procedural” approach to implement “PageObjects”
  21. 21. 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
  22. 22. 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
  23. 23. 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
  24. 24. Three Chords of “Procedural” PageUtils :)
  25. 25. 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")); }
  26. 26. 2. Modularity Collect your helpers in classes of correspondent context
  27. 27. 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
  28. 28. P.S. Be smart ;) – You can't use inheritance. ● If you have any conventions you need to remember to follow them
  29. 29. 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:)
  30. 30. 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)
  31. 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. 32. This is how your test model may look
  33. 33. “What are those classes in pagegetters package?” :)
  34. 34. 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; }
  35. 35. What's the point? O_o
  36. 36. From :( Login.open(baseurl); Home.open(username, password); Home.ensureHasProduct("Product_1"); Product.open("Product_1"); ProductTestTables.open(); ProductTestTables.addCategoryButton().shouldBe (visible);
  37. 37. Technically To (new ProductTestTablesPage( new ProductPage( new HomePage( new LoginPage(baseurl), username, password), "Product_1"))).get();
  38. 38. Actually To :) ProductTestTables.page("Product_1").get();
  39. 39. 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; }
  40. 40. 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.
  41. 41. Once you need some abstract classes to DRY your code... public abstract class AbstractPage<T extends SlowLoadableComponent<T>> extends SlowLoadableComponent<T>{ O_O
  42. 42. Typical isLoaded() Implementations
  43. 43. 'url-based' isLoaded implementation protected void isLoaded() throws Error { String url = driver.getCurrentUrl(); assertTrue(url.contains(pageUrl)); }
  44. 44. 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"); } }
  45. 45. Once you use @FindBy public void isLoaded() throws Error { if (loginButton != null) { assertElementIsDisplayed(loginButton); } else { fail("Login button is not loaded"); } }
  46. 46. Typical Selenide isLoaded() implementation public void isLoaded(){ Login.container().shouldBe(visible); }
  47. 47. 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>
  48. 48. If you wish... public abstract class AbstractPage extends SelenideLoadableComponent { public abstract void isLoaded(); } Home.page().get(); doCrazyStuff(); Home.page().isLoaded() // still?
  49. 49. Implementation Example
  50. 50. Initialize public class ProductPage extends SelenideLoadablePage{ private HomePage parent; protected String productName; public ProductPage(HomePage parent, String productName){ this.parent = parent; this.productName = productName; } ….
  51. 51. Load protected void load() { parent.get(); Home.ensureHasProduct(productName); Product.open(productName); }
  52. 52. isLoaded() public void isLoaded() { Breadcrumb.productLink(productName).shouldBe(visible); Product.testTableItem().shouldBe(visible); }
  53. 53. Factory public class Product { public static ProductPage page(String productName){ return new ProductPage(Home.page(), productName); } … }
  54. 54. If it would be so “simple”...
  55. 55. 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; } ...
  56. 56. 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(); } ….
  57. 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. 58. Though... The beast is not so scary after you write up to 10 first Lions Components :)
  59. 59. And You still can live only with PageUtils and keep LoadableComponents as options to be implemented by crazy devs:) QA Dev
  60. 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. 61. When Maybe Use? ● No confident urls ● Complex “deep” page hierarchy ● Authorization > Menus > SubMenus > Tabs > Extra > Extra...
  62. 62. When Use? ● Desired dependent End to End scenarios with “walking through pages” feature – emulating real user experience – big amount of such test cases
  63. 63. 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
  64. 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. 65. All Together
  66. 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. 67. Ideas to think about
  68. 68. 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
  69. 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. 70. LoadableComponent Factory Too more calls to page() ? Use Singleton Pattern
  71. 71. 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
  72. 72. 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
  73. 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. 74. Did it work for Manual QA?
  75. 75. Demo
  76. 76. Q&A
  77. 77. 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
  78. 78. ● 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/
  79. 79. Contacts ● yashaka@gmail.com ● skype: yashaolin ● twitter: @yashaka ● http://www.linkedin.com/in/iakivkramarenko

×