Please note, this is a STATIC archive of website developer.mozilla.org from November 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

Extending the MozMill element hierarchy

MozMill uses an element hierarchy for wrapping and manipulating DOM elements.  The base class is called MozMillElement.  All element types inherit from this class.  Mozmill provides some basic subclasses that implement additional functionality.  For example, the MozMillRadio class adds some methods for dealing specifically with radio buttons.  The MozMillDropList class adds methods for dealing with drop lists (such as those created using select and menulist).  While these provided subclasses provide the basic functionality needed to interact with the most common DOM elements, it may not be enough for your needs.  When this is the case you need to extend the default hierarchy.  See here for the actual implementation of the hierarchy.

Creating a new element subclass

The first thing you need to do is actually create the subclass.  In this example we will create a new subclass for a "link" element.

Prototypal inheritance

The first step is to create a class that inherits from the MozMillElement base class.  MozMill uses JavaScript's built-in prototypal inheritance model.  For more on this see this page.  For now though, let's just demonstrate by example:

MozMillLink.prototype = new MozMillElement();             // This makes the Link class inherit from the MozMillElement base class
MozMillLink.prototype.parent = MozMillElement.prototype;  // This is a convenience shortcut that allows to use 'this.parent' to refer to the MozMillElement properties
MozMillLink.prototype.constructor = MozMillLink;          // When we did the first line above, MozMillLink's constructor got overwritten by the parent's, change it back

// This is the new subclass' constructor
function MozMillLink (locatorType, locator, args){
  this.parent.constructor.call(this, locatorType, locator, args);
  /* do subclass specific constructor things here */
}

We will look at the parameters in a bit, but this is the basic inheritance pattern that every subclass must have.  Don't worry if you don't understand exactly what is going on here, I'll explain more as the tutorial progresses.  Note that at this point you can refer to any properties of the parent class using the this keyword.  For example, MozMillElement has a property called element which is the unwrapped DOM node.  You can reference this property by calling this.element anywhere inside the scope of your subclass.  If your subclass overrides a method in the parent class (e.g click()) but you want to invoke the implementation in the parent class rather than your subclass' implementation you can do this:

this.click();		       // calls the subclass' implementation of click
this.parent.click.call(this);  // calls the parent class' implementation of click

Constructor parameters

There are two required parameters that all subclasses need as well as several optional ones.  In addition, you can also make your own parameters that are specific to your subclass however you want.  Note that this section is only here for your reference.  As long as you aren't doing anything special with parameters being passed in, you probably don't need to change anything from the above code snippet.  Below is a description of the parameters:

locatorType
A string representing how the element will be searched for. This can be "Elem", "ID", "Link", "Selector", "Name", "XPath", or "Lookup".
locator
The string or object to use to actually locate the element. For example, if the locatorType is "ID", you would pass in the element's ID here.
args
An object containing additional optional arguments. Valid properties include "element", "owner", and "document".
Note: To add parameters, you can either parse them from the args object or simply add more to the end of the constructor.

The isType() static method

All MozMillElement subclasses should implement a static method called isType().  This method returns true if the passed in DOM element is this type of element and false otherwise.  This method is what stops us from instantiating a button element as a textbox, for example.  If you do not implement this method, it will return true by default and any type of DOM element can be instantiated as a link.  This can lead to much confusion when writing tests.  This is an example of what the isType() method might look like for our Link subclass:

MozMillLink.isType = function(node) {
  if (node.localName.toLowerCase() == "a") {  // if node is a link element
    return true;
  }
  return false;
}

Putting it all together

Now that you have your inheritance and your isType() method setup, all you need to do is actually write the implementation of your subclass and hook it into MozMill.  To hook your subclass into MozMill there are three things you'll need to do.  The first is to import mozelement.js from a resource: url.  The second is to push your subclass to the list of subclasses in mozelement.  Finally you'll need to export whatever variables, methods, and classes that you wish the test to have access to.  The code below is a final example of what your shared module might look like (using the Link example above).  You should save this in a .js file in a directory somewhere relative to where your tests live.  Note that you can put as many subclasses in the same file as you want.  For this example, lets assume we've saved this into a file called link.js in the same directory as the test that will be using it.

// import mozelement.js
Components.utils.import('resource://mozmill/modules/mozelement.js');
var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);  // Also import frame.js so we can report the same way mozmill does
this.subclasses.push(MozMillLink);  // push your subclass onto mozelements subclass list

// Subclass implementation
MozMillLink.prototype = new MozMillElement();             // This makes the Link class inherit from the MozMillElement base class
MozMillLink.prototype.parent = MozMillElement.prototype;  // This is a convenience shortcut that allows to use 'this.parent' to refer to the MozMillElement properties
MozMillLink.prototype.constructor = MozMillLink;          // When we did the first line above, MozMillLink's constructor got overwritten by the parent's, change it back

// This is the new subclass' constructor
function MozMillLink (locatorType, locator, args){
  this.parent.constructor.call(this, locatorType, locator, args);
  /* do subclass specific constructor things here */
}

// returns true if node is a link type
MozMillLink.isType = function(node) {
  if (node.localName.toLowerCase() == "a") {  // if node is a link element
    return true;
  }
  return false;
}

// Returns the location of the link
MozMillLink.prototype.getLinkLocation = function() {
  if (!this.element) {
    throw new Error("Element: " + this.getInfo() + ", not found!");
  }
  frame.events.pass({'function': 'MozMillLink.getLinkLocation()'});
  return this.element.href;
}

// export whatever variables, functions or classes you want your tests to have access to
exports.MozMillLink = MozMillLink;
 

While this implementation of MozMillLink is very basic and not very useful, it demonstrates how to put it all together.  Below is an example test file that demonstrates how this new subclass can be used.

var link = require("link");  // pass in the relative path to your subclass file (in this case 'link.js')

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

var testLink = function() {
  // This uses explicit instantiation and will be lazy loaded
  // It is up to you to make sure you instantiate only links as Link elements
  var elem = new link.MozMillLink('ID', 'linkID');
  controller.window.alert(elem.getLinkLocation());
  link.click(); // click is a property of the parent
  
  // This uses implicit instantiation and is safer (type will be determined automatically by the isType() method)
  elem = findElement.ID('linkID');
  controller.window.alert(elem.getLinkLocation());
  elem.click();
} 

That's basically all there is to it.  See the MozMill Element Object for a list of all subclasses and their methods in MozMill core.

Inheriting from a Subclass

You can inherit from any subclass, not just MozMillElement.  The process is the same as above except replace 'MozMillElement' with the name of the subclass you are inheriting from.  The only thing to keep in mind here is that when Mozmill is performing implicit instantiation it will return the first class in the subclasses array whose 'isType()' method returns true.  Mozmill will check the entire subclasses array before falling back to MozMillElement, but if you are subclassing another subclass it is important to make sure it appears in the array first.

For example, lets pretend we have a class called MyClass1 which inherits from MozMillRadio and second class called MyClass2 which inherits from MyClass1.  Instead of simply pushing these to the end of the array you probably want to do this:

 var mySubclasses = [MyClass2, MyClass1];
 this.subclasses = mySubclasses.concat(this.subclasses);

This way when performing implicit instantiation, mozmill will first check to see if the element is of type MyClass2, failing that it will check MyClass1, failing that it will check MozMillRadio and failing all of that it will return MozMillElement.

Document Tags and Contributors

Tags: 
 Last updated by: teoli,