So I haven’t worked on my WatiN Mac os x port for a few days, but I got back into it last night. The main hurdle at the moment is that the System.Diagnostics.Process class is not fully baked on Mono 2.4 on the Mac platform, because it doesn’t implement the /proc filesystem that linux does.

The correct thing to do would be to learn how the Mach kernel process structure works and fix mono, but I haven’t programmed in non managed C since 1990 and I would be very dangerous, especially at such a low level in mono.

So the first approach was to use the parts of mono that were working, namley Proces.Start() to call a shell script that I wrote to /tmp, then redirect standard out to a file, then parse that file and act accordingly.

StreamWriter sw = new StreamWriter("/tmp/watin-listprocess.sh",false);
sw.WriteLine (@"ps x | grep" + processname + " | grep -v grep > /tmp/watin-processlist.out");
sw.Flush();
sw.Close();

Process shScriptProcess = new Process();
shScriptProcess.StartInfo = new ProcessStartInfo("/bin/sh","/tmp/watin-listprocesses.sh");
shScriptProcess.Start();

A hack, yes, in fact a very messy hack. So messy there had to be a better way.

And there is …

Enter Monobjc. Monobjc is a managed wrapper around the Mac os x Cocoa API’s. A quick search pointed to the NSTask and NSPipe classes as a way to start a process with NSTask, and capture standard out with NSPipe, then parse the result.

A much more technically correct solution.

public static void Main(string[] args)
{
// spin up the objective-c runtime
ObjectiveCRuntime.LoadFramework("Cocoa");
ObjectiveCRuntime.Initialize();
NSAutoreleasePool pool = new NSAutoreleasePool();

// Create our process
NSTask task = new NSTask();
NSPipe standardOut = new NSPipe();
task.StandardOutput = standardOut;
task.LaunchPath = @"/bin/ps";

// add some arguments
NSString argumentString = new NSString("-ax");
NSArray arguments = NSArray.ArrayWithObject(argumentString);
task.Arguments = arguments;

// We have liftoff
task.Launch();

// Parse the output and display it to the console
NSData output = standardOut.FileHandleForReading.ReadDataToEndOfFile;
NSString outString = new NSString(output,NSStringEncoding.NSUTF8StringEncoding);
Console.WriteLine(outString);

// Dipose our objects, gotta love reference counting
pool.Release();
}

So this needs to be fully baked in and then put into my Watin on mac os x port and then it should work stand alone, without shell script assistance.

I didn’t think it would be this easy, but it’s working. I can now run a basic WatiN test on mac os x using mono.

The code is here http://code.google.com/p/mcwatin/. All credit to the WatiN guys, all I did was remove stuff :-)
Here’s what you need to get this running

Mono 2.4 and MonoDevelop preview for mac os x from here
The source code from http://code.google.com/p/mcwatin/
Firefox 3 with JSSH installed.

start firefox up with the following command line

/Applications/Firefox.app/Contents/MacOS/firefox -jssh

Then run the test. It will work but fail as exceptions are being thrown in the dispose method.

1. Get a subset of WatiN to compile with no PInvokes done

2. Get McWatiN to successfully start firefox. done

3. get McWatiN to run a basic test. done

4. Replace all the windows and IE stuff that was removed with code from Watir.

Building: McWatiN (Debug)

Building Solution McWatiN

Building: McWatiN (Debug)
Performing main compilation...

Build complete -- 0 errors, 0 warnings

---------------------- Done ----------------------

Build successful.

Two words that mean so much … this time they refer to WatiN built using MonoDevelop on Mac OS X.

1. Get a subset of WatiN to compile with no PInvokes done

2. Get McWatiN to successfully start firefox.

2. Replace all the windows and IE stuff that was removed with code from Watir.

I am currently thinking about developing and delivering a WatiN training course. Is this something that anyone would be interested in ? If so, drop me a line via the comments on this blog.

The guys at Thoughtworks have released yet another open source project. This one is called white and is on codeplex http://www.codeplex.com/white/. What is white? White is a library that sits on top of the .Net 3.0 UIAutomation API’s. I have written one of these myself and we use it internally, so I haven’t looked at white.

Combined with WatiN, and ItiN if you use InfoPath, with white you should be able to write 100% of your tests using managed code in Visual Studio 2008. We do!

In my last post I gave a glimpse into our new WatiN stack, another feature is that we have implemented the command pattern, and serialise the commands out as XML. This lets us build tests that have randomised execution and still replay them when they fail. As a nice by-product, we also can write tests as XML if we wish.

After a month of pushing my gray matter as far as it will go (which isn’t all that far compared to some of the people I know) I have implemented the next generation of automated testing on my project.

No it isn’t using excel to generate tests like some people claim, although that does have it’s place. I have implemented a stack based on the design used in the expression team as described by Michael Hunter aka “The Braidy Tester”.

This transforms a test case that was like this:

using (IE ie = new IE("http://www.google.com"))
{
    ie.TextField(Find.ByName("q")).TypeText("WatiN");
    ie.Button(Find.ByName("btnG")).Click();
    Assert.IsTrue(ie.ContainsText("WatiN"));
}

to

Logical.Search(DataProvider.GetProvider("SearchForWatin"));

The next layer down is:

public static void Search(DataProvider dataProvider)
{
    object[] parameters = {dataProvider};
    executioner.Add(parameters);
    SearchModel searchModel = new searchModel();
    searchModel.OpenSearchPage();
    VerificationManager.ActionStarting(parameters);
    searchModel.Search(dataProvider);
    VerificationManager.ActionEnding(parameters);
}

then onto the “guts” of the test in the physical model.

public void Search(DataProvider dataProvider)
{
    SearchTDO search = DataProvider.GetSearchTDO();
    controller.SetValue(Controls.Search.Google.txtSearch,
         search.SearchText);
    controller.Invoke(Controls.Search.Google.btnSearch);
}

In case you didn’t notice, WatiN is fully abstracted away out of the tests, as is the test data, verification and the intent of how things are done. This increases the complexity significantly, but it also allows us to handle changes VERY easily.

Tonight I noticed a cool project on codeplex called WAX http://www.codeplex.com/wax, whilst this isn’t something that I will use myself, I have done something similar in the past, and generating tests in excel can save a LOT of time.

The project that I am currently working on uses infopath forms. This was causing us a few hassles on the testing front, especially now we are using WatiN extensively. So after a few failed attempts at using the new .Net 3.0 automaton API’s and not really wanting to learn MSAA, I decided to create a specalised port of WatiN to test infopath and ItiN was born.

ItiN uses a hybrid approach allowing manipulation of an InfoPath document directly using XPath and the XML dom, or the view using traditional WatiN automation.

The full set of HTML controls supported by WatiN is not required for ItiN, so only: buttons, check boxes, radio buttons, textboxes and select lists are supported.

The ItiN framework I is open source on Codeplex here http://www.codeplex.com/itin.

A sample script using the framework that works against the 2003 sample IssueTrackerSimple form saved in c:\ is as follows.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using ItiN;
namespace Itin.Tests
{
[TestMethod()]
public void IssueTrackingSample()
{
// Open test form
string infopathFileName = @”c:\IssueTrackingSample.xml”;
InfopathTester FormTester = new InfopathTester(infopathFileName);
FormTester.SetInfopathNamespace(”xmlns:iss=’http://schemas.microsoft.com/office/infopath/2003/sample/IssueTracking’”);
FormTester.SetFormValue(@”//iss:title”, “Issue Title”);

// Click the send email button which does nothing as I don’t have email configured for infopath
FormTester.Button(ItiN.Find.ByValue("Send as E-mail")).ClickNoWait();
FormTester.SaveDocumentAs(@"c:\SavedForm.xml");
FormTester.CloseAndQuit();

// TODO: Implement code to verify target
Assert.Inconclusive("TODO: Implement code to verify target");
}
}
}

In a comment on a blog