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.
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.
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
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.
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
).
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.
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.
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.
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.
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.
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