WatiN Patterns #3: Don’t Over-specify

After a long hiatus, I’m resuming the WatiN Patterns series. Pattern #1 covered why and how your tests should clean up after themselves. Pattern #2 covered how you should name your tests and why they should only assert one thing.

Pattern #3 is about keeping your tests maintainable by specifying just enough in your element selectors.

If you’re using WatiN, you’re probably also using ASP.NET Web Forms, which means your controls get IDs like this: ctl1001_Content_Content_txtUsername. Though you gave your username text box the ID “txtUsername”, ASP.NET prepends the IDs of each of the container controls to your text box’s ID in the HTML that goes to the browser. This is good for preventing name collisions between controls in different user controls on the same page. But it can lead you to write fragile tests. Consider this example:


[Test]
public void ShouldAuthenticateUsersSuccessfully()
{
using (IE ie = new IE())
{
ie.GoTo("http://mytestapp/");
ie.TextField("ctl1001_Content_Content_txtUsername").TypeText("richard");
ie.TextField("ctl1001_Content_Content_txtPassword").TypeText("password");
ie.Button("ctl1001_Content_Content_btnLogin").Click();
Assert.IsTrue(ie.ContainsText("Welcome, Richard!"));
}
}

Suppose someone moves the login widgets outside one of those nested “Content” controls. Now our test fails. Did login stop working? Probably not. But, thanks to ASP.NET’s “helpful” ID scheme, we can no longer find the login widgets—their IDs have changed. We could update the test to the new IDs:


[Test]
public void ShouldAuthenticateUsersSuccessfully()
{
using (IE ie = new IE())
{
ie.GoTo("http://mytestapp/");
ie.TextField("ctl1001_Content_txtUsername").TypeText("richard");
ie.TextField("ctl1001_Content_txtPassword").TypeText("password");
ie.Button("ctl1001_Content_btnLogin").Click();
Assert.IsTrue(ie.ContainsText("Welcome, Richard!"));
}
}

Or we could solve the root problem of fragile tests:


[Test]
public void ShouldAuthenticateUsersSuccessfully()
{
using (IE ie = new IE())
{
ie.GoTo("http://mytestapp/");
ie.TextField(new Regex("txtUsername$")).TypeText("richard");
ie.TextField(new Regex("txtPassword$")).TypeText("password");
ie.Button(new Regex("btnLogin$")).Click();
Assert.IsTrue(ie.ContainsText("Welcome, Richard!"));
}
}

WatiN accommodates regular expressions instead of strings for IDs. Now we’ve specified, “a text field with an ID ending in txtUsername” (note the ending dollar sign), instead of, “a text field with exactly the ID ctl1001_Content_Content_txtUsername.” Much more flexible.

To keep your tests from being fragile, when selecting elements on the page, use the least specific method and value necessary to find the element you want.

Related Articles

Responses

  1. Hi Richard,

    This indeed is a good solution to a pitfall I see much to often. You can improve the syntax even more. Read this blog post to make this possible:

    ie.Button(“/btnLogin$/”).Click()

    Somewhat ruby like syntax for expressing a regular expression.

    Jeroen van Menen

    BTW Love cuke4nuke!

  2. Interesting feature, Jeroen. Thanks for the tip.

    To extend that idea, it would be nice if you could just register a lambda for the default constraints instead of having to create a new class and register it.