Using a config file to override targets in a Visual Studio test project

I wanted one of my coded UI tests to work in different environments, and to do that I wanted to override one of the URLs. I wanted to set that up in a config file rather than a data source. So, I created my own configuration data class for a custom section in App.Config (MSDN Article here):

Configuration Data Class

  1. public class TestSiteConfigurationData : ConfigurationSection
  2. {
  3.     private static ConfigurationPropertyCollection _Properties;
  4.     private static bool _ReadOnly;
  5.     private static readonly ConfigurationProperty _WebServerName = new ConfigurationProperty("WebServerName", typeof(string), "localhost", ConfigurationPropertyOptions.IsRequired);
  6.     private static readonly ConfigurationProperty _WebServerPort = new ConfigurationProperty("WebServerPort", typeof(int), 80);
  7.     private static readonly ConfigurationProperty _WebApplicationRootName = new ConfigurationProperty("WebApplicationRootName", typeof(string), "");
  8.  
  9.     public TestSiteConfigurationData()
  10.     {
  11.         //init properties
  12.         _Properties = new ConfigurationPropertyCollection();
  13.         _Properties.Add(_WebServerName);
  14.         _Properties.Add(_WebServerPort);
  15.         _Properties.Add(_WebApplicationRootName);
  16.     }
  17.  
  18.     // Properties
  19.     protected override ConfigurationPropertyCollection Properties
  20.     {
  21.         get
  22.         {
  23.             return _Properties;
  24.         }
  25.     }
  26.  
  27.     private new bool IsReadOnly
  28.     {
  29.         get
  30.         {
  31.             return _ReadOnly;
  32.         }
  33.     }
  34.  
  35.     // use this to disable property setting
  36.     private void ThrowIfReadOnly(string propertyName)
  37.     {
  38.         if (IsReadOnly) throw new ConfigurationErrorsException("the property " + propertyName + " is read only.");
  39.     }
  40.     // this enables the ThrowIfReadOnly method
  41.     protected override object GetRuntimeObject()
  42.     {
  43.         // to enable property setting just assign true to the following flag:
  44.         _ReadOnly = true;
  45.         return base.GetRuntimeObject();
  46.     }
  47.  
  48.     public string WebServerName
  49.     {
  50.         get { return (string)this["WebServerName"]; }
  51.         set
  52.         {
  53.             ThrowIfReadOnly("WebServerName");
  54.             this["WebServerName"] = value;
  55.         }
  56.     }
  57.  
  58.  
  59.     [IntegerValidator(MinValue=1, MaxValue=64000, ExcludeRange=false)]
  60.     public int WebServerPort
  61.     {
  62.         get { return (int) this["WebServerPort"]; }
  63.         set
  64.         {
  65.             ThrowIfReadOnly("WebServerPort");
  66.             this["WebServerPort"] = value;
  67.         }
  68.     }
  69.  
  70.     public string WebApplicationRootName
  71.     {
  72.         get { return (string)this["WebApplicationRootName"]; }
  73.         set
  74.         {
  75.             ThrowIfReadOnly("WebApplicationRootName");
  76.             this["WebApplicationRootName"] = value;
  77.         }
  78.     }
  79.  
  80. }

You’ll need to add a reference to the System.Configuration assembly for that to work. Adding annotations to validate the properties would also be a good idea.

Then I added the custom elements to my app.config file (which I added to my test project):

App.Config

  1. <?xml version="1.0"?>
  2. <configuration>
  3.   <configSections>
  4.     <section name="TestSiteConfigurationData"
  5.              type="Ordermanagement.web.uitest.TestSiteConfigurationData, Ordermanagement.web.uitest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
  6.              allowExeDefinition="MachineToApplication" restartOnExternalChanges="true"/>
  7.   </configSections>
  8.   <!--<TestSiteConfigurationData WebServerName="ordermanagement.hol.net" WebServerPort="80" WebApplicationRootName="" />-->
  9.   <TestSiteConfigurationData WebServerName="ordermgtweb.hol.net" WebServerPort="80" WebApplicationRootName="OrderManagementWeb" />
  10. </configuration>

The “type” attribute is “[Configuration Class], [assembly holding your extension without the dll/exe suffix], [version], [culture], [key]”.

Next, I created a helper class to format the URL for me:

Helper Class

  1. public class TestSiteConfigurationHelper
  2. {
  3.      TestSiteConfigurationData config;
  4.  
  5.      public TestSiteConfigurationData Config
  6.      {
  7.          get { return config; }
  8.          set { config = value; }
  9.      }
  10.  
  11.      public string RootUrl
  12.      {
  13.          get
  14.          {
  15.              UriBuilder urlBuilder = new UriBuilder();
  16.              urlBuilder.Host = config.WebServerName;
  17.              urlBuilder.Port = config.WebServerPort;
  18.              urlBuilder.Path = config.WebApplicationRootName;
  19.              return urlBuilder.Uri.ToString();
  20.          }
  21.      }
  22.  
  23.      public TestSiteConfigurationHelper()
  24.      {
  25.          config = (TestSiteConfigurationData)System.Configuration.ConfigurationManager.GetSection("TestSiteConfigurationData");
  26.      }
  27. }

And I called the helper class to replace the recorded URL with the configured one (see lines 1 and 7 below):

Updated Coded UI block

  1. TestSiteConfigurationHelper configHelper = new TestSiteConfigurationHelper();
  2. // To generate code for this test, select "Generate Code for Coded UI Test" from the shortcut menu and select one of the menu items.
  3. // For more information on generated code, see go.microsoft.com/fwlink/?LinkId=179463
  4. this.UIMap.LaunchBrowser();
  5.  
  6. //override url
  7. this.UIMap.NavigatetohttpordermgtholnetParams.UIBlankPageWindowsInteWindowUrl = configHelper.RootUrl; //default "ordermanagement.hol.net/";
  8. this.UIMap.Navigatetohttpordermgtholnet();
  9.  
  10. this.UIMap.ClickonLoginlink();
  11. this.UIMap.LoginasusernameusernamepasswordpasswordParams.UIUsernameEditText = TestContext.DataRow["username"].ToString();
  12. this.UIMap.LoginasusernameusernamepasswordpasswordParams.UIPasswordEditPassword = Playback.EncryptText(TestContext.DataRow["password"].ToString());
  13. this.UIMap.Loginasusernameusernamepasswordpassword();

There are more properties you’d want to overload, but I haven’t done that here. Vishal Joshi has a blog post that describes how to transform the app.config file for different build configurations, and that’s something else I’d like to do.

Some housekeeping is probably a good idea, but this is just a simple example.