BDD, ATDD, Page Objects: The Road to Sustainable Web Testing
-
Upload
wakaleo-consulting -
Category
Technology
-
view
6.076 -
download
0
Transcript of BDD, ATDD, Page Objects: The Road to Sustainable Web Testing
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
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();
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
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 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
JUnitIntegration tests
Acceptance tests
Easyb Plugin
Page Objects
Architecture - fitting it all together
Web Application
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
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
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
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