Wednesday 19 June 2013

Automated UI WatIN Test - Setting date and format for Jquery Datepicker

I came across  situation where I have to write test case in which user will select date from calendar and that selected date and format has to asset in test case. Below DatePickerField class is useful to set date and format in JQuery Datepicker

using System;
using WatiN.Core;

namespace Twenty57.Stadium.TestHelpers.WatiNElements
{
       public class DatePickerField : Control<TextField>
       {
              public override WatiN.Core.Constraints.Constraint ElementConstraint
              {
                     get { return Find.ByClass("hasDatepicker"); }
              }

              public DateTime? Date
              {
                     get
                     {
                           if (Element.Text == null) return null;

                           var date = Eval("{0}('getDate').toUTCString()", ElementReference);

                            return DateTime.Parse(date);
                     }
                     set
                     {
                           var javascriptDate = "null";
                           if (value.HasValue)
                           {
                                  var date = value.Value;
                                  javascriptDate = string.Format("new Date({0}, {1}, {2})", date.Year, date.Month - 1, date.Day);
                           }

                           CallDatepicker("'setDate', {0}", javascriptDate);
                     }
              }

              public string DateFormat
              {
                     get { return GetOption("dateFormat"); }
              }

              public bool Enabled
              {
                     get
                     {
                           var isDisabled = CallDatepicker("'isDisabled'");
                           return !bool.Parse(isDisabled);
                     }
              }

              public string GetOption(string option)
              {
                     return CallDatepicker("'option', '{0}'", option);
              }

              private string ElementReference
              {
                     get
                     {
                           return string.Format("window.jQuery({0}).datepicker", Element.GetJavascriptElementReference());
                     }
              }

              private string CallDatepicker(string script, params object[] args)
              {
                     var theScript = string.Format(script, args);
                     return Eval("{0}({1})", ElementReference, theScript);
              }

              private string Eval(string script, params object[] args)
              {
                     return Element.DomContainer.Eval(string.Format(script, args));
              }
       }
}

[Test]
              public void TestSetDate()
              {
                     this.browser.GoTo(this.appURL + "/Page1");
                     this.browser.WaitForComplete();

                     DateTime dateToValidate = new DateTime(2013, 06, 11);               
                     string dateText = dateToValidate.ToString(Config.DateFormat);

                     string controlId = "Page1_DatePicker1";
                     DatePickerField datePicker = this.browser.Control<DatePickerField>(Find.ById(controlId));
                     Assert.IsNotNull(datePicker, string.Format("DatePicker with Id [{0}] not found.", controlId));
                     Assert.IsTrue(datePicker.Enabled, "DatePicker should be enabled.");
                     datePicker.Date = dateToValidate;
                     Assert.AreEqual(dateText, datePickerTextField.Text);
                     Assert.AreEqual(JQueryUIDatePickerHelper.GetDateFormat(), datePicker.DateFormat, "Date format is incorrect.");
}

JQueryUIDatePickerHelper is class which maps C# date format with JQuery Datepicker format.

public static class JQueryUIDatePickerHelper
       {
              public static string GetDateFormat()
              {
                     string currentFormat = Config.DateFormat;

                     currentFormat = currentFormat.Replace("dddd", "DD");
                     currentFormat = currentFormat.Replace("ddd", "D");

                     if (currentFormat.Contains("MMMM"))
                     {
                           currentFormat = currentFormat.Replace("MMMM", "MM");
                     }
                     else if (currentFormat.Contains("MMM"))
                     {
                           currentFormat = currentFormat.Replace("MMM", "M");
                     }
                     else if (currentFormat.Contains("MM"))
                     {
                           currentFormat = currentFormat.Replace("MM", "mm");
                     }
                     else
                     {
                           currentFormat = currentFormat.Replace("M", "m");
                     }

                     currentFormat = currentFormat.Contains("yyyy") ? currentFormat.Replace("yyyy", "yy") : currentFormat.Replace("yy", "y");

                     return currentFormat;
              }

       }

Automated UI WatiN Test - JavaScript Keyup event not firing

While writing UI test case for textbox validator I came across a problem that JavaScript key up event is not firing. Below extension method  “ForceKeyUpEvent”  helps to fire JavaScript event.
using System;
using WatiN.Core;

namespace Twenty57.Stadium.TestHelpers.WatiNElements
{
       public static class WatiNExtensions
       {
              public static void ForceKeyUpEvent(this Element e)
              {
                     e.DomContainer.Eval("$('#" + e.Id + "').keyup();");
              }
       }
}

[Test]
              public void TestAmountValidator()
              {
                     string validatorControlID = "Page1_AmountTextBox";
                     string validatorMessage = "Please enter a valid amount";

                     this.browser.GoTo(this.appURL + "/Page1");
                     this.browser.WaitForComplete();

                     TextField textbox = this.browser.TextField(Find.ById(validatorControlID));
                     Assert.IsNotNull(textbox, string.Format("Validator textbox with Id [{0}] not found.", validatorControlID));
                     textbox.TypeText("Amount Validator");
                     textbox.ForceKeyUpEvent();
                     this.browser.WaitForComplete();

                     string errorspanId = validatorControlID + "_Error";
                     Span errorspan = this.browser.Span(Find.ById(errorspanId));
                     Assert.IsNotNull(errorspan, string.Format("Validator error span with Id [{0}] not found.", errorspanId));
                     if (validatorMessage == null)
                     {
                           Assert.AreEqual("none", errorspan.Style.Display, "Validation message display is not correct.");
                     }
                     else
                     {
                           Assert.AreEqual("inline", errorspan.Style.Display, "Validation message display is not correct.");
                           Assert.AreEqual(validatorMessage, errorspan.Text, "Validator message is not correct.");
                     }

              }

Monday 10 June 2013

Enabling Windows AutoAdminLogon with a batch file (Server 2008 R2)

When using Windows Azure, I came across the problem of needing to enable Windows' AutoAdminLogon automatically, upon the first startup of a VM.

The following script enables AutoAdminLogon once, upon startup, without a user being logged in. It then restarts the computer to apply the settings, and logs the configured user in automatically. A "Restarted" registry entry makes sure restart only happens once.



Store this script in a .bat file, and run the script on startup using Server 2008 R2's task scheduler. http://www.sevenforums.com/tutorials/67503-task-create-run-program-startup-log.html has a handy tutorial on how to do this.

Launching VMs from images and capturing images from VMs with the Azure REST API (C#)

Windows Azure's REST API is a web service which you can use to interact with your Azure subscription.

To use it, you first need to create and a certificate to access your subscription with the API. Microsoft describe the process quite well here: http://msdn.microsoft.com/en-us/library/windowsazure/gg551722.aspx

Once you have created your certificate and uploaded it on the Azure portal, you can use the code provided by Microsoft to use your new certificate in your C# project: http://msdn.microsoft.com/en-us/library/windowsazure/ee460782.aspx

A general method to call REST API XML

All requests can be generated through this code. You pass through the XML, the request URL, and request type. This request object stores a request ID which can be used to check the request status.



Creating a cloud service

Before launching a VM from an image, you need to create a cloud service for it. You need to have a cloud service for each image created.

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices
Request type: POST
Return the XML required with the following method:



Launching a VM from an image

Once you have manually created an image on the Azure portal, you can launch it from the REST API as follows:

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices/[service-name]/deployments
You need to use the service name of the service you just created.
Request type: POST
Return the XML with the following method:



Capturing an image from a VM

When you have finished working on the VM, it is important that the VM runs Sysprep.exe and shuts itself down. This can be done in a batch file with the following command:



When the VM is shut down, you can call the following code to capture an image: (Note that you can't overwrite images - you need to delete the old one first if you want the image to have the same name)

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices/[service-name]/deployments/[deployment-name]/roleinstances/[vm-name]/operations

You need to use the service name, deployment name and machine name configured earlier.

Request type: POST
Return the XML with the following method:



Deleting and discarding your VM

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices/[service-name]/deployments/[vm-name]
You need to use the names of the service and virtual machines created earlier.
Request type: DELETE
No request XML required.

Deleting your cloud service

Request URL: https://management.core.windows.net/[subscription-id]/services/hostedservices/[service-name]
You need to use the service name of the service you just created.
Request type: DELETE
No request XML required.

Checking the result of a REST API call

The REST API provides a way to check the result of your call. The first block of code described in this post includes a method MakeStatusRequest, which returns a 0 if a "successful" result is returned by Azure. Provide it with the URL https://management.core.windows.net/[subscription-id]/operations/[request-id]  (where request-id is the requestID of the request you passed earlier)

It is useful to put this in a loop which checks for a successful result, and returns failure upon timeout. I call it as such:





Using Windows Azure as a QA environment

I've recently been tasked with investigating whether or not Microsoft's Windows Azure cloud service is suitable for a Quality Assurance (QA) environment for our company software.

TL;DR: Azure is currently unsuitable for our QA environment, because of Azure's requirement of running Sysprep.exe before creating an image. Sysprep, and the problems caused by it, make our Upgrade test workflow practically unachievable.

What we require from a QA environment:

  • A Windows Server 2012 build server to kick off software builds and to store them. The build server needs to be up all the time.  
  • Windows Server 2008 R2 images of Virtual Machines (VMs). Each software package we own needs two of these - an Install VM to test a fresh install, and an Upgrade VM to test an upgrade from the previous version.

Our QA process:

  • At a scheduled time, the build server kicks off NAnt scripts which grabs our source code from our repository on the cloud, compiles it, and uses the WiX toolset to build an installer file for our software.
  • The build server launches a VM without the software installed. This "Install" VM uses automatically launched NAnt scripts to grab the install file from the build server, install the software, and run NUnit tests. The Install VM writes back a result to the build server. The install VM is then reverted back to its previous state.
  • If the Install VM's tests passed, the build server launches an "Upgrade" VM which has our software installed on it. The Upgrade VM uses the same install file to upgrade it's software, and tests are run. 
  • If the Upgrade VM's tests passed, the build server saves the state of the Upgrade VM, and publishes the install file.

Windows Azure

Azure seems to have been developed as a one-model-fits-all cloud service. The bits of interest to our QA process are:
  • Virtual machine instances: A virtual machine which can be running or stopped (shut down). You can only capture an image from an instance if the virtual machine is stopped. 
  • Images: Captured images of virtual machine instances. Virtual machines can be launched from these images.
  • Disks: Disks used by current or previous virtual machines. Disks have to be manually deleted after use.
  • Cloud services: In the QA process, a cloud service is essentially just a DNS with a web address to access each Virtual Machine instance you launch.
  • Storage: A storage account with a specific location. For QA purposes, we used only one storage account in one location. You pay for any data transfer between locations.
  • Virtual networks: Used to put all of your QA VMs on the same network, so they can communicate.

Windows Azure's REST API

I only used Azure's REST API, which provides enough functionality to create cloud services, launch VMs from images, capture images from VMs and delete disks.

In a future post, I'll explain how to use the API to launch VMs and play around with them.

Problems with Azure

Azure's model was intuitive enough, but after fiddling around with their services for a few days, I realised the following problems.
  • Azure provides no decent form of snapshotting. If you want to save the state of a VM, you need to run sysprep.exe, shut down the VM, capture an image, and launch it from scratch later on.
  • Sysprep.exe is compulsory before creating an image from a VM. takes a very long time (15-20 minutes) to run. What's more, Sysprep.exe managed to irreversibly destroy an image. Some developers claim that sysprep has a "3 generation limit," - after you run it three times, you can't run it any more without error. This may have been what destroyed my image.
  • Your main administrator account is reset when you launch a VM from an image. This means you lose your user's files, your settings, and so on. You need to create a secondary account with administrator privileges to get around this.
  • In the sysprep.exe and image launching process, some registry items like the crucial autoadminlogon settings are lost. I attempted to get around this by writing a batch file which sets the autoadminlogon registry entries on startup, and then restarting the machine to apply them. It was during testing of this script that Sysprep destroyed my image, so I never got to see the result of my batch file.
  • The REST API doesn't provide functionality to check the status of a VM. For instance, you can't check whether a VM is stopped or running, which you need to know to capture an image from a VM.  To get around this, your scripts to capture an image from a VM need to loop a preprogrammed amount of times, expect error codes, and wait until Azure returns a positive result code.
  • There is currently no way of giving your VMs static IP addresses. You also can't use the REST API to check a VMs IP address on the virtual network, which would be a very useful feature, Microsoft. Cough.

Conclusion

With all of these problems, it is difficult, but possible, to host a QA environment on Azure, provided that the QA VMs don't need to save state after each test. If you'll be launching the same non-updated image every test, then Azure might work for you.

However, our upgrade test workflow was not achievable with Azure. With enough effort, it might have been, but Sysprep.exe and other Azure hurdles just made it too much effort.

In the next few posts I will get into some of the technical aspects of using Azure for QA environment.