To ATDD And BeyondBetter Automated Acceptance Testing on the JVMJohn Ferguson Smart
Consulta                    nt            Trainer           Mentor           Author          Speaker          CoderJohn Fe...
The Plan 1) What is ATDD?2) How can I do it3) Let’s see it work
Acceptance Test Driven Development      The story of your app
Goals          Make money by selling our stuff online                   Display catalogFeatures/Epics                     ...
User stories                                       Browse Catalog                                       In order to find st...
User stories                                         Browse Catalog                                         In order to fin...
Automated acceptance testImplemented development tests   Implemented acceptance tests
Automated acceptance criteria                                Define your goals
Automated acceptance criteria                                keep you on track
Automated acceptance criteria                                Provide better visibility
Automated acceptance criteria                          Allow faster release cycles
Automated acceptance criteria                                Reduce Risk
Automated acceptance criteria                   31%	  faster	  deliveryDelivery	  Time                                Usin...
Automated acceptance criteria                 4	  4mes	  less	  defectsDefect	  Rate                              Using	  ...
Why only do QAat the end of the project?
Real quality cannot be injected at the end                            It must be part of the process
Leave the boring stuff to the automated tests...
...and empower your QA team          Let testers focus on more intelligent testing
20Keeping an eye on things
21(Think “Two-CDs”)
221   Discover your acceptance criteria    2   Automate your acceptance criteria        3   Implement your acceptance crit...
23        1        Discover your acceptance criteriaFeature: Browse CatalogIn order to find items that I would like to buy...
24                               1     Discover your acceptance criteriaAcceptance Criteria  See all the top-level categor...
25                       2       Automate your acceptance criteria  Story: Browse by category  In order to find items more...
26 2   Automate your acceptance criteria...but they will be reported as ‘pending’
27           3    Implement your acceptance criteriaNarrative:In order to find items more easilyAs a customerI want to be ...
283   Implement your acceptance criteria
293   Implement your acceptance criteria                                         JUnit
304   Execute your acceptance tests
31
32
33
34
35
36
Functional Test Coverage  “What was tested”          vs  “What’s been done”
What have we tested?                       Broken                       WorksIn Progress
What have we finished?              Specified but no tests                        Partially done?                          ...
What have we finished?
What have we finished?
What have we finished?
Better organised requirements
Stay on top of your scenarios
Stay on top of your scenarios
Stay on top of your scenariosBetter visibility on what is doneMore targeted reporting
Keep your scenarios organizedGoal:In order to increase revenue from commissions on classified ads salesAs the head of the ...
Store your requirements where it suits you
Customize Thucydides to work for you                                                                                      ...
Use tags for orthogonal concerns• Non-functional requirements• Iterations• Current iteration vs regression tests• Acceptan...
Use tags for orthogonal concerns@RunWith(ThucydidesRunner)                  Tagging in JUnit@Issue("NC-1")@WithTag(name="B...
Use tags for orthogonal concernsMeta:@tag component:search                                            Tagging in JBehaveSc...
Use tags for orthogonal concernsMeta:@tag component:searchScenario: Search by keywordGiven that I want to find products in...
More structured tests
Have you got web tests that look like this?
Have you got web tests that look like this?
Page Objects To The Rescue!
Page Objects To The Rescue! Better encapsulation Easier to read Easier to maintain Still a bit unclear what we are doing
Test Steps To The Rescue!Step methods focus on what we are doing
Test Steps To The Rescue!                       Step implementations detail the how
Test Steps To The Rescue!                                            Steps are ordinary methods    @Step    public void pr...
Test Steps To The Rescue!    @Step    def browses_product_categories(String... categories) {        selects_top_level_cate...
Test Steps To The Rescue!                 Test reports document the what and the how
Test Steps To The Rescue! Focus on what we are doing, not how we do it More reusability Better reporting
Even better with JBehave!Narrative:In order to find items more easilyAs a customerI want to be able to browse through the ...
Even better with JBehave!Narrative:In order to find items more easilyAs a customerI want to be able to browse through the ...
Even better with JBehave!Narrative:In order to find items more easilyAs a customerI want to be able to browse through the ...
Even better with JBehave!Narrative:In order to find items more easilyAs a customerI want to be able to browse through the ...
Better Page Objects
Thucydides Page Object support Fluent selectors Fluent matchers HTML tables Fluent waits
Thucydides Fluent Selectorspublic class HomePage extends PageObject {    @FindBy(name="adFilter.searchTerm")    WebElement...
Thucydides Fluent Selectorsclass ShoppingCartPage extends PageObject {    ShoppingCartPage(WebDriver driver) {        supe...
Thucydides Fluent Selectors  class ShoppingCartPage extends PageObject {      ShoppingCartPage(WebDriver driver) {        ...
Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         s...
Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         s...
Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         s...
Thucydides Fluent Selectors    List<String> getYAxes() {        def axesElements = findAll(".dygraph-axis-label-y");      ...
Thucydides Fluent Matchers
Thucydides Fluent Matchersimport static org.hamcrest.Matchers.is;import static net.thucydides.core.matchers.BeanMatchers.t...
Thucydides Fluent Matchersimport static org.hamcrest.Matchers.is;           import static net.thucydides.core.matchers.Bea...
Thucydides Fluent Matchersimport static org.hamcrest.Matchers.is;import static net.thucydides.core.matchers.BeanMatchers.t...
Thucydides Fluent MatchersList<Person> persons = Arrays.asList(new Person("Bill", "Oddie"),                               ...
Thucydides and Tablesimport static net.thucydides.core.pages.components.HtmlTable.rowsFrom;public class SearchResultsPage ...
Thucydides and Tables    @Test    public void clicking_on_artifact_should_display_details_page() {        developer.opens_...
Thucydides and Tables    @Test    public void clicking_on_artifact_should_display_details_page() {        developer.opens_...
Thucydides Fluent Waits Convenience methods Extends the WebDriver Wait API
Thucydides Fluent Waits    void addToCart(Integer quantity) {        $(quantitySelection).selectByVisibleText(quantity.toS...
Thucydides Fluent Waits    def searchByLocation(postcode) {        element(location).typeAndTab(postcode)        element(l...
Thucydides Assertsdef shouldDisplaySubcategory(subCategory) {    findBy(".SubCategoryList").then(By.linkText(subCategory))...
Smarter Webdriver coding
Smarter Webdriver coding   Prefer CSS to XPath
Smarter Webdriver coding    Use Nested Finds
Nested Findspublic void chooseCategory(String category) {    findBy("//div[@id=category-select]//a[contains(@class,arrow)]...
Smarter Webdriver coding  Confine Webdriver to   your Page Objects
Encapsulate Page Objectsclass ShoppingCartPage extends PageObject {    public ShoppingCartPage(WebDriver driver) {        ...
Smarter Webdriver coding Webdriver Query ~= JDBC Query
Learning More                                                                         stshttp://thucydi                   ...
To ATDD And Beyond       Better Automated Acceptance Testing on the JVM               Thank YouJohn Ferguson Smart
Upcoming SlideShare
Loading in …5
×

To atdd-and-beyond

2,358 views

Published on

To atdd-and-beyond

  1. 1. To ATDD And BeyondBetter Automated Acceptance Testing on the JVMJohn Ferguson Smart
  2. 2. Consulta nt Trainer Mentor Author Speaker CoderJohn Fer guson S mar t
  3. 3. The Plan 1) What is ATDD?2) How can I do it3) Let’s see it work
  4. 4. Acceptance Test Driven Development The story of your app
  5. 5. Goals Make money by selling our stuff online Display catalogFeatures/Epics Order products Build a wishlist User stories Browse Catalog In order to find stuff I would like to buy As a customer I want to be able to browse through the catalog
  6. 6. User stories Browse Catalog In order to find stuff I would like to buy As a customer I want to be able to browse through the catalogAcceptance criteria ☑  See  all  the  top-­‐level  categories  in  the  catalog ☑  Browse  through  the  sub-­‐categories ☑  See  all  the  products  in  a  category
  7. 7. User stories Browse Catalog In order to find stuff I would like to buy As a customer I want to be able to browse through the catalogAcceptance criteria ☑  See  all  the  top-­‐level  categories  in  the  catalog ☑  Browse  through  the  sub-­‐categories ☑  See  all  the  products  in  a  categoryScenario: See all top-level categoriesGiven I want to browse the catalogWhen I am on the home pageThen I should see the following product categories: Clothing, Accessories, Shoes Automated acceptance test
  8. 8. Automated acceptance testImplemented development tests Implemented acceptance tests
  9. 9. Automated acceptance criteria Define your goals
  10. 10. Automated acceptance criteria keep you on track
  11. 11. Automated acceptance criteria Provide better visibility
  12. 12. Automated acceptance criteria Allow faster release cycles
  13. 13. Automated acceptance criteria Reduce Risk
  14. 14. Automated acceptance criteria 31%  faster  deliveryDelivery  Time Using  ATDD Tradi4onal Provide more value
  15. 15. Automated acceptance criteria 4  4mes  less  defectsDefect  Rate Using  ATDD Tradi4onal Higher Quality
  16. 16. Why only do QAat the end of the project?
  17. 17. Real quality cannot be injected at the end It must be part of the process
  18. 18. Leave the boring stuff to the automated tests...
  19. 19. ...and empower your QA team Let testers focus on more intelligent testing
  20. 20. 20Keeping an eye on things
  21. 21. 21(Think “Two-CDs”)
  22. 22. 221 Discover your acceptance criteria 2 Automate your acceptance criteria 3 Implement your acceptance criteria 4 Execute your acceptance tests
  23. 23. 23 1 Discover your acceptance criteriaFeature: Browse CatalogIn order to find items that I would like to buyAs a customerI want to be able to browse through the catalog Story: Browse by category In order to find items more easily As a customer I want to be able to browse through the product categories Acceptance Criteria See all the top-level categories Browse through the category hierarchy Should display the correct products for each category Each category should have the correct sub-categories Define acceptance criteria for each story
  24. 24. 24 1 Discover your acceptance criteriaAcceptance Criteria See all the top-level categories Browse through the category hierarchy Should display the correct products for each category Each category should have the correct sub-categories Scenario: See all top-level categories Given I want to browse the catalog When I am on the home page Then I should see the following product categories: Clothing, Accessories, Shoes Clarify the acceptance criteria with examples
  25. 25. 25 2 Automate your acceptance criteria Story: Browse by category In order to find items more easily Acceptance Criteria As a customer top-level categories See all the I want Browse through the category the product categories to be able to browse through hierarchy Scenario: See all top-level categories Should display the correct products for each category Given I want to browse the catalog Each category should have the correct sub-categories When I am on the home page Then I should see the following product categories: Clothing, Accessories, ShoesNarrative:In order to find items more easilyAs a customerI want to be able to see what product categories existScenario: See all top-level categoriesGiven I want to browse the catalogWhen I am on the home pageThen I should see the following product categories: Clothing, Accessories, Shoes We now have an executable requirement
  26. 26. 26 2 Automate your acceptance criteria...but they will be reported as ‘pending’
  27. 27. 27 3 Implement your acceptance criteriaNarrative:In order to find items more easilyAs a customerI want to be able to see what product categories existScenario: See all top-level categoriesGiven I want to browse the catalogWhen I am on the home pageThen I should see the following product categories: Clothing, Accessories, Shoes
  28. 28. 283 Implement your acceptance criteria
  29. 29. 293 Implement your acceptance criteria JUnit
  30. 30. 304 Execute your acceptance tests
  31. 31. 31
  32. 32. 32
  33. 33. 33
  34. 34. 34
  35. 35. 35
  36. 36. 36
  37. 37. Functional Test Coverage “What was tested” vs “What’s been done”
  38. 38. What have we tested? Broken WorksIn Progress
  39. 39. What have we finished? Specified but no tests Partially done? Finished
  40. 40. What have we finished?
  41. 41. What have we finished?
  42. 42. What have we finished?
  43. 43. Better organised requirements
  44. 44. Stay on top of your scenarios
  45. 45. Stay on top of your scenarios
  46. 46. Stay on top of your scenariosBetter visibility on what is doneMore targeted reporting
  47. 47. Keep your scenarios organizedGoal:In order to increase revenue from commissions on classified ads salesAs the head of the classified ads departmentI want to increase the number of items sold via our classified ads Capability In order to increase the number of items I sell Feature As a seller In order to increase sales of advertised articles I want buyers to be able to view ads for items As a seller they might want to purchase I want potential buyers to be able to display only the ads for articles that they might be interested in purchasing. Story In order to find the items I am interested in faster As a buyer I want to be able to list all the ads with a particular keyword in the description or title.
  48. 48. Store your requirements where it suits you
  49. 49. Customize Thucydides to work for you RequirementsTagProvider getRequirements() getParentRequirementOf(testOutcome) Capability In order to increase the number of items I sell As a seller I want buyers to be able to view ads for items they might want to Feature In order to increase sales of advertised articles As a seller MyRequirementProvider I want potential buyers to be able to display only the ads for articles MYPROJ-123 Story In order to find the items I am interested in faster As a buyer I want to be able to list all the ads with a particular keyword in the description or title. Meta: @issue MYPROJ-123 Scenario: Search by keyword Given that I want to find products in the "<range>" range When I search for products by keyword "<keywords>" Then I should see a product with the title <expectedTitle>
  50. 50. Use tags for orthogonal concerns• Non-functional requirements• Iterations• Current iteration vs regression tests• Acceptance tests vs more detailed tests• Related features• System components• ...
  51. 51. Use tags for orthogonal concerns@RunWith(ThucydidesRunner) Tagging in JUnit@Issue("NC-1")@WithTag(name="Browse Ads")class BrowseAdsByCategory {    @Managed    public WebDriver webdriver    @ManagedPages(defaultUrl = "http://www.newsclassifieds-uat.appspot.com")    public Pages pages    @Steps    BuyerSteps buyer    @Test    void "Browse ads by category"() {        buyer.opens_home_page()        buyer.chooses_classification "Furniture & Homewares"        buyer.should_see_search_results_for "Furniture & Homewares"        buyer.chooses_entry(1).from().other_results()        buyer.should_see_details_for_the_selected_ad()        buyer.returns_to_search_results()        buyer.should_see_search_results_for "Furniture & Homewares"    }}
  52. 52. Use tags for orthogonal concernsMeta:@tag component:search Tagging in JBehaveScenario: Search by keywordGiven that I want to find products in the "<range>" rangeWhen I search for products by keyword "<keywords>"Then I should see a product with the title <expectedTitle>Examples:| range | keywords | expectedTitle || Simone | Simone | 3by3 Simone || Grey Steel Classic | Steel | 3by3 Grey Steel Classic || KV Bags | KV | KV Classic Satchel |
  53. 53. Use tags for orthogonal concernsMeta:@tag component:searchScenario: Search by keywordGiven that I want to find products in the "<range>" rangeWhen I search for products by keyword "<keywords>"Then I should see a product with the title <expectedTitle>Examples:| range | keywords | expectedTitle || Simone | Simone | 3by3 Simone || Grey Steel Classic | Steel | 3by3 Grey Steel Classic || KV Bags | KV | KV Classic Satchel |
  54. 54. More structured tests
  55. 55. Have you got web tests that look like this?
  56. 56. Have you got web tests that look like this?
  57. 57. Page Objects To The Rescue!
  58. 58. Page Objects To The Rescue! Better encapsulation Easier to read Easier to maintain Still a bit unclear what we are doing
  59. 59. Test Steps To The Rescue!Step methods focus on what we are doing
  60. 60. Test Steps To The Rescue! Step implementations detail the how
  61. 61. Test Steps To The Rescue! Steps are ordinary methods    @Step    public void proceedToPayment() {        adPreviewPage.proceedToPayment();    } They can have parameters    @Step    public void searches_for(String searchTerms) {         homePage.searchFor(searchTerms);    } They can be pending    @Pending @Step     public void searches_for(String searchTerms) {         // Not done yet     }     @Step("The carousel should display {0} at a time")     def should_see_a_number_of_visible_ads_in_the_carousel(int adCount) {         assert homePage.visibleCarouselAds.size() == adCount     } You can customize the title
  62. 62. Test Steps To The Rescue!    @Step    def browses_product_categories(String... categories) {        selects_top_level_category(categories.head())        categories.tail().each { subcategory ->            browse_through_subcategory(subcategory)        }    } Steps can call other steps    @Step    def selects_top_level_category(String category) {        homePage.select_top_level_category category    }        @Step    def browse_through_subcategory(subcategory) {        homePage.select_subcategory(subcategory)    }
  63. 63. Test Steps To The Rescue! Test reports document the what and the how
  64. 64. Test Steps To The Rescue! Focus on what we are doing, not how we do it More reusability Better reporting
  65. 65. Even better with JBehave!Narrative:In order to find items more easilyAs a customerI want to be able to browse through the product categoriesScenario: Browse through product categoriesGiven I am on the home pageWhen I browse through the product categories Clothing, Mens, ShirtsThen I should see a product with the title 3by3 Milano Stretch Cotton Shirt JBehave describes what we are doing Use normal Thucydides steps in the implementation
  66. 66. Even better with JBehave!Narrative:In order to find items more easilyAs a customerI want to be able to browse through the product categoriesScenario: Browse through product categoriesGiven I am on the home pageWhen I browse through the product categories Clothing, Mens, ShirtsThen I should see a product with the title 3by3 Milano Stretch Cotton Shirt class BrowseByCategorySteps extends BigCommerceJBehaveSteps{     @Steps     CustomerSteps customer; Use normal Thucydides steps     @Given(I am on the home page)     public void givenIAmOnTheHomePage() {         customer.opens_home_page()     }          @When(I browse through the product categories $categories)     public void whenIBrowseThroughTheProductCategories(List<String> categories) {         customer.browses_product_categories(*categories)     }     @Then(I should see a product with the title "$expectedTitle")     public void thenIShouldSeeAProductWithTheTitle(String expectedTitle) {         customer.should_see_product withTitle(expectedTitle)     } }
  67. 67. Even better with JBehave!Narrative:In order to find items more easilyAs a customerI want to be able to browse through the product categories ContextScenario: Browse through product categoriesGiven I am on the home pageWhen I browse through the product categories Clothing, Mens, ShirtsThen I should see a product with the title 3by3 Milano Stretch Cotton Shirt What How Illustrations
  68. 68. Even better with JBehave!Narrative:In order to find items more easilyAs a customerI want to be able to browse through the product categoriesScenario: Browse through product categoriesGiven I am on the home pageWhen I browse through the product categories Clothing, Mens, ShirtsThen I should see a product with the title 3by3 Milano Stretch Cotton Shirt Living documentation Easier to read Easier to maintain More work maintaining the .story files
  69. 69. Better Page Objects
  70. 70. Thucydides Page Object support Fluent selectors Fluent matchers HTML tables Fluent waits
  71. 71. Thucydides Fluent Selectorspublic class HomePage extends PageObject {    @FindBy(name="adFilter.searchTerm")    WebElement searchTerm;    @FindBy(css=".keywords button")    WebElement search;    public HomePage2(WebDriver driver) {        super(driver);    } Fluent selectors    public void chooseCategory(String category) {        findBy("#category-select").then(".arrow").then().click();        findBy("#category-select").then(By.linkText(category)).then().click();    }    public void enterKeywords(String keywords) {        $(searchTerm).type(keywords);    }    public void performSearch() {        $(search).click(); Thucydides helper methods    }}
  72. 72. Thucydides Fluent Selectorsclass ShoppingCartPage extends PageObject {    ShoppingCartPage(WebDriver driver) {        super(driver)    } Page Object returns domain classes    @FindBy(css = ".CartContents tbody tr")    List<WebElement> shoppingCartItems    List<CartItem> getCartItems() {        shoppingCartItems.collect { cartItemfromElement(it) }    }    CartItem cartItemfromElement(WebElement element) {        Integer quantity = Integer.parseInt(itemQuantity(element))        String product = productName(element)        BigDecimal itemPrice = priceOf(itemPrice(element))        BigDecimal totalPrice = priceOf(totalPrice(element))        return CartItem.containing(quantity).productsCalled(product).                                             withAnItemPriceOf(itemPrice).                                             andATotalOf(totalPrice)    } ... Building a CartItem from the WebElement
  73. 73. Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         super(driver)     } Classic WebDriver    private String =productName(WebElement tr")     @FindBy(css ".CartContents tbody cartItem) {        cartItem.findElement(By.className("ProductName")).text     List<WebElement> shoppingCartItems    }     List<CartItem> getCartItems() {    private String totalPrice(WebElement cartItem) {         shoppingCartItems.collect { cartItemfromElement(it) }        cartItem.findElement(By.className("CartItemTotalPrice")).text     }    }     CartItem cartItemfromElement(WebElement element) {    private String quantity = Integer.parseInt(itemQuantity(element))         Integer itemPrice(WebElement cartItem) {        cartItem.findElement(By.className("CartItemIndividualPrice")).text         String product = productName(element)    }         BigDecimal itemPrice = priceOf(itemPrice(element))         BigDecimal totalPrice = priceOf(totalPrice(element))    private String itemQuantity(WebElement cartItem) {        def itemQuantityDropdown         return CartItem.containing(quantity).productsCalled(product).          = cartItem.findElement(                                              withAnItemPriceOf(itemPrice).                     By.xpath(".//td[contains(@class,CartItemQuantity)]/select"))                                              andATotalOf(totalPrice)        element(itemQuantityDropdown).selectedValue     }    } ...
  74. 74. Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         super(driver)     } Using Thucydides Fluent Selectors     @FindBy(css = ".CartContents tbody tr")    private String productName(WebElement cartItem) {     List<WebElement> shoppingCartItems        element(cartItem).findBy(".ProductName").text    }     List<CartItem> getCartItems() {         shoppingCartItems.collect { cartItemfromElement(it) }    private String totalPrice(WebElement cartItem) {     }        element(cartItem).findBy(".CartItemTotalPrice").text    }     CartItem cartItemfromElement(WebElement element) {         Integer quantity = Integer.parseInt(itemQuantity(element))    private String itemPrice(WebElement cartItem) {         String product = productName(element)        element(cartItem).findBy(".CartItemIndividualPrice").text         BigDecimal itemPrice = priceOf(itemPrice(element))    }         BigDecimal totalPrice = priceOf(totalPrice(element))    private String itemQuantity(WebElement cartItem) {         return CartItem.containing(quantity).productsCalled(product).        element(cartItem).findBy(".CartItemQuantity").then("select").selectedValue                                              withAnItemPriceOf(itemPrice).    }                                              andATotalOf(totalPrice)     } ...
  75. 75. Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         super(driver)     } Using short-hand Thucydides Fluent Selectors     @FindBy(css = ".CartContents tbody tr")    private String productName(WebElement cartItem) {     List<WebElement> shoppingCartItems        $(cartItem).findBy(".ProductName").text    }     List<CartItem> getCartItems() {         shoppingCartItems.collect { cartItemfromElement(it) }    private String totalPrice(WebElement cartItem) {     }        $(cartItem).findBy(".CartItemTotalPrice").text    }     CartItem cartItemfromElement(WebElement element) {         Integer quantity = Integer.parseInt(itemQuantity(element))    private String itemPrice(WebElement cartItem) {         String product = productName(element)        $(cartItem).findBy(".CartItemIndividualPrice").text         BigDecimal itemPrice = priceOf(itemPrice(element))    }         BigDecimal totalPrice = priceOf(totalPrice(element))    private String itemQuantity(WebElement cartItem) {         return CartItem.containing(quantity).productsCalled(product).        $(cartItem).findBy(".CartItemQuantity").then("select").selectedValue                                              withAnItemPriceOf(itemPrice).    }                                              andATotalOf(totalPrice)     } ...
  76. 76. Thucydides Fluent Selectors class ShoppingCartPage extends PageObject {     ShoppingCartPage(WebDriver driver) {         super(driver)     } (Pure Java version)     @FindBy(css = ".CartContents tbody tr")    private String productName(WebElement cartItem) {     List<WebElement> shoppingCartItems        return $(cartItem).findBy(".ProductName").getText();    }     List<CartItem> getCartItems() {    private String totalPrice(WebElement cartItem) {         shoppingCartItems.collect { cartItemfromElement(it) }        return $(cartItem).findBy(".CartItemTotalPrice").getText();     }    }     CartItem cartItemfromElement(WebElement element) {    private String itemPrice(WebElement cartItem) {         Integer quantity = Integer.parseInt(itemQuantity(element))        return $(cartItem).findBy(".CartItemIndividualPrice").getText();         String product = productName(element)    }         BigDecimal itemPrice = priceOf(itemPrice(element))         BigDecimal totalPrice = priceOf(totalPrice(element))    private String itemQuantity(WebElement cartItem) {        return $(cartItem).findBy(".CartItemQuantity").then("select").getSelectedValue();    }         return CartItem.containing(quantity).productsCalled(product).                                              withAnItemPriceOf(itemPrice).                                              andATotalOf(totalPrice)     } ...
  77. 77. Thucydides Fluent Selectors    List<String> getYAxes() {        def axesElements = findAll(".dygraph-axis-label-y");        axesElements.collect { WebElement axis -> axis.text }    } Finding multiple elements
  78. 78. Thucydides Fluent Matchers
  79. 79. Thucydides Fluent Matchersimport static org.hamcrest.Matchers.is;import static net.thucydides.core.matchers.BeanMatchers.the;...    @Steps    public DeveloperSteps developer;    @Test    public void should_search_for_artifacts_by_name() {        developer.opens_the_search_page();        developer.searches_for("Thucydides");        developer.should_see_artifacts_where(the("ArtifactId", is("thucydides")),                                             the("GroupId", is("net.thucydides")));    } Using Thucydides matchers And Hamcrest matchers
  80. 80. Thucydides Fluent Matchersimport static org.hamcrest.Matchers.is; import static net.thucydides.core.matchers.BeanMatcherAsserts.shouldMatch;import static net.thucydides.core.matchers.BeanMatchers.the;... public class DeveloperSteps extends ScenarioSteps {    @Steps    public DeveloperSteps developer;     SearchPage searchPage;     SearchResultsPage searchResultsPage;    @Test    public void should_search_for_artifacts_by_name() {     public DeveloperSteps(Pages pages) {        developer.opens_the_search_page();         super(pages);        developer.searches_for("Thucydides");         searchResultsPage = pages.getPage(SearchResultsPage.class);        developer.should_see_artifacts_where(the("ArtifactId", is("thucydides")), searchPage = pages.getPage(SearchPage.class);                                             the("GroupId", is("net.thucydides")));     }    } The Page Object returns a     @Step     public void opens_the_search_page() { list of POJOs         searchPage.open();     } Applying the matchers     @Step     public void searches_for(String search_terms) {         searchPage.enter_search_terms(search_terms);         searchPage.starts_search();     }     @Step     public void should_see_artifacts_where(BeanMatcher... matchers) {         shouldMatch(searchResultsPage.getSearchResults(), matchers);
  81. 81. Thucydides Fluent Matchersimport static org.hamcrest.Matchers.is;import static net.thucydides.core.matchers.BeanMatchers.the;...    @Steps    public DeveloperSteps developer; The page object should return POJOs    @Test     public List<Artifact> getResults() {    public void should_search_for_artifacts_by_name() {         List<WebElement> rows = resultTable.findElements(By.xpath(".//tr[td]"));        developer.opens_the_search_page();         return convert(rows, toArtifacts());        developer.searches_for("Thucydides");     }        developer.should_see_artifacts_where(the("ArtifactId", is("thucydides")),                                             the("GroupId", is("net.thucydides")));    }     private Converter<WebElement, Artifact> toArtifacts() {         return new Converter<WebElement, Artifact>() {             public Artifact convert(WebElement row) {                 List<WebElement> cells = row.findElements(By.tagName("td"));                 String groupId = cells.get(0).getText();                 String artifactId = cells.get(1).getText();                 String latestVersion = cells.get(2).getText();                 return new Artifact(groupId, artifactId, latestVersion);             }         };     }
  82. 82. Thucydides Fluent MatchersList<Person> persons = Arrays.asList(new Person("Bill", "Oddie"), new Person("Tim", "Brooke-Taylor"));shouldMatch(persons, the("firstName", is(not("Tim"))));shouldMatch(persons, the("firstName", startsWith("B"))); Matcher work with simple POJOs
  83. 83. Thucydides and Tablesimport static net.thucydides.core.pages.components.HtmlTable.rowsFrom;public class SearchResultsPage extends PageObject {    WebElement resultTable;    public SearchResultsPage(WebDriver driver) {        super(driver);    }    public List<Map<String, String>> getSearchResults() {        return rowsFrom(resultTable);    }} Convenience methods: convert a table to a map of Strings
  84. 84. Thucydides and Tables    @Test    public void clicking_on_artifact_should_display_details_page() {        developer.opens_the_search_page();        developer.searches_for("Thucydides");        developer.open_artifact_where(the("ArtifactId", is("thucydides")),                                      the("GroupId", is("net.thucydides")));        developer.should_see_artifact_details_where(the("artifactId", is("thucydides")),                                                    the("groupId", is("net.thucydides")));    } Using matchers to click on a row in a table
  85. 85. Thucydides and Tables    @Test    public void clicking_on_artifact_should_display_details_page() {        developer.opens_the_search_page();        developer.searches_for("Thucydides");        developer.open_artifact_where(the("ArtifactId", is("thucydides")),                                      the("GroupId", is("net.thucydides")));        developer.should_see_artifact_details_where(the("artifactId", is("thucydides")),                                                    the("groupId", is("net.thucydides")));    } Pass through matchers @Step     public void open_artifact_where(BeanMatcher... matchers) {         searchResultsPage.clickOnFirstRowMatching(matchers);     } import static net.thucydides.core.pages.components.HtmlTable.filterRows; ...     @FindBy(css="#resultTable")     WebElement resultTable; Return matching rows     public void clickOnFirstRowMatching(BeanMatcher... matchers) {         List<WebElement> matchingRows = filterRows(resultTable, matchers);         WebElement targetRow = matchingRows.get(0);         WebElement detailsLink = $(targetRow).findBy(".//a[contains(@href,artifactdetails)]");         detailsLink.click();     }
  86. 86. Thucydides Fluent Waits Convenience methods Extends the WebDriver Wait API
  87. 87. Thucydides Fluent Waits    void addToCart(Integer quantity) {        $(quantitySelection).selectByVisibleText(quantity.toString())        $(addToCart).click()        waitForTextToAppear("added to your cart")    } A simple wait    void uploadExistingImage(String imageUrl) {        // upload an image        ...        waitForPresenceOf(".photo")    } Wait for an element to be rendered    def waitForPageToLoad() {        waitFor(500).milliseconds()        waitForAbsenceOf("#loading")    } Wait for an element to disappear
  88. 88. Thucydides Fluent Waits    def searchByLocation(postcode) {        element(location).typeAndTab(postcode)        element(locationGo).click()        waitFor("//div[@class=f-item][contains(.,${postcode})]")    } Waiting for an XPath expression    def filterByLocation(String state) {        element(locationList).click()        waitFor("#location-select li a")        findBy("//span[@id=location-select]//a[contains(.,$state)]").then().click()        waitForCondition().until(stateIsChosen(state))    } Wait for a non-trivial condition    Function<? super WebDriver, Boolean> stateIsChosen(final String state) {        return new Function<? super WebDriver, Boolean>() {            @Override            Boolean apply(driver) {                return state == getCurrentSelectedState()            };        }    } What are we waiting for
  89. 89. Thucydides Assertsdef shouldDisplaySubcategory(subCategory) {    findBy(".SubCategoryList").then(By.linkText(subCategory)).shouldBeCurrentlyVisible()} Check that this element is visible@FindBy(id=”search”)WebElement searchButtondef searchShouldBeEnabled() {    searchButton.shouldBeCurrentlyEnabled()} Check that this element is enabled
  90. 90. Smarter Webdriver coding
  91. 91. Smarter Webdriver coding Prefer CSS to XPath
  92. 92. Smarter Webdriver coding Use Nested Finds
  93. 93. Nested Findspublic void chooseCategory(String category) {    findBy("//div[@id=category-select]//a[contains(@class,arrow)]").click();    findBy("//div[@id=category-select]//a[.,contains($category)]").click();} Hard to read, hard to maintain public void chooseCategory(String category) {     findBy("#category-select").then(".arrow").then().click();     findBy("#category-select").then(By.linkText(category)).then().click(); } A more readable approach
  94. 94. Smarter Webdriver coding Confine Webdriver to your Page Objects
  95. 95. Encapsulate Page Objectsclass ShoppingCartPage extends PageObject {    public ShoppingCartPage(WebDriver driver) {        super(driver) Public API    }    public List<CartItem> getCartItems() {        shoppingCartItems.collect { cartItemfromElement(it) }    } Webdriver stays    @FindBy(css = ".CartContents tbody tr")    private List<WebElement> shoppingCartItems private    private CartItem cartItemfromElement(WebElement element) {        Integer quantity = Integer.parseInt(itemQuantity(element))        String product = productName(element)        BigDecimal itemPrice = priceOf(itemPrice(element))        BigDecimal totalPrice = priceOf(totalPrice(element))        return CartItem.containing(quantity).productsCalled(product).                                             withAnItemPriceOf(itemPrice).                                             andATotalOf(totalPrice)    } ...
  96. 96. Smarter Webdriver coding Webdriver Query ~= JDBC Query
  97. 97. Learning More stshttp://thucydi cydides-webte des.info github.com/thu http://thucydides-webtests.com
  98. 98. To ATDD And Beyond Better Automated Acceptance Testing on the JVM Thank YouJohn Ferguson Smart

×