BDD, ATDD, Page Objects: The Road to Sustainable Web Testing

53
BDD, ATDD, Page Objects The Road to Sustainable Web Testing John Ferguson Smart

Transcript of BDD, ATDD, Page Objects: The Road to Sustainable Web Testing

BDD, ATDD, Page ObjectsThe Road to Sustainable Web Testing

John Ferguson Smart

So who is this guy, anyway?

John Ferguson Smart

ConsultantTrainerMentorAuthorSpeakerCoder

Java Power Tools Bootcamp at Skills Matter

maven

JUnitHudson

LondonJanuary 24-28 2011

Selenium  2/WebDrive

r

TDD

BDD

ALL YOUR AGILE JAVA TOOLS TRAINING ARE BELONG TO US

Don’t let your web tests end up like this!

The Three Ways of Automated Web Testing

Record/Replay

Scripting

Page Objects

Record-replay automated tests

Promise Reality

Record-replay automated tests

Script-based automated tests

Selenium RC

HTMLUnit

Canoe Webtest

JWebUnit

Watir

Script-based automated tests

Selenium RC

HTMLUnit

Canoe Webtest

JWebUnit

Watir

What we’d like to have...D.R.Y

Don’t Repeat Yourself

Reusable building blocks

What we’d like to have...

A communication tool

What we’d like to have...

Introducing Page Objects

Speak your language

Reusable

Low maintenance

are reusable components

Page Objects

hide unnecessary details

Page Objects

are low maintenance

Page Objects

speak everybody’s language

Page Objects

An example

Page Objects in action

selenium = new DefaultSelenium("localhost", 4444, "*firefox", "http://www.google.com/");

selenium.open("http://www.google.com");selenium.waitForPageToLoad(5000);

selenium.type("q", "cats");selenium.click("BtnG");selenium.waitForPageToLoad(5000);

assertThat(selenium.isTextPresent("cats"), is(true));

The old way - Selenium RC

Page Objects in action

WebDriver driver = new FirefoxDriver();GoogleSearchPage page = new GoogleSearchPage(driver);

page.open();page.searchFor("cats");

assertThat(page.getTitle(), containsString("cats") );

page.close();

The new way - Using Page Objects

Page Objects in action

WebDriver driver = new FirefoxDriver();GoogleSearchPage page = new GoogleSearchPage(driver);

page.open();page.searchFor("cats");

assertThat(page.getTitle(), containsString("cats") );

page.close();

Page Objects in action

GoogleSearchPage

open()

close()

searchFor( query : String )

clickOnFeelingLucky()

openAdvancedSearchOptions()

...

The new way - Using Page Objects

Page Objects in action

Hides HTMLdetails

Usesbusiness

terms

The new way - another example

WebDriver driver = new FirefoxDriver();GoogleSearchPage page = new GoogleSearchPage(driver);

page.open()page.typeIntoSearchBox("cats");List<String> suggestions = page.getSuggestions();

assertThat(suggestions, hasItem("cats and dogs"));

page.close();

From Pages Objects to BDD

Taking expressive tests to the next level

BDD in actionWebDriver driver = new FirefoxDriver();GoogleSearchPage page = new GoogleSearchPage(driver);

page.open()page.typeIntoSearchBox("cats");List<String> suggestions = page.getSuggestions();

assertThat(suggestions, hasItem("cats and dogs"));

page.close();

But would your testers understand this?

BDD in actionusing "google-search"

scenario "Searching for 'cats' on Google", { when "the user types 'cats' in the search box", { onTheWebPage.typeIntoSearchBox "cats" } then "the drop-down suggestions should include 'cats and dogs'" theWebPage.suggestions.shouldHave "cats and dogs" }}

How about this?

Much morereadable

Still uses Page Objects under the hood

BDD in action

More readable reporting

So how does it work?using "google-search"

scenario "Searching for 'cats' on Google",{ when "the user types 'cats' in the search box", { onTheWebPage.typeIntoSearchBox "cats" } then "the drop-down suggestions should include 'cats and dogs'" theWebPage.suggestions.shouldHave "cats and dogs" }}

Easyb Plugin

Page Objects

Page Navigation

Automated Acceptance Tests

Where are your goal posts?

Automated Acceptance Tests

Unit tests are for developers

Acceptance tests are for everyone else

Automated Acceptance Tests

Unit tests are for developers

Acceptance tests are for everyone else

Automated Acceptance TestsPassing

acceptance tests

Pending acceptance

tests

Automated Acceptance Tests

Acceptance tests

Pending acceptance

tests

Automated Acceptance Teststags ["acceptance", "sprint-1"]

scenario "An empty grid should produce an empty grid",{ when "the user chooses to start a new game", { newGamePage = homePage.clickOnNewGameLink() } then "the user is invited to enter the initial state of the universe", { newGamePage.text.shouldHave "Please seed your universe" }}

scenario "The user can seed the universe with an initial grid",{ given "the user is on the new grid page", { newGridPage = homePage.clickOnNewGameLink() } when "that the user clicks on Go without picking any cells", { gridDisplayPage = newGridPage.clickOnGoButton() } then "the application will display an empty universe", { String[][] anEmptyGrid = [[".", ".", "."], [".", ".", "."], [".", ".", "."]] gridDisplayPage.displayedGrid.shouldBe anEmptyGrid }}

Implement these in Sprint 1

And now for the case studies

Case Study

Government online form processing

JUnitIntegration tests

Acceptance tests

Easyb Plugin

Page Objects

Architecture - fitting it all together

Web Application

The applicationPerl and Java

Lots of forms

Ugly colours

What the tester usesusing "ecert"

tags "TC02"

before "we are connected to the UAT environment", { given "we are connected to the UAT environment", { connecting.to('uat').withUser('a_tester') }}

scenario "The user opens the 'New Export Certificate' page and selects a country",{ when "the user clicks on the 'New Export Certificate' menu", { onTheWebPage.navigationPanel.clickOnNewExportCertificate() } and "the user chooses USA and clicks on 'Show Data Entry'", { onTheWebPage.selectDeclarationFormFor 'United States' } then "we should be on the US Export Certification Preparation page", { theWebPage.asText.shouldHave "Export Certificate Preparation" theWebPage.asText.shouldHave "Declarations for United States" } and "the 'Raise New Blank Export Certificate' is default and selected", { theWebPage.raiseNewBlankCertificate.shouldBeSelected() }}

Custom easyb plugin

Business-level tests

Plugin handles authentication

More nice screens

What the tester uses...scenario "The user fills in the Export Certificate Submission Form",{ when "we fill in the export certificate details", { theWebPage.with { certificateNumber = '123456' consignor = 'LANEXCO1' importerID = '123' importerName = 'ImportsRUs' importerRepresentative = 'local guy' officialInformation = 'very important' transportMode = 'AIR' carrierName = 'AirNZ' productItem(1).description = 'Product data' productItem(1).harmonizedSystemCode = '020110' productItem(1).with { process(1).with { type = 'Freezing' processingStartDate = '01/01/2010' processingEndDate = '02/01/2010' appliedBy = 'some dude' overrideSelected() }

...

Groovy shortcuts

Business-level tests

Handling nested forms

What the Page Objects look likepublic class ECertNavigationPanel extends AuthenticatedWebPage {

@FindBy(linkText="XML Submit") WebElement xmlSubmit; @FindBy(linkText="New Export Certificate") WebElement newExportCertificate;

public ECertNavigationPanel(WebDriver driver) { super(driver); } public WebElement getXmlSubmit() {...}

public WebElement getNewExportCertificate() {...}

public ECertSubmitXmlPage clickOnXmlSubmit() {...} public ExportCertificatePreparationPage clickOnNewExportCertificate() {...}}

WebDriver annotations

Case Study

Class ReportAn online reporting

tool for lawyers h"p://customfirst.com

Page Objects

Architecture - fitting it all together

Regression/Integration tests

Web Application

The application

!

RUI Application

Lots of AJAX

What the tests look like@Mixin (SeleniumTest)class ReportViewerTests extends AbstractSeleniumBaseTest {

ViewerPage viewerPage

public void setUp() { super.setUp() TestFixtures.loadData() viewerPage = new ViewerPage(selenium, contextPath) viewerPage.openHomePage() }

public void testClickingGLReportsIconShouldDisplaySubFolders() { viewerPage.clickFolderOpenIcon("GL Reports") assertTrue viewerPage.folderPresent("Accounts") assertTrue viewerPage.folderPresent("Test Reports") } public void testClickingOnASubFolderShouldDisplayReports() { viewerPage.clickFolderOpenIcon("GL Reports") assertTrue viewerPage.folderPresent("Test Reports") viewerPage.clickFolder "Test Reports" assertTrue viewerPage.reportRowPresent("Test Reports","Aged Debtors By Client") assertTrue viewerPage.reportRowPresent("Test Reports","Chart") } ...

Setting up the Page Object

Testing the app

What the Page Objects look likeclass ViewerPage extends AbstractPageObject{

public ViewerPage(def selenium, def contextPath) { super(selenium, contextPath) }

public void clickFolder(String folderName) {...}

public void clickFolderOpenIcon(String folderId) {...}

public boolean folderPresent(String folderName){...}

public boolean reportRowPresent(String folder,String rowName){...}

public boolean reportParameterPresent(String reportName,String parameterName){...}

public boolean reportRowTextPresent(String folder,String reportName,String text){...} ...

Business-friendly methods

Case Study

Financial software

The application

!

Looks a bit like this one...

Again, lots of AJAX

(but more complex)

(and top secret)

(Shhhhhh!)

Page Componentsclass RadioButton { WebElement button WebElement buttonContainer; def buttonId def driver void clickButton() { initButton() button.click(); } void shouldBeEnabled() { assert !isDisabled(); }

void shouldBeDisabled() { assert isDisabled(); } private boolean isDisabled() { initButton() return buttonContainer.getAttribute("class").contains("x-item-disabled"); }

private void initButton() { if (button==null) { buttonContainer=driver.findElement(By.id("gwt-debug-${buttonId}_BUTTON")); button=driver.findElement(By.xpath("//input[contains(@value, ${buttonId}-input)]")); } } }

Reusable component

Horrible nasty GWT code

Page Components @Test public void userShouldBecomeOwnerOfCurrentWorkItem() { page.workItemTree.selectEntryCalled("Stuff to do") ... page.assignButton.shouldBeEnabled() page.assignButton.click(); page.assignButton.shouldBeHidden(); page.retryButton.shouldBeEnabled(); page.saveButton.shouldBeDisabled() page.saveButton.shouldBePresent(); }

Click on a button

Custom asserts

scenario "No work items should initially appear on the screen",{ when "the user opens the page", { page.open() } and "the Item Tree 'Show all' check box should not be ticked", { assert page.itemTree.showAll.isNotChecked() } and "the Item Tree should contain no work items", { assert page.itemTree.isEmpty() }} BDD-style tests

Page Componentsclass Grid { def driver def gridId def getAt(int i) { def gridRows = getGridRows() return gridRows[i] }

def size() { def gridRows = getGridRows() return gridRows.size() } def shouldHaveARowWith(def map) { def gridRows = getGridRows() def matchingRowFound = true for(entry in map) { def gridRow = gridRows.find { row -> row[entry.key] == entry.value } matchingRowFound = gridRow != null } return matchingRowFound }

A Grid (table) component

It looks like an array

Page Components @Test void theSummaryItemChangesToReflectTreeChoice() { page.itemTree.selectEntryCalled("Stuff to do") assert page.itemSummaryGrid.size() == 1 def highlightedItemSummaryRow = page.itemSummaryGrid[0] assert highlightedItemSummaryRow.customerName == "ACME Inc" }

This is not an array

ATDD, BDD and Page Objects

0

John  Ferguson  SmartEmail:  [email protected]:  h"p://www.wakaleo.com

Twi"er:  wakaleo

Powerful, robust, low-maintenance

Clean, precise, well-designed