-
Notifications
You must be signed in to change notification settings - Fork 104
Custom Exporter Developer Documentation
Exporters are modules that convert a Selenium 2 script into a string representation. The default representation of a script is as a JSON structure, but scripts can also be exported in a variety of programming languages, for example Python or Java.
New exporters can be registered by push
ing them onto builder.selenium2.io.formats
.
An exporter is an object that must define the following properties:
-
name (string)
Name of the format (eg "Python") -
extension (string)
Extension of the exported file (eg ".py") -
format (function(script:builder.Script, name:string) -> string)
Formats abuilder.Script
into a string. -
canExport (function(stepType:builder.selenium2.stepTypes.*) -> boolean)
Whether the given step type can be exported. -
nonExportables (function(script:builder.Script) -> list of string)
Given a script, produce a list of the names of step types in the script that cannot be exported.
This very simple raw exporter outputs the step types of the script:
builder.selenium2.io.formats.push({
name: "StepTypes",
extension: ".txt",
format: function(script, name) {
var out = name + ":\n";
for (var i = 0; i < script.steps.length; i++) {
out += script.steps[i].type.getName() + "\n";
}
return out;
},
canExport: function(stepType) { return true; },
nonExportables: function(script) { return []; }
});
To export a script into a programming language, the exporter needs to output the following things:
- Setup: creating the webdriver instance, etc.
- Teardown: closing the instance, etc.
- Mapping step types onto code fragments.
- Escaping parameters: eg
"foo"
has to become"\"foo\""
. - Mapping different locator strategies onto code fragments
- Dealing with negated steps.
Assert/Verify/Wait
steps can be negated. For example, you can assert that some text is present, or you can assert that the text is not present. In Selenium 1 this is handled using two separate step types. In Selenium 2 - and internally in Builder - this is handled by having anegated
flag on steps. - Variable storage:
Store
steps need to be mapped to variable assignment. - Variable substitution: The
${varname}
syntax has to be turned into appropriate expressions. For example,foo${bar}
has to become"foo" + bar
.
To make creating an exporter easier, Builder has the function builder.selenium2.io.createLangFormatter
, which takes a set of information about an export language and processes it into a formatter that can then be added using builder.selenium2.io.formats.push
.
The information required is a JS object with the following fields. Each part of the process will be explained in detail below.
-
name (string)
Name of the format (eg "Python") -
extension (string)
Extension of the exported file (eg ".py") -
start (string)
Text at the start of the exported string -
end (string)
Text at the end of the exported string -
escapeValue (function(stepType, value, pName) -> string)
Correctly escapes a parameter value -
locatorByForType (function(stepType, locatorType, locatorIndex) -> string)
Returns code needed for a particular locator strategy -
usedVar (function(varName, varType) -> string)
Returns code for accessing previously defined variable -
unusedVar (function(varName, varType) -> string)
Returns code for setting new variable -
not (string)
The negation operator, eg!
ornot
-
lineForType (map of string to string/function(step, escapeValue))
Maps the names of step types onto strings with a substition syntax, or in rare cases onto functions that process the step into text.
The exported string is built up bit by bit. The start
text is the first thing added. Then, each step in the script is processed and appended. Finally, the end
text is appended.
Steps are looked up in lineForType
by name. If the resulting object is a string, the step parameters and any negation tokens are substituted in, and the result is appended to the exported string being built.
If the resulting object is a function, it's called with the step to be processed and the escapeValue
function as parameters. It should return a string which is then directly appended with no substitution being done by the exporter system.
Curly brace expressions like {url}
are replaced with the parameter value of that name. For example, in Python, the get
step type is mapped by lineForType
onto
wd.get({url})
If the url
parameter of a get
step is http://sebuilder.com
, substitution produces the following line:
wd.get("http://sebuilder.com")
Note the quotes, which were added by escapeValue
.
Different languages have different string syntaxes, so each exporter needs a function, escapeValue
, that can turn a raw string into the correct expression for the export language. escapeValue
should put quotes around the string, escape any expressions (such as other quotes) that need escaping, and substitute in variable values (see below for more details).
Examples (in Python):
-
foo
->"foo"
-
"kitten"
->"\"kitten\""
-
${title}
->title
-
Bob ${lastname}
->"Bob " + lastname
Different locator strategies need different code chunks. For example, if you want to locate by id in Python, you use the find_element_by_id
function, and if you want to locate by xpath it's find_element_by_xpath
.
The substitution syntax for locator strategies is to append By
at the end of the parameter name. For example, {locator}
gets substituted with the locator's value (eg "submit_button"
), and {locatorBy}
with the code fragment for the locator strategy (eg find_element_by_id
). Locator values are escaped using escapeValue
.
Most of the time, which code fragment to use is determined entirely by the locator type, but there can be individual step types that require different code. For example, in Python, assertElementPresent
and verifyElementPresent
require the find_elements
function rather than the find_element ones
.
The locatorByForType
function is called with the name of the step type, the locator type, and the index of the locator parameter. The step type could for example be clickElement
and the locator type can be any of class
, id
, link text
, xpath
, css selector
and name
. Finally, the locatorIndex
allows you to return different text for different locator parameters in the same step type.
So in Python, calling locatorByForType("clickElement", "id", 0)
returns find_element_by_id
. The string for clickElement
is wd.{locatorBy}({locator}).click()
, which after substitution becomes wd.find_element_by_id("submit_button").click
.
Code generated for negated steps has to invert some of its tests. To support this without having to create two versions of the code for the negated and non-negated cases, there are two bits of substitution syntax that can be used.
{negNot}
is substituted with the exporter's not
value if the step is negated, and substituted with the empty string if the step is not negated.
{posNot}
is substituted with the empty string if the step is negated, and substituted with the exporter's not
value if the step is negated.
For example, the string for verifyCurrentUrl
in Python is
if {posNot}(wd.current_url == {url}):
print(\"{negNot}verifyCurrentUrl failed\")
In this case, since we want to print a warning if the current URL does not match the supplied URL, there should be a not
before the condition in the normal case. If we want to verify the reverse, that the current URL does not match the supplied URL, the not
is left out.
In the warning message, we want the not
to show up in the negated case to indicate that the negated case was tested.
So in the non-negated case, this becomes:
if not (wd.current_url == {url}):
print(\"verifyCurrentUrl failed\")
And in the negated case:
if (wd.current_url == {url}):
print(\"not verifyCurrentUrl failed\")
(Note that the not
value in the Python exporter is "not "
, including a trailing space.)
Selenium supports storing values in variables. Storing is done using the store
commands. To translate these correctly, there is a substitution syntax to produce variable assignment code.
Expressions of the form ${{varName}:varType}
are processed by calling either unusedVar
or usedVar
. The former is called if this is the first time a variable expression with this varName
is encountered. The latter is called otherwise.
The system looks up the name of the variable in the parameter given by {varName}
, but does not escape it. The name of the variable and its type are supplied to the unusedVar
or usedVar
function. The varType is optional.
For example, in Python, if the step is storeTitle(variable='foo')
, the string for it is ${{variable}} = wd.title
. The {variable}
is looked up in the step, and the result, foo
, is supplied to usedVar
, which returns foo
.
In Java, the string is ${{variable}:String} = wd.getTitle();
. If there has already been another step using the variable foo
, ${{variable}:String}
is turned into foo
. Otherwise, it's turned into String foo
.
This means that the first time foo
is used we get String foo = wd.getTitle();
, and the second time we get foo = wd.getTitle();
.
Having stored a value in a variable, it may then be referenced in parameter values using the ${varname}
syntax. This must be handled by escapeValue
. For example, in Python, Bob ${lastname}
has to be turned into "Bob " + lastname
. Or in PHP, it has to be turned into "Bob " . $lastname
.
This process is handled by some transducer code which can be adapted to the needs of the particular language you are writing an exporter for.
- Parameters:
{paramname}
- Locator values:
{paramname}
- Locator type code fragments:
{paramnameBy}
- Variables:
${{varName}:varType}
Within escapeValue
:
- Variables:
${varName}
It's heavily recommended you read the code of some of the exporters. It can also be worthwhile just duplicating an exporter and replacing its parts piecemeal.
If you have any queries, don't hesitate to contact David Stark.
We would love more exporters for more languages! If you've written one, send us an email, or submit a pull request on GitHub, and we'll incorporate the exporter into Builder.
Alternatively, you can also create a small plugin that installs your custom exporter.