Tutorial: Introduction to Mozmill

  • Revision slug: Mozmill/First_Steps/Tutorial:_Introduction_to_Mozmill
  • Revision title: Tutorial: Introduction to Mozmill
  • Revision id: 49083
  • Created:
  • Creator: Ctalbert
  • Is current revision? No
  • Comment 6 words added

Revision Content

{{ Draft() }}

MozMill is a test tool/framework for writing automated tests for Gecko based applications (Firefox, Thunderbird, Songbird, etc). It is built as an extension which includes an IDE to help you write, run, and debug tests. MozMill has an extensive API to help you write functional tests that simulate user interactions, as well as a full unittest API.

Understanding the Framework

 

Before we dive in to writing full fledged tests we should learn a little bit about the MozMill framework. The test files you write will be imported as “modules” and the functions in them will be run in specific ways based on naming conventions. Any function that has a name that begins with “test” will be run as a test. There are also four special functions you can define; setupModule, teardownModule, setupTest and teardownTest which are used to define common steps before a module or a test is run and also how to remove anything that might impact another test or module.

var setupModule = function (module) {
   module.controller = mozmill.getBrowserController();
}

var testSomething = function () {
   controller.open('http://www.google.com')
}

If your test file is run alongside other tests they will all be imported before any test functions are run as part of a "collection"step. This means that anything in the base of your test will only work as expected when your test is run individually so we need another way to define steps that are run before your test functions are run.setupModule is run immediately before running your test functions. If it fails the framework will mark all the tests in the module skipped.

If you do anything that would leave the product in a different state than before your tests were run you should return the system to it's previous state in a teardownModule.

Functional MozMill Tests

A good way to think about seperating tests and modules is to first setup a common state for multiple tests in a setupModule, then tests seperate simply by when their steps no longer effect each other. If
there is some sort of simple state needed for each test that also needs to be torn down so that it does not effect the next test you can use setupTest and teardownTest.

var setupModule = function(module) {

 module.controller = mozmill.getBrowserController();
}
var setupTest = function(test) {
 controller.click(new elementslib.Elem(controller.menus.File['New  Tab']))
}
var teardownTest = function(test) {
 controller.click(new elementslib.Elem(controller.menus.File['Close  Tab']))
}
var testGoogle = function(){
 controller.open('http://www.google.com');
}

var testYahoo = function(){
 controller.open('http://www.yahoo.com');
}

 


The test above creates a new tab and opens a website in it, then it closes that tab. Since all tests and setupTest/teardownTest need to interact with a browser window we create a controller for it in setupModule and attach it to the test module. Since all of the test functions need a new tab to be opened and then closed, we've written a setupTest and teardownTest method call that opens and closes a new tab. Now each test function is free to interact with a fresh browser tab and not worry about leaving behind any state inside of that tab.

This test introduces you to the primary test object in MozMill: the controller. Since MozMill runs inside the application and has access to all windows for testing you need to create a controller instance
for every window your test uses. Most of the time you'll be interacting with just the browser window so the above example is simple. The main mozmill resource provides a bunch of top level convenience functions for you to use in getting common windows in a non-destructive manner. mozmill.getBrowserController() will give you a new controller instance for the Browser window if one is open, if one is not open one will be opened for you and used.

If you run the test above in MozMill you'll notice that it runs entirely too fast to be useful. That's because all the test does right now is type in the urlbar and hit enter for each page; it doesn't
actually wait for the page to be loaded. Let's edit our test functions and add some calls that wait for the page to be loaded.

var testGoogle = function(){
 controller.open('http://www.google.com');
 controller.waitForPageLoad(controller.tabs.activeTab);
}
var testYahoo = function(){
 controller.open('http://www.yahoo.com');
 controller.waitForPageLoad(controller.tabs.activeTab);
}


Since waitForPageLoad can be used on any tab, it needs to be passed to the document that you want to wait to be loaded. Browser controllers have a convenient API for accessing tab documents and activeTab will always be the currently focused tab.

If you run the test again you'll notice a much longer time before closing each tab. Although waitForPageLoad can be very useful, in most cases it's better just to wait for the element we need.  We don't need the image to be finished loading before we can type in the search box and complete a search, so let's just wait for the search box. Let's pull up the Google test.

var testGoogle = function(){
 controller.open('http://www.google.com');
 var e = new elementslib.Name(controller.tabs.activeTab, "q");
 controller.waitForElement(e);
 controller.type(e, "mozilla");
 controller.click(new elementslib.Name(controller.tabs.activeTab,  
"btnG"));
 controller.sleep(3000);
}

This introduces you to elementslib. elementslib is a resource module that contains a series of Elem objects which are abstractions for interface nodes. These objects implement different methods of
describing any document interface node. Content tabs mostly contain DOM nodes and XUL(i.e. chrome) interfaces contain a mix of normal DOM nodes and special anonymous nodes; so different resolution methods are necessary for different interface nodes. Any interface node can be resolved using one of these Elem objects but since many expressions would be impossible to write by hand the MozMill IDE includes an Interface Inspector.

Load the Google search page into the browser and make sure that the Mozmill IDE window is also visible next to it. Click on the "Inspector" link in the Mozmill IDE to enable the Mozmill inspector. Now, as you mouse over elements on the browser's window, you'll notice a textbox being populated in the Mozmill Inspector dialog.  If you browse over the "Google" Image, you'll see this in the inspector dialog:

Mozmill Inspector Window

Element:: new elementslib.XPath(controller.tabs.activeTab, "/html/body/center/img")
Controller:: new mozmill.getBrowserController()
Validation:: true


The first line, "Element:: new elementslib.XPath(controller.tabs.activeTab, "/html/body/center")", is the code needed to create a new Elem object for the node you are inspecting. This is what you pass to controller.click and other user simulation methods that need elementlib objects. You'll notice that this call assumes you have a controller instance available named "controller" for the window you are inspecting. The second line, "Controller:: new mozmill.getBrowserController()", shows you what call you need to make to get that controller if you haven't created one already. The third line, "Validation:: true", is for validation of the expression. If true, it means that mozmill verified that the generated expression shown above worked and found the same object that you are currently inspecting.

Causing Your Own Failures


The main difference between automated functional tests and automated unittests is that unittests are not all synchronous. If a call in a unit test fails, that does not necessarily mean the next call should fail.  This is why unittest APIs don't usually cause exceptions and instead tend to trigger silent framework failures.

Functional tests tend to by highly synchronous. Since tests tend to simulate user interactions any failure along the way is almost assured to cause the rest of the steps to fail. Failures in MozMill are triggered by normal JavaScript exceptions. If a failure happens inside the API, an exception is thrown. This means that if you'd like to assert something in your code you may also simply call your own exception:

var testSomething () {
   controller.open('http://www.google.com');
   controller.waitForPageLoad();
   if (controller.window.document.title != 'Google') {
     throw 'Title does not match "Google" !=  ' + controller.window.document.title;
   }
}

 

Revision Source

<p>{{ Draft() }}</p>
<p>MozMill is a test tool/framework for writing automated tests for Gecko based applications (Firefox, Thunderbird, Songbird, etc). It is built as an extension which includes an IDE to help you write, run, and debug tests. MozMill has an extensive API to help you write functional tests that simulate user interactions, as well as a full unittest API.</p>
<h2>Understanding the Framework</h2>
<p> </p>
<p>Before we dive in to writing full fledged tests we should learn a little bit about the MozMill framework. The test files you write will be imported as “modules” and the functions in them will be run in specific ways based on naming conventions. Any function that has a name that begins with “test” will be run as a test. There are also four special functions you can define; setupModule, teardownModule, setupTest and teardownTest which are used to define common steps before a module or a test is run and also how to remove anything that might impact another test or module.</p>
<pre>var setupModule = function (module) {
   module.controller = mozmill.getBrowserController();
}

var testSomething = function () {
   controller.open('<a class="moz-txt-link-freetext" href="http://www.google.com/">http://www.google.com</a>')
}

</pre>
<p>If your test file is run alongside other tests they will all be imported before any test functions are run as part of a "collection"step. This means that anything in the base of your test will only work as expected when your test is run individually so we need another way to define steps that are run before your test functions are run.setupModule is run immediately before running your test functions. If it fails the framework will mark all the tests in the module skipped.</p>
<p>If you do anything that would leave the product in a different state than before your tests were run you should return the system to it's previous state in a teardownModule.</p>
<h2>Functional MozMill Tests</h2>
<p>A good way to think about seperating tests and modules is to first setup a common state for multiple tests in a setupModule, then tests seperate simply by when their steps no longer effect each other. If <br>
there is some sort of simple state needed for each test that also needs to be torn down so that it does not effect the next test you can use setupTest and teardownTest.</p>
<p>var setupModule = function(module) {</p>
<pre> module.controller = mozmill.getBrowserController();</pre>
<pre>}</pre>
<pre>var setupTest = function(test) {</pre>
<pre> controller.click(new elementslib.Elem(controller.menus.File['New  Tab']))</pre>
<pre>}</pre>
<pre>var teardownTest = function(test) {</pre>
<pre> controller.click(new elementslib.Elem(controller.menus.File['Close  Tab']))</pre>
<pre>}</pre>
<pre>var testGoogle = function(){</pre>
<pre> controller.open('<a class="moz-txt-link-freetext" href="http://www.google.com/">http://www.google.com</a>');</pre>
<pre>}

var testYahoo = function(){</pre>
<pre> controller.open('<a class="moz-txt-link-freetext" href="http://www.yahoo.com/">http://www.yahoo.com</a>');</pre>
<pre>}</pre>
<p> </p>
<p><br>
The test above creates a new tab and opens a website in it, then it closes that tab. Since all tests and setupTest/teardownTest need to interact with a browser window we create a controller for it in setupModule and attach it to the test module. Since all of the test functions need a new tab to be opened and then closed, we've written a setupTest and teardownTest method call that opens and closes a new tab. Now each test function is free to interact with a fresh browser tab and not worry about leaving behind any state inside of that tab.<br>
<br>
This test introduces you to the primary test object in MozMill: the controller. Since MozMill runs inside the application and has access to all windows for testing you need to create a controller instance <br>
for every window your test uses. Most of the time you'll be interacting with just the browser window so the above example is simple. The main mozmill resource provides a bunch of top level convenience functions for you to use in getting common windows in a non-destructive manner. mozmill.getBrowserController() will give you a new controller instance for the Browser window if one is open, if one is not open one will be opened for you and used.<br>
<br>
If you run the test above in MozMill you'll notice that it runs entirely too fast to be useful. That's because all the test does right now is type in the urlbar and hit enter for each page; it doesn't <br>
actually wait for the page to be loaded. Let's edit our test functions and add some calls that wait for the page to be loaded.</p>
<pre>var testGoogle = function(){</pre>
<pre> controller.open('<a class="moz-txt-link-freetext" href="http://www.google.com/">http://www.google.com</a>');</pre>
<pre> controller.waitForPageLoad(controller.tabs.activeTab);</pre>
<pre>}</pre>
<pre>var testYahoo = function(){</pre>
<pre> controller.open('<a class="moz-txt-link-freetext" href="http://www.yahoo.com/">http://www.yahoo.com</a>');</pre>
<pre> controller.waitForPageLoad(controller.tabs.activeTab);</pre>
<pre>}</pre>
<p><br>
Since waitForPageLoad can be used on any tab, it needs to be passed to the document that you want to wait to be loaded. Browser controllers have a convenient API for accessing tab documents and activeTab will always be the currently focused tab.<br>
<br>
If you run the test again you'll notice a much longer time before closing each tab. Although waitForPageLoad can be very useful, in most cases it's better just to wait for the element we need.  We don't need the image to be finished loading before we can type in the search box and complete a search, so let's just wait for the search box. Let's pull up the Google test.</p>
<pre>var testGoogle = function(){</pre>
<pre> controller.open('<a class="moz-txt-link-freetext" href="http://www.google.com/">http://www.google.com</a>');</pre>
<pre> var e = new elementslib.Name(controller.tabs.activeTab, "q");</pre>
<pre> controller.waitForElement(e);</pre>
<pre> controller.type(e, "mozilla");</pre>
<pre> controller.click(new elementslib.Name(controller.tabs.activeTab,  </pre>
<pre>"btnG"));</pre>
<pre> controller.sleep(3000);</pre>
<pre>}</pre>
<p>This introduces you to elementslib. elementslib is a resource module that contains a series of Elem objects which are abstractions for interface nodes. These objects implement different methods of <br>
describing any document interface node. Content tabs mostly contain DOM nodes and XUL(i.e. chrome) interfaces contain a mix of normal DOM nodes and special anonymous nodes; so different resolution methods are necessary for different interface nodes. Any interface node can be resolved using one of these Elem objects but since many expressions would be impossible to write by hand the MozMill IDE includes an Interface Inspector.<br>
<br>
Load the Google search page into the browser and make sure that the Mozmill IDE window is also visible next to it. Click on the "Inspector" link in the Mozmill IDE to enable the Mozmill inspector. Now, as you mouse over elements on the browser's window, you'll notice a textbox being populated in the Mozmill Inspector dialog.  If you browse over the "Google" Image, you'll see this in the inspector dialog:<br>
<br>
<img alt="Mozmill Inspector Window" src="http://people.mozilla.org/~clint/mozmill-inspector.png"></p>
<pre>Element:: new elementslib.XPath(controller.tabs.activeTab, "<em class="moz-txt-slash"><span class="moz-txt-tag">/</span>html/body<span class="moz-txt-tag">/</span></em>center/img")</pre>
<pre>Controller:: new mozmill.getBrowserController()</pre>
<pre>Validation:: true</pre>
<p style="text-align: left;"><br>
The first line, "Element:: new elementslib.XPath(controller.tabs.activeTab, "/html/body/center")", is the code needed to create a new Elem object for the node you are inspecting. This is what you pass to controller.click and other user simulation methods that need elementlib objects. You'll notice that this call assumes you have a controller instance available named "controller" for the window you are inspecting. The second line, "Controller:: new mozmill.getBrowserController()", shows you what call you need to make to get that controller if you haven't created one already. The third line, "Validation:: true", is for validation of the expression. If true, it means that mozmill verified that the generated expression shown above worked and found the same object that you are currently inspecting.</p>
<h2>Causing Your Own Failures</h2>
<p style="text-align: left;"><br>
The main difference between automated functional tests and automated unittests is that unittests are not all synchronous. If a call in a unit test fails, that does not necessarily mean the next call should fail.  This is why unittest APIs don't usually cause exceptions and instead tend to trigger silent framework failures.<br>
<br>
Functional tests tend to by highly synchronous. Since tests tend to simulate user interactions any failure along the way is almost assured to cause the rest of the steps to fail. Failures in MozMill are triggered by normal JavaScript exceptions. If a failure happens inside the API, an exception is thrown. This means that if you'd like to assert something in your code you may also simply call your own exception:</p>
<pre>var testSomething () {</pre>
<pre>   controller.open('<a class="moz-txt-link-freetext" href="http://www.google.com/">http://www.google.com</a>');</pre>
<pre>   controller.waitForPageLoad();</pre>
<pre>   if (controller.window.document.title != 'Google') {</pre>
<pre>     throw 'Title does not match "Google" !=  ' + controller.window.document.title;</pre>
<pre>   }</pre>
<pre>}</pre>
<p> </p>
Revert to this revision