SlideShare ist ein Scribd-Unternehmen logo
1 von 132
Downloaden Sie, um offline zu lesen
@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 widgetry from other
components (the tests)
(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("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"));

}
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);

}
…
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);

}
…
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();

}

}
– https://github.com/SeleniumHQ/selenium/wiki/LoadableComponent
“The LoadableComponent is a base class that aims to make
writing PageObjects less painful.”
PageObjects as LoadableComponents
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
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
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
“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;

}

}
“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();

}
…
“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
“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;

}

}

“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
“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()

.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");

}
PageObjects Implementation
public class Google {

public Google open() {

Selenide.open("/ncr");

return this;

}



public void search(String text) {

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

}

}
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;

}

}
PageObjects Implementation
public class NewPage {

public void shouldHaveTitle(String text) {

Selenide.Wait().until(titleIs(text));

}

}
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 widgetry from other
components (the tests)
Why may we need Encapsulation?
• Encapsulate for cohesion?
• that’s ok… pretty reasonable to keep related things together
(locators and actions on their elements)
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? ;)
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…
Why may we need Encapsulation?
• Encapsulate to narrow User Oriented behavioural API to be
readable enough and convenient in usage?
• :)
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?
@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”?
@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!
@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:)
@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 ;)
@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! ;)
@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 ;)
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"));

}
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
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!
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"));

}
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();

}

}
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
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();

}

}
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
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"));

}
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"));

}
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
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
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"));

}
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
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
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)
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 efficient!
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 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?
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
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();

}

}
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:))
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
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")

.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");

}
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();

}

}
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();

}

}
StepsObjects?
public class UserOnNewPage {



public UserOnNewPage expectsTitle(String text) {

Selenide.Wait().until(titleIs(text));

return this;

}

}
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
StepsObjects?
• over-engineered => at least two classes instead of one
• lower cohesion
• related things are not kept in one place
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
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
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
(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;

}

}
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
• 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
(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;

}

}
(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;

}

}
(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"));

}
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)
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("/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 ;)
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
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 ;)
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 ;)
Driver management?
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> ;)
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> ;)
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 ;)
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();

}

}
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();

}

}
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!");

}
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…
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
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!");

}
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
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!");

}
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!");

}
Still enough open points to take into account…
• But sometimes… Life is complicated:) And for some mystic reason
we “can’t” use API calls…
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!");

}
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”
KISS
=
not necessarily Easiness
Consider…
public class Widget {

private final SelenideElement container;



public Widget(SelenideElement container) {

this.container = container;

}



public SelenideElement self() {

return this.container;

}

}
Easy
public class Post extends Widget{


public Post(SelenideElement container) {

super(container);

}



public void shouldBe(String withText) {

self().shouldHave(text(withText));

}

}
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”…
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
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.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);

}
@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
WaitingLoadable?
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
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
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
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 asserts in steps)
• No explicit waits at all! (like wait for all page is loaded)
• Implicit tech details
• like waiting
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
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 @

Weitere ähnliche Inhalte

Was ist angesagt?

Grails Simple Login
Grails Simple LoginGrails Simple Login
Grails Simple Login
moniguna
 

Was ist angesagt? (20)

Appium + selenide comaqa.by. Антон Семенченко
Appium + selenide comaqa.by. Антон СеменченкоAppium + selenide comaqa.by. Антон Семенченко
Appium + selenide comaqa.by. Антон Семенченко
 
Angular - Chapter 4 - Data and Event Handling
 Angular - Chapter 4 - Data and Event Handling Angular - Chapter 4 - Data and Event Handling
Angular - Chapter 4 - Data and Event Handling
 
ScreenPlay Design Patterns for QA Automation
ScreenPlay Design Patterns for QA AutomationScreenPlay Design Patterns for QA Automation
ScreenPlay Design Patterns for QA Automation
 
Javascript Prototypal Inheritance - Big Picture
Javascript Prototypal Inheritance - Big PictureJavascript Prototypal Inheritance - Big Picture
Javascript Prototypal Inheritance - Big Picture
 
Advanced JavaScript
Advanced JavaScriptAdvanced JavaScript
Advanced JavaScript
 
Hibernate presentation
Hibernate presentationHibernate presentation
Hibernate presentation
 
jQuery -Chapter 2 - Selectors and Events
jQuery -Chapter 2 - Selectors and Events jQuery -Chapter 2 - Selectors and Events
jQuery -Chapter 2 - Selectors and Events
 
Design functional solutions in Java, a practical example
Design functional solutions in Java, a practical exampleDesign functional solutions in Java, a practical example
Design functional solutions in Java, a practical example
 
React-JS Component Life-cycle Methods
React-JS Component Life-cycle MethodsReact-JS Component Life-cycle Methods
React-JS Component Life-cycle Methods
 
Introduction to JavaScript (1).ppt
Introduction to JavaScript (1).pptIntroduction to JavaScript (1).ppt
Introduction to JavaScript (1).ppt
 
Serenity and the Journey Pattern
Serenity and the Journey PatternSerenity and the Journey Pattern
Serenity and the Journey Pattern
 
JavaScript Basics and Best Practices - CC FE & UX
JavaScript Basics and Best Practices - CC FE & UXJavaScript Basics and Best Practices - CC FE & UX
JavaScript Basics and Best Practices - CC FE & UX
 
Setting up Page Object Model in Automation Framework
Setting up Page Object Model in Automation FrameworkSetting up Page Object Model in Automation Framework
Setting up Page Object Model in Automation Framework
 
Clean Architecture Essentials - Stockholm Software Craftsmanship
Clean Architecture Essentials - Stockholm Software CraftsmanshipClean Architecture Essentials - Stockholm Software Craftsmanship
Clean Architecture Essentials - Stockholm Software Craftsmanship
 
Monadic Java
Monadic JavaMonadic Java
Monadic Java
 
Spring MVC
Spring MVCSpring MVC
Spring MVC
 
Getting started with Redux js
Getting started with Redux jsGetting started with Redux js
Getting started with Redux js
 
ECMA Script
ECMA ScriptECMA Script
ECMA Script
 
Whitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applicationsWhitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applications
 
Grails Simple Login
Grails Simple LoginGrails Simple Login
Grails Simple Login
 

Ähnlich wie Kiss PageObjects [01-2017]

Agile NCR 2013 - Gaurav Bansal- web_automation
Agile NCR 2013 - Gaurav Bansal- web_automationAgile NCR 2013 - Gaurav Bansal- web_automation
Agile NCR 2013 - Gaurav Bansal- web_automation
AgileNCR2013
 
Javascript unit testing, yes we can e big
Javascript unit testing, yes we can   e bigJavascript unit testing, yes we can   e big
Javascript unit testing, yes we can e big
Andy Peterson
 

Ähnlich wie Kiss PageObjects [01-2017] (20)

Revolution or Evolution in Page Object
Revolution or Evolution in Page ObjectRevolution or Evolution in Page Object
Revolution or Evolution in Page Object
 
Agile NCR 2013 - Gaurav Bansal- web_automation
Agile NCR 2013 - Gaurav Bansal- web_automationAgile NCR 2013 - Gaurav Bansal- web_automation
Agile NCR 2013 - Gaurav Bansal- web_automation
 
Javascript unit testing, yes we can e big
Javascript unit testing, yes we can   e bigJavascript unit testing, yes we can   e big
Javascript unit testing, yes we can e big
 
Stencil the time for vanilla web components has arrived
Stencil the time for vanilla web components has arrivedStencil the time for vanilla web components has arrived
Stencil the time for vanilla web components has arrived
 
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
 
Three Simple Chords of Alternative PageObjects and Hardcore of LoadableCompon...
Three Simple Chords of Alternative PageObjects and Hardcore of LoadableCompon...Three Simple Chords of Alternative PageObjects and Hardcore of LoadableCompon...
Three Simple Chords of Alternative PageObjects and Hardcore of LoadableCompon...
 
KISS Automation.py
KISS Automation.pyKISS Automation.py
KISS Automation.py
 
[@IndeedEng] Building Indeed Resume Search
[@IndeedEng] Building Indeed Resume Search[@IndeedEng] Building Indeed Resume Search
[@IndeedEng] Building Indeed Resume Search
 
Javascript first-class citizenery
Javascript first-class citizeneryJavascript first-class citizenery
Javascript first-class citizenery
 
Comprehensive Browser Automation Solution using Groovy, WebDriver & Obect Model
Comprehensive Browser Automation Solution using Groovy, WebDriver & Obect ModelComprehensive Browser Automation Solution using Groovy, WebDriver & Obect Model
Comprehensive Browser Automation Solution using Groovy, WebDriver & Obect Model
 
HTML5 for the Silverlight Guy
HTML5 for the Silverlight GuyHTML5 for the Silverlight Guy
HTML5 for the Silverlight Guy
 
How AngularJS Embraced Traditional Design Patterns
How AngularJS Embraced Traditional Design PatternsHow AngularJS Embraced Traditional Design Patterns
How AngularJS Embraced Traditional Design Patterns
 
The Theory Of The Dom
The Theory Of The DomThe Theory Of The Dom
The Theory Of The Dom
 
Prototype Js
Prototype JsPrototype Js
Prototype Js
 
Web UI test automation instruments
Web UI test automation instrumentsWeb UI test automation instruments
Web UI test automation instruments
 
ActiveWeb: Chicago Java User Group Presentation
ActiveWeb: Chicago Java User Group PresentationActiveWeb: Chicago Java User Group Presentation
ActiveWeb: Chicago Java User Group Presentation
 
Javascript Design Patterns
Javascript Design PatternsJavascript Design Patterns
Javascript Design Patterns
 
Beyond pageobjects
Beyond pageobjectsBeyond pageobjects
Beyond pageobjects
 
Stencil the time for vanilla web components has arrived
Stencil the time for vanilla web components has arrivedStencil the time for vanilla web components has arrived
Stencil the time for vanilla web components has arrived
 
Webdriver with Thucydides - TdT@Cluj #18
Webdriver with Thucydides - TdT@Cluj #18Webdriver with Thucydides - TdT@Cluj #18
Webdriver with Thucydides - TdT@Cluj #18
 

Mehr von Iakiv Kramarenko

Mehr von Iakiv Kramarenko (10)

Отзывы на раннюю версию курса «Искусство Автоматизации с Java + Selenide»
Отзывы на раннюю версию курса «Искусство Автоматизации с Java + Selenide» Отзывы на раннюю версию курса «Искусство Автоматизации с Java + Selenide»
Отзывы на раннюю версию курса «Искусство Автоматизации с Java + Selenide»
 
Dont be fooled with BDD, automation engineer! ;)
Dont be fooled with BDD, automation engineer! ;)Dont be fooled with BDD, automation engineer! ;)
Dont be fooled with BDD, automation engineer! ;)
 
Easy automation.py
Easy automation.pyEasy automation.py
Easy automation.py
 
Web ui tests examples with selenide, nselene, selene & capybara
Web ui tests examples with  selenide, nselene, selene & capybaraWeb ui tests examples with  selenide, nselene, selene & capybara
Web ui tests examples with selenide, nselene, selene & capybara
 
Selenide Alternative in Practice - Implementation & Lessons learned [Selenium...
Selenide Alternative in Practice - Implementation & Lessons learned [Selenium...Selenide Alternative in Practice - Implementation & Lessons learned [Selenium...
Selenide Alternative in Practice - Implementation & Lessons learned [Selenium...
 
Selenide alternative in Python - Introducing Selene [SeleniumCamp 2016]
Selenide alternative in Python - Introducing Selene [SeleniumCamp 2016]Selenide alternative in Python - Introducing Selene [SeleniumCamp 2016]
Selenide alternative in Python - Introducing Selene [SeleniumCamp 2016]
 
You do not need automation engineer - Sqa Days - 2015 - EN
You do not need automation engineer  - Sqa Days - 2015 - ENYou do not need automation engineer  - Sqa Days - 2015 - EN
You do not need automation engineer - Sqa Days - 2015 - EN
 
Polyglot automation - QA Fest - 2015
Polyglot automation - QA Fest - 2015Polyglot automation - QA Fest - 2015
Polyglot automation - QA Fest - 2015
 
Write Selenide in Python 15 min
Write Selenide in Python 15 minWrite Selenide in Python 15 min
Write Selenide in Python 15 min
 
Automation is Easy! (python version)
Automation is Easy! (python version)Automation is Easy! (python version)
Automation is Easy! (python version)
 

Kürzlich hochgeladen

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 

Kürzlich hochgeladen (20)

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 

Kiss PageObjects [01-2017]

  • 3. Afterwords There are good practices in context, but there are no best practices. (c) Cem Kaner, James Bach
  • 4. Afterwords Preface There are good practices in context, but there are no best practices. (c) Cem Kaner, James Bach
  • 7. Keep It Simple Stupid!
  • 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)
  • 11. The boring part :p Classic “Horror” Examples
  • 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. 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. 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. 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. – 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. 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. 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. 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. “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. “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. “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. “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. “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. “The LoadableComponent is a base class that aims to make writing PageObjects less painful.” o_O ???
  • 26. WTF?
  • 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. 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. 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. PageObjects Implementation public class NewPage {
 public void shouldHaveTitle(String text) {
 Selenide.Wait().until(titleIs(text));
 }
 }
  • 34. So why do we need PageObjects? Despite of DRYing the code…
  • 35. So why do we need PageObjects? Despite of DRYing the code…
  • 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. 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. 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. 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. Why may we need Encapsulation? • Encapsulate to narrow User Oriented behavioural API to be readable enough and convenient in usage? • :)
  • 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. @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. @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. @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. @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. @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. @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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. Why should we care? To be efficient!
  • 65. Ok, Where is KISS here? It is exactly about leveraging convenient techniques in concrete context to make it simpler and efficient!
  • 66. Reviewing some techniques of PageObjects design from KISS point of view
  • 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. 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. 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. 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. 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. StepsObject? GIVEN PageObject as an HTML Page API THEN StepsObject stands for User Behaviour API
  • 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. 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. 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. StepsObjects? public class UserOnNewPage {
 
 public UserOnNewPage expectsTitle(String text) {
 Selenide.Wait().until(titleIs(text));
 return this;
 }
 }
  • 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. StepsObjects? • over-engineered => at least two classes instead of one • lower cohesion • related things are not kept in one place
  • 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. 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. 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. (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. 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. • 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. (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. (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. (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. 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. KISS *= simplicity instead of over-engineering = YAGNI = You Ain’t Gonna Need It
  • 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. 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. 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. 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 ;)
  • 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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”
  • 111. Consider… public class Widget {
 private final SelenideElement container;
 
 public Widget(SelenideElement container) {
 this.container = container;
 }
 
 public SelenideElement self() {
 return this.container;
 }
 }
  • 112. Easy public class Post extends Widget{ 
 public Post(SelenideElement container) {
 super(container);
 }
 
 public void shouldBe(String withText) {
 self().shouldHave(text(withText));
 }
 }
  • 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. 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. Simple is not Easy
  • 117. assert KISS != magic assert KISS == explicit
  • 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. @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
  • 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. 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. 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. KISS Answer • User oriented automation based on waiting loadable elements over loadable pages waiting their components
  • 125. Diaspora Demo How often do you open Facebook, and proceed to profile or messages, while the stream is not loaded yet? ;)
  • 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. 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. PageObjects Secret ;) NO Selenium Webdriver, use more concise wrappers!
  • 129. PageObjects Secret ;) Prefer test automation tools over browser automation tools
  • 130. Afterwords There are good practices in context, but there are no best practices. (c) Cem Kaner, James Bach
  • 131. Q&A