Introducing JavaScript Execution on InternetExplorer and CrossBrowser in Coded UI Test

This blog assumes that you have a prior understanding of Coded UI Test and some experience with Recording and Playback on either Internet Explorer or Cross Browser.  A good starting point for Cross Browser testing would be here. This blog explains the new “JavaScript Execution” capability of the Coded UI test on Internet Explorer as well as Cross Browser that we added in VS Update 2. 

With Update 2, Users will be able to run JavaScripts on the webpages they are testing. This will help the users in achieving certain complex actions or using custom controls that are otherwise difficult or entirely not possible due to the limitations of the web browser or coded ui test or any other issues. This API will also serve as an alternative to the actions that take relatively long time to execute in comparison with running a simple JavaScript. It might also simplify the test code if multiple steps are required in achieving the desired effect without JavaScript..

Supported Versions:                                                                                                                                  

  1. IE version – IE9/ classic IE10 
  2. Firefox version – 15+
  3. Chrome version – 21+
  4. Visual Studio Ultimate/Premium – VS 2012 RTM  + VS 2012 Update 1 +  VS 2012 Update 2
  5. Selenium NET Bindings and the Selenium ChromeDriver made available to you through an installer on Visual Studio Gallery.

API Definition and Usage:

Upon Installation of the VS Update 2, this feature will automatically be available as API  (mentioned below) in BrowserWindow Class.

object ExecuteScript(string script, params object[] args)

I hope that the readers of this blog are already acquainted with the BrowserWindow Class. BrowserWindow class is the type of the window objects that get generated if you record on internet explorer using Coded UI Test. You can refer to the Cross Browser Scenario videos mentioned in this link  for Firefox or Chrome. If you are either using coded ui test builder to generate code or hand coding the test, then you should have access to the window object of the browser, which is of type BrowserWindow. For Handcoding coded UI Tests, you can refer to this blog.

You can call the “ExecuteScript” method using the window object. ExecuteScript takes the script as first argument and any optional arguments to the script, if any. If there is a return value expected, then the API will return that value, otherwise it will return null. 

Script Syntax:

Script Syntax follows the ECMA standards and no further editing is required. However, if you want to return a value from the script, you will have to use the return keyword at the end of the script. If you add a return at the middle of script, then any script code after the return value statement will not be executed. Reason for this behavior is due to the fact that “ExecuteScript” cannot know which value to return from the script, unless explicitly told. JavaScript can be any complex script with hundreds of lines containing variable definitions, function definitions, function calls etc. To make the decision of return value easier, “ExecuteScript” will return the value based on the return keyword. Please note that the usage of “return” keyword inside function definitions is not affected due to this.  

Script can directly call any JavaScript functons that are already defined inside the web page. Any Script errors will result in UITestJscriptExecutionException. 

Arguments Usage:

Arguments for the script can be variables that you cannot directly embed in the script or based on dynamically changing variables. Arguments types that can be sent to the API are “double”, “long”, “bool”, “string”,  “HtmlControl” or a List of objects where each object can be any one of the aforementioned types. Script should specify arguments as per ECMA standards. “ExecuteScript” supports the same return types as the argument types. we also support Nested Lists, but we suggest shallow nest depth. If an HTML element that you are trying to return is no longer available after script execution and before script can return the said control, either due to page navigation or dynamic controls, then the API will return NULL and will not throw UITestControlNotAvailableException. Reason being that the Coded UI Test cannot rollback the executed script just because of one or few controls of dynamic nature.

Example:

long result = (long) browserWindow.ExecuteScript(" return addnumbers(arguments[0], arguments[1]); " , 2, 3 );

Note the usage of return keyword so that the function result can be returned back to the test. Script will get executed as if the arguments[i] are substituted with the corresponding argument value. Any incorrect arguments or bad argument indexes will result in an UITestJscriptExecutionException.

 

Usage Examples:

All the below examples assume that “browserWindow” is the browser window object of your Coded UI Test.

Example 1 – Note here that the ExecuteScript API does not support Int, and only supports long:

long result = (long) browserWindow.ExecuteScript(" return addnumbers(arguments[0], arguments[1]); " , 2, 3 ); // result is 5

Example 2 – ExecuteScript can return the specific HtmlControls from the JavaScript and one can use them as UI Test Controls. Here, the code returns the Amazon’s search box from the script and sets the text using UI Test Control APIs:

 // Launch Amazon main window using BrowserWindow.Launch
// Alternatively, launch site manually and add it to UIMap using coded ui test builder
BrowserWindow browserWindow = BrowserWindow.Launch("http://www.amazon.com");
// Get the search text box of Amazon web site using its id
HtmlEdit searchBox = (HtmlEdit) browserWindow.ExecuteScript("return document.getElementById(arguments[0])", "twotabsearchtextbox");
searchBox.Text = "Visual Studio 2012";

Example 3 – ExecuteScript can take HtmlControl as an argument:

 BrowserWindow window = BrowserWindow.Launch("http://www.bing.com");
// Here I added the Bing Search text box to my UI Map using Coded UI Test Builder
window.ExecuteScript("arguments[0].value = 'CodedUITest'", this.UIMap.UIBingWindowsInternetEWindow.UIBingDocument.UIEnteryoursearchtermCustom);

Example 4 – ExecuteScript can return nested arrays from the Script. API will return a List<object> and each object has to be typecasted by the user based on their expectations of return values:

 List<object> listOfObjects = (List<object>) browserWindow.ExecuteScript(@" var array = new Array();
array[0] = 'string';
array[1] = 20;
array[2] = document.getElementById('textbox1');
array[3] = new Array();
array[3][0] = 25.34;
array[3][1] = 'string2';
return array; ");

Example 5 – Returning custom attributes. If the webpage you are testing like the one below, then JavaScript Execution is very useful and easy to use to read any custom attributes: 

 // Launch Amazon main window using BrowserWindow.Launch
// Alternatively, launch site manually and add it to UIMap using coded ui test builder
BrowserWindow browserWindow = BrowserWindow.Launch("http://www.amazon.com");
// Amazon.com uses a custom attribute which we can read using Javascript
// <div id="nav_subcats_5" class="nav_browse_subcat nav_super_cat" data-nav-promo-id="digital-games-software" .....
string promoId = (string)browserWindow.ExecuteScript("return document.getElementById('nav_subcats_5').getAttribute('data-nav-promo-id');");

Example 6 – Using JavaScript to check Document Ready State to implement Wait logic (especially in Cross Browser Scenario):

 // Launch Amazon main window using BrowserWindow.Launch
// Alternatively, launch site manually and add it to UIMap using coded ui test builder
BrowserWindow.CurrentBrowser = "chrome";
BrowserWindow browserWindow = BrowserWindow.Launch("http://www.amazon.com");

// Create new stopwatch
Stopwatch stopwatch = new Stopwatch();

// Begin timing
stopwatch.Start();

bool isDocumentReady = false;
// Try for 3 minutes
while (!isDocumentReady && stopwatch.ElapsedMilliseconds < 120000)
{
isDocumentReady = (bool)browserWindow.ExecuteScript("return document.readyState == 'complete'");
// wait for half a second before trying again
Thread.Sleep(500);
}
if (!isDocumentReady) throw new TimeoutException();
// Continue otherwise

Limitations:

1)  If the JavaScript takes long time to execute, the ExecuteScript API will NOT timeout. Currently, CUIT does not provide any way of setting any kind of execution timeout for this API. User will have to implement his/her own way of timing the execution.

2)  “ExecuteScript” API will not return “Int” types directly even if you are expecting an Integer. User will have to cast it to “long” at first and then to “Int” if they really want “Int”. See Usage Example 1.

3)  API cannot define functions in a normal way such that the user can use them later as if they are defined in the web page. User can define and call the function normally  in the same script i.e. single execution call, but cannot do it across multiple execution calls.  Our API cannot directly inject the function into the webpage, but can hold onto a function variable which can be used later to execute the function. One can use execute a function after defining it in the below fashion:

// you cannot define a function in a normal way and use it later
browserWindow.ExecuteScript("addnum = function(arg0, arg1) { return (arg0 + arg1); }; ");
long result = (long) window.ExecuteScript("return addnum(2,3)");

4) To access elements within frames, user will have to do it themselves using HTML Dom Frame/IFrame objects within the script itself. “ExecuteScript” API will not do the frame switching.

 // Launch W3C Schools frame example page using BrowserWindow.Launch
BrowserWindow browserWindow = BrowserWindow.Launch("http://www.w3schools.com/html/html_iframe.asp");

// There is another W3C Schools page embedded inside the original page
// We are retreving an inner control inside iframe
HtmlImage w3CLogoImage = (HtmlImage)browserWindow.ExecuteScript("return document.getElementsByTagName('iframe')[0].contentDocument.getElementsByTagName('img')[0];");

// Read html image width
Console.WriteLine(w3CLogoImage.Width);