Second Life of a Hungarian SharePoint Geek

August 9, 2010

Using Explorer add-ins as web application “test engines”

Filed under: IE add-in, Testing — Tags: , — Peter Holpar @ 20:07

If you develop web applications day-by-day and that applications require a lot of user inputs in terms of typing text in text boxes, selecting values from lists and  check boxes, you probably know how frustrating it is for a developer to type and click so much each time just to test his code and  how useful it would be to have a tool that makes it automatically.

// Note: You can say that there are such tools, and you are right, but these tools do not always do exactly what you need and do cost usually a lot of bucks, as they can do lot more than you actually need.

I felt the same when I worked on a project with rather complex user interfaces. To test functionality that is integrated in the UI I had to fill out dozens of text boxes, select from another dozen check box and list, repeating these steps several times in each hour, typically with the same sets of input data.

After a few days I got an idea of creating a simple IE add-in to help me in this tedious work. Although I was moved to more exciting projects in the meantime, where I can get more on my programming skills than typing speed, I’ve created a sample add-in driven just by curiosity.

Again, I chose Add-in Express 2008 for Internet Explorer to implement this sample (you can find the latest version here).

Besides the add-in project I’ve created a simplified web application project for a user interface that handles fictitious contracts. I stored contract test data in XML files. This data is used to populate contract field on UI, but more on that later.

Of course, in the real world you would have more complicated user interfaces and more contract types, but I hope I can illustrate the main points easier this way.

You can get both the add-in and contracts web projects (including some sample contract XML files) in Visual Studio 2008 format here in a single file download. The solution was tested on IE 7 and 8.

Before the internal details let’s see the add-in in action. After the add-in is installed, one can display it by selecting the WebApp AutoFill menu from the Explorer Bar menu of IE, as shown below (IE 7 screen):

image

The folder location of the contract data XML is stored in the registry (HKEY_CURRENT_USER\Software\Company\TestBar key,  ContractFolderPath value), but you can set that from the UI as well by clicking on the ellipsis (…).

image

In this case I used the XML files provided with the web project in the sample (TestContracts\ContractSamples), as reflected on the add-in UI.

image

When you start the web application project, a private contract form is displayed by default.

image

The add-in UI adapts to the content of the browser, and displays the test contact XML files that stores private contract data.

image

When you select one of the test data files, the input fields are populated based on the data contained in the file.

image

When you change to the business contracts using the link in the menu form, a business contract is displayed.

image

At the same time the add-in UI is updated with the business contract test data XML files.

image

Again, selecting one of those test files, the input fields are populated.

image

Now let’s see, how I mapped contract type and contract data between the UI and XML data files.

As you can see in the source of the ASPX page, I’ve used a hidden field called CustomerType and note the values of the ID property of the web control, that will be translated to the HTML name property when the page is rendered.

image

In the XML sample below you can see the corresponding values:

image

Now it is time to see some lines of code.

When a new page is loaded, I refresh the add-in UI. First I try to get the right frame (GetFrame method) and the customer type from the hidden field (GetValueOfHiddenField method) then update the file list (UpdateContractFileList method) accordingly.

  1. private void TestBar_DocumentComplete(object pDisp, string url)
  2.  {
  3.      RefreshInfo();
  4.  }
  5.  
  6.  private void RefreshInfo()
  7.  {
  8.      CustomerType customerType = CustomerType.Unknown;
  9.      try
  10.      {
  11.          if (IEApp != null)
  12.          {
  13.              if (Visible)
  14.              {
  15.  
  16.                  HTMLDocument doc = HTMLDocument;
  17.  
  18.                  if (doc != null)
  19.                  {
  20.                      IHTMLWindow2 mainFrame = BrowserHelper.GetFrame(doc, "CONTRACT");
  21.  
  22.                      if (mainFrame != null)
  23.                      {
  24.                          IHTMLDocument2 mainFrameDoc = mainFrame.document;
  25.  
  26.                          String customerTypeString = BrowserHelper.GetValueOfHiddenField(mainFrameDoc, "CustomerType");
  27.  
  28.                          customerType = Utils.ParseEnum<CustomerType>(customerTypeString);
  29.                      }
  30.                  }
  31.              }
  32.          }
  33.      }
  34.      catch (UnauthorizedAccessException ex)
  35.      {
  36.          IEApp.StatusText = String.Format("{0} – The page may contain frames from different domains.", ex.Message);
  37.      }
  38.      catch (Exception ex)
  39.      {
  40.          MessageBox.Show(ex.Message);
  41.      }
  42.  
  43.      // Update current values and diplay in case of change
  44.  
  45.      CustomerType formerCustomerType = CustomerType;
  46.      if ((customerType != CustomerType.Unknown))
  47.      {
  48.          CustomerType = customerType;
  49.          customerTypeLabel.Text = CustomerType.ToString();
  50.      }
  51.      else
  52.      {
  53.          CustomerType = CustomerType.Unknown;
  54.          customerTypeLabel.Text = NA_TEXT;
  55.      }
  56.      // we update only in case of change
  57.      if ((formerCustomerType != CustomerType))
  58.      {
  59.          UpdateContractFileList();
  60.      }
  61.  }

Note, that I’ve found that there might be an UnauthorizedAccessException exception raised  in the add-in when the frames are from different domains. That behavior might have been changed in the latest releases of the product. I show the exception in this case in the Status Bar of the browser.

It is the code of the GetFrame and GetValueOfHiddenField methods:

  1. public static IHTMLWindow2 GetFrame(HTMLDocument document, String frameName)
  2.         {
  3.             IHTMLWindow2 result = null;
  4.             if (document != null)
  5.             {
  6.                 FramesCollection frames = (FramesCollection)document.frames;
  7.                 for (int i = 0; i < frames.length; i++)
  8.                 {
  9.                     object refIndex = i;
  10.                     IHTMLWindow2 frame = (IHTMLWindow2)frames.item(ref refIndex);
  11.                     if (frame.name == frameName)
  12.                     {
  13.                         result = frame;
  14.                         break;
  15.                     }
  16.                 }
  17.             }
  18.             return result;
  19.         }
  20.  
  21.         public static String GetValueOfHiddenField(object document, String name)
  22.         {
  23.             String result = null;
  24.             IHTMLDocument3 htmlDoc = (IHTMLDocument3)document;
  25.             IHTMLElementCollection elements = htmlDoc.getElementsByName(name);
  26.             for (int i = 0; i < elements.length; i++)
  27.             {
  28.                 object refIndex = i;
  29.                 IHTMLElement element = (IHTMLElement)elements.item(name, refIndex);
  30.                 try
  31.                 {
  32.                     if ((element.tagName.ToUpper() == "INPUT") && (((String)(element.getAttribute("TYPE", 0))).ToUpper() == "HIDDEN"))
  33.                     {
  34.                         result = ((String)(element.getAttribute("VALUE", 0)));
  35.                         break;
  36.                     }
  37.                 }
  38.                 catch (Exception ex)
  39.                 {
  40.                     MessageBox.Show(ex.Message);
  41.                 }
  42.             }
  43.             return result;
  44.         }

The code of the UpdateContractFileList method is shown here:

  1. private void UpdateContractFileList()
  2. {
  3.     fileList.Items.Clear();
  4.     if ((!String.IsNullOrEmpty(_contractFolderPath)) && (Directory.Exists(_contractFolderPath))
  5.       && (CustomerType != CustomerType.Unknown))
  6.     {
  7.         foreach (String file in Directory.GetFiles(_contractFolderPath, "*.xml"))
  8.         {
  9.             if (IsTypeMatch(file, CustomerType))
  10.             {
  11.                 fileList.Items.Add(GetFileName(file));
  12.             }
  13.         }
  14.     }
  15. }

When the user select a new file from the list, I try to populate the input fields one-by-one based on the current customer type and the test data.

  1. private void fileList_SelectedIndexChanged(object sender, EventArgs e)
  2.  {
  3.      object fileListItem = fileList.SelectedItem;
  4.      if (fileListItem != null)
  5.      {
  6.          HTMLDocument doc = HTMLDocument;
  7.          IHTMLWindow2 mainFrame = BrowserHelper.GetFrame(doc, "CONTRACT");
  8.          if (mainFrame != null)
  9.          {
  10.              IHTMLDocument2 mainFrameDoc = mainFrame.document;
  11.              try
  12.              {
  13.                  XmlDocument contractXml = new XmlDocument();
  14.                  contractXml.Load(_contractFolderPath + "\\" + (String)fileListItem);
  15.  
  16.                  if (_customerType == CustomerType.Private)
  17.                  {
  18.                      BrowserHelper.FillTextBox(mainFrameDoc, contractXml, "FirstName");
  19.                      BrowserHelper.FillTextBox(mainFrameDoc, contractXml, "LastName");
  20.                      BrowserHelper.FillTextBox(mainFrameDoc, contractXml, "City");
  21.                      BrowserHelper.FillTextBox(mainFrameDoc, contractXml, "Street");
  22.  
  23.                      BrowserHelper.SelectRadioButton(mainFrameDoc, contractXml, "Gender");
  24.                  }
  25.                  else if (_customerType == CustomerType.Business)
  26.                  {
  27.                      BrowserHelper.FillTextBox(mainFrameDoc, contractXml, "CompanyName");
  28.                      BrowserHelper.FillTextBox(mainFrameDoc, contractXml, "TaxNumber");
  29.                      BrowserHelper.FillTextBox(mainFrameDoc, contractXml, "City");
  30.                      BrowserHelper.FillTextBox(mainFrameDoc, contractXml, "Street");
  31.  
  32.                      BrowserHelper.SelectRadioButton(mainFrameDoc, contractXml, "EmployeeCount");
  33.                  }
  34.                  
  35.  
  36.              }
  37.              catch (Exception ex)
  38.              {
  39.                  MessageBox.Show(ex.Message);
  40.              }
  41.          }
  42.      }
  43.  
  44.  }

It is the code of the SelectRadioButton and the FillTextBox methods used to update the fields:

  1. public static void SelectRadioButton(object document, XmlDocument contractXml, String fieldName)
  2. {
  3.     try
  4.     {
  5.         IHTMLDocument3 htmlDoc = (IHTMLDocument3)document;
  6.         IHTMLElementCollection elements = htmlDoc.getElementsByName(fieldName);
  7.  
  8.         String fieldValue = contractXml.SelectSingleNode(String.Format("//*[@name='{0}']", fieldName)).InnerText;
  9.  
  10.         for (int i = 0; i < elements.length; i++)
  11.         {
  12.             object refIndex = i;
  13.             IHTMLElement element = (IHTMLElement)elements.item(fieldName, refIndex);
  14.  
  15.             if ((element.tagName.ToUpper() == "INPUT") && (((String)(element.getAttribute("TYPE", 0))).ToUpper() == "RADIO"))
  16.             {
  17.                 if (((String)(element.getAttribute("VALUE", 0))).ToUpper() == fieldValue.ToUpper())
  18.                 {
  19.                     element.click();
  20.                     break;
  21.                 }
  22.             }
  23.         }
  24.     }
  25.     catch (Exception ex)
  26.     {
  27.         MessageBox.Show(ex.Message);
  28.     }
  29. }
  30.  
  31. public static void FillTextBox(object document, XmlDocument contractXml, String fieldName)
  32. {
  33.     try
  34.     {
  35.         IHTMLDocument3 htmlDoc = (IHTMLDocument3)document;
  36.         IHTMLElementCollection elements = htmlDoc.getElementsByName(fieldName);
  37.  
  38.         String fieldValue = contractXml.SelectSingleNode(String.Format("//*[@name='{0}']", fieldName)).InnerText;
  39.  
  40.         if (elements.length > 0)
  41.         {
  42.             object refIndex = 0;
  43.             IHTMLElement element = (IHTMLElement)elements.item(fieldName, 0);
  44.  
  45.             element.setAttribute("VALUE", fieldValue, 0);
  46.         }
  47.     }
  48.  
  49.     catch (Exception ex)
  50.     {
  51.         MessageBox.Show(ex.Message);
  52.     }
  53. }

You should add new methods to handle other types of input beyond text and check box.

I hope the sample above illustrates well how practical it can be sometimes to use an IE add-in to fill out web application test data. It helps you to spend more time on issues that require more creativity than typing.

Note: The add-in solution contains a post-build step that deploys the built assembly into the default installation location. Of course, it runs correctly only after you have installed the add-in and you left the default location on installation. You might want to alter or remove that step depending on your needs.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: