Thursday, January 22, 2009

Handling Modal window with Selenium

Aim:
To unblock Selenium when Modal Window appears on the screen.

Basics:
Internet Explorer has provided additional function, showModalDialog, to deal with Modal type windows. When we open a window using showModalDialog the java-script execution gets suspended till the window gets closed. With this feature in place parent window can set itself under wait state expecting return value from the popup window. The popup window before closing itself needs to set its returnValue property, which will be used by the parent window. A sample code is given below.

<strong>Main.htm</strong>
<script type="”text/javascript”">
function getUser5()
{
var retValue=window.showModalDialog('popup.htm','',…);
alert(retValue);
}
</script>
<a id="'btnModal2'" onclick="return getUser5();">Open Popup</a>




popup.htm

<script>
function dosubmit()
{
window.returnValue=document.getElementById("txtName");
window.close();
}
</script>




<form id="'frm'">
Name<input id="txtName" title="Your Google Toolbar can fill this in for you. Select AutoFill" style="BACKGROUND-COLOR: #ffffa0"></input>
<a id="'btnClose'" onclick="return dosubmit();">Submit</a>
</form>



In above example, the parent window opens up a modal window and waits for the modal to return some value. The user enters some text in textbox on the modal window and clicks the link button. The link button calls the javascript to set the returnValue and the closes itself. The return value received by parent is used for further execution.

Problem with Selenium:
Selenium works on Javascript. It needs to move its handle across windows to perform its operation. When showModalDialog is called the javascript gets suspended for the parent. Selenium whose handle is still pointing the parent window also gets suspended. As a result all the successive commands in Selenium script ultimately get suspended.

Possible workaround:
To retain the normal flow of the selenium the only solution left is to bypass showModalDialog call with normal window.open function call. This can be achieved by defining a global function as shown below:

window.showModalDialog = function( sURL, vArguments, sFeatures)
{
if(retVal!=null) return retVal;
modalWin = window.open(sURL, 'modal', sFeatures)
}

However return value from these popup could not be tracked as these windows do not have returnValue parameter. This can be overcome by writing extra line of code using selenium script to get the required value from the popup window.
Let’s consider a simple javascript function that opens up a modal window into three sections:



function processModal ()
{
Prescript
Var returnValue = Open Modal window
Postscript
}



Opening a window with this function unblocks the Selenium. However the Postscript gets executed too early. Assuming Postscript doesn’t do any function with null set to returnValue, we can invoke the processModal twice. The first time it will open the popup and fetch the value from it. The next time it will bypass any window open executing just the Postscript based on the return value captured during the first run.



Implementation:
Based on above consideration I have written three functions:

  • ClickAndSelectModalDialog: This function accepts the control id on clicking which the modal dialog opens. It bypasses the modal call to open non-Modal window. It also moves the handle to the popup.




  • public bool ClickAndSelectModalDialog(string controlId)
    {
    try
    {
    if (ClickForModalDialog(controlId))
    {
    SelectTopWindow();
    string jscript = "";
    {
    jscript += "if(selenium.browserbot.getCurrentWindow().close){";
    jscript += "window.attachEvent(\"onbeforeunload\", function _selhandler(){";
    jscript += "if(window.opener && window.opener._UTL_SetSelRetVar){";
    jscript += "window.opener._UTL_SetSelRetVar(window.returnValue);";
    jscript += "}});}";
    }
    _selObj.GetEval(jscript);
    return true;
    }
    else throw new Exception("Error occured while opening a modal window.");
    }
    catch (Exception exc)
    {
    throw new Exception("Error occured while Clicking and selecting a modal window.", exc);
    }
    }




    public bool ClickForModalDialog(string controlId)
    {
    try
    {
    string jscript = "";
    jscript = "typeof(window.g_selRetVar) != 'undefined' ? window.g_selRetVar : 'undefined'";
    string modalValue = _selObj.GetEval(jscript);
    Log.Info("Modal Return value is " + modalValue);
    { //Bypass showModalDialog call
    jscript = "if(selenium.browserbot.getCurrentWindow().showModalDialog){";
    { // Define variable g_selRetVar
    jscript += "if (typeof(selenium.browserbot.getCurrentWindow().g_selRetVar) == 'undefined') selenium.browserbot.getCurrentWindow().g_selRetVar = null;";
    }
    { //Define function _UTL_SetSelRetVar
    jscript += "selenium.browserbot.getCurrentWindow()._UTL_SetSelRetVar = function (val){ window.g_selRetVar = val; window.status = window.g_selRetVar + ' is returned from child';};";
    }
    jscript += "selenium.browserbot.getCurrentWindow().showModalDialog = function( sURL, vArguments, sFeatures)";
    jscript += "{if ((typeof(window.g_selRetVar) != 'undefined') && (window.g_selRetVar!=null)) {var temp = window.g_selRetVar; window.g_selRetVar = null; return temp;}";
    jscript += "selenium.browserbot.getCurrentWindow().open(sURL, 'modal', sFeatures);";
    jscript += "};}";
    }
    _selObj.GetEval(jscript);

    _modalControl = controlId;
    this.Click(_modalControl);
    if (modalValue == null || modalValue == "" || modalValue == "undefined")
    WaitForPopUp("modal", "60000");

    return true;
    }
    catch (Exception exc)
    {
    Log.Error(exc);
    throw new Exception("Unable to open modal window.", exc);
    }
    }




    public bool AcceptModalValue()
    {
    this.ClickForModalDialog(_modalControl);
    _modalControl = "";
    Log.Info(DateTime.Now.ToString() + " : Parent window accepted value from Modal window.");
    return true;
    }


    Example:

    Consider Page to be the object of Class that defines above method and other Selenium methods.(Wrapper class)



    //Open/Select Modal Window
    Page.ClickAndSelectModalDialog("//a[@title='Change domain']");
    //Perform on Modal Wiondow
    Page.SelectFrame("//IFRAME");
    string xpath = "//INPUT[contains(@value,'Global')]";
    Page.Click(xpath);
    //Perform on Modal Wiondow Continued...
    Page.Click("idtask_Next");
    //Select Parent Window
    Page.SelectWindow(null);
    //Invoke parent to accept Madal value
    Page.AcceptModalValue();


    Note: If you want to handle this globally you need to add this code in your selenium jar file. The steps are given under Changing Selenium Jar to globally handle Modal Dialog box
    Download modified Selenium RC