Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Tuesday, October 6, 2015

Thoughts on C# and reimplementing the Abstract PageObject Pattern with C#

Quick Thoughts on C#

I've started a new job in a C#.Net shop after working in Python for 1 1/2 years.  One of the things I'm dealing with now is re-writing some of the framework stuff I've written over the years in C#.  There are some things I like and don't like about C# vs Python.

Likes

  • I like strongly typed languages in that tools for linting, syntax checking, and re factoring tends to be much better.


Don't likes

  • Closures are a bit tricky to deal with.  In python it was very easy to go crazy with lambda statements, which I did very liberally especially with wait and exception handling wrappers.
  • Decorators didn't seem as straight forward.
  • REPL - scripting languages you can evaluate nearly anything in the REPL.  C# however, you can do somethings, but say importing new classes gets tricky.




Re-implementing Abstract PageObject pattern in C#

Thought I share this, since I've reimpelmented it in C#.  The orginal implementation I've done in Java you can find here, http://engineeringquality.blogspot.com/2012/06/creating-generic-advanced-page-factory.html.

For C#, most of it follows very similar to Java.

using System;
using System.Linq;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
 
namespace web
{
    internal class AbstractPageFactory
    {
        /// <summary>
        /// An advanced version of Selenium's PageFactor.InitElements that uses reflection to infer subtypes.
        /// </summary>
        /// <typeparam name="T">PageObject or Interface</typeparam>        
        /// <param name="searchContext">Selenium webdriver or webelement</param>
        /// <returns></returns>
        public static T InitElements<T>(ISearchContext searchContext)
        {
            var type = typeof (T);
            var subtypes = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(type.IsAssignableFrom);
 
            Exception lastException = null;
            foreach (var subtype in subtypes.Where(subtype => subtype.IsClass).Where(subtype => !subtype.IsAbstract))
            {
                try
                {
                    var pageObj = Activator.CreateInstance(subtype, searchContext);
                    PageFactory.InitElements(searchContext, pageObj);
 
                    return (T) pageObj;
                }
                catch (Exception e)
                {
                    lastException = e;
                }
            }
 
            if (lastException != null)
                throw new NoSuchWindowException("Could not find a matching PageObject. Last exception:" + lastException);
 
            throw new NoSuchWindowException("No matching PageObjects found.");
        }
    }
}

Friday, May 22, 2015

Avoid typos in your [Category] attributes by extending CategoryAttribute

NUnit categories are a great way to categorizing your tests so you can stage your test runs.  For example, I can have a Smoke and Regression set of test cases, and additionally also tag them by features as well.  That way when I run my NUnit tests, I can filter by the set I want, and run just the tests of the features I'm testing.

The Problem

However, one problem with the Category tag is if you're in a team, and each person is typing the same category strings over and over again, you run into the problem of someone doing a typo somewhere along the way that the compiler cannot catch.

   public class SomeTest  
   {  
     [Category("TestSuite.Smoke")]  
     [Category("Feature.UserInfoForm")]  
     [Category("Speed.Fast")]  
     [Description("Test survey form fields are sticky.")]  
     public void PartiallySubmittedFormShouldRemainSticky()  
       ....  

Imagine having hundreds of tests, where that same string gets typed over and over again.

Possible Solution

One solution you can do is use constants contained in a static class,

   public class SomeTest  
   {  
     [Category(TestType.Category.Smoke)]  
     [Category(TestType.Duration.Short)]  
     [Category(TestType.Stability.Stable)]  
     [Description("Test survey form fields are sticky.")]   
     public void PartiallySubmittedFormShouldRemainSticky()  
       ....  

A more elegant way

we can extend the category attribute like this, and use it in a more expressive way that's more appealing to the eyes.
   public static class TestCategory  
   {  
     public class SmokeAttribute : CategoryAttribute  
     {  
       public SmokeAttribute() : base("TestCategory.Smoke")  
       {  
       }  
     }  
     public class SmokeRegression : CategoryAttribute  
     {  
       public SmokeRegression()  
         : base("TestCategory.Regression")  
       {  
       }  
     }  
   }  

It will look a bit cleaner in usage, and makes your test code look like something less improvised.

   public class SomeTest  
   {  
     [TestCategory.Smoke]  
     [Stability.Stable]  
     [Speed.Short]  
     [Description("Test survey form fields are sticky.")]  
     public void PartiallySubmittedFormShouldRemainSticky()  
       ....  

Tuesday, August 19, 2014

Detecting Ajax completion in JQuery

One of the annoying problems of waiting for an ajax action to complete in a typical Selenium test is you end up polling for that expected element to change, and wait there much longer than you have to for your test to fail.

This can be annoying when running in CI, and you have 100's to 1000's of test.  If one service is down, then every test that exercises that service can end up waiting it's maximum wait time, causing your results to come in much slower than it should.

Polling for elements can also waste a lot of CPU and memory on the host system on pages that have high amounts of nested elements.  This is because each element find operation requires javascript code to traverse the DOM to find the target element.

Ideally if your ajax request is throwing a 500 error and nothing is happening on the screen, you want to fail this test as soon as that could be detected.  However, selenium does not expose the network layer of your browser, so you don't know what's going on under the hood.

Fortunately some frameworks have callbacks

JQuery for example has a '.ajaxStop()' call back that can be used to register handlers when all outgoing ajax operations are completed.

 //C#
class JQueryUtil
class JQueryUtil
{
public static void InjectAjaxLatch(IWebDriver driver)
{
driver.ExecuteJavaScript<bool>("window._FTW_js_latch = false; return true;");
driver.ExecuteJavaScript<bool>("$(document).ajaxStop(function(){window._FTW_js_latch = true;});return true;");
}
public static void WaitJsLatch(IWebDriver driver, TimeSpan waitTime=default(TimeSpan))
{
const string script = "return window._FTW_js_latch;";
FtwExecutionUtils.WaitUntil(()=>driver.ExecuteJavaScript<bool>(script) == true, waitTime);
}
public static void WaitUntil(Func<bool> condition,
TimeSpan timeout = default(TimeSpan),
TimeSpan pollingInterval = default(TimeSpan))
{
var sw = new Stopwatch();
sw.Start();
while (sw.Elapsed < timeout)
{
if (condition)
{
return;
}
Thread.Sleep(pollingInterval);
}
throw new TimeoutException(".WaitUntil() call has timed out.");
}
}


How this works is pretty simple.  First execute the top method, it'll set a variable to create a call back to set a JavaScript latch that tells us when all ajax operations complete.  Then you perform your action, like enter that search query on some live search form.  Then immediately after, we call the WaitJSLatch method which creates a simple wait condition that polls on the JavasScript latch waiting for all ajax operations to complete.

The benefits of this is allowing our tests to fail faster, and also run a bit leaner.  Since locating elements in Selenium is a pretty expensive operation on a page with high levels of nested elements.  JavaScript injection will complete faster and causing less stress on the CPU of the host system.  This is especially useful when running on Selenium grid on a node with many browsers running at the same time.

Friday, July 25, 2014

Speeding up your test setup by launching your WebDriver asynchronously

There seems to be a much higher overhead to starting new instances of Browsers/WebDriver than it does to perform simple Rest and Database queries typically used in test setup.

Implementing a async wrapper method for instantiating new webdriver

C# offers the keyword 'async' and 'await'.  these 2 keywords allow you to turn methods into methods that return task wrappers that act like promises.

Here's how I put mine async webdriver init together

     public async Task<IWebDriver> NewWebDriverAsync()  
     {  
       var task = Task.Run(() => NewDriver());  
       return await task;  
     }  


Before (5 seconds on IE10):
     [SetUp]  
     public void SetUp()  
     {  
       //This will perform the register instance along with the first upgrade transaction data.  
       var regBuilder =  
         new RestfulRegistrationBuilder().WithApplication(ApplicationEnum.ConciergeService);  
       _registrationData = regBuilder.Build();  
       var upgradeBuilder =  
         new RestfulUpgradeTransactionBuilder().WithHandleAndAppIdFromRegistrationData(_registrationData);  
       _upgradeTransactionData = upgradeBuilder.Build();  
       _driver = NewWebDriver();      
     }  


After (2 seconds on IE10):
     [SetUp]  
     public void SetUp()  
     {  
       var driverInitTask = NewWebDriverAsync();  
       //This will perform the register instance along with the first upgrade transaction data.  
       var regBuilder =  
         new RestfulRegistrationBuilder().WithApplication(ApplicationEnum.ConciergeService);  
       _registrationData = regBuilder.Build();  
       Console.WriteLine("Registered Instance " + _registrationData);  
       var upgradeBuilder =  
         new RestfulUpgradeTransactionBuilder().WithHandleAndAppIdFromRegistrationData(_registrationData);  
       _upgradeTransactionData = upgradeBuilder.Build();  
       Console.WriteLine("Performed Upgrade " + _upgradeTransactionData);  
       _driver = driverInitTask.Result;        
     }