Last Friday, I explained the concept of Selenium Page Objects to my colleague and I feel like I didn’t explain it in a simple way enough. I have spent some time today to come up with explanation that relates the page object pattern to the concept of abstraction in programming
Object Abstraction
Let’s say we have a class that processes news messages
class NewsListener{
public void onNewsMessage(String rawMsg){
//Example of raw message; "ID1234:Alert-News content"
String newsId = rawMsg.substring(0 , rawMsg.indexOf("-"));
if( newsId.substring( newsId.indexOf(":") + 1 ).equals("Alert") ){
notifyAlertListener(newsId, rawMsg);
}else{
renderNewsContent(newsId, rawMsg);
}
}
}
It seems like the NewsListener class know the news raw message really well. But the class is unlikely to be the only one that needs to get news ID and message type out of the raw message; AlertListener and NewsContentPanel may also need to use those values too. Although, those two lines of raw message parsing seems to be easy enough to be copied to all other classes but any decent developers know that will definitely causes problems
- Tight Coupling and Code Duplication – This is obvious, the parsing code is copied to every place that need to know message ID and message type. If the format of the message has changed then every class needs to be modified
- Wrong level of abstraction – The main business logic of the class is to dispatch message to the right listener according to message type. It should not care how the type is derived from the message
We can improve the code by introduce an abstraction; NewsMessage, to encapsulate the internal message format in one place and expose just the information that need to be used by other classes
public void onNewsMessage(NewsMessage msg){
if( msg.isAlert() ){
notifyAlertListener(msg);
}else{
renderNewsContent(msg);
}
}
Selenium Page Objets
Let’s look at a test case to verify that the target page will not show any project information when there is no project stored in database
public class ListProjectPageTest {
private static WebDriver driver;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
driver = new FirefoxDriver();
}
@Test
public void listProjectsWhenNoProjectInDB(){
driver.get( URLConfig.get(“list-project-page”) );
WebElement table = findElementIgnoreException( By.cssSelector("#container > table") );
int numberOfProjShowing = table.findElements( By.xpath("tbody/tr") ).size();
assertEquals(0, numberOfProjShowing );
}
}
Just like the NewsListener example, the test just want to assert the number of projects that have been showed on the target page but it choose to use Selenium driver to get the information directly from HTML DOM node. This code for retrieving the number of rows must be copied into every test case that needs the same information and the changes in HTML structure will risk breaking all those test cases
Page Objects is a pattern to capture web UI components into object. We can create ProjectsPage to encapsulate all the logic to get project’s information in one place and let our test cases work with our web UI at the higher level of abstraction
@Test
public void listProjectsWhenNoProjectInDB(){
ListProjectsPage page = ListProjectsPage.navigateToPage(driver);
assertEquals(0, page.getNumberOfProjectsShowing() );
}
Below is example of how the page objects could be implemented
public class ListProjectsPage{
private final WebDriver driver;
private final WebElement tableElem;
private ListProjectsPage(WebDriver driver) {
tableElem = findElementIgnoreException( By.cssSelector("#container > table") );
}
public static ListProjectsPage navigateToPage(WebDriver driver){
driver.get( getURL("list-projects-page") );
return new ListProjectsPage(driver);
}
public int getNumberOfProjectsShowing(){
return tableElem.findElements( By.xpath("tbody/tr") ).size();
}
}
The pattern will make test cases less brittle and easy to read. Test cases will come close to the promise of “executable requirement”


0 Visitor Comments
Trackbacks/Pingbacks