unexpected-modal-header

Selenium WebDriver – How To Handle Annoying Random Popup / Alerts

Overview:

In this article, Lets see how we could handle random popups / alerts / ads / some unexpected conditions & how we could recover from that.

Problem Statement:

I had an application which used to show popups / ads, as shown below, at random. As per the requirement it can be shown anytime (there was some complex business rules behind that. but lets ignore that for the time being). The user had to either click on the close button or take some action on the popup before continuing. It would make our selenium tests fail as this popup will steal the focus. It would be annoying for us – but it was generating some revenue for our organization!!

ele-proxy-002

While automating this application, We wanted to have our selenium scripts to be something like this…

doSomethingA();
doSomethingB();
doSomethingC();

But our selenium scripts used to be something like this.

public void checkForPopupAndKill(){
    if(popup.isDisplayed()){
        popup.close();
    }
}

checkForPopupAndKill();
doSomethingA();
checkForPopupAndKill();
doSomethingB();
checkForPopupAndKill();
doSomethingC();

The team had to include checkForPopupAndKill before each and every statement to handle the popup. As you see,the script looked horrible!!  This is because the source/client (here it is our selenium scripts) is directly interacting with the target object (actual application). As the popup can be shown anytime in the application, it would steal the focus when it appears. So , say, any click even on the main application can not be done as it would throw an exception that ‘Other element would receive the click’. So, we could not escape from adding checkForPopupAndKill  for every statement as per this approach.

ele-proxy-003

 

I was looking for something like an intermediary object between the source and target and let the intermediary object handle this popup. So that your selenium scripts do not have to worry about killing the popup.

ele-proxy-004

If we could use an intermediary  object – we could write statements as we wanted to have as shown below. All these statement do not get executed directly on the target object. Instead the intermediary object decides what to do!

doSomethingA();
doSomethingB();
doSomethingC();

Java Dynamic Proxy:

Java supports the creation of dynamic proxy classes and instances. Proxy objects are useful in many situations to act as an intermediary between a client object and a target object. We could create a proxy object by implementing InvocationHandler interface.

Lets create an ElementProxy class which implements this interface. Basically, the proxy’s invoke method would be called first before the actual method is invoked. As our idea was to call checkForPopupAndKill before invoking any action on a WebElement, this could be the right place to include the checkForPopupAndKill method.

public class ElementProxy implements InvocationHandler {

    private final WebElement element;

    public ElementProxy(WebElement element) {
        this.element = element;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //before invoking actual method check for the popup
        this.checkForPopupAndKill();
        //at this point, popup would have been closed if it had appeared. element action can be called safely now.
        Object result = method.invoke(element, args);
        return result;
    }

    private void checkForPopupAndKill() {
        if (popup.isDisplayed()) {
            System.out.println("You damn popup, you appearded again!!?? I am gonna kill you now!!");
            popup.close();
        }
    }
    
}

We need to wrap our regular WebElement with this proxy object. We basically need a class which has a method to accept a WebElement and returns the WebElement with some wrapper.

public class ElementGuard {

    public static WebElement guard(WebElement element) {
        ElementProxy proxy = new ElementProxy(element);
        WebElement wrappdElement = (WebElement) Proxy.newProxyInstance(ElementProxy.class.getClassLoader(),
                                                                       new Class[] { WebElement.class },
                                                                       proxy);
        return wrappdElement;
    }

}

When we call the below statement, it creates wrapper around element and returns the element.

ElementGuard.guard(element)

ele-proxy-005

If we try to call the click method of the WebElement, it would always call the ‘invoke’ method first, then it would call the ‘click’

ele-proxy-006

 

I create the below class to init elements of the page object with wrapper elements.

public class MyPageFactory {
    
    public static <T> void initElements(WebDriver driver, T pageobject){
        
        //first init elements
        PageFactory.initElements(driver, pageobject);
        
        //then access all the WebElements and create a wrapper
        for(Field f:pageobject.getClass().getDeclaredFields()){
            if(f.getType().equals(WebElement.class)){
                boolean accessible = f.isAccessible();
                f.setAccessible(true);
                //reset the webelement with proxy object
                f.set(pageobject, ElementGuard.guard((WebElement) f.get(pageobject)));
                f.setAccessible(accessible);
            }  
        }

    }
    
}

My sample page would be something like this – without much change.

public class RegistrationPage {

    private final WebDriver driver;

    @FindBy(name = "firstName")
    private WebElement firstName;

    @FindBy(name = "lastName")
    private WebElement lastName;

    @FindBy(name = "email")
    private WebElement userName;

    @FindBy(name = "password")
    private WebElement password;

    @FindBy(name = "confirmPassword")
    private WebElement confirmPassword;

    @FindBy(name = "register")
    private WebElement submit;

    public RegistrationPage(WebDriver driver) throws Exception {
        this.driver = driver;
        MyPageFactory.initElements(driver, this);
    }

    public RegistrationPage launch() {
        driver.get("http://newtours.demoaut.com/mercuryregister.php");
        return this;
    }

    public RegistrationPage setFirstName(String firstName) {
        this.firstName.sendKeys(firstName);
        return this;
    }

    public RegistrationPage setLastName(String lastName) {
        this.lastName.sendKeys(lastName);
        return this;
    }

    public RegistrationPage setUserName(String userName) {
        this.userName.sendKeys(userName);
        return this;
    }

    public RegistrationPage setPassword(String password) {
        this.password.sendKeys(password);
        return this;
    }

    public RegistrationPage setConfirmPassword(String confirmPassword) {
        this.confirmPassword.sendKeys(confirmPassword);
        return this;
    }

    public void submit() {
        this.submit.click();
    }

}

My tests would be something like this.

RegistrationPage r = new RegistrationPage(driver);

r.launch()
 .setFirstName("fn")
 .setLastName("ln")
 .setUserName("un")
 .setPassword("password")
 .setConfirmPassword("password");

When I execute the tests, all the popup checks happens behind the scenes.

going to call :sendKeys with args : fn
going to call :sendKeys with args : ln
You damn popup, you appearded again!!?? I am gonna kill you now!!
going to call :sendKeys with args : un
going to call :sendKeys with args : password
going to call :sendKeys with args : password

Summary:

By using Dynamic Proxy object,  all the unexpected events like this can he handled behind the scenes. Our page object and tests look exactly the same. But any action goes via the proxy object. I do not have to repeat the checkForPopupAndKill method every time. proxy object takes care of it!  Not only this popup or alert, any logging, reporting etc can be handled like this.

 

Happy Testing & Subscribe 🙂

 

Share This:

Categories: Articles, Best Practices, Framework, Page Object Design, Selenium

Leave a Reply

Your email address will not be published. Required fields are marked *