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.
@yashaka 01.2017
KISS PageObjects
Plan
Intro
Classic examples
KISS PageObjects Demo
Retrospective
Q&A
Afterwords
There are good practices in context,
but there are no best practices.
(c) Cem Kaner, James Bach
Afterwords Preface
There are good practices in context,
but there are no best practices.
(c) Cem Kaner, James Bach
Intro
KISS?
Keep It Simple Stupid!
PageObjects?
–Martin Fowler
Page objects are a classic example of encapsulation - they hide
the details of the UI structure and widgetr...
(c) https://martinfowler.com/bliki/PageObject.html
The boring part :p
Classic “Horror” Examples
Classic Usage of PageObjects
@Test

public void search() {

Google google = new Google(driver);

google.open().search("Sel...
Classic PageObjects (PageFactory)
public class Google {

private WebDriver driver;

private WebDriverWait wait;



@FindBy...
Classic PageObjects (PageFactory)
public class Google {

private WebDriver driver;

private WebDriverWait wait;



@FindBy...
Classic PageObjects (PageFactory)
…
public Google open() {

this.driver.get("http: //google.com/ncr");

return this;

}


...
– https://github.com/SeleniumHQ/selenium/wiki/LoadableComponent
“The LoadableComponent is a base class that aims to make
w...
Classic
public class Google extends LoadableComponent<Google>{

…



protected void load() {

this.driver.get("http: //goo...
Classic
public class Google extends LoadableComponent<Google>{

…



protected void load() {

this.driver.get("http: //goo...
public class Google extends LoadableComponent<Google>{

…



protected void load() {

this.driver.get("http: //google.com/...
“Super-duper” WaitingLoadableComponents
public class Search extends LoadingComponent<Search> {



@FindBy(name = "q")

pri...
“Super-duper” WaitingLoadableComponents
public class Results extends LoadingComponent<Results> {



@FindBy(css = ".srg>.g...
“Super-duper” WaitingLoadableComponents
public class GooglePage extends WaitingLoadableComponent<GooglePage> {

private Se...
“Super-duper” WaitingLoadableComponents
public class GoogleSearchResultsPage 

extends LoadingComponent<GoogleSearchResult...
“Super-duper” WaitingLoadableComponents
@Test

public void search() {

GooglePage google = new GooglePage(driver);

Google...
“The LoadableComponent is a base class that aims to make
writing PageObjects less painful.” o_O ???
WTF?
PageObjects Demo
Demo src log
PageObjects Usage
@Test

public void search() {

new Google().open().search("Selenide");

new SearchResults()

.shouldHave...
PageObjects Implementation
public class Google {

public Google open() {

Selenide.open("/ncr");

return this;

}



publi...
PageObjects Implementation
public class SearchResults {

private ElementsCollection elements(){

return $$(".srg>.g");

}
...
PageObjects Implementation
public class NewPage {

public void shouldHaveTitle(String text) {

Selenide.Wait().until(title...
Retrospective
So why do we need
PageObjects?
Despite of DRYing the code…
So why do we need PageObjects?
Despite of DRYing the code…
–Martin Fowler
Page objects are a classic example of encapsulation - they hide
the details of the UI structure and widgetr...
Why may we need Encapsulation?
• Encapsulate for cohesion?
• that’s ok… pretty reasonable to keep related things together
...
Why may we need Encapsulation?
• Encapsulate to hide something internal to be not broken by client
(tests code)?
• but wha...
Why may we need Encapsulation?
• Encapsulate to hide something internal so on change it will not
broke the client (tests c...
Why may we need Encapsulation?
• Encapsulate to narrow User Oriented behavioural API to be
readable enough and convenient ...
A point to think about…
“Encapsulate to narrow User Oriented behavioural API to be readable
enough and convenient in usage...
@Test

public void search() {

open("/ncr");

$(By.name("q")).setValue("Selenide").pressEnter();

$$(".srg>.g").shouldHave...
@Test

public void search() {

open("/ncr");

$(By.name("q")).setValue("Selenide").pressEnter();

$$(".srg>.g").shouldHave...
@Test

public void search() {

open("/ncr");

$(By.name("q")).setValue("Selenide").pressEnter();

$$(".srg>.g").shouldHave...
@Test

public void search() {

open("/ncr");

$(By.name("query")).setValue("Selenide").pressEnter();

$$(".result").should...
@Test

public void search() {

open("/ncr");

$(By.name("q")).setValue("Selenide").pressEnter();

$$(".srg>.g").shouldHave...
@Test

public void search() {

open("/ncr");

$(By.name("q")).setValue("Selenide").pressEnter();

$$(".srg>.g").shouldHave...
For “Sr. Automation Engineers”?
@Test

public void search() {

Google google = new Google();

google.open().search("Seleni...
For “Sr. Automation Engineers”?
@Test

public void search() {

Google google = new Google();

google.open().search("Seleni...
For “Sr. Automation Engineers”?
@Test

public void search() {

Google google = new Google();

google.open().search("Seleni...
Remember classic version?
@Test

public void search() {

Google google = new Google(driver);

google.open().search("Seleni...
Remember classic version?
public class Google {

private WebDriver driver;

private WebDriverWait wait;



@FindBy(name = ...
Remember classic version?
public class Google {

private WebDriver driver;

private WebDriverWait wait;



@FindBy(name = ...
for “Sr. Automation version”
public class Google {

public Google open() {

Selenide.open("/ncr");

return this;

}



pub...
for “Sr. Automation version”
public class Google {

public Google open() {

Selenide.open("/ncr");

return this;

}



pub...
For “Sr. Automation Engineers”?
@Test

public void search() {

Google google = new Google();

google.open().search("Seleni...
For “Jr. Automation Engineers”?
@Test

public void search() {

new Google().open().search("Selenide");

new SearchResults(...
For “Jr. Automation Engineers”?
@Test

public void search() {

new Google().open().search("Selenide");

new SearchResults(...
For “Jr. Automation Engineers”?
@Test

public void search() {

new Google().open().search("Selenide");

new SearchResults(...
For “Manual Testers”?
@Test

public void search() {

google.home.open().search("Selenide");

google.results

.shouldHaveSi...
For “Manual Testers”?
@Test

public void search() {

google.home.open().search("Selenide");

google.results

.shouldHaveSi...
For “Manual Testers”?
@Test

public void search() {

google.home.open().search("Selenide");

google.results

.shouldHaveSi...
For “Manual Testers”?
@Test

public void search() {

google.home.open().search("Selenide");

google.results

.shouldHaveSi...
Why should we care?
To be efficient!
Ok, Where is KISS here?
It is exactly about leveraging
convenient techniques
in concrete context
to make it simpler and ef...
Reviewing some techniques of
PageObjects design
from KISS point of view
–Martin Fowler
I favor having no assertions in page objects. I think you can avoid
duplication by providing assertion libr...
Recall “For Sr. Automation Engineers” version…
@Test

public void search() {

Google google = new Google();

google.open()...
Assertion-free PageObject
public class Google {

public Google open() {

Selenide.open("/ncr");

return this;

}



public...
But… what if…
• The end user is not “Sr. Automation” ?
• Finally he may be even a Manual Tester…
• The UI is so complex th...
Assertions-free PageObjects?
• break some forms of encapsulation
• reveal too much of technical details (in context of end...
StepsObject?
GIVEN PageObject as an HTML Page API
THEN StepsObject stands for User Behaviour API
StepsObjects?
@Test

public void search() {

new GoogleUser().opensGoogle()

.searches("Selenide")

.expectsNumberOfResult...
StepsObjects?
public class GoogleUser {



private final Google google = new Google();



public GoogleUser opensGoogle() ...
StepsObjects?
public class UserOnResultsPage extends GoogleUser {



private final Google google = new Google();



public...
StepsObjects?
public class UserOnNewPage {



public UserOnNewPage expectsTitle(String text) {

Selenide.Wait().until(titl...
HTML-only PageObject?
public class Google {



public SelenideElement search() {

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

}



public Ele...
StepsObjects?
• over-engineered => at least two classes instead of one
• lower cohesion
• related things are not kept in o...
Assertions-included PageObjects (HTML + Steps)!
• full encapsulation
• Tell Don’t Ask
• high cohesion
• End User oriented....
Assertions-included PageObjects!
• full encapsulation
• Tell Don’t Ask
• high cohesion
• KISS: one class instead of two on...
Assertions-included PageObjects!
• full encapsulation
• Tell Don’t Ask
• high cohesion
• KISS: one class instead of two on...
(For “Jr. Automation Engineers” versions)
public class SearchResults {


private ElementsCollection elements(){

return $$...
Assertions-included PageObjects!
• full encapsulation
• Tell Don’t Ask
• high cohesion
• KISS: one class instead of two on...
• full encapsulation
• Tell Don’t Ask
• high cohesion
• KISS: one class instead of two ones for the same thing from the Us...
(For “Manual Testers” version)
public class SearchResults {



private ElementsCollection elements(){

return $(".srg>.g")...
(For “Manual Testers” version)
public class Result{

private final SelenideElement container;



public Result(SelenideEle...
(For “Manual Testers” version)
@Test

public void search() {

google.home.open().search("Selenide");

google.results

.sho...
KISS Pageobjects for Jr. and Manual Testers, where
extra reporting is needed
=
2 in 1:
HTML PageObjects
+
StepsObjects wit...
KISS
*=
simplicity instead of over-engineering
=
YAGNI = You Ain’t Gonna Need It
public class Google {

private SelenideElement searchInput = $(By.name("q"));



public Google open() {

Selenide.open("/n...
public class Google {

public ElementsCollection results(){

return $$(".srg>.g");

}
public class Google {

public Elemen...
public class Google {

public final ElementsCollection results = $$(".srg>.g");

public class Google {

public ElementsCol...
public class Google {

public final ElementsCollection results = $$(".srg>.g");

though this teaches you kind of the “true...
Driver management?
Driver management?
• Why to bother with this bulky management if YAGNI?
• the only relevant case is when you have to work ...
But sometimes that’s the case…
• Why to bother with this bulky management if YAGNI?
• the only relevant case is when you h...
Winning a bit of speed…
@Test

public void shareMessageToFollowers() {

SelenideDriver selenideBrowser = new SelenideDrive...
By making PageObjects more “bulky” :|
public class Diaspora {

private final SelenideDriver driver;



public Diaspora(Sel...
Instead of much cleaner
public class Diaspora {



public Diaspora open() {

Selenide.open("/");

return this;

}



publi...
That can be used to achieve the same goal by
using corresponding API helpers:
@Test

public void shareMessageToFollowers()...
Still enough open points to take into account…
• Will API calls simulate the same real behaviour?
• Like Logged in user do...
Still enough open points to take into account…
• Like Logged in user does nothing but his stream is updated…
• Do we actua...
Smarter simulation with API calls@Test

public void authorSharesMessage() {

new ApiCall().ensureSignedIn(

SecretData.Sel...
Still enough open points to take into account…
• Like Logged in user does nothing but his stream is updated…
• Do we actua...
Smarter simulation with API calls@Test

public void authorSharesMessage() {

new ApiCall().ensureSignedIn(

SecretData.Sel...
Atomic, faster, and so more efficient than “real simulation”
@Test

public void authorSharesMessage() {

new ApiCall().ensu...
Still enough open points to take into account…
• But sometimes… Life is complicated:) And for some mystic reason
we “can’t...
Only then we need “explicit driver management”
SelenideDriver selenideBrowser = new SelenideDriver(new FirefoxDriver());

...
public class Diaspora {

private final SelenideDriver driver;



public Diaspora(SelenideDriver driver) {

this.driver = d...
KISS
=
not necessarily Easiness
Consider…
public class Widget {

private final SelenideElement container;



public Widget(SelenideElement container) {

t...
Easy
public class Post extends Widget{


public Post(SelenideElement container) {

super(container);

}



public void sho...
Easy
public class Post extends Widget{


public Post(SelenideElement container) {

super(container);

}



public void sho...
Simple
public class Post {


private final SelenideElement container;



public Post(SelenideElement container) {

this.co...
Simple is not Easy
–Rich Hickey
“Simple Made Easy”
assert KISS != magic
assert KISS == explicit
@Test

public void shareMessage() {

new Diaspora().open().signIn(

SecretData.Selenide.username, SecretData.Selenide.pass...
@Test

public void shareMessage() {

new Diaspora().open().signIn(

SecretData.Selenide.username, SecretData.Selenide.pass...
WaitingLoadable?
Recall “Super-duper” WaitingLoadableComponents
@Test

public void search() {

GooglePage google = new GooglePage(driver);
...
But what if do not want to wait all results? We are
interested only in the first one!
@Test

public void search() {

Google...
WaitingLoadable may break the real user behaviour
in some contexts…
@Test

public void search() {

GooglePage google = new...
KISS Answer
• User oriented automation based on waiting loadable elements over
loadable pages waiting their components
Diaspora Demo
How often do you open Facebook, and proceed to profile or messages,
while the stream is not loaded yet? ;)
So KISS leads to
• User Oriented waiting
• No explicit waits to finalise step
• => Explicit Test Logic (No test-logic asser...
PageObjects Summary
• Readable and user oriented
• not HTML oriented
• Assert-steps included
• if “newcomers-oriented”
• T...
PageObjects Secret ;)
NO Selenium Webdriver,
use more concise wrappers!
PageObjects Secret ;)
Prefer
test automation tools
over
browser automation tools
Afterwords
There are good practices in context,
but there are no best practices.
(c) Cem Kaner, James Bach
Q&A
Thank you!
@yashaka 01.2017
github.com/automician
automician.com
seleniumcourses.com
yashaka @
Upcoming SlideShare
Loading in …5
×

Kiss PageObjects [01-2017]

2,806 views

Published on

This talk was given at "The KISS Method and PageObjects" meetup (https://goo.gl/2DBuxj).

The talk is about KISS (Keep It Simple Stupid) principle applied to PageObjects design with a comparison to other more classic approaches.

The slides contain both Selenium Webdriver and Selenide examples.

src code for the talk: https://github.com/yashaka/talks/tree/master/kiss-pageobjects

Published in: Technology
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Kiss PageObjects [01-2017]

  1. 1. @yashaka 01.2017 KISS PageObjects
  2. 2. Plan Intro Classic examples KISS PageObjects Demo Retrospective Q&A
  3. 3. Afterwords There are good practices in context, but there are no best practices. (c) Cem Kaner, James Bach
  4. 4. Afterwords Preface There are good practices in context, but there are no best practices. (c) Cem Kaner, James Bach
  5. 5. Intro
  6. 6. KISS?
  7. 7. Keep It Simple Stupid!
  8. 8. PageObjects?
  9. 9. –Martin Fowler Page objects are a classic example of encapsulation - they hide the details of the UI structure and widgetry from other components (the tests)
  10. 10. (c) https://martinfowler.com/bliki/PageObject.html
  11. 11. The boring part :p Classic “Horror” Examples
  12. 12. Classic Usage of PageObjects @Test
 public void search() {
 Google google = new Google(driver);
 google.open().search("Selenide");
 wait.until(numberOf(google.getResults(), 10));
 assertThat(google.getResults().get(0).getText(), 
 containsString("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 Google google = new Google(driver);
 google.open().search("Selenide");
 google.followResultLink(0);
 wait.until(titleIs("Selenide: concise UI tests in Java"));
 }
  13. 13. Classic PageObjects (PageFactory) public class Google {
 private WebDriver driver;
 private WebDriverWait wait;
 
 @FindBy(name = "q")
 private WebElement searchInput;
 
 @FindBy(css = ".srg>.g")
 private List<WebElement> results;
 
 public List<WebElement> getResults() {
 return this.results;
 }
 
 public Google(WebDriver driver) {
 this.driver = driver;
 this.wait = new WebDriverWait(driver, 4);
 PageFactory.initElements(driver, this);
 } …
  14. 14. Classic PageObjects (PageFactory) public class Google {
 private WebDriver driver;
 private WebDriverWait wait;
 
 @FindBy(name = "q")
 private WebElement searchInput;
 
 @FindBy(css = ".srg>.g")
 private List<WebElement> results;
 
 public List<WebElement> getResults() {
 return this.results;
 }
 
 public Google(WebDriver driver) {
 this.driver = driver;
 this.wait = new WebDriverWait(driver, 4);
 PageFactory.initElements(driver, this);
 } …
  15. 15. Classic PageObjects (PageFactory) … public Google open() {
 this.driver.get("http: //google.com/ncr");
 return this;
 }
 
 public Google search(String text) {
 this.searchInput.sendKeys(text + Keys.ENTER);
 return this;
 }
 
 public void followResultLink(int index) {
 wait.until(numberIsAtLeast(results, index + 1))
 .get(index)
 .findElement(By.cssSelector(".r>a"))
 .click();
 }
 }
  16. 16. – https://github.com/SeleniumHQ/selenium/wiki/LoadableComponent “The LoadableComponent is a base class that aims to make writing PageObjects less painful.” PageObjects as LoadableComponents
  17. 17. Classic public class Google extends LoadableComponent<Google>{
 …
 
 protected void load() {
 this.driver.get("http: //google.com/ncr");
 }
 
 protected void isLoaded() throws Error {
 String url = driver.getCurrentUrl();
 assertTrue("Not on the google page: " + url, url.contains(" www.google.com"));
 } public class Google {
 …
 
 public Google open() {
 this.driver.get("http: //google.com/ncr");
 return this;
 } LoadableComponent
  18. 18. Classic public class Google extends LoadableComponent<Google>{
 …
 
 protected void load() {
 this.driver.get("http: //google.com/ncr");
 }
 
 protected void isLoaded() throws Error {
 String url = driver.getCurrentUrl();
 assertTrue("Not on the google page: " + url, url.contains(" www.google.com"));
 } public class Google {
 …
 
 public Google open() {
 this.driver.get("http: //google.com/ncr");
 return this;
 } LoadableComponent
  19. 19. public class Google extends LoadableComponent<Google>{
 …
 
 protected void load() {
 this.driver.get("http: //google.com/ncr");
 }
 
 protected void isLoaded() throws Error {
 String url = driver.getCurrentUrl();
 assertTrue("Not on the google page: " + url, url.contains(" www.google.com"));
 } “The LoadableComponent is a base class that aims to make writing PageObjects less painful.” o_O ??? LoadableComponent
  20. 20. “Super-duper” WaitingLoadableComponents public class Search extends LoadingComponent<Search> {
 
 @FindBy(name = "q")
 private WebElement element;
 
 public Search(WebDriver driver) {
 super(driver);
 }
 
 public boolean isLoaded() {
 return element.isDisplayed();
 }
 
 public Search query(String text) {
 this.element.clear();
 this.element.sendKeys(text + Keys.ENTER);
 return this;
 }
 }
  21. 21. “Super-duper” WaitingLoadableComponents public class Results extends LoadingComponent<Results> {
 
 @FindBy(css = ".srg>.g")
 private List<WebElement> elements;
 
 public Results(WebDriver driver) {
 super(driver);
 }
 
 public boolean isLoaded() {
 return this.elements.size() == 10;
 }
 
 public void followResultLink(int index){
 this.elements.get(index) .findElement(By.cssSelector(".r>a")).click();
 } …
  22. 22. “Super-duper” WaitingLoadableComponents public class GooglePage extends WaitingLoadableComponent<GooglePage> {
 private Search search;
 
 public GooglePage(WebDriver driver) {
 super(driver);
 this.search = new Search(driver);
 }
 
 protected void load() {
 this.driver.get("http: //google.com/ncr");
 }
 
 public boolean isLoaded() {
 return this.search.isLoaded();
 }
 
 public GoogleSearchResultsPage search(String text) {
 this.search.query(text);
 return new GoogleSearchResultsPage(this.driver).get();
 }
 } “super” waiting for loading trick
  23. 23. “Super-duper” WaitingLoadableComponents public class GoogleSearchResultsPage 
 extends LoadingComponent<GoogleSearchResultsPage> {
 private Search search;
 private Results results;
 
 public GoogleSearchResultsPage(WebDriver driver) {
 super(driver);
 this.search = new Search(driver);
 this.results = new Results(driver);
 }
 
 public boolean isLoaded() {
 return this.results.isLoaded();
 }
 
 public Results getResults() {
 return this.results;
 }
 }

  24. 24. “Super-duper” WaitingLoadableComponents @Test
 public void search() {
 GooglePage google = new GooglePage(driver);
 GoogleSearchResultsPage resultsPage = google.get().search("Selenide");
 assertThat(resultsPage.getResults().size(), equalTo(10));
 assertThat(resultsPage.getResults().get(0).getText(),
 containsString("Selenide: concise UI tests in Java"));
 } all “super” waiting starts here now here we can use common assert without waiting
  25. 25. “The LoadableComponent is a base class that aims to make writing PageObjects less painful.” o_O ???
  26. 26. WTF?
  27. 27. PageObjects Demo
  28. 28. Demo src log
  29. 29. PageObjects Usage @Test
 public void search() {
 new Google().open().search("Selenide");
 new SearchResults()
 .shouldHaveSize(10)
 .shouldHaveResultText(0, "Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 new Google().open().search("Selenide");
 new SearchResults().followResultLink(0);
 new NewPage().shouldHaveTitle"Selenide: concise UI tests in Java");
 }
  30. 30. PageObjects Implementation public class Google {
 public Google open() {
 Selenide.open("/ncr");
 return this;
 }
 
 public void search(String text) {
 $(By.name("q")).setValue(text).pressEnter();
 }
 }
  31. 31. PageObjects Implementation public class SearchResults {
 private ElementsCollection elements(){
 return $$(".srg>.g");
 }
 
 public void followResultLink(int index) {
 this.elements().get(index).find(".r>a").click();
 }
 
 public SearchResults shouldHaveSize(int number) {
 this.elements().shouldHave(size(number));
 return this;
 }
 
 public SearchResults shouldHaveResultText(int index, String text) {
 this.elements().get(index).shouldHave(text(text));
 return this;
 }
 }
  32. 32. PageObjects Implementation public class NewPage {
 public void shouldHaveTitle(String text) {
 Selenide.Wait().until(titleIs(text));
 }
 }
  33. 33. Retrospective
  34. 34. So why do we need PageObjects? Despite of DRYing the code…
  35. 35. So why do we need PageObjects? Despite of DRYing the code…
  36. 36. –Martin Fowler Page objects are a classic example of encapsulation - they hide the details of the UI structure and widgetry from other components (the tests)
  37. 37. Why may we need Encapsulation? • Encapsulate for cohesion? • that’s ok… pretty reasonable to keep related things together (locators and actions on their elements)
  38. 38. Why may we need Encapsulation? • Encapsulate to hide something internal to be not broken by client (tests code)? • but what may be broken? o_O Have you ever had any thought on resetting some page field from outside? ;)
  39. 39. Why may we need Encapsulation? • Encapsulate to hide something internal so on change it will not broke the client (tests code)? i.e. to provide stable API • ok… but then next question to think on: • where is that line which frame the “stable API”? • maybe it’s less abstract than you got used to…
  40. 40. Why may we need Encapsulation? • Encapsulate to narrow User Oriented behavioural API to be readable enough and convenient in usage? • :)
  41. 41. A point to think about… “Encapsulate to narrow User Oriented behavioural API to be readable enough and convenient in usage” • developers? • Sr. Automation engineers? • Jr. Automation engineers? • Manual Testers? Readable and convenient for whom?
  42. 42. @Test
 public void search() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").shouldHave(size(10));
 $$(".srg>.g").get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").get(0).find(".r>a").click();
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } For “developers”?
  43. 43. @Test
 public void search() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").shouldHave(size(10));
 $$(".srg>.g").get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").get(0).find(".r>a").click();
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } For “developers”? API is already readable and simple!
  44. 44. @Test
 public void search() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").shouldHave(size(10));
 $$(".srg>.g").get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").get(0).find(".r>a").click();
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } For “developers”? developers know locators that are already readable for them too:)
  45. 45. @Test
 public void search() {
 open("/ncr");
 $(By.name("query")).setValue("Selenide").pressEnter();
 $$(".result").shouldHave(size(10));
 $$(".result").get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 open("/ncr");
 $(By.name("query")).setValue("Selenide").pressEnter();
 $$(“.result").get(0).find(".result-link").click();
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } For “developers”? actually they do make them readable when they write tests ;)
  46. 46. @Test
 public void search() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").shouldHave(size(10));
 $$(".srg>.g").get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").get(0).find(".r>a").click();
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } For “developers”? copy&paste rules! ;)
  47. 47. @Test
 public void search() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").shouldHave(size(10));
 $$(".srg>.g").get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 open("/ncr");
 $(By.name("q")).setValue("Selenide").pressEnter();
 $$(".srg>.g").get(0).find(".r>a").click();
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } For “developers”? With Find&Replace over DRY ;)
  48. 48. For “Sr. Automation Engineers”? @Test
 public void search() {
 Google google = new Google();
 google.open().search("Selenide");
 google.results().shouldHave(size(10));
 google.results().get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 Google google = new Google();
 google.open().search("Selenide");
 google.followResultLink(0);
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 }
  49. 49. For “Sr. Automation Engineers”? @Test
 public void search() {
 Google google = new Google();
 google.open().search("Selenide");
 google.results().shouldHave(size(10));
 google.results().get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 Google google = new Google();
 google.open().search("Selenide");
 google.followResultLink(0);
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } hiding awful locators for “beauty” :D
  50. 50. For “Sr. Automation Engineers”? @Test
 public void search() {
 Google google = new Google();
 google.open().search("Selenide");
 google.results().shouldHave(size(10));
 google.results().get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 Google google = new Google();
 google.open().search("Selenide");
 google.followResultLink(0);
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } too lazy for locators’ copy&paste:) - autocomplete rules!
  51. 51. Remember classic version? @Test
 public void search() {
 Google google = new Google(driver);
 google.open().search("Selenide");
 wait.until(numberOf(google.getResults(), 10));
 assertThat(google.getResults().get(0).getText(), 
 containsString("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 Google google = new Google(driver);
 google.open().search("Selenide");
 google.followResultLink(0);
 wait.until(titleIs("Selenide: concise UI tests in Java"));
 }
  52. 52. Remember classic version? public class Google {
 private WebDriver driver;
 private WebDriverWait wait;
 
 @FindBy(name = "q")
 private WebElement searchInput;
 
 @FindBy(css = ".srg>.g")
 private List<WebElement> results;
 
 public List<WebElement> getResults() {
 return this.results;
 }
 
 public Google(WebDriver driver) {
 this.driver = driver;
 this.wait = new WebDriverWait(driver, 4);
 PageFactory.initElements(driver, this);
 } … … public Google open() {
 this.driver.get("http: //google.com/ncr");
 return this;
 }
 
 public Google search(String text) {
 this.searchInput.sendKeys(text + Keys.ENTER);
 return this;
 }
 
 public void followResultLink(int index) {
 wait.until(numberIsAtLeast(results, index + 1))
 .get(index)
 .findElement(By.cssSelector(".r>a"))
 .click();
 }
 }
  53. 53. Remember classic version? public class Google {
 private WebDriver driver;
 private WebDriverWait wait;
 
 @FindBy(name = "q")
 private WebElement searchInput;
 
 @FindBy(css = ".srg>.g")
 private List<WebElement> results;
 
 public List<WebElement> getResults() {
 return this.results;
 }
 
 public Google(WebDriver driver) {
 this.driver = driver;
 this.wait = new WebDriverWait(driver, 4);
 PageFactory.initElements(driver, this);
 } … … public Google open() {
 this.driver.get("http: //google.com/ncr");
 return this;
 }
 
 public Google search(String text) {
 this.searchInput.sendKeys(text + Keys.ENTER);
 return this;
 }
 
 public void followResultLink(int index) {
 wait.until(numberIsAtLeast(results, index + 1))
 .get(index)
 .findElement(By.cssSelector(".r>a"))
 .click();
 }
 } locators and actions are bloated with tech details of browser management and waits
  54. 54. for “Sr. Automation version” public class Google {
 public Google open() {
 Selenide.open("/ncr");
 return this;
 }
 
 public void search(String text) {
 $(By.name("q")).setValue(text).pressEnter();
 }
 
 public ElementsCollection results(){
 return $$(".srg>.g");
 }
 
 public void followResultLink(int index) {
 this.results().get(index).find(".r>a").click();
 }
 }
  55. 55. for “Sr. Automation version” public class Google {
 public Google open() {
 Selenide.open("/ncr");
 return this;
 }
 
 public void search(String text) {
 $(By.name("q")).setValue(text).pressEnter();
 }
 
 public ElementsCollection results(){
 return $$(".srg>.g");
 }
 
 public void followResultLink(int index) {
 this.results().get(index).find(".r>a").click();
 }
 } clean abstraction over locators and actions
  56. 56. For “Sr. Automation Engineers”? @Test
 public void search() {
 Google google = new Google();
 google.open().search("Selenide");
 google.results().shouldHave(size(10));
 google.results().get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 Google google = new Google();
 google.open().search("Selenide");
 google.followResultLink(0);
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 }
  57. 57. For “Jr. Automation Engineers”? @Test
 public void search() {
 new Google().open().search("Selenide");
 new SearchResults()
 .shouldHaveSize(10)
 .shouldHaveResultText(0, "Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 new Google().open().search("Selenide");
 new SearchResults().followResultLink(0);
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 }
  58. 58. For “Jr. Automation Engineers”? @Test
 public void search() {
 new Google().open().search("Selenide");
 new SearchResults()
 .shouldHaveSize(10)
 .shouldHaveResultText(0, "Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 new Google().open().search("Selenide");
 new SearchResults().followResultLink(0);
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } smaller page objects per context
  59. 59. For “Jr. Automation Engineers”? @Test
 public void search() {
 new Google().open().search("Selenide");
 new SearchResults()
 .shouldHaveSize(10)
 .shouldHaveResultText(0, "Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 new Google().open().search("Selenide");
 new SearchResults().followResultLink(0);
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } built in asserts
  60. 60. For “Manual Testers”? @Test
 public void search() {
 google.home.open().search("Selenide");
 google.results
 .shouldHaveSize(10)
 .result(0).shouldContain("Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 google.home.open().search("Selenide");
 google.results.result(0).followLink();
 assertThat(titleIs("Selenide: concise UI tests in Java"));
 }
  61. 61. For “Manual Testers”? @Test
 public void search() {
 google.home.open().search("Selenide");
 google.results
 .shouldHaveSize(10)
 .result(0).shouldContain("Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 google.home.open().search("Selenide");
 google.results.result(0).followLink();
 assertThat(titleIs("Selenide: concise UI tests in Java"));
 } one entry point to Test Model
  62. 62. For “Manual Testers”? @Test
 public void search() {
 google.home.open().search("Selenide");
 google.results
 .shouldHaveSize(10)
 .result(0).shouldContain("Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 google.home.open().search("Selenide");
 google.results.result(0).followLink();
 assertThat(titleIs("Selenide: concise UI tests in Java"));
 } simpler one parameter methods
  63. 63. For “Manual Testers”? @Test
 public void search() {
 google.home.open().search("Selenide");
 google.results
 .shouldHaveSize(10)
 .result(0).shouldContain("Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 google.home.open().search("Selenide");
 google.results.result(0).followLink();
 assertThat(titleIs("Selenide: concise UI tests in Java"));
 } chainable methods of Fluent PageObjects (.result(0) returns Result object)
  64. 64. Why should we care? To be efficient!
  65. 65. Ok, Where is KISS here? It is exactly about leveraging convenient techniques in concrete context to make it simpler and efficient!
  66. 66. Reviewing some techniques of PageObjects design from KISS point of view
  67. 67. –Martin Fowler I favor having no assertions in page objects. I think you can avoid duplication by providing assertion libraries for common assertions - which can also make it easier to provide good diagnostics. Page objects are commonly used for testing, but should not make assertions themselves. Their responsibility is to provide access to the state of the underlying page. It's up to test clients to carry out the assertion logic. https://martinfowler.com/bliki/PageObject.html Assertions-free PageObjects?
  68. 68. Recall “For Sr. Automation Engineers” version… @Test
 public void search() {
 Google google = new Google();
 google.open().search("Selenide");
 google.results().shouldHave(size(10));
 google.results().get(0).shouldHave(text("Selenide: concise UI tests in Java"));
 }
 
 @Test
 public void followFirstLink() {
 Google google = new Google();
 google.open().search("Selenide");
 google.followResultLink(0);
 Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
 } assertions from “assertion library” provided by Selenide
  69. 69. Assertion-free PageObject public class Google {
 public Google open() {
 Selenide.open("/ncr");
 return this;
 }
 
 public void search(String text) {
 $(By.name("q")).setValue(text).pressEnter();
 }
 
 public ElementsCollection results(){
 return $$(".srg>.g");
 }
 
 public void followResultLink(int index) {
 this.results().get(index).find(".r>a").click();
 }
 }
  70. 70. But… what if… • The end user is not “Sr. Automation” ? • Finally he may be even a Manual Tester… • The UI is so complex that assertions become also more composite? • Crazy Managers demand higher level of abstraction in reports • (they are “crazy”, so you can’t teach them what is good or bad:))
  71. 71. Assertions-free PageObjects? • break some forms of encapsulation • reveal too much of technical details (in context of end user level of understanding) from PageObject to outside • over-engineered • may lead to create separate StepsObject • or additional layers in implementation (like BDD layer) • loose high cohesion
  72. 72. StepsObject? GIVEN PageObject as an HTML Page API THEN StepsObject stands for User Behaviour API
  73. 73. StepsObjects? @Test
 public void search() {
 new GoogleUser().opensGoogle()
 .searches("Selenide")
 .expectsNumberOfResults(10)
 .expectsResultWithText(0, "Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 new GoogleUser().opensGoogle()
 .searches("Selenide")
 .followsResultLink(0)
 .expectsTitle("Selenide: concise UI tests in Java");
 }
  74. 74. StepsObjects? public class GoogleUser {
 
 private final Google google = new Google();
 
 public GoogleUser opensGoogle() {
 open("/ncr");
 return this;
 }
 
 public UserOnResultsPage searches(String text) {
 this.google.search().setValue(text).pressEnter();
 return new UserOnResultsPage();
 }
 }
  75. 75. StepsObjects? public class UserOnResultsPage extends GoogleUser {
 
 private final Google google = new Google();
 
 public UserOnResultsPage expectsNumberOfResults(int number) {
 this.google.results().shouldHave(size(number));
 return this;
 }
 
 public UserOnResultsPage expectsResultWithText(int index, String text) {
 this.google.results().get(index).shouldHave(text(text));
 return this;
 }
 
 public UserOnNewPage followsResultLink(int index) {
 this.google.resultLink(index).click();
 return new UserOnNewPage();
 }
 }
  76. 76. StepsObjects? public class UserOnNewPage {
 
 public UserOnNewPage expectsTitle(String text) {
 Selenide.Wait().until(titleIs(text));
 return this;
 }
 }
  77. 77. HTML-only PageObject? public class Google {
 
 public SelenideElement search() {
 return $(By.name("q"));
 }
 
 public ElementsCollection results(){
 return $$(".srg>.g");
 }
 
 public SelenideElement resultLink(int index) {
 return this.results().get(index).find(".r>a");
 }
 } now the code that describes the same page components are spread over several classes
  78. 78. StepsObjects? • over-engineered => at least two classes instead of one • lower cohesion • related things are not kept in one place
  79. 79. Assertions-included PageObjects (HTML + Steps)! • full encapsulation • Tell Don’t Ask • high cohesion • End User oriented. He needs “behavioural object”, He actually does not care about: • Bloated with code? • KISS answer: break down into smaller PageObjects • you will have still two or even more classes but for exactly one thing from the User point of view Their responsibility is to provide access to the state of the underlying page (c) Martin Fowler
  80. 80. Assertions-included PageObjects! • full encapsulation • Tell Don’t Ask • high cohesion • KISS: one class instead of two ones for the same thing from the User point of view • Bloated with code? • KISS answer: break down into smaller PageObjects • you will have still two or even more classes but for exactly one thing from the User point of view
  81. 81. Assertions-included PageObjects! • full encapsulation • Tell Don’t Ask • high cohesion • KISS: one class instead of two ones for the same thing from the User point of view • Bloated with code? • KISS answer: break down into smaller PageObjects • you will have still two or even more classes but for exactly one thing from the User point of view Advocates of assertion-free page objects say that including assertions mixes the responsibilities of providing access to page data with assertion logic, and leads to a bloated page object. (c) Martin Fowler
  82. 82. (For “Jr. Automation Engineers” versions) public class SearchResults { 
 private ElementsCollection elements(){
 return $$(".srg>.g");
 }
 
 public void followResultLink(int index) {
 this.elements().get(index).find(".r>a").click();
 }
 
 public SearchResults shouldHaveSize(int number) {
 this.elements().shouldHave(size(number));
 return this;
 }
 
 public SearchResults shouldHaveResultText(int index, String text) {
 this.elements().get(index).shouldHave(text(text));
 return this;
 }
 }
  83. 83. Assertions-included PageObjects! • full encapsulation • Tell Don’t Ask • high cohesion • KISS: one class instead of two ones for the same thing from the User point of view • Bloated with code? • KISS answer: break down into smaller PageObjects aka Widgets • you will have still two or even more classes but for exactly one thing from the User point of view
  84. 84. • full encapsulation • Tell Don’t Ask • high cohesion • KISS: one class instead of two ones for the same thing from the User point of view • Bloated with code? • KISS answer: break down into smaller PageObjects aka Widgets • you will have still two or even more classes but for exactly one thing from the User point of view Despite the term "page" object, these objects shouldn't usually be built for each page, but rather for the significant elements on a page (c) Martin Fowler
  85. 85. (For “Manual Testers” version) public class SearchResults {
 
 private ElementsCollection elements(){
 return $(".srg>.g");
 }
 
 public Result result(int index) {
 return new Result(this.elements().get(index));
 }
 
 public SearchResults shouldHaveSize(int number) {
 this.elements().shouldHave(size(number));
 return this;
 }
 }
  86. 86. (For “Manual Testers” version) public class Result{
 private final SelenideElement container;
 
 public Result(SelenideElement container) {
 this.container = container;
 }
 
 public void followLink() {
 this.container.find(".r>a").click();
 }
 
 public Result shouldContain(String text) {
 this.container.shouldHave(text(text));
 return this;
 }
 }
  87. 87. (For “Manual Testers” version) @Test
 public void search() {
 google.home.open().search("Selenide");
 google.results
 .shouldHaveSize(10)
 .result(0).shouldContain("Selenide: concise UI tests in Java");
 }
 
 @Test
 public void followFirstLink() {
 google.home.open().search("Selenide");
 google.results.result(0).followLink();
 assertThat(titleIs("Selenide: concise UI tests in Java"));
 }
  88. 88. KISS Pageobjects for Jr. and Manual Testers, where extra reporting is needed = 2 in 1: HTML PageObjects + StepsObjects with Asserts (being broken down to “Widgets” if needed)
  89. 89. KISS *= simplicity instead of over-engineering = YAGNI = You Ain’t Gonna Need It
  90. 90. public class Google {
 private SelenideElement searchInput = $(By.name("q"));
 
 public Google open() {
 Selenide.open("/ncr");
 return this;
 }
 
 public void search(String text) {
 this.searchInput.setValue(text).pressEnter();
 }
 } public class Google {
 
 public Google open() {
 Selenide.open("/ncr");
 return this;
 }
 
 public void search(String text) {
 $(By.name("q")).setValue(text).pressEnter();
 }
 } vs YAGNI version you ain’t gonna need it ;)
  91. 91. public class Google {
 public ElementsCollection results(){
 return $$(".srg>.g");
 } public class Google {
 public ElementsCollection results = $$(".srg>.g");
 vs YAGNI version you ain’t gonna need this form of encapsulation ;) the only probable thing with a real risk of change is the locator
  92. 92. public class Google {
 public final ElementsCollection results = $$(".srg>.g");
 public class Google {
 public ElementsCollection results = $$(".srg>.g");
 vs YAGNI version you ain’t gonna need this form of protection ;)
  93. 93. public class Google {
 public final ElementsCollection results = $$(".srg>.g");
 though this teaches you kind of the “true safe” programming and this can be kind of “good habit” to be trained ;)
  94. 94. Driver management?
  95. 95. Driver management? • Why to bother with this bulky management if YAGNI? • the only relevant case is when you have to work with two opened browsers in one test. But how many such tests did you wrote? ;) • remember that for “parallel testing” case you have ThreadLocal<WebDriver> ;)
  96. 96. But sometimes that’s the case… • Why to bother with this bulky management if YAGNI? • the only relevant case is when you have to work with two opened browsers in one test. But how many such tests did you wrote? ;) • remember that for “parallel testing” case you have ThreadLocal<WebDriver> ;)
  97. 97. Winning a bit of speed… @Test
 public void shareMessageToFollowers() {
 SelenideDriver selenideBrowser = new SelenideDriver(new FirefoxDriver());
 SelenideDriver yashakaBrowser = new SelenideDriver(new FirefoxDriver());
 
 new Diaspora(selenideBrowser).open().signIn(
 SecretData.Selenide.username, SecretData.Selenide.password);
 /* Yashaka follows Selenide ;) */
 new Diaspora(yashakaBrowser).open().signIn(
 SecretData.Yashaka.username, SecretData.Yashaka.password);
 
 new NewPost(selenideBrowser).start().write("Selenide 4.2 released!").share();
 new Stream(yashakaBrowser).post(0).shouldBe("Selenide 4.2 released!");
 } by missing the “logout” step for Selenide user and verifying ER in already opened 2nd browser ;)
  98. 98. By making PageObjects more “bulky” :| public class Diaspora {
 private final SelenideDriver driver;
 
 public Diaspora(SelenideDriver driver) {
 this.driver = driver;
 }
 
 public Diaspora open() {
 Selenide.open("/");
 return this;
 }
 
 public void signIn(String username, String password) {
 new NavBar(this.driver).select("Sign in");
 new Form(this.driver.element("#new_user"))
 .set("USERNAME", username)
 .set("PASSWORD", password)
 .submit();
 }
 }
  99. 99. Instead of much cleaner public class Diaspora {
 
 public Diaspora open() {
 Selenide.open("/");
 return this;
 }
 
 public void signIn(String username, String password) {
 new NavBar().select("Sign in");
 new Form($("#new_user"))
 .set("USERNAME", username)
 .set("PASSWORD", password)
 .submit();
 }
 }
  100. 100. That can be used to achieve the same goal by using corresponding API helpers: @Test
 public void shareMessageToFollowers() {
 new Diaspora().open().signIn( SecretData.Selenide.username, SecretData.Selenide.password);
 new NewPost().start().write("Selenide 4.2 released!").share();
 new API().ensureLoggedIn( SecretData.Yashaka.username, SecretData.Yashaka.password);
 new Stream().post(0).shouldBe("Selenide 4.2 released!");
 }
  101. 101. Still enough open points to take into account… • Will API calls simulate the same real behaviour? • Like Logged in user does nothing but his stream is updated…
  102. 102. Still enough open points to take into account… • Like Logged in user does nothing but his stream is updated… • Do we actually need the simulation to be “like real”? • We still can use API calls to check that “new post” from an author gets the storage • And in separate test “the follower can do nothing” and after populating the storage by API call with a “new author’s post” - we will verify (via Selenium) that follower sees the post
  103. 103. Smarter simulation with API calls@Test
 public void authorSharesMessage() {
 new ApiCall().ensureSignedIn(
 SecretData.Selenide.username, SecretData.Selenide.password);
 
 new NewPost().start().write("Selenide 4.2 released!").share();
 new Stream().post(0).shouldBe("Selenide 4.2 released!");
 new ApiCall().assertMessageInStorage( SecretData.Selenide.username, "Selenide 4.2 released!");
 }
 
 @Test
 public void followerSeesNewMessageInTheStream() {
 new Diaspora().ensureSignedIn(
 SecretData.Yashaka.username, SecretData.Yashaka.password);
 
 new ApiCall().createMessageInStorage( SecretData.Selenide.username, "Selenide 4.2 released!");
 new Stream().post(0).shouldBe("Selenide 4.2 released!");
 }
  104. 104. Still enough open points to take into account… • Like Logged in user does nothing but his stream is updated… • Do we actually need the simulation to be “like real”? • We still can use API calls to check that “new post” from an author gets the storage • And in separate test “the follower can do nothing” and after populating the storage by API call with a “new author’s post” - we will verify (via Selenium) that follower sees the post
  105. 105. Smarter simulation with API calls@Test
 public void authorSharesMessage() {
 new ApiCall().ensureSignedIn(
 SecretData.Selenide.username, SecretData.Selenide.password);
 
 new NewPost().start().write("Selenide 4.2 released!").share();
 new Stream().post(0).shouldBe("Selenide 4.2 released!");
 new ApiCall().assertMessageInStorage( SecretData.Selenide.username, "Selenide 4.2 released!");
 }
 
 @Test
 public void followerSeesNewMessageInTheStream() {
 new Diaspora().ensureSignedIn(
 SecretData.Yashaka.username, SecretData.Yashaka.password);
 
 new ApiCall().createMessageInStorage( SecretData.Selenide.username, "Selenide 4.2 released!");
 new Stream().post(0).shouldBe("Selenide 4.2 released!");
 }
  106. 106. Atomic, faster, and so more efficient than “real simulation” @Test
 public void authorSharesMessage() {
 new ApiCall().ensureSignedIn(
 SecretData.Selenide.username, SecretData.Selenide.password);
 
 new NewPost().start().write("Selenide 4.2 released!").share();
 new Stream().post(0).shouldBe("Selenide 4.2 released!");
 new ApiCall().assertMessageInStorage( SecretData.Selenide.username, "Selenide 4.2 released!");
 }
 
 @Test
 public void followerSeesNewMessageInTheStream() {
 new Diaspora().ensureSignedIn(
 SecretData.Yashaka.username, SecretData.Yashaka.password);
 
 new ApiCall().createMessageInStorage( SecretData.Selenide.username, "Selenide 4.2 released!");
 new Stream().post(0).shouldBe("Selenide 4.2 released!");
 }
  107. 107. Still enough open points to take into account… • But sometimes… Life is complicated:) And for some mystic reason we “can’t” use API calls…
  108. 108. Only then we need “explicit driver management” SelenideDriver selenideBrowser = new SelenideDriver(new FirefoxDriver());
 SelenideDriver yashakaBrowser = new SelenideDriver(new FirefoxDriver()); ... @Test
 public void shareMessageToFollowers() {
 new Diaspora(selenideBrowser).open().signIn(
 SecretData.Selenide.username, SecretData.Selenide.password);
 /* Yashaka follows Selenide ;) */
 new Diaspora(yashakaBrowser).open().signIn(
 SecretData.Yashaka.username, SecretData.Yashaka.password);
 
 new NewPost(selenideBrowser).start().write("Selenide 4.2 released!").share();
 new Stream(yashakaBrowser).refresh().post(0).shouldBe(“Selenide 4.2 released!");
 }
  109. 109. public class Diaspora {
 private final SelenideDriver driver;
 
 public Diaspora(SelenideDriver driver) {
 this.driver = driver;
 }
 
 public Diaspora open() {
 Selenide.open("/");
 return this;
 }
 
 public void signIn(String username, String password) {
 new NavBar(this.driver).select("Sign in");
 new Form(this.driver.element("#new_user"))
 .set("USERNAME", username)
 .set("PASSWORD", password)
 .submit();
 }
 } Only then we need “explicit driver management”
  110. 110. KISS = not necessarily Easiness
  111. 111. Consider… public class Widget {
 private final SelenideElement container;
 
 public Widget(SelenideElement container) {
 this.container = container;
 }
 
 public SelenideElement self() {
 return this.container;
 }
 }
  112. 112. Easy public class Post extends Widget{ 
 public Post(SelenideElement container) {
 super(container);
 }
 
 public void shouldBe(String withText) {
 self().shouldHave(text(withText));
 }
 }
  113. 113. Easy public class Post extends Widget{ 
 public Post(SelenideElement container) {
 super(container);
 }
 
 public void shouldBe(String withText) {
 self().shouldHave(text(withText));
 }
 } too much of “implicit magic”…
  114. 114. Simple public class Post { 
 private final SelenideElement container;
 
 public Post(SelenideElement container) {
 this.container = container;
 }
 
 public void shouldBe(String withText) {
 this.container.shouldHave(text(withText));
 }
 } “Explicit is better than implicit” (c) The Zen of Python, PEP-20
  115. 115. Simple is not Easy
  116. 116. –Rich Hickey “Simple Made Easy”
  117. 117. assert KISS != magic assert KISS == explicit
  118. 118. @Test
 public void shareMessage() {
 new Diaspora().open().signIn(
 SecretData.Selenide.username, SecretData.Selenide.password);
 new NewPost().shareNewPost("Selenide 4.2 released!”)
 } hidden “start new post” test-logic, and hidden assertion Magic public void writeAndShare(String text) {
 this.start().write(text).share();
 new Stream().post(0).shouldBe(text);
 }
  119. 119. @Test
 public void shareMessage() {
 new Diaspora().open().signIn(
 SecretData.Selenide.username, SecretData.Selenide.password);
 new NewPost().start().write("Selenide 4.2 released!").share();
 new Stream().post(0).shouldBe("Selenide 4.2 released!");
 } explicit test-logic-step explicit assert-step KISS
  120. 120. WaitingLoadable?
  121. 121. Recall “Super-duper” WaitingLoadableComponents @Test
 public void search() {
 GooglePage google = new GooglePage(driver);
 GoogleSearchResultsPage resultsPage = google.get().search("Selenide");
 assertThat(resultsPage.getResults().size(), equalTo(10));
 assertThat(resultsPage.getResults().get(0).getText(),
 containsString("Selenide: concise UI tests in Java"));
 } all “super” waiting for all results starts here now here we can use common assert without waiting
  122. 122. But what if do not want to wait all results? We are interested only in the first one! @Test
 public void search() {
 GooglePage google = new GooglePage(driver);
 GoogleSearchResultsPage resultsPage = google.get().search("Selenide");
 assertThat(resultsPage.getResults().size(), equalTo(10));
 assertThat(resultsPage.getResults().get(0).getText(),
 containsString("Selenide: concise UI tests in Java"));
 } all “super” waiting for all 10 results starts here now here we can use common assert without waiting
  123. 123. WaitingLoadable may break the real user behaviour in some contexts… @Test
 public void search() {
 GooglePage google = new GooglePage(driver);
 GoogleSearchResultsPage resultsPage = google.get().search("Selenide");
 assertThat(resultsPage.getResults().size(), equalTo(10));
 assertThat(resultsPage.getResults().get(0).getText(),
 containsString("Selenide: concise UI tests in Java"));
 } all “super” waiting for all 10 results starts here now here we can use common assert without waiting
  124. 124. KISS Answer • User oriented automation based on waiting loadable elements over loadable pages waiting their components
  125. 125. Diaspora Demo How often do you open Facebook, and proceed to profile or messages, while the stream is not loaded yet? ;)
  126. 126. So KISS leads to • User Oriented waiting • No explicit waits to finalise step • => Explicit Test Logic (No test-logic asserts in steps) • No explicit waits at all! (like wait for all page is loaded) • Implicit tech details • like waiting
  127. 127. PageObjects Summary • Readable and user oriented • not HTML oriented • Assert-steps included • if “newcomers-oriented” • Top Down design • YAGNI • no driver management • no over-optimisation • no redundant fields • Explicit Test Logic • No test-logic asserts in steps • no hidden steps logic • User Oriented waiting • No explicit waits to finalise step • No explicit waits at all! (like wait for all page is loaded) • Implicit tech details • like waiting • Fluent where adds conciseness and readability • no extra variables
  128. 128. PageObjects Secret ;) NO Selenium Webdriver, use more concise wrappers!
  129. 129. PageObjects Secret ;) Prefer test automation tools over browser automation tools
  130. 130. Afterwords There are good practices in context, but there are no best practices. (c) Cem Kaner, James Bach
  131. 131. Q&A
  132. 132. Thank you! @yashaka 01.2017 github.com/automician automician.com seleniumcourses.com yashaka @

×