There are a number of key things that I have learnt are key to have easy to maintain, reliable automated tests.
Map your controls in a single place
Your controls should be mapped into a single location, so that if a control changes, you only need to change it in a single location. Having it in a single location allows you to make a one line code change if a developer re-maps the controls. I also map the control type so that the developer can change from a link to a button, and all I need to do is change this control file.
Abstract the test automation tool where possible
I essentially have three actions that I perform 99% of the time, Invoke, Setvalue and GetValue. Invoke will click a link or button, check or uncheck a checkbox etc. SetValue will put values into controls, like putting text into a text box. GetValue gets data from the controls, which is primarily used for verification.
Each automation action, for example performing a search, will only call these invoke, Set and get methods. This allows almost full abstraction of the underlying automation tool, it also allows you to have a single place in your code where the lower level control specific methods are called.
Use regular expressions to map your control names
A previous commercial tool that I used would cut of control strings at 256 characters. Many applications, like asp.net can dynamically create control names based on their location in page controllers. Using regular expressions lets you define the control in a way that the developer can move it anywhere on the page and your tests will still run.
Create test data objects to hold your test data
I create test data objects that normally map pretty close to 1:1 with the controls on a page, then I create an expected and actual object, then pass these objects around the various layers in the stack. Creating additional tests in some cases can then be as simple as using an existing test with different data.
Separate the test from the implementation
Separation of concerns is a key technical design principle that you should apply from your tests. The intent of the test case should be separated from the physical implementation that does the interaction with the page as much as possible. For example instead of ...
[Test]
public void SearchOnGoogle()
{
using (var browser = new IE("http://www.google.com"))
{
browser.TextField(Find.ByName("q")).TypeText("Stack Exchange");
browser.Button(Find.ByName("btnG")).Click();
Assert.IsTrue(browser.ContainsText("Stack Exchange - Free, Community-Powered Q&A"));
}
}
Your tests should, at the highest level have a domain specific API so.
[Test]
public void SearchOnGoogle()
{
SearchEngine.WebSearch("Stack Exchange");
SearchEngine.Verifcation.WebSearch("Stack Exchange - Free, Community-Powered Q&A");
}
}
...
public void WebSearch(string value)
{
ControlHandler.SetValue(Controls.txtSearchValue, value);
ControlHandler.Invoke(Controls.btnSearch);
}
For indepth information about this topic read Capture The Essence Of Your Test Cases by Michael Hunter