HTTP Error Codes in WatiN 1.3

One of the biggest surprises when I started working with WatiN was
the omission of a mechanism to check for error conditions. A partial
solution using a subclass has been posted
before, but it doesn’t quite cover all the bases. Specifically, it’s
missing a mechanism to attach existing Internet Explorer instances to
objects of the enhanced subtype. Depending on the site under test’s use
of pop-ups, this could be a rather severe limitation. So let’s see how
we can fix it.

As WatiN is open source, one option is to just patch the existing implementation to include the desired behavior. I’ve uploaded a patch with tests here, but the gist of the patch is quite similar to the solution referenced above:

protected void AttachEventHandlers()
{
ie.BeforeNavigate2 += (object pDisp, ref object URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel) =>
{
ErrorCode = null;
};
ie.NavigateError += (object pDisp, ref object URL, ref object Frame, ref object StatusCode, ref bool Cancel) =>
{
ErrorCode = (HttpStatusCode)StatusCode;
};
}

/// <summary>
/// HTTP Status Code of last error, or null if the last request was successful
/// </summary>
public HttpStatusCode? ErrorCode
{
get;
private set;
}

Before every request we clear out the error code, with errors captured as an enum value borrowed from System.Net.

We complete the patch by placing calls to our AttachEventHandlers() method in two places:

  1. The constructor that accepts an existing SHDocVw.InternetExplorer handle.
  2. The CreateNewIEAndGoToUri() method used by every other constructor.

At this point we can now assert success:

using (IE ie = new IE("http://solutionizing.net/"))
{
Assert.That(ie.ErrorCode, Is.Null);
}

Or specific kinds of failure:

using (IE ie = new IE("http://solutionizing.net/4040404040404"))
{
Assert.That(ie.ErrorCode, Is.EqualTo(HttpStatusCode.NotFound));
}

See the patch above for a more complete set of example tests.

Private Strikes Again

It’s wonderful that we have the option to make our own patched build
with the desired behavior, but what if we would rather use the binary
distribution? Well through the magic of inheritance we can get most of
the way there pretty easily:

public class MyIE : IE
{
public MyIE()
{
Initialize();
}
public MyIE(object shDocVwInternetExplorer)
: base(shDocVwInternetExplorer)
{
Initialize();
}
public MyIE(string url)
: base(url)
{
Initialize();
}

// Remaining c'tors left as an exercise

// Property named ie for consistency with the private field in the parent
protected InternetExplorer ie
{
get { return (InternetExplorer)InternetExplorer; }
}

protected void Initialize()
{
AttachEventHandlers();
}

// AttachEventHandlers() and ErrorCode as defined above
}

But as I suggested before, this is where we run into a bit of a snag. The IE class also provides a set of static AttachToIE() methods that, as their name suggests, return an IE
object for an existing Internet Explorer window. These static methods
have the downside that they are hard-coded to return objects of type IE, not our enhanced MyIE
type. And because all the relevant helper methods are private and not
designed for reuse, we have no choice but to pull them into our
subclass in their entirety:

    public new static MyIE AttachToIE(BaseConstraint findBy)
{
return findIE(findBy, Settings.AttachToIETimeOut, true);
}
public new static MyIE AttachToIE(BaseConstraint findBy, int timeout)
{
return findIE(findBy, timeout, true);
}
public new static MyIE AttachToIENoWait(BaseConstraint findBy)
{
return findIE(findBy, Settings.AttachToIETimeOut, false);
}
public new static MyIE AttachToIENoWait(BaseConstraint findBy, int timeout)
{
return findIE(findBy, timeout, false);
}

private static MyIE findIE(BaseConstraint findBy, int timeout, bool waitForComplete)
{
SHDocVw.InternetExplorer internetExplorer = findInternetExplorer(findBy, timeout);

if (internetExplorer != null)
{
MyIE ie = new MyIE(internetExplorer);
if (waitForComplete)
{
ie.WaitForComplete();
}

return ie;
}

throw new IENotFoundException(findBy.ConstraintToString(), timeout);
}

protected static SHDocVw.InternetExplorer findInternetExplorer(BaseConstraint findBy, int timeout)
{
Logger.LogAction("Busy finding Internet Explorer matching constriant " + findBy.ConstraintToString());

SimpleTimer timeoutTimer = new SimpleTimer(timeout);

do
{
Thread.Sleep(500);

SHDocVw.InternetExplorer internetExplorer = findInternetExplorer(findBy);

if (internetExplorer != null)
{
return internetExplorer;
}
} while (!timeoutTimer.Elapsed);

return null;
}

private static SHDocVw.InternetExplorer findInternetExplorer(BaseConstraint findBy)
{
ShellWindows allBrowsers = new ShellWindows();

int browserCount = allBrowsers.Count;
int browserCounter = 0;

IEAttributeBag attributeBag = new IEAttributeBag();

while (browserCounter < browserCount)
{
attributeBag.InternetExplorer = (SHDocVw.InternetExplorer) allBrowsers.Item(browserCounter);

if (findBy.Compare(attributeBag))
{
return attributeBag.InternetExplorer;
}

browserCounter++;
}

return null;
}

The original version of the first findInternetExplorer() is private. Were it protected instead, we would only have had to implement our own findIE() to wrap the found InternetExplorer object in our subtype.

I won’t go so far as to say private methods are a code smell, but they certainly can make the O in OCP more difficult to achieve.

So there you have it, two different techniques for accessing HTTP
error codes in WatiN 1.3. At some point I’ll look at adding similar
functionality to 2.0, if it’s not already there. And if someone on the
project team see this, feel free to run with it.

Related Articles:

    About Keith Dahlby

    I'm a .NET developer, Git enthusiast and language geek from Cedar Rapids, IA. I work as a software guru at J&P Cycles and studied Human-Computer Interaction at Iowa State University.
    This entry was posted in testing, WatiN. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
    • Daaron

      Hi,
      I’m on the project team, and I like where you’re going with this. The main reason Jeroen didn’t have something like this is that it requires ShDocVw, and he wanted to keep it as agnostic as possible. As it stands now, we’re trying to support IE, FF, and Chrome, but only IE surfaces the event you use in the code above. I don’t know of a good way to make this cross browsers, but that’s what I would want in order to add it to the core.

    • http://www.lostechies.com/members/dahlbyk/default.aspx Keith Dahlby

      Hey Daaron, thanks for stopping by. I can certainly understand wanting the APIs to be consistent across the supported browsers. After a bit of tinkering tonight I’m pretty sure I can get something hacked together for Firefox using its observer service. Any suggestions where to start on extending Chrome?

    • http://ww.watin.net Jeroen van Menen

      Hi Keith,

      Thanks for the great article. Still no way to check the HTTP error code in WatiN 2.0 but the AttachTo functinoality has greatly been improved. No copy and pasting needed any longer. Just use

      MyIE myIe = Browser.AttachTo(Find.ByXX);

      Read this for more info: http://watinandmore.blogspot.com/2010/01/browserattachto-and-iattachto.html

      HTH

      Jeroen van Menen
      Lead dev WatiN