This part of the tutorial is one of the two major purposes of the plugin system for Selenium-IDE; how to add custom methods to the drop-down. (The other major task is adding a custom formatter. That is next on the docket.) This aspect of the plugin I expect will be used the most. Imagine the Selenium-Flex API as just a plugin for Se-IDE which magically extends the API on install. Or a commercial entity’s custom izations automatically installed. This will, I hope, hope the flood gates for adoption (and extension) of Se-IDE the way Se-Core/Se-RC has been.

Before you try this at home though, this relies on functionality that literally was only checked in 10 minutes ago so trying it with the current public release is not going to work. You have to check it out from the Google Code project and you should modify your install.rdf to require (at least) version 1.0.4-SNAPSHOT of Se-IDE.

<requires><description><id>{a6fd85ed-e919-4a43-a5af-8da18bda539f}</id><minversion>1.0.4-SNAPSHOT</minversion><maxversion>1.*</maxversion></description></requires>

Now, to the actual extension of the API.

Like everything with the Se-IDE plugin system, you have to overlay something in the existing Se-IDE. In this case we are going to overlay one of the main xul files with one that will load our extension so add this to the chrome.manifest.

# custom extension(s)
overlay chrome://selenium-ide/content/selenium-ide-overlay.xul chrome://preflight/content/extensions/extension-loader.xul

For this example, I’m going to add the RandomString functionality that was contributed to the OpenQA wiki. Random data is super important to well designed automation. I’ve copied that whole file into my project under content/extensions/extension-random.js. I’m not going to outline it here, but trust me, it is the same as on the wiki.

What does need to be shown here is the overlay that loads the extension.

<?xml version="1.0"??><?xml-stylesheet href="chrome://global/skin/" type="text/css"??><overlay id="preflight_extension_loader_overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"><script src="chrome://selenium-ide/content/api.js" type="application/x-javascript"></script><script src="chrome://preflight/content/preflight.js" type="application/x-javascript"></script><script type="application/javascript">
        ide_api = new API();
        ide_api.addPluginProvidedUserExtension("chrome://preflight/content/extensions/extension-random.js");
    </script></overlay>

I’m not sure that I like the <html:script> part, but it works, which is good enough for now. And is pretty clear about its intent which is something I like as well. So what does it do? Well, two distinct things, but we’ll worry about loading it first and come back to the second.

  • Includes the Se-IDE API into this XUL’s scope
  • Creates an instance of the API
  • Informs Se-IDE of the extension we want to load using the addPluginProvidedUserExtension method. (If you have more than one extension, just make multiple calls to it.)

Recompile your plugin and restart Firefox and marvel in your new methods being in the Se-IDE command dropdown.

But we’re not done yet. A lot of people might say this is mission accomplished, but I come from the testing space where I notice shortcuts and incompletions. This means the plugin needs to clean up after itself. Which of course can’t be easy, now can it? A plugin can have multiple states: installed and enabled, installed and disabled, and of course, uninstalled. And since we are adding our extension into Se-IDE, when we remove or disable our plugin we need to tell Se-IDE that its added ‘stuff’ isn’t available.

To accomplish this we need to tap deep into Firefox magic and install ‘observers’ for events that happen within the browser such as ‘uninstall’ and ‘disable’. I won’t go into detail on this, but you can read about it more here. I also can’t lay claim to thinking out this next chunk of code. It was taken more-or-less line-for-line from here (credit given when credit due).

Back to cleaning up after ourselves.

Notice in the overlay the inclusion of a preflight.js? This is where I decided to put my observer code.

const PREFLIGHT_ID = "preflight@adam.goucher";

function initializePreflightObserver() {
    preflightObserver.register();
}

var preflightObserver = {
    _uninstall : false,
    observe : function(subject, topic, data) {
        if (topic == "em-action-requested") {
            subject.QueryInterface(Components.interfaces.nsIUpdateItem);
            if (subject.id == PREFLIGHT_ID) {
                if (data == "item-uninstalled") {
                    this._uninstall = true;
                } else if (data == "item-disabled") {
                    this._uninstall = true;
                } else if (data == "item-cancel-action") {
                    this._uninstall = false;
                }
            }
        } else if (topic == "quit-application-granted") {
            if (this._uninstall) {
            	// your uninstall stuff goes here
            	
            	// this section removes the extension we added
                var branch = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("extensions.selenium-ide.");
                var current_ppue = branch.getCharPref("pluginProvidedUserExtensions");
                if (typeof current_ppue != "undefined") {
                	// need one 'if' block like below for each one you added
                    if (current_ppue.search("chrome://preflight/content/extensions/extension-random.js") != -1) {
                        branch.setCharPref("pluginProvidedUserExtensions", current_ppue.replace("chrome://preflight/content/extensions/extension-random.js", ""));
                    }
                }
            }
            this.unregister();
        }
    },
    register : function() {
        var observerService = Components.classes["@mozilla.org/observer-service;1"].     getService(Components.interfaces.nsIObserverService);
        observerService.addObserver(this, "em-action-requested", false);
        observerService.addObserver(this, "quit-application-granted", false);
    },
    unregister : function() {
        var observerService = Components.classes["@mozilla.org/observer-service;1"].      getService(Components.interfaces.nsIObserverService);
        observerService.removeObserver(this,"em-action-requested");
        observerService.removeObserver(this, "quit-application-granted");
    }
}

window.addEventListener("load", initializePreflightObserver, false);

You can pretty much copy-and-paste the above code changing only things that say ‘preflight’ and have it work. Well, except for the section in the middle that I somewhat annotated. If you registered an extension, you need to remove it. If you have any other bits to cleanup as well, they should go there too.

(I couldn’t put this logic in the Se-IDE itself as there was some wierd circular dependencies around when observers get added / called so you just need to do it or you will give your users an annoying pop-up.)

From here, its just a matter of recompile and restart and you have successfully extended the API that ships with Se-IDE without user intervention.

It’s not as elegant as I had hoped (regarding the removal) but it works and is still relatively simple. Now lets start getting those ‘just add this to the user extension box’ additions to Se-IDE wrapped in plugins as registered somewhere. (I guess one of the next steps will be to make a page on Selenium HQ somewhere for these.)