Professional Documents
Culture Documents
Kisspageobjects 01 2017 170113192904
Kisspageobjects 01 2017 170113192904
Kisspageobjects 01 2017 170113192904
@yashaka 01.2017
Plan
Intro
Classic examples
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?
Page objects are a classic example of encapsulation - they hide
the details of the UI structure and widgetry from other
components (the tests)
–Martin Fowler
(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();
}
}
PageObjects as LoadableComponents
– https://github.com/SeleniumHQ/selenium/wiki/LoadableComponent
Classic 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"));
}
public class Google {
…
public Google open() {
this.driver.get("http: //google.com/ncr");
return this;
}
Classic 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"));
}
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"));
}
@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
–Martin Fowler
Why may we need Encapsulation?
• 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
• :)
A point to think about…
“Encapsulate to narrow User Oriented behavioural API to be readable
enough and convenient in usage”
• Manual Testers?
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”?
@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
API is already readable and simple!
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"));
}
developers know locators that are already readable for them too:)
For “developers”?
@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"));
}
copy&paste rules! ;)
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"));
}
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
Assertions-free PageObjects?
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.
–Martin Fowler
https://martinfowler.com/bliki/PageObject.html
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
assertions from “assertion library” provided by Selenide
public void followFirstLink() {
Google google = new Google();
google.open().search("Selenide");
google.followResultLink(0);
Selenide.Wait().until(titleIs("Selenide: concise UI tests in Java"));
}
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” ?
• (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
• lower cohesion
• high cohesion
• End User oriented. He needs “behavioural object”, He actually does not care
about:
• 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
• high cohesion
• KISS: one class instead of two ones for the same thing from the User point of
view
• 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
• high cohesion
• KISS: one class instead of two ones for the same thing from the User point of
view
KISS
• answer: break down into smaller PageObjects
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.
• you will have still two or even more classes but for exactly one thing from
(c) Martin Fowler
the User point of view
(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
• high cohesion
• KISS: one class instead of two ones for the same thing from the User point of
view
• you will have still two or even more classes but for exactly one thing from
the User point of view
• full encapsulation
• you will have still two or even more classes but for exactly one thing from
the User point of view
(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
vs YAGNI version
the only probable thing with a real risk of change is the locator
public class Google {
public final ElementsCollection results = $$(".srg>.g");
vs YAGNI version
• 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? ;)
• 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? ;)
@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…
• We still can use API calls to check that “new post” from an author
gets the storage
• We still can use API calls to check that “new post” from an author
gets the storage
...
@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!");
}
Only then we need “explicit driver management”
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();
}
}
KISS
=
not necessarily Easiness
Consider…
–Rich Hickey
assert KISS != magic
assert KISS == explicit
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().shareNewPost("Selenide 4.2 released!”)
}
@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
WaitingLoadable?
Recall “Super-duper” WaitingLoadableComponents
Diaspora Demo
So KISS leads to
• User Oriented waiting
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 @
github.com/automician
automician.com
seleniumcourses.com
@yashaka 01.2017