Three simple approaches for implementing web UI automation were discussed: PageObjects, LoadableComponents, and PageUtils. PageObjects use object-oriented inheritance but can become complex. LoadableComponents encapsulate page loading logic but may lead to overcomplicated hierarchies. PageUtils use a procedural approach with functions to represent pages and elements, avoiding complexity while enabling easy tests. Functional programming concepts like higher-order functions can help address lack of DRYness in the procedural approach. The best approach depends on the project needs in terms of teaching others, speed of development, and ability to handle future changes.
social pharmacy d-pharm 1st year by Pragati K. Mahajan
Three simple chords of Alternative "PageObjects
1. Three simple chords of Alternative
“PageObjects” and Hardcore of
LoadableComponents
Iakiv Kramarenko
2. Conventions :)
● Sympathy colors***:
Green =
Orange =
Red =
*** often are subjective and applied to specific context ;)
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. 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. 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. 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
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. 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. 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. Sometimes...
● Conventions can be very easy
● No severe refactoring is coming
– Test Automation Project is not a NASA Space Shuttle ;).
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. 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
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. P.S. Be smart ;)
– You can't use inheritance.
● If you have any conventions you need to remember to
follow them
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. 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. 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
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. Once you need some abstract
classes to DRY your code...
public abstract class
AbstractPage<T extends SlowLoadableComponent<T>>
extends SlowLoadableComponent<T>{
O_O
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. Once you use @FindBy
public void isLoaded() throws Error {
if (loginButton != null) {
assertElementIsDisplayed(loginButton);
} else {
fail("Login button is not loaded");
}
}
48. If you wish...
public abstract class AbstractPage extends
SelenideLoadableComponent {
public abstract void isLoaded();
}
Home.page().get();
doCrazyStuff();
Home.page().isLoaded() // still?
59. And
You still can live 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?
● Desired dependent End to End scenarios with “walking
through pages” feature
– emulating real user experience
– big amount of such test cases
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. 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
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. 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
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. 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. 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”)
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. ● 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/