Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

A journey beyond the page object pattern

10,617 views

Published on

A story of one organisation taking an idea, formed by Antony Marcano and evolved by many, to solve some of the challenges that encountered with the growth of Selenium/WebDriver PageObjects in their Automated Tests. Talk co-presented with two of the people who have been a part of the journey – including Kostas Mamalis & Jan Molak.

Published in: Technology, Design
  • I've written about DRY page objects in my blog post: http://burdettelamar.wordpress.com/2014/03/21/keep-your-page-objects-dry/
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Was this presentation recorded?
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

A journey beyond the page object pattern

  1. 1. SCREENPLAY A Journey Beyond The PageObject Pattern Antony Marcano Jan Molak Kostas Mamalis
  2. 2. CONTACT • Kostas Mamalis @agiletestinguk kostas@masterthought.net • Antony Marcano @AntonyMarcano antony@riverglide.com • Jan Molak @JanMolak jan.molak@smartcodeltd.co.uk
  3. 3. CONTEXT
  4. 4. IN THE BEGINNING • Long established financial institution • Used Scrum with 2 week Sprints • Outsourced development to large consultancy • No automated acceptance tests (few unit tests)
  5. 5. ALONG THE JOURNEY • Realised that automated tests were essential • Wrote lots of Cucumber tests • Backed by Selenium/WebDriver • Used the PageObject Pattern
  6. 6. SELENIUM AND THE PAGE OBJECT PATTERN Illustrated with the Pet Clinic
  7. 7. TESTING THE PET CLINIC
  8. 8. WEBDRIVER EXAMPLE DesiredCapabilities capabilities = new DesiredCapabilities(); WebDriver driver = new PhantomJSDriver(desiredCapabilities()); driver.get(baseUrl+"owners/find.html"); driver.findElement(By.cssSelector("#search-owner-form button")).click(); assertThat( driver.findElements(By.cssSelector("owners tbody tr")).size(), is(10) );
  9. 9. FINDING ALL OWNERS - WEBDRIVER EXAMPLE DesiredCapabilities capabilities = new DesiredCapabilities(); WebDriver driver = new PhantomJSDriver(desiredCapabilities()); driver.get(baseUrl+"owners/find.html"); driver.findElement(By.cssSelector("#search-owner-form button")).click(); assertThat( driver.findElements(By.cssSelector("owners tbody tr")).size(), is(10) );
  10. 10. FINDING ALL OWNERS - PAGEOBJECT EXAMPLE DesiredCapabilities capabilities = new DesiredCapabilities(); WebDriver driver = new PhantomJSDriver(desiredCapabilities()); driver.get(baseUrl+"owners/find.html"); FindOwnersPage findOwners = PageFactory.initElements(driver, FindOwnersPage.class); OwnersPage owners = findOwners.findWith(EMPTY_SEARCH_TERMS); assertThat(owners.numberOfOwners(), is(10));
  11. 11. PROBLEMS AROSE • Large PageObject classes • Brittle test-code (less than raw Selenium) • Duplication across PageObjects for each of the ‘portals’
  12. 12. THEY TRIED THE FOLLOWING • Separate behaviour into Navigation Classes • Reduce duplication with inheritance Causing ... • Large Navigation classes • Deep inheritance hierarchy
  13. 13. EFFECTS ON THE TEAM • Took longer and longer to add new tests • Got harder to diagnose problems • Low trust in the ‘test framework’ and Cucumber • Reduced faith in automated testing • Impacted morale
  14. 14. WHAT WAS THE ANSWER?
  15. 15. Antony Marcano at first AAFTT in 2007
  16. 16. THE INSIGHT Roles ← Who ➥ Goals ← Why ➥ Tasks ← What ➥ Actions ← How Inspired by Kevin Lawrence’s talk at the first AAFTT in 2007 More of his thinking here: http://www.developertesting.com/archives/month200710/20071013-In%20Praise%20of %20Abstraction.html
  17. 17. 2008 - JNARRATE @Test public void should_be_able_to_edit_a_page() { Given.thatThe(wiki).wasAbleTo(beAtThe(PointWhereItHasBeen.JUST_INSTALLED)); And.thatThe(user).wasAbleTo(navigateToTheHomePage()); And.thatThe(user).wasAbleTo(navigateToTheHomePage()); When.the(user).attemptsTo( tas k changeTheContent().to("Welcome to Acceptance Test Driven Development") tas k ); Then.the(textOnTheScreen().ofThe(user)). tas k shouldBe("Welcome to Acceptance Test Driven Development"); } Playing with fluent APIs and started to explore the model of Tasks & Actions (although back then the labels I used more like Kevin’s labels).
  18. 18. 2009 - SCREENPLAY - A TASK public void perform() { you.need(To.doTheFollowing( // actions Click.onThe(OptionsMenu.EDIT_BUTTON), ClearTheContent.ofThe(Editor.CONTENT_PANEL), Type.theText(newContent). intoThe(Editor.CONTENT_PANEL), Click.onThe(Editor.SAVE_BUTTON) )); }
  19. 19. 2012 - THE JOURNEY PATTERN Actor performs Tasks has Abilities composed of enable Actions interact with Screen contains Elements
  20. 20. JOURNEY PATTERN APPLIED JUNIT Roles ➥ Goals ➥ Tasks ← Who ← Why ← What ➥ Actions ← How Actor theReceptionist = new Actor().with(WebBrowsing.ability()); @Test public void should_find_all_owners_by_default theReceptionist.attemptsTo( Go.to(findOwnersScreen.url()), Search.forOwnersWith(EMPTY_SEARCH_TERMS), Count.theNumberOfOwners() ); Enter.the(searchTerms). into(findOwnersScreen.searchTerms), Click.onThe(findOwnersScreen.searchButton)
  21. 21. JOURNEY PATTERN APPLIED CUCUMBER Roles ← Who As a Pet Clinic Receptionist ➥ Goals ← Why Scenario: Find all owners by default ← What When I search for owners with BLANK search terms @When (“^I search for owners with BLANK search terms$”) ➥ Tasks theReceptionist.attemptsTo( Search.forOwnersWith(EMPTY_SEARCH_TERMS) ); ➥ Actions ← How Enter.the(searchTerms). into(findOwnersScreen.searchTerms),
  22. 22. PUTTING IT ALL TOGETHER Actor theReceptionist = new Actor().with(WebBrowsing.ability()); theReceptionist.attemptsTo( Go.to(findOwnersScreen.url()), Search.forOwnersWith(EMPTY_SEARCH_TERMS), Count.theNumberOfOwners() ); assertThat( theReceptionist.sawThatThe(numberOfOwners()), was(theExpectedNumberOfOwners) );
  23. 23. A TASK … private static String searchTerms; @Override public void performAs(Actor asAReceptionist) { asAReceptionist.attemptTo( Enter.the(searchTerms).into(findOwnersScreen.searchTerms), Click.onThe(findOwnersScreen.searchButton) ); } public SearchForOwnersWith(String searchTerms) { this.searchTerms = searchTerms; } …
  24. 24. A SCREEN @Url("owners/find.html") public class FindOwnersScreen extends WebScreen { @LocateBy(css="#search-owner-form input") public ScreenElement searchTerms; @LocateBy(css="#search-owner-form button") public ScreenElement searchButton; }
  25. 25. AN ACTION public class Enter extends WebDriverInteraction implements Perform { private String text; private ScreenElement field; public void performAs(Actor actor) { web(actor).findElement(field.locator()).sendKeys(text); } public Enter(String text) { this.text = text; } public static Enter the(String text) {return new Enter(text);} public Perform into(ScreenElement field) { this.field = field; return this; } }
  26. 26. PROBLEMS SOLVED • Smaller “Screen” classes • Small, focused “Task” classes • Readable code • Consistent metaphor • Minimal inheritance • Removed need for duplication across behaviours previously in PageObjects or “Navigation” classes
  27. 27. DESIGN PRINCIPLES • DRY - navigational steps in one place • Separation of Concerns - Page Structure and Actions separate • Small Classes - easy to comprehend • Single Responsibility - classes focused on one thing and one thing only • Minimise conditional logic - navigational if-thens replaced with composable sequences
  28. 28. SCREENPLAY REVIVED Under development for everyone to use Watch This Space!
  29. 29. CONTACT • Kostas Mamalis @agiletestinguk kostas@masterthought.net • Antony Marcano @AntonyMarcano antony@riverglide.com • Jan Molak @JanMolak jan.molak@smartcodeltd.co.uk
  30. 30. THANK YOU!

×