Welcome to MSDN Blogs Sign in | Join | Help

Adventures in Automation: Firefox, Part I

WatiN is my tool of choice for automating Internet Explorer, but I need to automate Firefox as well. I'm not happy with any of my options, and I've been wanting to explore automation with MUIA for the last several weeks, so I decided to play around with it a bit this evening.

Attaching to Firefox

The first thing I need to do is attach to a running instance of Firefox. Being a good TDD'er, I write my test first. Here it is:

        [Fact]

        public void firefox_attach_will_find_the_correct_automation_element()

        {

            StartFirefox();

 

            IBrowser browser = Firefox.Attach("Mozilla Firefox");

 

            Assert.NotNull(((Firefox)browser).Instance);

            Assert.Equal("MozillaUIWindowClass", ((Firefox)browser).Instance.Current.ClassName);

        }

I know the Firefox window's class name from a previous automation effort, so I use it for my assertion (I imagine it will be hard to find good assertions going forward, as so much of the validation will be visual). Now I can create the Attach method of my Firefox class. But where should I begin? I need to know what Firefox's automation element tree looks like. Using UISpy.exe would make that easy, but I'm having trouble getting it installed. I decide to just hack at the MUIA namespace (System.Windows.Automation) and see where it gets me.

I know I'll need to reference the MUIA assembly. I expect it to be System.Windows.Automation, but I'm wrong. A quick web search shows me what I need: UIAutomationClient and UIAutomationProvider. While I'm in the references dialog, I also notice UIAutomationTypes. I add all three for good measure.

I know from previous reading that AutomationElement.RootElement provides access to the top of the automation element tree. It's the Windows Desktop. But trying to loop through its children would be slow (and stupid). My test provides the window's title, so I decide to search on that. I give this code a run: 

        public static IBrowser Attach(string windowTitle)

        {

            AutomationElement firefox = AutomationElement.RootElement.FindFirst(

                TreeScope.Children,

                new PropertyCondition(

                    AutomationElement.NameProperty,

                    windowTitle));

 

            if (firefox == null)

                    throw new InvalidOperationException("Unable to find the specified Firefox instance.");

 

            return new Firefox(firefox);

        }

Success! My test passes, and on the first try (that's not common for me). Before I move on, I'll explain the meat of this method, the FindFirst invocation.

The TreeScope.Children argument tells the FindFirst method to search only the immediate children of the Windows Desktop (as opposed to TreeScope.Descendants, which searches everything down the tree). The property condition specifies the search criteria. In this case, I searched by the name property. There are many other options as well (AutomationIdProperty and IsInvokePatternAvailableProperty are both good ones).

Maximizing the Browser

I'm pretty happy. I've made good progress for 10 or so minutes. I want to try one other thing, also very simple. I want to automate maximizing the browser window. Here is my unit test:

        [Fact]

        public void show_window_will_maximize_firefox()

        {

            StartFirefox();

            IBrowser browser = Firefox.Attach("Mozilla Firefox");

 

            browser.ShowWindow();

 

            Assert.NotNull(((Firefox)browser).Instance);

            Assert.Equal(

                AutomationElement.RootElement.Current.BoundingRectangle,

                ((Firefox)browser).Instance.Current.BoundingRectangle);

        }

My ShowWindow method should simulate the user clicking the maximize button (if you are wondering why I named a maximize method ShowWindow, it's because it will do more later; I plan to keep this tool as source compatible with WatiN as I can). In MUIA, clicking buttons is done with an InvokePattern. And I happen to remember reading that the maximize button has an AutomationID property of "Maximize". So, I create this ShowWindow method:

        public void ShowWindow()

        {

            AutomationElement maximizeButton = instance.FindFirst(

                TreeScope.Descendants,

                new PropertyCondition(AutomationElement.AutomationIdProperty, "Maximize"));

 

            if (maximizeButton != null)

                ((InvokePattern)maximizeButton.GetCurrentPattern(InvokePattern.Pattern)).Invoke();

        }

The code is pretty simple. Try to get the maximize button, and if I get it invoke it. It's important to note that the maximize button is only present when the window isn't already maximized. If the window is maximized, it's a restore button instead.

I run my tests. Uh oh, the new test fails:

TestCase 'BrowserAutomator.UnitTests.FirefoxTests.show_window_will_maximize_firefox2'

failed: Assert.Equal() Failure

Expected: 0,0,1920,1106

Actual:   -4,-4,1928,1054

Because I set the initial size for my Firefox window to 1024x768, I know that the browser was actually maximized. But comparing the bounding rectangles of the Windows Desktop and Firefox won't work. So, I go to "Plan B," and changed my test to this:

        [Fact]

        public void show_window_will_maximize_firefox()

        {

            StartFirefox();

 

            IBrowser browser = Firefox.Attach("Mozilla Firefox");

            browser.ShowWindow();

 

            Assert.NotNull(((Firefox)browser).Instance);

            Native.Types.WindowPlacement windowPlacement = new Native.Types.WindowPlacement();

            Native.Methods.GetWindowPlacement(

                new IntPtr(((Firefox)browser).Instance.Current.NativeWindowHandle),

                ref windowPlacement);

            Assert.Equal(Native.Constants.ShowCommandMaximize, windowPlacement.ShowCommand);

 

        }

I run this test and it passes. I'm done for the night.

Afterthoughts

There are some real problems with my code as it currently is. For instance, if Firefox is already running, my unit tests fail. If Firefox was previously closed unexpectedly, my unit tests hang on the "Restore Previous Session" dialog.

I foresee other difficulties as well. An example is waiting for the page to finish loading. In a tool like WatiN, the browser's object model can be queried to determine whether the page is ready. I'm hoping I can query the text of the status bar, which always reads "Done" when the page is ready. We'll see. I'm sure there will be many problems such as this ahead.

Next Time

In part two of my Firefox automation adventures I will automate a Google search. It seems that most of the browser automation tools use this as their canonical example, so I might as well get it out of the way. More importantly, typing text and clicking buttons are simple but very useful steps forward.

Regards, DM

Published Saturday, December 08, 2007 6:33 AM by Drew Miller
Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# MSDN Blog Postings » Adventures in Automation: Firefox, Part I

# re: Adventures in Automation: Firefox, Part I

Hi Drew,

This has always been lurking somewhere in the back of my mind. Nice to read your firefox automation adventures using UIAutomation. Currently we are developing FireFox support for WatiN using jssh, but this seems to be another very usable approach.

I'm looking forward to your next step.

Jeroen van Menen

Lead dev WatiN

Saturday, December 08, 2007 11:00 AM by Jeroen van Menen

# re: Adventures in Automation: Firefox, Part I

Hello Jeroen,

I used FireWatir quite a bit before joining Microsoft, so I'm familiar with the jssh approach. If Ruby was a viable choice for me here, I'd definitely be using xWatir.

I played around with automating Firefix via jssh for a bit, but it was frustrating and I didn't want to take the dependency in the first place. Plus, I still need to automate Opera and Safari 3, and I don't know how feasible the jssh approach will be for them.

Of course, I would love to see WatiN get "Fire-y," whether through jssh or any other way. Then I could focus on the next automation challenge instead of this!

Regards, DM

Saturday, December 08, 2007 2:29 PM by Drew Miller

# re: Adventures in Automation: Firefox, Part I

Well, I've already found an apparent dead end with the MUIA approach. I'd read on the web that the way Firefox builds its UI confuses MUIA, and now I've seen it for myself. More on this in part two, including my new approach.

Sunday, December 09, 2007 2:20 PM by Drew Miller

# re: Adventures in Automation: Firefox, Part I

I want to get the url from firefox. As I'm not a very good in .net I can't get through this problem. So can you please help me out.

Tuesday, March 18, 2008 8:45 AM by Vinit

# re: Adventures in Automation: Firefox, Part I

where could I get FireFox class(object)?

Thursday, March 27, 2008 4:03 AM by bang

Leave a Comment

(required) 
required 
(required) 
 
Page view tracker