Let us examine the different ways of handling exceptions during automated testing.
A simple example of Selenium WebDriver implicit exception handling can be described as follows:
// create a method to retrieve the text from an element on a page
@FindBy(id="submit")
protected M submit;
public String getText(WebElement element) throws Exception {
return element.getText();
}
// use the method
LoginPO.getText(submit);
Now, when using an assertion method, TestNG will implicitly throw an exception if the condition is not met:
// create a method to verify the text from an element on a page
@FindBy(id="submit")
protected M submit;
public void verifyText(WebElement element,
String expText)
throws AssertionError {
assertEquals(element.getText(),
expText,
"Verify Submit Button Text");
}
// use the method
LoginPO.verifyText(submit, "Sign Inx");
// throws AssertionError
java.lang.AssertionError: Verify Text Label expected [ Sign Inx]
but found [ Sign In]
Expected : Sign Inx
Actual : Sign In
<Click to see difference>
When using the TestNG's assertEquals methods, a difference viewer will be engaged if the comparison fails. There will be a link in the stacktrace in the console to open it. Since it is an overloaded method, it can take a number of data types, such as String, Integer, Boolean, Arrays, Objects, and so on. The following screenshot displays the TestNG difference viewer:
In cases where the user can predict when an error might occur in the application, they can check for that error and explicitly raise an exception if it is found. Take the login function of a browser or mobile application as an example. If the user credentials are incorrect, the app will throw an exception saying something like "username invalid, try again" or "password incorrect, please re-enter".
The exception can be explicitly handled in a way that the actual error message can be thrown in the exception. Here is an example of the login method we wrote earlier with exception handling added to it:
@FindBy(id="myApp_exception")
protected M error;
/**
* login - method to login to app with error handling
*
* @param username
* @param password
* @throws Exception
*/
public void login(String username,
String password)
throws Exception {
if ( !this.username.getAttribute("value").equals("") ) {
this.username.clear();
}
this.username.sendKeys(username);
if ( !this.password.getAttribute( "value" ).equals( "" ) ) {
this.password.clear();
}
this.password.sendKeys(password);
submit.click();
// exception handling
if ( BrowserUtils.elementExists(error, Global_VARS.TIMEOUT_SECOND) ) {
String getError = error.getText();
throw new Exception("Login Failed with error = " + getError);
}
}
Now, sometimes the user will want to trap an exception instead of throwing it, and perform some other action such as retry, reload page, cleanup dialogs, and so on. In cases like that, the user can use try...catch in Java to trap the exception. The action would be included in the try clause, and the user can decide what to do in the catch condition.
Here is a simple example that uses the ExpectedConditions method to look for an element on a page, and only return true or false if it is found. No exception will be raised:
/**
* elementExists - wrapper around the WebDriverWait method to
* return true or false
*
* @param element
* @param timer
* @throws Exception
*/
public static boolean elementExists(WebElement element, int timer) {
try {
WebDriver driver = CreateDriver.getInstance().getCurrentDriver();
WebDriverWait exists = new WebDriverWait(driver, timer);
exists.until(ExpectedConditions.refreshed(
ExpectedConditions.visibilityOf(element)));
return true;
}
catch (StaleElementReferenceException |
TimeoutException |
NoSuchElementException e) {
return false;
}
}
In cases where the element is not found on the page, the Selenium WebDriver will return a specific exception such as ElementNotFoundException. If the element is not visible on the page, it will return ElementNotVisibleException, and so on. Users can catch those specific exceptions in a try...catch...finally block, and do something specific for each type (reload page, re-cache element, and so on):
try {
....
}
catch(ElementNotFoundException e) {
// do something
}
catch(ElementNotVisibleException f) {
// do something else
}
finally {
// cleanup
}
Earlier, the login method was introduced, and in that method, we will now call one of the synchronization methods waitFor(title, timer) that we created in the utility classes. This method will wait for the login page to appear with the title element as defined. So, in essence, after the URL is loaded, the login method is called, and it synchronizes against a predefined page title. If the waitFor method doesn't find it, it will throw an exception, and the login will not be attempted.
It's important to predict and synchronize the page object methods so that they do not get out of "sync" with the application and continue executing when a state has not been reached during the test. This becomes a tedious process during the development of the page object methods, but pays big dividends in the long run when making those methods "robust". Also, users do not have to synchronize before accessing each element. Usually, you would synchronize against the last control rendered on a page when navigating between them.
In the same login method, it's not enough to just check and wait for the login page title to appear before logging in; users must also wait for the next page to render, that being the home page of the application. So, finally, in the login method we just built, another waitFor will be added:
public void login(String username,
String password) throws Exception {
BrowserUtils.waitFor(getPageTitle(),
getElementWait());
if ( !this.username.getAttribute("value").equals("") ) { this.username.clear();
}
this.username.sendKeys(username);
if ( !this.password.getAttribute( "value" ).equals( "" ) ) { this.password.clear();
}
this.password.sendKeys(password); submit.click();
// exception handling
if ( BrowserUtils.elementExists(error,
Global_VARS.TIMEOUT_SECOND) ) {
String getError = error.getText();
throw new Exception("Login Failed with error = " + getError);
}
// wait for the home page to appear
BrowserUtils.waitFor(new MyAppHomePO<WebElement>().getPageTitle(),
getElementWait());
}
When building the page object classes, there will frequently be components on a page that are common to multiple pages, but not all pages, and rather than including the similar locators and methods in each class, users can build a common class for just that portion of the page. HTML tables are a typical example of a common component that can be classed.
So, what users can do is create a generic class for the common table rows and columns, extend the subclasses that have a table with this new class, and pass in the dynamic ID or locator to the constructor when extending the subclass with that table class.
Let's take a look at how this is done:
/**
* WebTable Page Object Class
*
* @author Name
*/
public class WebTablePO { private WebElement table;
/** constructor
*
* @param table
* @throws Exception
*/
public WebTablePO(WebElement table) throws Exception { setTable(table);
}
/**
* setTable - method to set the table on the page
*
* @param table
* @throws Exception
*/
public void setTable(WebElement table) throws Exception { this.table = table;
}
/**
* getTable - method to get the table on the page
*
* @return WebElement
* @throws Exception
*/
public WebElement getTable() throws Exception { return this.table;
}
....
Now, the structure of the class is simple so far, so let's add in some common "generic" methods that can be inherited and extended by each subclass that extends the class:
// Note: JavaDoc will be eliminated in these examples for simplicity sake
public int getRowCount() {
List<WebElement> tableRows = table.findElements(By.tagName("tr"));
return tableRows.size();
}
public int getColumnCount() {
List<WebElement> tableRows = table.findElements(By.tagName("tr")); WebElement headerRow = tableRows.get(1);
List<WebElement> tableCols = headerRow.findElements(By.tagName("td"));
return tableCols.size();
}
public int getColumnCount(int index) {
List<WebElement> tableRows = table.findElements(By.tagName("tr")); WebElement headerRow = tableRows.get(index);
List<WebElement> tableCols = headerRow.findElements(By.tagName("td"));
return tableCols.size();
}
public String getRowData(int rowIndex) {
List<WebElement> tableRows = table.findElements(By.tagName("tr")); WebElement currentRow = tableRows.get(rowIndex);
return currentRow.getText();
}
public String getCellData(int rowIndex, int colIndex) { List<WebElement> tableRows = table.findElements(By.tagName("tr")); WebElement currentRow = tableRows.get(rowIndex);
List<WebElement> tableCols = currentRow.findElements(By.tagName("td")); WebElement cell = tableCols.get(colIndex - 1);
return cell.getText();
}
Finally, let's extend a subclass with the new WebTablePO class, and implement some of the methods:
/**
* Homepage Page Object Class
*
* @author Name
*/
public class MyHomepagePO<M extends WebElement> extends WebTablePO<M> {
public MyHomepagePO(M table) throws Exception { super(table);
}
@FindBy(id = "my_table") protected M myTable;
// table methods
public int getTableRowCount() throws Exception { WebTablePO table = new WebTablePO(getTable());
return table.getRowCount();
}
public int getTableColumnCount() throws Exception { WebTablePO table = new WebTablePO(getTable()); return table.getColumnCount();
}
public int getTableColumnCount(int index) throws Exception {
WebTablePO table = new WebTablePO(getTable()); return table.getColumnCount(index);
}
public String getTableCellData(int row, int column) throws Exception {
WebTablePO table = new WebTablePO(getTable()); return table.getCellData(row, column);
}
public String getTableRowData(int row) throws Exception {
WebTablePO table = new WebTablePO(getTable()); return table.getRowData(row).replace("\n", " ");
}
public void verifyTableRowData(String expRowText) { String actRowText = "";
int totalNumRows = getTableRowCount();
// parse each row until row data found
for ( int i = 0; i < totalNumRows; i++ ) {
if ( this.getTableRowData(i).contains(expRowText) ) { actRowText = this.getTableRowData(i);
break;
}
}
// verify the row data try {
assertEquals(actRowText, expRowText, "Verify Row Data");
}
catch (AssertionError e) {
String error = "Row data '" + expRowText + "' Not found!"; throw new Exception(error);
}
}
}
We saw, how fairly effective it is to handle object class methods, especially when it comes to handling synchronization and exceptions.
You read an excerpt from the book Selenium Framework Design in Data-Driven Testing by Carl Cocchiaro. The book will show you how to design your own automation testing framework without any hassle.