Introduction
In the Design & Architecture Series, we talk about ways of creating maintainable test framework. In this article, I am going to elaborate on a topic that most people don't understand how important it is - configurations. Most engineers use hard-coded values directly in their code and experience later lots of problems when they try to execute their tests in different environments. I am going to show you how to get your configurations from app.config and change them without recompilation. In the next article from the series, I will show you how to change these settings based on the environment where the tests will be executed.
What Problem Are We Trying to Solve?
Below you can find a code that most people usually write. Hard-coding the numbers directly in their code so that they cannot change it easily per environment or build configuration. The best way of using such settings is reading them from the app.config file. Later, you can transform these values based on the build configuration. I will show you how to do that in the next article from the series.
public sealed class BrowserSettings
{
public BrowserSettings(Browsers type)
{
this.Type = type;
}
public static BrowserSettings DefaultChomeSettings
{
get
{
return new BrowserSettings(Browsers.Chrome)
{
BrowserExeDirectory = string.Empty,
PageLoadTimeout = 60,
ScriptTimeout = 60,
ElementsWaitTimeout = 60
};
}
}
public static BrowserSettings DefaultFirefoxSettings
{
get
{
return new BrowserSettings(Browsers.Firefox)
{
BrowserExeDirectory = Environment.CurrentDirectory,
PageLoadTimeout = 60,
ScriptTimeout = 60,
ElementsWaitTimeout = 60
};
}
}
public static BrowserSettings DefaultInternetExplorerSettings
{
get
{
return new BrowserSettings(Browsers.InternetExplorer)
{
BrowserExeDirectory = Environment.CurrentDirectory,
PageLoadTimeout = 60,
ScriptTimeout = 60,
ElementsWaitTimeout = 60
};
}
}
public Browsers Type { get; private set; }
public int ScriptTimeout { get; set; }
public int PageLoadTimeout { get; set; }
public int ElementsWaitTimeout { get; set; }
public string BrowserExeDirectory { get; set; }
}
Work with Config Files C#
There are a couple of ways of reading these settings from the app.config file. I am going to show you two of them. The first uses a section that defines the different timeouts using XML attributes. The second uses different XML nodes.
Create Config Section with Attributes
First, we will go through the first variation that utilises XML attributes.
App.config
Under the configSections
node, you need to register your new section. It should contain the name of the section (name attribute). Then you need to specify the full name of the C# class where the section is defined and its assembly name. Use the following format - FullNameSpace.NameOfTheClass
, AssemblyName
.
="1.0"="utf-8"
<configuration>
<configSections>
<section name="TimeoutSettingsAsAttributes"
type="HybridTestFramework.UITests.Core.Configuration.TimeoutSettingsAsAttributes,
HybridTestFramework.UITests.Core"/>
</configSections>
<TimeoutSettingsAsAttributes
waitForAjaxTimeout="30000"
sleepInterval="1000"
elementToBeVisibleTimeout="30000"
elementToExistTimeout="60000"
elementToNotExistTimeout="10000"
elementToBeClickableTimeout="30000"
elementNotToBeVisibleTimeout="10000"
elementToHaveContentTimeout="30000"/>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
</startup>
</configuration>
As you can see under the configSections
node, you can find the new section - TimeoutSettingsAsAttributes
. The different attributes represent our numbers that we will later use in the tests. Of course, you can use any C# type not only int
- string
, bool
, etc.
TimeoutSettingsAsAttributes
To be able to read the numbers from the app.config file, you need to define a mapping C# class - TimeoutSettingsAsAttributes
(the one that we specified under the configSection
node). You need to add a reference and a using
statement to System.Configuration
. This is an assembly that comes from .NET framework. It contains the ConfigurationProperty
attribute that we use here to map the XML attributes to C# properties. Also, your class needs to derive from ConfigurationSection
. The first parameter of the ConfigurationProperty
will direct the engine which XML attribute to read. You can also specify a default value and whether the property is required. Through this["nameOfTheAttribute"]
, the actual reading is happening.
public class TimeoutSettingsAsAttributes : ConfigurationSection
{
public TimeoutSettingsAsAttributes()
{
}
[ConfigurationProperty("waitForAjaxTimeout",
DefaultValue = "10000", IsRequired = true)]
public int WaitForAjaxTimeout
{
get
{
return (int)this["waitForAjaxTimeout"];
}
set
{
this["waitForAjaxTimeout"] = value;
}
}
[ConfigurationProperty("sleepInterval", IsRequired = true)]
public int SleepInterval
{
get
{
return (int) this["sleepInterval"];
}
}
[ConfigurationProperty("elementToBeVisibleTimeout", IsRequired = true)]
public int ElementToBeVisibleTimeout
{
get
{
return (int) this["elementToBeVisibleTimeout"];
}
}
[ConfigurationProperty("elementToExistTimeout", IsRequired = true)]
public int ?lementToExistTimeout
{
get
{
return (int) this["elementToExistTimeout"];
}
}
[ConfigurationProperty("elementToNotExistTimeout", IsRequired = true)]
public int ?lementToNotExistTimeout
{
get
{
return (int) this["elementToNotExistTimeout"];
}
}
[ConfigurationProperty("elementToBeClickableTimeout", IsRequired = true)]
public int ?lementToBeClickableTimeout
{
get
{
return (int) this["elementToBeClickableTimeout"];
}
}
[ConfigurationProperty("elementNotToBeVisibleTimeout", IsRequired = true)]
public int ?lementNotToBeVisibleTimeout
{
get
{
return (int) this["elementNotToBeVisibleTimeout"];
}
}
[ConfigurationProperty("elementToHaveContentTimeout", IsRequired = true)]
public int ?lementToHaveContentTimeout
{
get
{
return (int) this["elementToHaveContentTimeout"];
}
}
}
TimeoutSettingsAsAttributesProvider
The provider class is the one that we use in the tests to get the numbers from the app.config file. It contains static
properties. In its static
constructor, we read the XML section and deserialize it to our TimeoutSettingsAsAttributes
C# class.
public class TimeoutSettingsAsAttributesProvider : ConfigurationSection
{
private static readonly TimeoutSettingsAsAttributes timeoutSettings;
static TimeoutSettingsAsAttributesProvider()
{
try
{
timeoutSettings =
(TimeoutSettingsAsAttributes)
ConfigurationManager.GetSection
(sectionName: "TimeoutSettingsAsAttributes");
}
catch (ConfigurationErrorsException ex)
{
throw new ConfigurationErrorsException(
message: "Please configure correctly
the TimeoutSettingsAsAttributes section.",
inner: ex);
}
}
public static int WaitForAjaxTimeout
{
get
{
return timeoutSettings.WaitForAjaxTimeout;
}
}
public static int SleepInterval
{
get
{
return timeoutSettings.SleepInterval;
}
}
public static int ElementToBeVisibleTimeout
{
get
{
return timeoutSettings.ElementToBeVisibleTimeout;
}
}
public static int ?lementToExistTimeout
{
get
{
return timeoutSettings.?lementToExistTimeout;
}
}
public static int ?lementToNotExistTimeout
{
get
{
return timeoutSettings.?lementToNotExistTimeout;
}
}
public static int ?lementToBeClickableTimeout
{
get
{
return timeoutSettings.?lementToBeClickableTimeout;
}
}
public static int ?lementNotToBeVisibleTimeout
{
get
{
return timeoutSettings.?lementNotToBeVisibleTimeout;
}
}
public static int ?lementToHaveContentTimeout
{
get
{
return timeoutSettings.?lementToHaveContentTimeout;
}
}
}
Create Config Section with Dedicated Nodes
The first approach using attributes is a little bit unreadable in my opinion. Because of that, I am going to show you the second one where we are going to use dedicated XML nodes.
App.config
First, we register the new section using the same approach. Then we create the TimeoutSettings
but instead specifying the numbers using attributes. We use dedicated XML nodes that have the value
attribute where the actual number is.
="1.0"="utf-8"
<configuration>
<configSections>
<section name="TimeoutSettings"
type="HybridTestFramework.UITests.Core.Configuration.TimeoutSettings,
HybridTestFramework.UITests.Core"/>
</configSections>
<TimeoutSettings>
<waitForAjaxTimeout value="30000"/>
<sleepInterval value="1000"/>
<elementToBeVisibleTimeout value="30000"/>
<elementToExistTimeout value="60000"/>
<elementToNotExistTimeout value="10000"/>
<elementToBeClickableTimeout value="30000"/>
<elementNotToBeVisibleTimeout value="10000"/>
<elementToHaveContentTimeout value="30000"/>
</TimeoutSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
</startup>
</configuration>
TimeoutSettings
Again, we need to create the class that maps the section to C#. Again, we need a reference and a using
statement to System.Configuration
. The only difference compared to the TimeoutSettingsAsAttributes
class is that the properties here are of type ValueConfigElement<int>
. In short, this type describes our XML nodes and tells the engine that there is a value
attribute where the number is.
public class TimeoutSettings : ConfigurationSection
{
public TimeoutSettings()
{
}
[ConfigurationProperty("waitForAjaxTimeout")]
public ValueConfigElement<int> WaitForAjaxTimeout
{
get
{
return (ValueConfigElement<int>) this["waitForAjaxTimeout"];
}
}
[ConfigurationProperty("sleepInterval")]
public ValueConfigElement<int> SleepInterval
{
get
{
return (ValueConfigElement<int>) this["sleepInterval"];
}
}
[ConfigurationProperty("elementToBeVisibleTimeout")]
public ValueConfigElement<int> ElementToBeVisibleTimeout
{
get
{
return (ValueConfigElement<int>) this["elementToBeVisibleTimeout"];
}
}
[ConfigurationProperty("elementToExistTimeout")]
public ValueConfigElement<int> ?lementToExistTimeout
{
get
{
return (ValueConfigElement<int>) this["elementToExistTimeout"];
}
}
[ConfigurationProperty("elementToNotExistTimeout")]
public ValueConfigElement<int> ?lementToNotExistTimeout
{
get
{
return (ValueConfigElement<int>) this["elementToNotExistTimeout"];
}
}
[ConfigurationProperty("elementToBeClickableTimeout")]
public ValueConfigElement<int> ?lementToBeClickableTimeout
{
get
{
return (ValueConfigElement<int>) this["elementToBeClickableTimeout"];
}
}
[ConfigurationProperty("elementNotToBeVisibleTimeout")]
public ValueConfigElement<int> ?lementNotToBeVisibleTimeout
{
get
{
return (ValueConfigElement<int>) this["elementNotToBeVisibleTimeout"];
}
}
[ConfigurationProperty("elementToHaveContentTimeout")]
public ValueConfigElement<int> ?lementToHaveContentTimeout
{
get
{
return (ValueConfigElement<int>) this["elementToHaveContentTimeout"];
}
}
}
ValueConfigElement
You can use the ValueConfigElement
with any C# type. Here we derive from the ConfigurationElement
class.
public class ValueConfigElement<T> : ConfigurationElement
{
public ValueConfigElement()
{
}
[ConfigurationProperty("value")]
public T Value
{
get
{
return (T) this["value"];
}
}
}
TimeoutSettingsProvider
To use the settings in tests, we need again a provider class. It is almost identical to TimeoutSettingsAsAttributesProvider
with the only difference that in order to get the number, we call the Value
property of the elements.
public class TimeoutSettingsProvider : ConfigurationSection
{
private static readonly TimeoutSettings timeoutSettings;
static TimeoutSettingsProvider()
{
try
{
timeoutSettings =
(TimeoutSettings)
ConfigurationManager.GetSection(sectionName: "TimeoutSettings");
}
catch (ConfigurationErrorsException ex)
{
throw new ConfigurationErrorsException(
message: "Please configure correctly the TimeoutSettings section.",
inner: ex);
}
}
public static int WaitForAjaxTimeout
{
get
{
return timeoutSettings.WaitForAjaxTimeout.Value;
}
}
public static int SleepInterval
{
get
{
return timeoutSettings.SleepInterval.Value;
}
}
public static int ElementToBeVisibleTimeout
{
get
{
return timeoutSettings.ElementToBeVisibleTimeout.Value;
}
}
public static int ?lementToExistTimeout
{
get
{
return timeoutSettings.?lementToExistTimeout.Value;
}
}
public static int ?lementToNotExistTimeout
{
get
{
return timeoutSettings.?lementToNotExistTimeout.Value;
}
}
public static int ?lementToBeClickableTimeout
{
get
{
return timeoutSettings.?lementToBeClickableTimeout.Value;
}
}
public static int ?lementNotToBeVisibleTimeout
{
get
{
return timeoutSettings.?lementNotToBeVisibleTimeout.Value;
}
}
public static int ?lementToHaveContentTimeout
{
get
{
return timeoutSettings.?lementToHaveContentTimeout.Value;
}
}
}
Config Sections Usage in Tests
Usage Config Section with Attributes
To use the settings in tests, you need to call the static
provider class - TimeoutSettingsAsAttributesProvider
and call one of its properties. Of course, the project should contain the mentioned app.config file with the correct configurations.
[TestMethod]
public void GetConfigValues_From_TimeoutSettingsAsAttributes()
{
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsAsAttributesProvider),
nameof(TimeoutSettingsAsAttributesProvider.WaitForAjaxTimeout),
TimeoutSettingsAsAttributesProvider.WaitForAjaxTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsAsAttributesProvider),
nameof(TimeoutSettingsAsAttributesProvider.SleepInterval),
TimeoutSettingsAsAttributesProvider.SleepInterval);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsAsAttributesProvider),
nameof(TimeoutSettingsAsAttributesProvider.ElementToBeVisibleTimeout),
TimeoutSettingsAsAttributesProvider.ElementToBeVisibleTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsAsAttributesProvider),
nameof(TimeoutSettingsAsAttributesProvider.?lementNotToBeVisibleTimeout),
TimeoutSettingsAsAttributesProvider.?lementNotToBeVisibleTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsAsAttributesProvider),
nameof(TimeoutSettingsAsAttributesProvider.?lementToBeClickableTimeout),
TimeoutSettingsAsAttributesProvider.?lementToBeClickableTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsAsAttributesProvider),
nameof(TimeoutSettingsAsAttributesProvider.?lementToExistTimeout),
TimeoutSettingsAsAttributesProvider.?lementToExistTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsAsAttributesProvider),
nameof(TimeoutSettingsAsAttributesProvider.?lementToHaveContentTimeout),
TimeoutSettingsAsAttributesProvider.?lementToHaveContentTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsAsAttributesProvider),
nameof(TimeoutSettingsAsAttributesProvider.?lementToNotExistTimeout),
TimeoutSettingsAsAttributesProvider.?lementToNotExistTimeout);
}
Usage Config Section with Dedicated Nodes
To use the settings in tests, you need to call the static
provider class - TimeoutSettingsProvider
and call one of its properties. Of course, the project should contain the mentioned app.config file with the correct configurations.
[TestMethod]
public void GetConfigValues_From_TimeoutSettings()
{
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsProvider),
nameof(TimeoutSettingsProvider.WaitForAjaxTimeout),
TimeoutSettingsProvider.WaitForAjaxTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsProvider),
nameof(TimeoutSettingsProvider.SleepInterval),
TimeoutSettingsProvider.SleepInterval);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsProvider),
nameof(TimeoutSettingsProvider.ElementToBeVisibleTimeout),
TimeoutSettingsProvider.ElementToBeVisibleTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsProvider),
nameof(TimeoutSettingsProvider.?lementNotToBeVisibleTimeout),
TimeoutSettingsProvider.?lementNotToBeVisibleTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsProvider),
nameof(TimeoutSettingsProvider.?lementToBeClickableTimeout),
TimeoutSettingsProvider.?lementToBeClickableTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsProvider),
nameof(TimeoutSettingsProvider.?lementToExistTimeout),
TimeoutSettingsProvider.?lementToExistTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsProvider),
nameof(TimeoutSettingsProvider.?lementToHaveContentTimeout),
TimeoutSettingsProvider.?lementToHaveContentTimeout);
Console.WriteLine("From {0}- {1} = {2}",
nameof(TimeoutSettingsProvider),
nameof(TimeoutSettingsProvider.?lementToNotExistTimeout),
TimeoutSettingsProvider.?lementToNotExistTimeout);
}