views:

949

answers:

3

When trying to run a very simple WatiN 2.0 (CTP3) test in Visual Studio 2008 I found that the first one always executes fine. The second test method seem to break something in the IE object producing the following exception:

Test method testProject.WatinTest.testTwo threw exception: System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used..

A sample code is below. Due to the way the initialization method is workin in VS2008 the browser variable has to be defined as static which I believe could be a key to the problem. Unfortunately unless the browser is opened in the common method it means a separate window for every test which is not ideal

I would be very grateful for any ideas on how to fix that. Google search and SO search did not produce any useful results so I hope that a good answer to this question will help the community. Many thanks,


    private static IE ie

    [ClassInitialize]
    public static void testInit(TestContext testContext)
    {
        ie = new IE("http://news.bbc.co.uk");
    }

    [TestMethod]
    public void testOne()
    {
        Assert.IsTrue(ie.ContainsText("Low graphics"));
    }

    [TestMethod]
    public void testTwo()
    {
        Assert.IsTrue(ie.ContainsText("Low graphics"));
    }
A: 

I think it is related to the way you are keeping a hold to the IE variable. I have been using it with no trouble as in the sample: http://watin.sourceforge.net/. I see watin tests as more functional/integration tests than unit tests, so each test is more like a story with different interactions. I use the Page Object pattern to help structure the code - see http://code.google.com/p/webdriver/wiki/PageObjects.

Ps. I have considered reusing the instance but haven't really tried it. Opening/closing instances of the browser do add time to the tests.

eglasius
Thank you. The examples on WatiN page are working fine for me but for the real-live scenarios I would like something more. We could use the Page Object pattern but putting it here would make the sample code unreadable. Thanks again
Ilya Kochetov
Of course WatiN is not really about unit testing but integration/functional, but just because you run unit test runner, it doesn't mean it has to be unit test. If you write functional/integration tests based on unit test infrastructure, you get runner, reporting, VS integration like with unit test, which is BIG gain, specially if you want to run it on CI server, where your test are executed every time new build of application is deployed.
yoosiba
+5  A: 

Hi Ilya,

I have heard this problem before and meant to investigate this for a while. Now that WatiN 2.0 beta 1 is available I sat down and created a helper class to solve this problem with Visual Studios test runner. Following the helper class and the revamped test class. I also blogged about this solution to give it even more exposure.

public class IEStaticInstanceHelper
{
    private IE _ie;
    private int _ieThread;
    private string _ieHwnd;

    public IE IE
    {
        get
        {
            var currentThreadId = GetCurrentThreadId();
            if (currentThreadId != _ieThread)
            {
                _ie = IE.AttachToIE(Find.By("hwnd", _ieHwnd));
                _ieThread = currentThreadId;
            }
            return _ie;
        }
        set
        {
            _ie = value;
            _ieHwnd = _ie.hWnd.ToString();
            _ieThread = GetCurrentThreadId();
        }
    }

    private int GetCurrentThreadId()
    {
        return Thread.CurrentThread.GetHashCode();
    }
}

And the test class using this helper:

[TestClass]
public class UnitTest 
{
    private static IEStaticInstanceHelper ieStaticInstanceHelper;

    [ClassInitialize]
    public static void testInit(TestContext testContext)
    {
        ieStaticInstanceHelper = new IEStaticInstanceHelper();
        ieStaticInstanceHelper.IE = new IE("http://news.bbc.co.uk");
    }

    public IE IE
    {
        get { return ieStaticInstanceHelper.IE; }
        set { ieStaticInstanceHelper.IE = value; }
    }

    [ClassCleanup]
    public static void MyClassCleanup()
    {
        ieStaticInstanceHelper.IE.Close();
        ieStaticInstanceHelper = null;
    }

    [TestMethod]
    public void testOne()
    {
        Assert.IsTrue(IE.ContainsText("Low graphics"));
    }

    [TestMethod]
    public void testTwo()
    {
        Assert.IsTrue(IE.ContainsText("Low graphics"));
    }
}

HTH, Jeroen

Jeroen van Menen
Brilliant, Jeroen. That's a great answer and I really hope it will help lots of people who have the same problem. Also I hope that now between your blog and SO Google will be able to give meaningful suggestions when people will search for solutions
Ilya Kochetov
+1  A: 

I was looking for this same thing, and the answer of jvmenen helped me out. However, there has been some updates to WatiN since that answer was given, so I had to rewrite the helper class a bit to be consistent with the latest WatiN version (currently 2.0.20).

WatiN no longer contains the IE.AttachToIE function, so I had to modify that a bit. In addition I also made the helper class using generics, so that any browser type can be used and not only IE (I believe IE and Firefox is supported by WatiN now, and Chrome is to come).

So in case anybody else is looking for this as well, here is my version of the IEStaticInstanceHelper (now called StaticBrowserInstanceHelper):

class StaticBrowserInstanceHelper<T> where T : Browser
{
    private Browser _browser;
    private int _browserThread;
    private string _browserHwnd;

    public Browser Browser
    {
        get
        {
            int currentThreadId = GetCurrentThreadId();
            if (currentThreadId != _browserThread)
            {
                _browser = Browser.AttachTo<T>(Find.By("hwnd", _browserHwnd));
                _browserThread = currentThreadId;
            }
            return _browser;
        }
        set
        {
            _browser = value;
            _browserHwnd = _browser.hWnd.ToString();
            _browserThread = GetCurrentThreadId();
        }
    }

    private int GetCurrentThreadId()
    {
        return Thread.CurrentThread.GetHashCode();
    }
}

Hope this helps somebody :)

Nailuj