Friday, January 28, 2011

QUnit tests under Continuous Integration

My team is addicted to writing unit tests and our CI is always hungry for more. So we decided to write unit tests for our javascript. There are a lot of choices of javascript testing frameworks. We chose to go with QUnit mostly because we use jQuery. Writing the QUnit tests were really easy and fun. Now we wanted to take it further and run our QUnit tests within our TeamCity Continuous Integration. To run the QUnit tests we wanted to use Selenium to open up the web pages with our tests and parse the results. I googled around to find if anyone else had already solved this problem, but the closest result I found was someone who used Watir instead.

http://www.lostechies.com/blogs/joshuaflanagan/archive/2008/09/18/running-jquery-qunit-tests-under-continuous-integration.aspx


Learning from the lost techies example, I wrote a parser using Selenium. I have a QUnit class which extends my Selenium class. The QUnit base class opens the supplied webpage with the QUnit tests using Selenium, reads all the html results, converts it to xml, traverses the xml and parses the results. This simply checks to see all your qunit test results pass. If one of them fails the entire Selenium test fails.

Here is the base class for all my QUnit tests:
using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using NUnit.Framework;
using TidyNet;

namespace MyProject.UI.Tests {
 [TestFixture]
 public abstract class QunitTest : SeleniumTest {

  public string WebPage;

  [SetUp]
  public override void SetUp () {
   base.SetUp();
  }

  [TearDown]
  public override void TearDown () {
   base.TearDown();
  }

  public void RunQunitTests () {

   Selenium.Open(WebPage);
   var tests = GetQUnitTestResults();

   foreach (UnitTest test in tests) {
    Assert.IsTrue(test.pass, test.message);
   }

  }

  protected IEnumerable GetQUnitTestResults () {
   var html = new StringBuilder("<" + "html>"); //I can't have an html tag in my blogger post :X
   html.Append(Selenium.GetHtmlSource());
   html.Append("<" + "/html>");
   var xml = convertToXML(html.ToString());
   var stream = new StringReader(xml);
   var xmlReader = new XmlTextReader(stream);
   var xpathDoc = new XPathDocument(xmlReader);
   var xpathNav = xpathDoc.CreateNavigator();
   var nodeIterator = xpathNav.Select("//ol[@id='qunit-tests']/li");
   var unitTests = new ArrayList();
   try {
    while (nodeIterator.MoveNext()) {
     var testResult = new UnitTest {
      pass = nodeIterator.Current.GetAttribute("class", "") == "pass"
     };
     nodeIterator.Current.MoveToFollowing("span", "");
     testResult.module = nodeIterator.Current.Value.Replace(Environment.NewLine, " ");
     nodeIterator.Current.MoveToFollowing("span", "");
     testResult.testName = nodeIterator.Current.Value.Replace(Environment.NewLine, " ");

     if (!testResult.pass) {
      nodeIterator.Current.MoveToParent();
      nodeIterator.Current.MoveToFollowing("ol", "");
      var messages = nodeIterator.Current.SelectDescendants("span", "", false);
      messages.MoveNext();
      testResult.message = messages.Current.Value.Replace(Environment.NewLine, " ");
      messages.MoveNext();
      testResult.expected = messages.Current.Value.Replace(Environment.NewLine, " ");
      messages.MoveNext();
      testResult.actual = messages.Current.Value.Replace(Environment.NewLine, " ");
     }

     unitTests.Add(testResult);
    }
   } catch {

   }

   return unitTests;
  }

  private string convertToXML (string html) {
   var tidy = new Tidy();

   /* Set the options you want */
   tidy.Options.DocType = DocType.Omit;
   tidy.Options.DropFontTags = true;
   tidy.Options.LogicalEmphasis = true;
   tidy.Options.Xhtml = true;
   tidy.Options.XmlOut = true;
   tidy.Options.MakeClean = true;
   tidy.Options.TidyMark = false;

   /* Declare the parameters that is needed */
   var tmc = new TidyMessageCollection();
   var input = new MemoryStream();
   var output = new MemoryStream();

   byte [] byteArray = Encoding.UTF8.GetBytes(html);
   input.Write(byteArray, 0, byteArray.Length);
   input.Position = 0;
   tidy.Parse(input, output, tmc);

   string result = Encoding.UTF8.GetString(output.ToArray());
   return result;

  }

 }
}



Here is a test which utilizes my QUnit base class:



Putting our javascript unit tests into our CI process has been extremely valuable. We've caught multiple javascript bugs within seconds of a bad js check in. If anyone is doing any cool stuff with QUnit or Selenium and continuous integration I'd love to know.

16 comments:

  1. thanks a lot .. I spent days trying to implement a solution with rhino and envjs and I failed, I finally found a solution that works for me. Thanks.

    ReplyDelete
  2. Excellent post!!! Selenium automation testing tool makes your software validation process lot simpler. Keep on updating your blog with such awesome information. Selenium Course in Chennai

    ReplyDelete
  3. Latest technology have created a greater impact over testing web applications. This vital in identifying important issues that raises in web appplications. Thanks for sharing this information in here. Keep blogging article like this.

    Selenium training in chennai | Best selenium training institutes in chennai | Big Data Training Chennai

    ReplyDelete
  4. The content published here was worth able to read and share. The aspect in which you have written the content is amazing. I have bookmarked this page for future use. Thanks for sharing this in here. Keep blogging content like this.

    Software testing training in chennai | Software testing course in chennai | Software testing institute in chennai

    ReplyDelete
  5. The usage of third party storage system for the data storage can be avoided in cloud computing and we can store, access the data through internet.
    cloud computing training in chennai | cloud computing courses in chennai

    ReplyDelete
  6. As the demand of quality web application keeps on increasing, the tool like Selenium IDE is getting very popular all over the world. Thus, taking Selenium Training in Chennai will help you to enter software testing industry.
    Regards,
    Selenium Training institute in Chennai | Selenium Training Chennai

    ReplyDelete
  7. Excellent post!!!. The strategy you have posted on this technology helped me to get into the next level and had lot of information in it.
    salesforce training in chennai | salesforce training institute in chennai

    ReplyDelete
  8. Well Said, you have furnished the right information that will be useful to anyone at all time. Thanks for sharing your Ideas.
    oracle training in chennai | oracle training institutes in chennai

    ReplyDelete

  9. Great content thanks for sharing this informative blog which provided me technical information keep posting.
    Selenium Training in Chennai | Selenium Testing Course in Chennai

    ReplyDelete
  10. Well Said, you have furnished the right information that will be useful to anyone at all time. Thanks for sharing your Ideas.
    Salesforce Training in Chennai | Salesforce Training Institute in Chennai

    ReplyDelete
  11. Everyone wants to get unique place in the IT industry’s for that you need to upgrade your skills, your blog helps me improvise my skill set to get good career, keep sharing your thoughts with us.
    Salesforce Course in Chennai|Salesforce Training Chennai

    ReplyDelete
  12. I heve read your blog it's very interesting and informative. Keep sharing.
    erp providers in chennai | erp software solutions in chennai

    ReplyDelete
  13. Nice blog. Thank you for sharing. The information you shared is very effective for learners I have got some important suggestions from it. erp in chennai.

    ReplyDelete
  14. Nice post. Very interesting to read. Thank you for Sharing.
    erp software in chennai

    ReplyDelete
  15. Thank you for sharing such a informative information with us. Keep on sharing the blog like this.Dot Net Training Institute in Chennai | Dot Net Training Institute in Velachery

    ReplyDelete