Overview:
As an automation engineer, you might very well know how to interact with default HTML elements like text box, radio button, image, check box etc. Selenium WebDriver considers all these elements as WebElement & provides a set of methods to play with these elements.
Sometimes It adds unnecessary confusions. For ex: While working with a radio button, webdriver identifies this as a WebElement. This interface has a getText method which does not seem to add any value for a radio button. As per the implementation, getText method returns the innetText of an element. As radio buttons, made of HTML INPUT tag, can not hold any text inside. So, It will always return null. For this element, we would be mostly interested in getting the value attribute. Not the innerText. This particular issue is applicable for check boxes and text boxes as well.
In order to avoid this confusion, we can add another level of abstraction to the WebElement using ‘Composition over inheritance’ principle. One such example would be drop down boxes in Selenium WebDriver. A drop down box is mostly used as a Select instead of WebElement. So only the required methods are available for Select.
Select s = new Select(webelement);
s.selectByVisibleText(txt);
So, a text box can also be designed as shown here. By wrapping the WebElement with TextBox, we hide all the unwanted methods from client accidentally invoking it.
This same idea can be used to automate custom controls as well.
Custom Controls:
Modern web applications with the use of HTML5 / CSS3 / JavaScript API / JQuery UI come with many stunning, user friendly, specially made controls to interact with the applications. Lets consider JQuery Slider for example. JQueryUI with the help of css and js make a div and span elements look like a real slider.
<div id="slider" class="ui-slider ui-corner-all ui-slider-horizontal ui-widget ui-widget-content">
<span tabindex="0" class="ui-slider-handle ui-corner-all ui-state-default" style="left: 0%;"></span>
</div>
Sometimes, these elements could be present in multiple pages in our application. So, It makes perfect sense for us to wrap this as a Slider element to include in our Page Object Model instead of identifying them as WebElements. Otherwise we might end up having duplicate codes or multiple helper classes etc.
Page Object with a Slider:
Lets create a simple Page Object Model for this page. Our aim here is to test only the slider. Lets assume other contents in the pages are always fine – so we can ignore them.
My Page Object Model for this page would be as shown here.
public class JQuerySliderPage {
private final WebDriver driver;
private Slider slider;
public JQuerySliderPage(final WebDriver driver){
this.driver = driver;
}
public void goTo(){
this.driver.get("http://jqueryui.com/slider/");
this.driver.switchTo().frame(0);
this.slider = new Slider(driver);
}
public Slider getSlider() {
return slider;
}
}
The above POM has a Slider & its implementation is as shown here.
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class Slider {
private final JavascriptExecutor js;
private final Actions action;
@FindBy(css = "span.ui-slider-handle")
private WebElement sliderHandle;
public Slider(final WebDriver driver){
PageFactory.initElements(driver, this);
this.js = (JavascriptExecutor) driver;
this.action = new Actions(driver);
}
public void moveBy(int p) {
action.click(sliderHandle).build().perform();
Keys key = p > 0 ? Keys.ARROW_RIGHT : Keys.ARROW_LEFT;
p = Math.abs(p);
for (int i = 0; i < p; i++) {
action.sendKeys(key).build().perform();
}
}
public String getValue() {
return (String) js.executeScript("return arguments[0].style.left", sliderHandle);
}
}
Thus all the unwanted methods of WebElement has been removed for the user.
Lets test if the slider is working as expected.
public class SliderTest {
private WebDriver driver;
private JQuerySliderPage sliderPage;
@Test
public void goTo() {
this.driver = new ChromeDriver();
this.sliderPage = new JQuerySliderPage(driver);
this.sliderPage.goTo();
}
@Test(dependsOnMethods = "goTo")
public void moveRightBy25() {
sliderPage.getSlider().moveBy(25);
Assert.assertEquals("25%", sliderPage.getSlider().getValue());
}
@Test(dependsOnMethods = "moveRightBy25")
public void moveLeftBy20() {
sliderPage.getSlider().moveBy(-20);
Assert.assertEquals("5%", sliderPage.getSlider().getValue());
}
@Test(dependsOnMethods = "moveLeftBy20")
public void moveRightBy50() {
sliderPage.getSlider().moveBy(50);
Assert.assertEquals("55%", sliderPage.getSlider().getValue());
}
}
After the test execution, the result came as success.
GitHub:
The source code is available here.
Demo:
Summary:
To model a Slider, we added an abstraction layer for a WebElement. Using the same concept, we can also model other complex elements like Date Picker, Progress bar etc. By doing so, we improve the code readability, maintainability & re-usability. Now If even these elements are present in multiple pages, we can simply find them as Slider as shown above.
Happy Testing & Subscribe 🙂
Thanks, great article.
Great Article.. 🙂 .. i just have a doubt on this method..
public Slider getSlider() {
return slider;
}
This is something on lines of composition instead of Inheritance even i am not too wrong. My doubt is how does slider variable gets its Object. We have declared it as “private Slider slider” in JQueryUIPage class. Also the getSlider does not have the code as “return new Slider”. Does getSlider implicitly instanciate slider variable? Is this some kind of dependency injection.
Thanks much
Avi
Avi, yes, it is dependency injection. Like WebElement, we model the slider as Slider object and inject it using FindBy