Wrapping up with WebAii

This fourth article in my series on Silverlight Testing with WebAii UI Automation FX assumes you have read all previous articles in the series.  In this article we are going to look at creating wrappers for controls and types within the System Under Test (SUT) to make tests more readable and promote re-usability.

Before we begin, I suspect at least some readers may be wondering why we haven’t written more actual tests in this series to date, so let me answer that now.  Firstly in all the examples I found there was plenty of example test code demonstrating how to Find, Wait and Assert against elements using WebAii.  However I found very little on best practice and how to structure test projects so I decided to write this series based on what I have chosen as my best practice and one of the key features I chose to use was wrappers instead of writing the same or similar Find statements over and over.  So up to this point I have avoided writing more than a simple test to demonstrate a point, perhaps I should have written this before the last one but I chose to highlight the basic steps of a test first.

 

Creating a WebAii Wrapper

 

So what is a WebAii Wrapper.  To be honest I think Wrapper is a bit of a misnomer and I prefer to think of them as Adapters.  Why, because in my world a wrapper suggests something that completely encapsulates the behaviour of the object it wraps, where as an adapter simply simply exposes the interface of the target object available to an alternative platform.  You might think the distinction is trivial and both could apply, but to emphasise my point, when I explained the concept of a wrapper to colleagues and suggested that developers should be responsible for creating one for each control we use in the system there were some strong objections because the immediate impression was that they would need to replicate all behaviour of a control in a wrapper.  I had to agree, if that was what was intended it would be too much to ask and a real maintenance overhead.  Instead I believe wrappers should be lightweight, easy to create and limited to exposing access to constituent parts and/or properties of the real control, which in my mind is an adapter rather than a wrapper.  However the term used in documentation for WebAii is wrapper so we will stick to using this.

 

A WebAii Wrapper is simply a class that inherits from ArtOfTest.WebAii.Silverlight.FrameworkElement or from another valid WebAii Wrapper.  If you remember back to the last article it is the FrameworkElement that provides the Find, Wait and User objects that support automation and user interaction, obviously we’ll need these in our wrappers so the inheritance requirement makes sense.  Wrapper classes will usually expose Properties that execute Find queries to expose content encapsulated by the wrapped control.  Let’s create our first wrapper to see this in action:

 

  • Add a new Class Library project to your solution, I called mine WebAii.Samples.Wrappers
  • Add references to ArtOfTest.WebAii.Common.dll and ArtOfTest.WebAii.dll
  • Add a new class to the new project called UserBar.cs
  • Edit the new class so that it inherits from ArtOfTest.WebAii.Silverlight.FrameworkElement

 

You should have something like this:

   1: using ArtOfTest.WebAii.Silverlight;
   2:  
   3: namespace WebAii.Samples.Wrappers
   4: {
   5:     public class UserBar: FrameworkElement
   6:     {
   7:  
   8:     }
   9: }

So what are we creating here?  If you think back to the previous article you may remember that the text we wanted to verify was contained in a PresenceNameLabel control, which was nested within a UserBar control.  We are going to create a wrapper for the UserBar control and expose the text as a simple string property.  Then we will modify our test to use this wrapper.

 

You may be wondering why we created a new project for our wrappers, well aside from any design principles and best practices that might have suggested this was the thing to do, as mentioned before I think it should be the responsibility of developers to create these wrappers as part of the normal development process, putting them in a separate project means they can do so without being concerned about the test project.

 

The system sets the presence text so we are unlikely to need any kind of test that sets it, therefore we are going to expose the text via a read only property.  Edit the body of the class to add the stub for the property like this:

 

   1: using ArtOfTest.WebAii.Silverlight;
   2:  
   3: namespace WebAii.Samples.Wrappers
   4: {
   5:     public class UserBar: FrameworkElement
   6:     {
   7:         public string PresenceText
   8:         {
   9:             get
  10:             {
  11:                 
  12:             }
  13:         }
  14:     }
  15: }

 

So what should the body of the getter look like.  Looking back at the test we wrote in the last article should give you a clue:


   1: var presenceNameLabel = App.Find.ByName("userNameText");
   2: var textBlock = presenceNameLabel.Find.ByType<TextBlock>();

 

For now we can use this almost as is, so edit the getter body to look like this:

   1: public string PresenceText
   2: {
   3:     get
   4:     {
   5:         var presenceNameLabel = this.Find.ByName("userNameText");
   6:         var textBlock = presenceNameLabel.Find.ByType<TextBlock>();
   7:  
   8:         return textBlock.Text;
   9:     }
  10: }

Two things to notice here.  Firstly I have removed App. from the first Find statement.  This is no longer necessary because our wrapper has it’s own scoped Find object.  Second I have added a return statement to expose the Text property of the TextBlock returned by the second Find statement.

 

Now I can here at least one or two of you asking “Why aren’t we creating a PresenceNameLabel wrapper”, we will but first I want to get our original test working with our new UserBar wrapper.

 

Edit the body of the TheUsersNameIsDisplayed test method in GivenASuccessfulLogin.cs to look like this:

   1: var userBar = App.Find.ByType<UserBar>();
   2: Assert.IsTrue(userBar.PresenceText.Contains("COX, Oliver (Dr)"));

 

Notice we have replaced the two Find statements for a single Find.ByType using our new wrapper. You should be able to run this test and still get a pass.  My CodeRush subscription is due for renewal and I am evaluating a new productivity plugin from Telerik called JustCode and as you can see the test passed for me.

 

webAii5.1 

 

Accessing Top Level Elements

 

So we have our first wrapper working, but I am still not too 100% happy about using the App.Find method directly in my test code.  So what are my alternatives?  With the help of Silverlight Spy I can see that the root element of the MS CUI Primary Care demonstrator is an element of type PrimaryCarePlayer and an obvious solution would be to create a WebAii wrapper for this and expose descendants such as the UserBar as properties.  However I would still need at least one App.Find call in each test class (probably in my SetUp method), no major hardship but it still requires some work when creating each new test class.  I decided to go a different route and use extension methods so that I could start a test with something like App.GetUserBar() and work with the object from there.  Just a personal preference thing I guess, but here is my extension class:

   1: using ArtOfTest.WebAii.Silverlight;
   2: using ArtOfTest.WebAii.Silverlight.UI;
   3:  
   4: namespace WebAii.Samples.Wrappers
   5: {
   6:     public static class SilverlightAppExtensions
   7:     {
   8:         private static Grid rootElement;
   9:         private static UserBar userBar;
  10:         
  11:         public static UserBar GetUserBar(this SilverlightApp instance)
  12:         {
  13:             return (userBar ?? (userBar = instance.GetRootElement().Find.ByType<UserBar>()));
  14:         }
  15:  
  16:         private static Grid GetRootElement(this SilverlightApp instance)
  17:         {
  18:             return ( rootElement ?? (rootElement = instance.Find.ByName<Grid>("rootGrid")));
  19:         }
  20:     }
  21: }

 

As you may notice I have implemented a bit of caching in this class so that multiple references to the same object do not repeat the Find operation.  Personally I think this is something that should be seen as a best practice.

 

Revisiting UserBar

 

Earlier I mentioned earlier we might have created another wrapper for the PresenceNameLabel. We will do that now and refactor our UserBar and look at automation properties in the process.

 

Let’s start by creating a wrapper and moving some of the code from UserBar into it.

 

Add a new class called PresenceNameLabel.cs to the wrappers project and edit it to look like this:

 

   1: using ArtOfTest.WebAii.Silverlight;
   2: using ArtOfTest.WebAii.Silverlight.UI;
   3:  
   4: namespace WebAii.Samples.Wrappers
   5: {
   6:     public class PresenceNameLabel: FrameworkElement
   7:     {
   8:         private TextBlock textBlock;
   9:         private Button button;
  10:  
  11:         public string Text
  12:         {
  13:             get
  14:             {
  15:                 return (this.textBlock ?? (this.textBlock = this.Find.ByType<TextBlock>())).Text;
  16:             }
  17:         }
  18:  
  19:         public Button Button
  20:         {
  21:             get
  22:             {
  23:                 return (this.button ?? (this.button = this.Find.ByName<Button>("PART_PresenceButton")));
  24:             }
  25:         }
  26:     }
  27: }
Our new wrapper exposes two properties Text uses the Find.ByType of the base FrameworkElement to retrieve the text content of the child TextBlock, while the Button property uses the Find.ByName method to get a strongly typed reference to the child button in both cases references to child elements are cached.

 

Now we can modify our UserBar wrapper to use the PresenceNameLabel wrapper like this:

   1: using ArtOfTest.WebAii.Silverlight;
   2:  
   3: namespace WebAii.Samples.Wrappers
   4: {
   5:     public class UserBar: FrameworkElement
   6:     {
   7:         private PresenceNameLabel presenceNameLabel;
   8:  
   9:         public string PresenceText
  10:         {
  11:             get
  12:             {
  13:                 return this.PresenceNameLabel.Text;
  14:             }
  15:         }
  16:  
  17:         public PresenceNameLabel PresenceNameLabel
  18:         {
  19:             get
  20:             {
  21:                 return (this.presenceNameLabel ??
  22:                         (this.presenceNameLabel =
  23:                          this.Find.ByName<PresenceNameLabel>("userNameText")));    
  24:             }
  25:         }
  26:     }
  27: }

Here I’ve added a property that exposes the new PresenceNameLabel as a property so that later we can test the functionality of the button, and used this new property to provide the text via the PresenceText property our existing test depends on.  My final version of the test class for this article looks like this and still passes:

   1: using NUnit.Framework;
   2: using WebAii.Samples.Wrappers;
   3:  
   4: namespace WebAii.Samples
   5: {
   6:     [TestFixture]
   7:     public class GivenASuccessfulLogin: TestBase
   8:     {
   9:         [Test]
  10:         public void TheUsersNameIsDisplayed()
  11:         {
  12:             var userBar = this.App.GetUserBar();
  13:             Assert.IsTrue(userBar.PresenceText.Contains("COX, Oliver (Dr)"));            
  14:         }
  15:     }
  16: }

 

Revisiting PresenceNameLabel

 

One more thing I want to introduce you to before closing off this article Automation Properties.  If developers have taken the time to create custom or user controls they are almost going to expose some of the functionality or state as properties so it follows that at some point you will want to use or expose some of these properties in wrappers.  One of the bits of magic that Silverlight Spy includes is the Object Browser, which enables you to see the actual types built or used within the Silverlight application.  Using this I was able to identify that the PresenceNameLabel type exposes a Text property.  So instead of using a Find operation to get the underlying TextBlock and returning it’s Text property in our wrapper we can simply return the value of the property on the actual type.  To do this we need to use an AutomationProperty and some related methods of the base FrameworkElement type.

 

AutomationProperty represents the metadata or definition of a property you are interested in.  With your metadata prepared you pass it to the GetProperty method inherited from FrameworkElement and you get the result.  Edit the body of the Text getter on the PresenceNameLabel wrapper to look like this:

   1: var property = new AutomationProperty("Text", typeof(string));
   2: return this.GetProperty(property).ToString();

Here we create an AutomationProperty passing the name of the property and it’s type, but notice in line 2 that we have to cast the return from GetProperty.

 

With this change our test should pass, but I don’t know about you but I don’t like the ugliness of this casting the return and also this looks like something that will be used often so I am going to finish off by wrapping it up in an extension method like this:

   1: using ArtOfTest.WebAii.Silverlight;
   2:  
   3: namespace WebAii.Samples.Wrappers
   4: {
   5:     public static class FrameworkElementExtensions
   6:     {
   7:         public static TResult GetProperty<TResult>(this FrameworkElement instance, string propertyName)
   8:         {
   9:             var property = new AutomationProperty(propertyName, typeof(TResult));
  10:             return (TResult)instance.GetProperty(property);
  11:         }
  12:     }
  13: }

 

Now I can reduce the body of the Text property getter to:

 

   1: return this.GetProperty<string>("Text");

My test still passes and I am happy about losing the ugliness.  I also have a nice simple way of getting the value of a custom property in my wrappers.

 

Contains the latest version of the sample code.

NB: I upgraded to the latest version of the free WebAii framework from Telerik before writing this article so if you plan on running my code you will need to either change the references to all ArtOfTest assemblies or install the latest version from http://www.telerik.com/community/download-free-products.aspx

blog comments powered by Disqus

Tag cloud

Previous Articles