Click here to Skip to main content
15,885,546 members
Articles / Programming Languages / Javascript

Create Custom Selenium IDE Export to WebDriver

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
14 Dec 2015Ms-PL3 min read 11.6K   2  
Tutorial how to create custom Selenium IDE export to WebDriver tests. JavaScript code examples and explanations how to customize it for your solution.The post Create Custom Selenium IDE Export to WebDriver appeared first on Automate The Planet.

Introduction

If you are in the “test automation business”, most probably you are familiar with the Selenium IDE. Selenium provides a record/playback tool for authoring tests without learning a test scripting language (Selenium IDE). It gives you the ability to export your tests to number of popular programming languages, including Java, C#, Groovy, Perl, PHP, Python and Ruby. However, the built-in formatters cannot be customized and usually are generating not very useful code (not following your framework design). In this article, I am going to share with you how you can extend the Selenium IDE and add your custom Selenium IDE Export format template.

Create Custom Selenium IDE Export

Use Firefox Extensions

There are tons of already created Selenium IDE export plug-ins you can always see if some of them are working for you.

Selenium IDE Export Firefox Extension

Allows users to take advantage of WebDriver without having to modify their tests. You can download it from its official add-on page.

Create Custom Selenium IDE Export to WebDriver

You can add any format you like by writing JavaScript code. Follow the steps below:

  • Open Firefox and Selenium IDE
  • Open Options dialog
  • Click Formats tab
  • Create a new format by clicking “Add” button
Selenium IDE Formats Tab

­There are three empty functions- parse, format, formatCommands.

Parse Function

The parse function is almost opposite of format. This function parses the String and updates test case.

JavaScript
function parse(testCase, source) {
  var doc = source;
  var commands = [];
  while (doc.length > 0) {
    var line = /(.*)(\r\n|[\r\n])?/.exec(doc);
    var array = line[1].split(/,/);
    if (array.length >= 3) {
      var command = new Command();
      command.command = array[0]; command.target = array[1];
      command.value = array[2]; commands.push(command);
    }
    doc = doc.substr(line[0].length);
  }
  testCase.setCommands(commands);
}

FormatCommands Function

The formatCommands function is similar to format function. You can add additional logic to it. In my code, format will call formatCommands.

Format Function

The “format” function creates an array of commands that contain command object (Command, Target, Value).

JavaScript
function format(testCase, name) {
  var result = '';
  var commands = testCase.commands;
  for (var i = 0; i < commands.length; i++) {
    var command = commands[i];
    if (command.type == 'command') {
          result += command.command + ',' + command.target + ',' +     command.value + "\n";
    }
  }
  return result;    
}

Create Complete Selenium IDE Format to WebDriver C# Framework

If we have the following sample base test class, our goal might be to export our Selenium IDE tests to C# code that uses the methods provided by this base test class.

C#
public class BaseWebDriverTest
{
    private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

    public void TestInit(IWebDriver driver, string baseUrl, int timeOut)
    {
        this.Driver = driver;
        this.BaseUrl = baseUrl;
        this.Wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeOut));
        this.TimeOut = timeOut;
    }

    public IWebDriver Driver { get; set; }

    public string BaseUrl { get; set; }

    public WebDriverWait Wait { get; set; }

    public int TimeOut { get; set; }

    public IWebElement GetElement(By by)
    {
        IWebElement result = null;
        try
        {
            result = this.Wait.Until(x => x.FindElement(by));
        }
        catch (TimeoutException ex)
        {
            log.Error(ex.Message);
            throw new NoSuchElementException(by, this, ex);
        }

        return result;
    }

    public bool IsElementPresent(By by)
    {
        try
        {
            this.Driver.FindElement(by);
            return true;
        }
        catch (NoSuchElementException ex)
        {
            log.Error(ex.Message);
            return false;
        }
    }

    public void WaitForElementPresent(By by)
    {
        this.GetElement(by);
    }
}

Below, you can find the full source code of the new Selenium IDE Export format.

JavaScript
var doc = '';
var newLine = "\n";
var tab = "\t";

this.configForm = '<description>Variable for Selenium instance</description>' +
                  '<textbox id="options_receiver" value="this.Browser"/>' +
                  '<description>MethodName</description>' +
                  '<textbox id="options_methodName" value="TESTMETHODNAME" />' +
                  '<description>Base URL</description>' +
                  '<textbox id="options_baseUrl" value="http://home.telerik.com"/>';

this.name = "C# Anton (Driver)";
this.testcaseExtension = ".cs";
this.suiteExtension = ".cs";
this.driver = true;

this.options = {
    receiver: "this.Browser",
    methodName: "TestMethodName",
    baseUrl: "http://automatetheplanet.com/",
    header: 'public void TestMethod()\n{\n',
    footer:
    '}\n' + newLine + newLine,
    defaultExtension: "cs"
};

function parse(testCase, source) {
    doc = source;

    var commands = [];
    var sep = {
        comma: ",",
        tab: "\t"
    };
    var count = 0;
    while (doc.length > 0) {
        var line = /(.*)(\r\n|[\r\n])?/.exec(doc);
        count++;
        var array = line[1].split(sep);
        if (array.length >= 3) {
            var command = new Command();
            command.command = array[0];
            command.target = array[1];
            command.value = array[2];
            commands.push(command);
        }
        doc = doc.substr(line[0].length);

    }
    testCase.setCommands(commands);
}

function format(testCase, name) {
    return formatCommands(testCase.commands);
}

function formatCommands(commands) {
    var result = '';
    result += this.options['header'];

    for (var i = 0; i < commands.length; i++) {
        var command = commands[i];
        if (command.type == 'command') {
            var currentResult = '';

            if (result.indexOf("\""+ "command.target" + "\"") = -1)
            {
                currentResult = setCommand(command);
                result += currentResult;
            }       
        }
    }
    result += this.options['footer'];

    return result;
}

setCommand = function (command) {
    var waitForStart = tab + "this.CurrentElement = this.GetElement((";
    var waitForEnd = "));" + newLine + tab;
    var result = tab;

    switch (command.command.toString()) {
        case 'open':
            if (command.target.toString().substring(0, 4) == "http") {
                result += options['receiver'] + 'Navigate().GoToUrl("' + command.target.toString() + '");';
            }
            else if (command.target.toString() == "/") {
                result += options['receiver'] + 'Navigate().GoToUrl(baseUrl);';
            }
            else if (isStoredVariable(command.target)) {
                result += options['receiver'] + 'Navigate().GoToUrl(' + getStoredVariable(command.target) + ');';
            }
            else {
                result += options['receiver'] + 'Navigate().GoToUrl(baseUrl + "' + command.target.toString() + '");';
            }
            break;
        case 'clickAndWait':
            result = waitForStart + searchContext(command.target.toString()) + waitForEnd;
            result += "this.CurrentElement.Click();";
            break;
        case 'click':
            result = waitForStart + searchContext(command.target.toString()) + waitForEnd;
            result += "this.CurrentElement.Click();";
            break;
        case 'type':
            result = waitForStart + searchContext(command.target.toString()) + waitForEnd;
            result += "this.CurrentElement.Clear();";
            result += newLine + tab;
            if (isStoredVariable(command.target)) {
                result += "this.CurrentElement.SendKeys(\"" + getStoredVariable(command.value) + "\");";
            }
            else {
                result += "this.CurrentElement.SendKeys(\"" + command.value + "\");";
            }
            break;
        case 'assertTextPresent':        
            result = getWaitForText(command);
            break;
        case 'verifyTextPresent':
            result = getWaitForText(command);
            break;
        case 'verifyText':
            result = getWaitForText(command);
            break;
        case 'assertText':
            result = getWaitForText(command);
            break;
        case 'waitForElementPresent':
            result = tab + 'this.WaitForElementPresent(' + searchContext(command.target.toString()) + ');';
            break;
        case 'verifyElementPresent':
            result = tab + 'this.WaitForElementPresent(' + searchContext(command.target.toString()) + ');';
            break;
        case 'verifyElementNotPresent':
            result = tab + 'this.WaitForElementNotPresent(' + searchContext(command.target.toString()) + ');';
            break;
        case 'waitForElementNotPresent':
            result = tab + 'this.WaitForElementNotPresent(' + searchContext(command.target.toString()) + ');';
            break;
        case 'waitForTextPresent':
            result = tab + 'this.WaitForTextPresent(\"' + command.target.toString() + '\");';
            break;
        case 'waitForTextNotPresent':
            result = tab + 'this.WaitForTextNotPresent(\"' + command.target.toString() + '\");';
            break;
        case 'verifyTextPresent':
            result = tab + 'this.WaitForTextPresent(\"' + command.target.toString() + '\");';
            break;
        case 'verifyTextNotPresent':
            result = tab + 'this.WaitForTextNotPresent(\"' + command.target.toString() + '\");';
            break;
        case 'waitForText':
            result = tab + 'this.WaitForText(' + searchContext(command.target.toString()) + ',\"' + command.value + '\");';
            break;
        case 'waitForChecked':
            result = tab + 'this.WaitForChecked(' + searchContext(command.target.toString()) + ');';
            break;
        case 'waitForNotChecked':
            result = tab + 'this.WaitForNotChecked(' + searchContext(command.target.toString()) + ');';
            break;
        case 'store':
            result = tab + "IJavaScriptExecutor js = (IJavaScriptExecutor) this.Driver;" + newLine;
            result += tab + 'string ' + command.value + '= ' + 'js.ExecuteScript(\"' + command.target + '\");';
            break;
        case 'storeEval':
            result = "JavascriptExecutor js = (JavascriptExecutor) this.Driver;" + newLine;
            result = tab + 'string ' + command.value + '= ' + 'js.executeScript(\"' + command.target + '\");';
            break;
        case 'storeValue':
            result = waitForStart + searchContext(command.target.toString()) + waitForEnd;
            result += 'string ' + command.value + '= ' + 'this.CurrentElement.GetAttribute("value");';
            break;
        case 'storeAttribute':
            result = waitForStart + searchContext(command.target.toString()) + waitForEnd;
            result += 'string ' + command.value + '= ' + 'this.CurrentElement.GetAttribute(\"' + getTargetAttribute(command.target) + '\");';
            break;
        case 'gotoIf':
            var strTarget = '';
            var byTarget = '';
            if (command.target.startsWith('selenium.isElementPresent')) {
                strTarget = getIfTargetElementIsPresent(command.target);
                byTarget = searchContext(strTarget);
                result = tab + 'if(' + '!IsElementPresent(' + byTarget + '))' + tab;
                result += '{' + newLine + tab;
            }
            else if (command.target.startsWith('selenium.isElementNotPresent')) {
                strTarget = getIfTargetElementIsNotPresent(command.target);
                byTarget = searchContext(strTarget);
                result = tab + 'if(' + 'IsElementPresent(' + byTarget + ')' + tab;
                result += tab + '{' + newLine + tab;
            }
            break;
        case 'selectFrame':
            result = options['receiver'] + 'SwitchTo().Frame(\"' + command.target + '\");';
            break;
        case 'label':
            result = tab + '}';
            break;
        case 'pause':
            result = tab + 'Thread.Sleep(' + command.target + ');';
            break;
        case 'echo':
            if (isStoredVariable(command.target)) {
                result = tab + 'Console.WriteLine(\"' + getStoredVariable(command.target) + '\");';
            }
            else {
                result = tab + 'Console.WriteLine(\"' + command.target + '\");';
            }
            break;
        case 'setTimeout':
            result = tab + 'timeOut= ' + command.target + ';';
            break;
        case 'select':
            result = waitForStart + searchContext(command.target.toString()) + waitForEnd;
            result += ' SelectElement selectElement = new SelectElement(this.WaitFor);'
             + newLine + tab + 'selectElement.SelectByValue(quantity);' + newLine + tab;
            break;
        default:
            result = '// The command: #####' + command.command + '##### is not supported in the current version of the exporter';
    }
    result += newLine;
    return result;
}

searchContext = function (locator) {
    if (locator.startsWith('xpath')) {
        return 'By.XPath("' + locator.substring('xpath='.length) + '")';
    }
    else if (locator.startsWith('//')) {
        return 'By.XPath("' + locator + '")';
    }
    else if (locator.startsWith('css')) {
        return 'By.CssSelector("' + locator.substring('css='.length) + '")';
    }
    else if (locator.startsWith('link')) {

        return 'By.LinkText("' + locator.substring('link='.length) + '")';
    }
    else if (locator.startsWith('name')) {

        return 'By.Name("' + locator.substring('name='.length) + '")';
    }
    else if (locator.startsWith('tag_name')) {

        return 'By.TagName("' + locator.substring('tag_name='.length) + '")';
    }
    else if (locator.startsWith('partialID')) {

        return 'By.XPath("' + "//*[contains(@id,'" + locator.substring('partialID='.length) + "')]" + '")';
    }
    else if (locator.startsWith('id')) {

        return 'By.Id("' + locator.substring('id='.length) + '")';
    }
    else {
        return 'By.Id("' + locator + '")';
    }
};

function isStoredVariable(commandValue) {

    return commandValue.substring(0, 1) == "$";
}

function getStoredVariable(commandValue) {

    return commandValue.substring(2, (commandValue.length - 1));
}

function getIfTargetElementIsPresent(commandValue) {

    return commandValue.substring(27, (commandValue.length - 2));
}

function getIfTargetElementIsNotPresent(commandValue) {

    return commandValue.substring(30, (commandValue.length - 2));
}

function getTargetAttribute(commandValue) {

    return commandValue.substring(commandValue.indexOf('@') + 1);
}

function getWaitForText(command) {
    var result = '';
    if (command.value.toString() == "") {
        result = tab + 'this.WaitForText(\"' + command.target + '\");';
    }
    else {
        result = tab + 'this.WaitForText(' + searchContext(command.target.toString()) + ',\"' + command.value + '\");';
    }
    return result;
}

There are a couple of interesting parts of the presented code. Most of the magic happens inside the setCommand function. There, through a switch statement, the function generates a string for the corresponding C# code for the used Selenium IDE command. The locator expressions are made in the searchContext.

One more thing, you can use XUL XML (XML User Interface Language) to describe how the format settings form will look like.

JavaScript
this.configForm = '<description>Variable for Selenium instance</description>' +
                  '<textbox id="options_receiver" value="this.Browser"/>' +
                  '<description>MethodName</description>' +
                  '<textbox id="options_methodName" value="TESTMETHODNAME" />' +
                  '<description>Base URL</description>' +
                  '<textbox id="options_baseUrl" value="http://home.telerik.com"/>';

 

After you add different text boxes and other controls, you can later use the default values set in them in your Selenium IDE Export format.

XUL XML Selenium IDE Export Format

Create Selenium IDE Export for Page Object Map Generation

As you probably know, I am a big fan of the Page Object Design Pattern. Until now, I have introduced to you a couple of different ways to use the design pattern. Usually, the most tedious part of it is the creation of the element map classes. We can ease the process with the help of Selenium IDE and a new Selenium IDE Export format. This format is going to export to a new file only the elements in the below format.

View the code on Gist.

I haven’t figured a smart way to name the generated elements because of that the first version is producing them with an increasing index suffix added to the element’s name.

Here is the full source code.

JavaScript
var doc = '';
var index = 1;
var tab = "\t";

this.configForm = '<description>Variable for Selenium instance</description>' +
                  '<textbox id="options_receiver" value="this.driver"/>' +
                  '<description>ClassName</description>' +
                  '<textbox id="options_className" value="PageName" />'

this.name = "C# Anton (Driver)";
this.testcaseExtension = ".cs";
this.suiteExtension = ".cs";
this.driver = true;

this.options = {
    receiver: "this.driver",
    className: "PageName",
    header: 'public partial class Page : BasePage\n{\n',
    footer:
    '}\n' + "\n" + "\n",
    defaultExtension: "cs"
};

function parse(testCase, source) {
    doc = source;

    var commands = [];
    var sep = {
        comma: ",",
        tab: "\t"
    };
    var count = 0;
    while (doc.length > 0) {
        var line = /(.*)(\r\n|[\r\n])?/.exec(doc);
        count++;
        var array = line[1].split(sep);
        if (array.length >= 3) {
            var command = new Command();
            command.command = array[0];
            command.target = array[1];
            command.value = array[2];
            commands.push(command);
        }
        doc = doc.substr(line[0].length);

    }
    testCase.setCommands(commands);
}

function format(testCase, name) {
    return formatCommands(testCase.commands);
}

function formatCommands(commands) {
    var result = '';
    result += this.options['header'];

    for (var i = 0; i < commands.length; i++) {
        var command = commands[i];
        if (command.type == 'command') {
            var currentResult = '';
            currentResult = setCommand(command);
            result += currentResult;
        }
    }
    result += this.options['footer'];

    return result;
}

setCommand = function (command) {
    var startPropElement = tab + "public IWebElement Element" + index++ + "\n" + tab + "{" + "\n" + tab + tab + "get" + "\n" + tab + tab + "{" + "\n" + tab + tab + tab;
    var endPropElement = "\n" + tab + tab + "}" + "\n" + tab + "}" + "\n";
    var result = tab;

    switch (command.command.toString()) {
        case 'clickAndWait':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'click':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'type':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'assertTextPresent':        
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'verifyTextPresent':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'verifyText':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'assertText':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'waitForElementPresent':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'verifyElementPresent':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'verifyElementNotPresent':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'waitForElementNotPresent':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'waitForTextPresent':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'waitForTextNotPresent':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'waitForChecked':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'waitForNotChecked':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'storeValue':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        case 'storeAttribute':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;      
        case 'select':
            result = startPropElement + searchContext(command.target.toString()) + endPropElement;
            break;
        default:
    }

    result += "\n";
    return result;
}

searchContext = function (locator) {
    if (locator.startsWith('xpath')) {
        return 'return this.driver.FindElement(By.XPath("' + locator.substring('xpath='.length) + '"));';
    }
    else if (locator.startsWith('//')) {
        return 'return this.driver.FindElement(By.XPath("' + locator + '"));';
    }
    else if (locator.startsWith('css')) {
        return 'return this.driver.FindElement(By.CssSelector("' + locator.substring('css='.length) + '"));';
    }
    else if (locator.startsWith('link')) {

        return 'return this.driver.FindElement(By.LinkText("' + locator.substring('link='.length) + '"));';
    }
    else if (locator.startsWith('name')) {

        return 'return this.driver.FindElement(By.Name("' + locator.substring('name='.length) + '"));';
    }
    else if (locator.startsWith('tag_name')) {

        return 'return this.driver.FindElement(By.TagName("' + locator.substring('tag_name='.length) + '"));';
    }
    else if (locator.startsWith('partialID')) {

        return 'return this.driver.FindElement(By.XPath("' + "//*[contains(@id,'" + locator.substring('partialID='.length) + "')]" + '"));';
    }
    else if (locator.startsWith('id')) {

        return 'return this.driver.FindElement(By.Id("' + locator.substring('id='.length) + '"));';
    }
    else
    {
        return 'return this.driver.FindElement(By.Id("' + locator + '"));';
    }
};

So Far in the 'Pragmatic Automation with WebDriver' Series

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
CEO Automate The Planet
Bulgaria Bulgaria
CTO and Co-founder of Automate The Planet Ltd, inventor of BELLATRIX Test Automation Framework, author of "Design Patterns for High-Quality Automated Tests: High-Quality Test Attributes and Best Practices" in C# and Java. Nowadays, he leads a team of passionate engineers helping companies succeed with their test automation. Additionally, he consults companies and leads automated testing trainings, writes books, and gives conference talks. You can find him on LinkedIn every day.

Comments and Discussions

 
-- There are no messages in this forum --