Extending the MozMill element hierarchy

  • Revision slug: Mozmill/Mozmill_Element_Object/Extending_the_MozMill_element_hierarchy
  • Revision title: Extending the MozMill element hierarchy
  • Revision id: 88690
  • Created:
  • Creator: ahal
  • Is current revision? No
  • Comment 36 words added

Revision Content

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 selects and menulists).  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 https://github.com/mozautomation/mozmill/blob/master/mozmill/mozmill/extension/resource/modules/mozelement.js 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, lets 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 subclasses constructor
function MozMillLink (locatorType, locator, args){
  this.parent.call(this, locatorType, locator, args);
  /* do subclass specific constructor things here */
}

 I'll talk about 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.

Constructor Parameters

There are two required parameters that all subclasses require 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.  Can be 'Elem', 'ID', 'Link', 'Selector', 'Name', 'XPath', '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 ID of the element 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.  Below is an example implementation that might be what a finished subclass looks like.

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 subclasses constructor
function MozMillLink (locatorType, locator, args){
  this.parent.call(this, locatorType, locator, args);
  this.href = this.element.href;
}

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

MozMillLink.prototype.getLinkLocation = function() {
  return this.href;
}

 

While this implementation is very basic and not very useful, it demonstrates how to put it all together.  You might use this class like the example below:

// This uses lazy loading
var link = new MozMillLink('ID', 'linkID');
controller.window.alert(link.getLinkLocation());
link.click(); // click is a property of the parent

// This uses explicit instantiation
var link = controller.window.document.getElementById('linkID');
if (MozMillLink.isType(link)) {
  link = new MozMillLink('ID', 'linkID', {'element':link})
}
controller.window.alert(link.getLinkLocation());
link.click();

In the next section I talk about how to hook all this up into Mozmill so that it can be used by your tests.

 

Hooking your Subclass into Mozmill

TODO: Describe the process of writing a shared module - https://developer.mozilla.org/en/Mozmill_Tests/Shared_Modules

Revision Source

<p>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 selects and menulists).  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 <a class=" link-https" href="https://github.com/mozautomation/mozmill/blob/master/mozmill/mozmill/extension/resource/modules/mozelement.js" title="https://github.com/mozautomation/mozmill/blob/master/mozmill/mozmill/extension/resource/modules/mozelement.js">https://github.com/mozautomation/mozmill/blob/master/mozmill/mozmill/extension/resource/modules/mozelement.js</a> for the actual implementation of the hierarchy.</p>
<p> </p>
<h3>Creating a new Element Subclass</h3>
<p>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.</p>
<h4>Prototypal Inheritance</h4>
<p>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 <a href="/en/JavaScript/Guide/Inheritance_constructor_prototype" title="JavaScript/Guide/Inheritance constructor prototype">this page.</a>  For now though, lets just demonstrate by example:</p>
<pre class="brush: js">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 subclasses constructor
function MozMillLink (locatorType, locator, args){
  this.parent.call(this, locatorType, locator, args);
  /* do subclass specific constructor things here */
}
</pre>
<p> I'll talk about 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.</p>
<h4>Constructor Parameters</h4>
<p>There are two required parameters that all subclasses require 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:</p>
<pre><strong>locatorType -</strong> A string representing how the element will be searched for.  Can be 'Elem', 'ID', 'Link', 'Selector', 'Name', 'XPath', 'Lookup'.
<strong>locator -</strong> The string or object to use to actually locate the element.  For example if the locatorType is 'ID' you would pass in the ID of the element here.
<strong>args -</strong> An object containing additional optional arguments. Valid properties include 'element', 'owner' and 'document'.
</pre>
<p>Note, to add parameters you can either parse them from the args object or simply add more to the end of the constructor.</p>
<h4>The isType() Static Method</h4>
<p>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:</p>
<pre class="brush: js">MozMillLink.isType = function(node) {
  if (node.localName.toLowerCase() == "a") {  // if node is a link element
    return true;
  }
  return false;
}
</pre>
<h4>Putting it all Together</h4>
<p>Now that you have your inheritance and your isType method setup, 'all' you need to do is actually write the implementation of your subclass.  Below is an example implementation that might be what a finished subclass looks like.</p>
<pre class="brush: js">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 subclasses constructor
function MozMillLink (locatorType, locator, args){
  this.parent.call(this, locatorType, locator, args);
  this.href = this.element.href;
}

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

MozMillLink.prototype.getLinkLocation = function() {
  return this.href;
}
</pre>
<p> </p>
<p>While this implementation is very basic and not very useful, it demonstrates how to put it all together.  You might use this class like the example below:</p>
<pre>// This uses lazy loading
var link = new MozMillLink('ID', 'linkID');
controller.window.alert(link.getLinkLocation());
link.click(); // click is a property of the parent

// This uses explicit instantiation
var link = controller.window.document.getElementById('linkID');
if (MozMillLink.isType(link)) {
  link = new MozMillLink('ID', 'linkID', {'element':link})
}
controller.window.alert(link.getLinkLocation());
link.click();
</pre>
<p>In the next section I talk about how to hook all this up into Mozmill so that it can be used by your tests.</p>
<p> </p>
<h3>Hooking your Subclass into Mozmill</h3>
<div class="note">TODO: Describe the process of writing a shared module - <a href="/en/Mozmill_Tests/Shared_Modules" title="https://developer.mozilla.org/en/Mozmill_Tests/Shared_Modules">https://developer.mozilla.org/en/Mozmill_Tests/Shared_Modules</a></div>
Revert to this revision