Extending the Selenium IDE by Adding Commands to the Context Menu – Part 2

Improving the ‘It works’ commandBuilder

The improved 'It works' command builder in action

This is the second part of the series of posts on extending the Selenium IDE using Command Builders. Command builders allow you to add items to the Selenium IDE context menu in Firefox providing convenient shortcuts to common and useful items. If you have not read the first part, I would highly recommend reading it now.

I prefer objects. I prefer objects with functions and passing these functions where required. I have rewritten the ‘It works’ commandBuilder to make it part of an object so it can be extended easily. Here is the new version of the ‘It works’ commandBuilder.

var MyCommandBuilder = {
    generateMenuItem : function (window) {
        return { command : "echo", target: "It works!!!" };
    }
};
CommandBuilders.add('action', MyCommandBuilder.generateMenuItem);

Instead of defining the builder function inline as an anonymous function, an object is created with the builder function as a method named generateMenuItem. This method is registered as the builder function. I think this is a much cleaner approach. To those new to JavaScript, the advantages may not be immediately obvious. As we proceed further, you should see some value in this approach, but you can decide for yourself which approach works better for you. Put the above code in a file named itWorksCommandBuilder2.js and load it in Selenium IDE as you did in the first part of this series. While you are in the Options dialog, also remove the old ‘It works’ commandBuilder. Restart the Selenium IDE and try out the context menu. You should still see the “echo It works” on the context menu.

Now, we are going to use some data from outside the builder function. Keeping this kind of data together with the function that uses it is part of good design principles. This was the primary reason for making the builder function a part of an object. We are going to start with a very simple example, viz., simply accessing a field named unique and showing it in the echo command. The field is also incremented every time it is accessed. Here is the code.

var MyCommandBuilder = {
    generateMenuItem : function (window) {
        return { command : "echo", target: "It works [" +
            MyCommandBuilder.unique++ + "]!!!"
        };
    },

    unique : 42
};
CommandBuilders.add('action', MyCommandBuilder.generateMenuItem);

Now every time you right click on an element, a new flavour of the It works! command will be shown. Put the above code in a file named itWorksCommandBuilder3.js load it in the Selenium IDE through the Options dialog as you have already done before. Don’t forget to remove the old command builder as well as restart the Selenium IDE. Now go to the Show All Available Commands sub-menu. It really works! :-) How many time is this function called? I leave the answer to this question as an excercise for you. This answer might save you some headaches in the future..

We have already seen that the command object can have two fields, viz., the command field and the target field. It can also have some more fields. A command of type “action” can also have a value field and a disabled field. A Selenese command is usually divided into three parts, viz., the command, the target and the value parts. The three fields on the command object correspond to these. The fourth field, i.e. disabled field is a boolean field that takes values true or false determines if the menu item is shown as a normal menu item or a disabled menu item, usually greyed out. If you leave out the disabled field of the command object, it will be treated as having false and a normal menu item will be shown as we have seen earlier.

Now, lets add some dynamism! (Is there such a word?) We will create one more version of the ‘It works’ command builder that disables the menu item for every odd version of the command.

var MyCommandBuilder = {
    generateMenuItem : function (window) {
        return {
   disabled: ((MyCommandBuilder.unique % 2) == 1),
   command : "echo",
   target: "It works [" + MyCommandBuilder.unique++ + "]!!!"
 };
    },

    unique : 42
};
CommandBuilders.add('action', MyCommandBuilder.generateMenuItem);

This version is almost the same as the earlier one. The difference is that now there is also a disabled field in the returned object. The value of this disabled field depends on whether the value of the unique field is odd or even. So, put the above code in a file named itWorksCommandBuilder4.js and as you have done before, load it in the Selenium IDE, remove the old one and restart the Selenium IDE.

Can you feel the power already?

This entry was posted in Selenium Internals and tagged , , , , , , , . Bookmark the permalink.

22 Responses to Extending the Selenium IDE by Adding Commands to the Context Menu – Part 2

  1. Ann says:

    Hi, What is the fate of the Selenium IDE for Firefox? It works great in v3.6 but I can’t find anything on the roadmap for Firefox 4, and it fails to install if one tries it. Your blogs show up at the top of google for issues of this sort (congratulations? ) so I wonder whether you can clarify. Is the Selenium IDE considered obsolete? I made a lot of use of the simple record and playback mechanism. I want to uninstall Firefox 3.6 and just use Firefox 4 but that’s not a viable option for me until the Selenium IDE plug-in works for v4 or …. something else more difficult. I don’t see the IDE available for some other browser. It seems everything else is much more complicated to install and configure. But maybe I just missed the memo and hope you have time to clarify. THANK YOU.

  2. Christine says:

    Hi Samit,

    I can apply the CommandBuilder so that it appears in the context menu and when clicked, it shows the recorded command but what is the next step to making it function. Do you have additional resources or examples that applies the command? I want to store the item I highlighted and right clicked and extract it to a database. Did you have any suggestions on how I can approach this?

    thanks,
    Christine

    • Samit Badle says:

      Hi Christine,

      You might want to take a look at the examples from my workshop/presentation from Selenium Conference 2011. The are linked from this blog post http://blog.reallysimplethoughts.com/2011/04/07/awesome-selenium-conference-2011/

      It solves exactly what you are trying to do. Let me know if it helps.

      Cheers,
      Samit

      • Christine says:

        Hi Samit,

        Thank you very much for the help. I was able to run your example and this time, it executed the custom command (commands in this case). I was wondering if you could help me understand the blocks of code so that I will be able to customize it to storeText, storeTables, Images,or anything I right click.
        —–Does this block set browser preferences? Will I need this for my extension to work properly?

        this.branch = Components.classes[“@mozilla.org/preferences-service;1”].getService(Components.interfaces.nsIPrefService).getBranch(“extensions.selenium-ide.plugin.be-efficient.”);
        this.branch.QueryInterface(Components.interfaces.nsIPrefBranch2);
        this.branch.addObserver(“”, this, false);
        this.setEnabled(this.branch.getBoolPref(“enabled”));
        }

        ——-what does this block do?
        BeEfficientExt.prototype.observe = function(subject, topic, data) {
        if (topic == “nsPref:changed”) {
        this.setEnabled(this.branch.getBoolPref(“enabled”));
        }
        };

        —–also not sure here
        BeEfficientExt.prototype.setEnabled = function(newValue) {
        this.enabled = newValue;
        document.getElementById(“beefficient-button”).checked = newValue;
        };

        ——and here either
        BeEfficientExt.prototype.toggleEnabled = function() {
        this.branch.setBoolPref(“enabled”, !this.enabled);
        };

        ——does this just create the function?
        this.editor.beefficient = new BeEfficientExt();

        I really want to learn selenium and if you can refer me to some other resources, that would be great as well. Also what is the util command used for and what does it do?

        Thank you again.

        —Christine

        • Samit Badle says:

          Hi Christine,

          The first four blocks are purely management related to allow you to turn on and off the extension and can be safely skipped (with changes to not use the enabled stuff elsewhere).

          The new BeEfficient() creates the object. I created the util command type to serve the purpose you want to use it for. My blog is the only source that I know of. There were some discussions on it on the developer discussion group and in some of the issues on google code. I am a bit short of time, but I hope to continue my command builder series soon. You might also want to learn a bit about Firefox addon development using the links I have provided in my slides.

          Cheers,
          Samit

  3. Christine says:

    Samit,

    Thanks for the quick reply. Your Command Builder tutorials are very helpful and thank you for sharing your knowledge with your blog. I will continue to follow your blog and Command Builder series and I hope to learn more about Selenium as well.

    Thanks again,
    Christine

  4. Christine says:

    Hi Samit,

    I was getting an error when I replayed my test case for storing text. I was wondering if you can point out what I was doing incorrectly. This is how my execute command looks like:

    KDadExt.prototype.executeCommand = function(utilCommand) {
    var cmdRa = new Array();
    var document = utilCommand.window.document;
    if (document) {
    cmdRa.push({
    command: ‘storeText’,
    target: utilCommand.recorder.findLocators, value.match is not a function, fileName -> chrome://selenium-ide/content/selenium-core/scripts/selenium-api.js, lineNumber -> 2528, stack -> ((function (element) {return this.locatorBuilders.buildAll(element);}))@chrome://selenium-ide/content/selenium-core/scripts/selenium-api.js:2528 ()@chrome://selenium-ide/content/selenium-core/scripts/selenium-executionloop.js:109 (-5)@chrome://selenium-ide/content/selenium-core/scripts/selenium-executionloop.js:78 (-5)@chrome://selenium-ide/content/selenium-core/scripts/htmlutils.js:60 , name -> TypeError

    Thanks,
    Christine

  5. Christine says:

    Sorry,

    That last post was missing part of my execute command.

    if (document) {
    cmdRa.push({
    command: ‘storeText’
    target: utilCommand.recorder.findLocators,
    value:
    });
    }
    return cmdRa;

    Thanks,
    Christine

    • Samit Badle says:

      Hi Christine,

      The ‘value: ‘ line is missing a real value. These are key, value pairs. For example, in “command: ‘storeText’”, command is the key and value is ‘storeText’. The value line seems to be missing this part.

      Perhaps changing the line to the following will help
      value: “”

      Cheers,
      Samit

  6. Christine says:

    Hi Samit,

    Thanks again for replying and helping me. I may have specified the target and value incorrectly because I get errors for the code below. Se-IDE records the test as |storeText| |function(element){ return this.locatorBuilders.buildAll(element);}| |”empty for value”| and when replayed, throws [error] Unexpected Exception, message: value.match is not a function.

    command: ‘storeText’,
    target: utilCommand.recorder.findLocators,
    value: “”

    I thought that the target and value I specified was incorrect so I also tried the code below and Se-IDE doesn’t record it at all.

    command: ‘storeText’,
    target: utilCommand.recorder.findLocators(element),
    value: exactMatchPattern(getText)

    I know that the prepare command took care of assigning the values for the recorder and element but I’m not sure why when I use it in the execute command, I get an error or it doesn’t execute at all. I understand that the target is where to find the element and in this case its where we clicked on the page and the value has to match what we clicked but I think that my syntax is incorrect. Any thoughts or what I am missing or doing wrong?

    Christine

  7. Christine says:

    Hi Samit,

    I have still had no luck in my script to store text that I have right clicked on. I am getting the same exception (from last question 08-19) and I’m not sure why the target specifies:
    function(element){ return this.locatorBuilders.buildAll(element);}
    for the target when recorded to the test. Will you help me understand how to get any store/storeText/storeAttribute/etc. to work with CommandBuilders?

    Thanks,
    Christine

    • Samit Badle says:

      Hi Christine,

      The line creates the list of locators for the target part of the command. Why don’t you email me the full code so that I can take a quick look at it. I hope the next couple of instalments on the command builders series would make things clear. I need to balance the free time I get on several things, but I hope to get to them in a few weeks.

      Cheers,
      Samit

  8. Christine says:

    Hi Samit,

    Couldn’t find your email so my code is below.

    function TestExt() {
    CommandBuilders.add(‘util’, this.prepareCommand, this.executeCommand);

    }

    TestExt.prototype.prepareCommand = function(window) {
    var cmd = {
    command : “Extension Test!”,
    disabled : false,
    window : window,
    recorder : this.getRecorder(window),
    element : this.getRecorder(window).clickedElement
    };
    return cmd;
    };

    TestExt.prototype.executeCommand = function(utilCommand) {
    var cmdRa = new Array();
    var document = utilCommand.window.document;
    if (document) {
    cmdRa.push({
    command: ‘storeText’,
    target: utilCommand.recorder.findLocators,
    value: “”
    });
    cmdRa.push({ command : “echo”, target: “It works!!” });
    }
    return cmdRa;
    };

    this.editor.testext = new TestExt();

    Thanks for taking a look at this.
    –Christine

    • Samit Badle says:

      Hi Christine,

      Take a look at these two issues, they have a couple of examples.
      http://code.google.com/p/selenium/issues/detail?id=1073
      http://code.google.com/p/selenium/issues/detail?id=442

      I have not looked closely at the code, but the line
      target: utilCommand.recorder.findLocators,
      is incorrect. findLocators function requires a parameter which should the element that you are trying to find the locator for. Try changing it to the following:-
      target: utilCommand.recorder.findLocators(utilCommand.element),

      This is a JavaScript syntax related issue not Selenium IDE related. It might help to take a quick look at any of the numerous JavaScript books to speed things up.

      Cheers,
      Samit

  9. Christine says:

    Samit,

    Thank you so much!! I am now able to store Text with the extension. I also want to try storing other page elements such as images, links, etc. Do you know if I can use the utilCommand in a conditional statement?

    Something like:
    KDadExt.prototype.executeCommand = function(utilCommand) {
    var cmdRa = new Array();
    var document = utilCommand.window.document;
    if (document) {
    //if page element clicked is text, store the text
    cmdRa.push({
    command: ‘storeText’,
    target: utilCommand.recorder.findLocators(utilCommand.element),
    value: exactMatchPattern(getText(utilCommand.element))
    });
    //if page element clicked is an image, store the image
    some code
    //if page element clicked is a link, store the link
    some code
    //….other page elements (table content, buttons, etc..)
    }
    return cmdRa;
    };

    Do you know if this is possible or how I can do this?

    Thanks,
    Christine

  10. Christine says:

    Hi Samit,

    I was wondering if you can help with some selenium/command builder syntax. My script is using the storeAttribute command to store an image, link, document, or media file. I can store text with:

    cmdRa.push({ command: ‘storeText’,
    target: utilCommand.recorder.findLocators(utilCommand.element),
    value: exactMatchPattern(getText(utilCommand.element)) });

    But when I use the same format with storeAttribute, it will not record to Selenium at all.

    cmdRa.push({ command: ‘storeText’,
    target: utilCommand.recorder.findLocators(utilCommand.element),
    value: exactMatchPattern(getText(utilCommand.element)) });

    Can you tell me what I am doing wrong with my syntax??

    Thanks,
    Christine Bowyer

    • Christine says:

      Sorry, the format I used for storeAttribute is

      cmdRa.push({ command: ‘storeAttribute’,
      target: utilCommand.recorder.findLocators(utilCommand.element),
      value: exactMatchPattern(getAttribute(utilCommand.element)) });

      This does not record in SE-IDE.

      Christine

      • Samit Badle says:

        Hi Christine,

        Can you also give me the error that you get? From a quick look, it seems that it should record fine, but not playback as expected.

        Cheers,
        Samit

  11. Christine says:

    Samit,

    I don’t get an error from using:

    cmdRa.push({ command: ‘storeAttribute’,
    target: utilCommand.recorder.findLocators(utilCommand.element),
    value: exactMatchPattern(getAttribute(utilCommand.element)) });

    Selenium IDE does not record anything(images, links) I right click on, so the custom command is not recorded and the test is empty.

    Christine

    • Samit Badle says:

      Hi Christine,

      That is normally an indication that there was an error. The best way to proceed is to use a try catch block in the function and alert the error. It would be nice to grab a book to Javascript. There are several good ones that would help you speed up your work. Once you send me the error message, I can help you further. I suspect that the getAttribute function call is incorrect.

      Cheers,
      Samit

Leave a Reply

Your email address will not be published. Required fields are marked *