Syntax of defining DSL: It is basically a mapping of DSL and Java method calls. All of your DSL mappings will be in a FitNesse table with DSLAdapter as the class name, appearing in first column of the first row
Basic Syntax is: DSL follwed by Actual Java Method call
Each argument to DSL & Java method appears in a separate table cell. All arguments of DSL appear as %. All the arguments to a Java method call are defined as positional parameters of DSL such as {1}, {2},…{n} delimited by comma character “,”.
So for a method called max(arg1, arg2) if you want to define a DSL such as “get the max of 2 numbers num1 and num2″ then it would look like this:
!| DSL Adapter |
| get the max of 2 numbers | % | and | % | max | {1}, {2} |
% is just a place holder over here for arguments and {1} is the first argument in DSL and {2} is second parameter in DSL. Once defined now to use this DSL in writing tests simply write this:
| get the max of 2 numbers | 25 | and | 28 | |
Once you take a look at some more examples it will be much more clear how to create and modify your own DSL.
Let’s take a look at some more examples:
While writing web tests using Selenium there often is a need for pausing the test for 1 or 2 sec to let Selenium do frame loading (esp needed in Frames based testing where main page has loaded but some iframe still needs time to load). Selenium doesn’t have a pause method and ONLY way to pause it is by calling sleep(long ms) method in java.lang.Thread. Testers can simply write: | java.lang.Thread.sleep | 2000 | to get 2 second pause but since it is a pure Java call and sometimes too technical for test authors it is better to define a DSL for that using following syntax:
!| DSL Adapter |
| pause the test for | % | seconds | java.lang.Thread.sleep | {1}000 |
Having defined this DSL now test authors will just need to write this in their web tests:
| pause the test for | 2 | seconds |
which is much more readable and user friendly.
Now lets take a look at my DSL for Selenium API. Define this DSL in a SetUp page to make it available for all the test pages.
!| DSL Adapter |
| user starts the browser | start | |
| user opens the URL | % | open | {1} |
| page has the title | getTitle | |
| user types | % | into | % | field | type | {2}, {1} |
| user sees the text on the page | % | isTextPresent | {1} |
| page has an element named | % | isElementPresent | {1} |
| page loads in less than | % | seconds | waitForPageToLoad | {1}000 |
| page has URL | getLocation | |
| user clicks on the submit button | click | //button[@type='submit'] |
| user clicks on the link named | % | click | link={1} |
| user clicks on the button named | % | clickAt | {1},"" |
| get text from element named | % | getText | xpath=id('{1}') |
| get xpath text from address | % | getText | xpath={1} |
| user removes the cookie named | % | at path | % | deleteCookie | {1}, {2} |
| user selects an option | % | from the drop-down | % | select | {2}, label={1} |
| pause the test for | % | seconds | java.lang.Thread.sleep | {1}000 |
| user stops the browser | stop | |
Using above DSL my Google web test will look like this. First add this test table to start Selenium test in SetUp page:
!define ds {com.thoughtworks.selenium.DefaultSelenium}
!define site {http://www.google.com}
!| Generic Fixture | selenium=${ds} | localhost | 4444 | *pifirefox | ${site} |
| user starts the browser |
And write this test table in your main test page GoogleTest:
!define q {fitnesse generic fixture}
!| Generic Fixture | selenium= |
| user opens the URL | http://www.google.com |
| page has the title | | Google |
| page has an element named | q | | true |
| page has an element named | btnG | | true |
| user types | ${q} | into | q | field |
| user clicks on the button named | btnG |
| page loads in less than | 5 | seconds |
| page has the title | | ${q} - Google Search |
| user clicks on the link named | Next |
| page loads in less than | 5 | seconds |
| user clicks on the link named | Next |
| page loads in less than | 5 | seconds |
| user sees the text on the page | Results 21 - 30 | | true |
and finally this small test table in TearDown page to stop the Selenium test:
!| Generic Fixture | selenium= | | user stops the browser |
Few important things to be noted here are:
- A new instance of Selenium is being created in SetUp page and being used in main page and TearDown page using a variable selenium=.
- By using a variable to store Selenium instance and reusing it, you can move common initialization code in SetUp and common cleanup code in TearDown page. Also it will make sure that in the event of an exception being thrown (and that may happen often in web testing) your cleanup (TearDown page) will ALWAYS be called.
- Test tables are not calling any of the Selenium APIs, only DSL is being used to direct Selenium run all the test steps.
- Even though Selenium’s waitForPageToLoad method expects you to pass milli seconds, using DSL we pass seconds value only by appending 000 in the argument. Using this approach you can always hide the complexity of the method arguments from the test authors.
- Customizing this DSL is super easy. Lets say some user doesn’t like the text user opens the URL and wants user opens a page instead. In that case just go to the above FitNesse’s SetUp page and edit the table that starts with DSL Adapter and replace the text so it will become like this:
| user opens a page | http://www.google.com | - If the underlying class (Selenium in this case) adds some new APIs with its new release, any other Fixture implementation approach would require a corresponding code change and new release. Whereas this DSL adapter just needs couple of new lines in a FitNesse SetUp page to define new “mapping” between DSL and new API calls.
Update: As requested by Aristotelis, I am putting up the DSL that I use myself for Selenium API. It is by no means comprehensive but so far it has fulfilled my requirements. Feel free to add new definitions to this since its really pretty straight forward to add new DSL definitions.
!| DSL Adapter |
| user types | % | into | % | field | type | {2}, {1} |
| user starts the browser | start | |
| user opens the URL | % | open | {1} |
| user opens the page | % | open | {1} |
| page has the title | getTitle | |
| user sees the text on the page | % | isTextPresent | {1} |
| page has an element named | % | isElementPresent | {1} |
| page loads in less than | % | seconds | waitForPageToLoad | {1}000 |
| page has URL | getLocation | |
| user clicks on submit button | click | //button[@type='submit'] |
| user clicks on the link named | % | click | link={1} |
| user clicks on link named | % | click | link={1} |
| user clicks on the button named | % | clickAt | {1},"" |
| user clicks on button named | % | clickAt | {1},"" |
| user maximizes the window | windowMaximize | |
| get text from element named | % | getText | xpath=id('{1}') |
| get xpath text from address | % | getText | xpath={1} |
| user stops the browser | stop | |
| user removes the cookie named | % | at path | % | deleteCookie | {1}, {2} |
| user selects an option | % | from the drop-down named | % | select | {2}, label={1} |
| user pauses the test for | % | seconds | java.lang.Thread.sleep | {1}000 |
| body of the page | getBodyText | |
| element | % | has value | getAttribute | {1} |
| all the cookies are | getCookie | |
| source of the page | getHtmlSource | |
| go back 1 page | goBack | |
| value of the input field | % | getValue | {1} |
| get text from the table cell | % | getTable | {1} |
| execution speed is | getSpeed | |
| user checks field | % | check | {1} |
| create cookie with name-value pair | % | and options | % | createCookie | {1}, {2} |
Good Layout and design. I like your blog. I just added your RSS feed to my Google News Reader. .
Jason Rakowski
Hi Anubhava,
as soon as i looked at Fitnesse i disliked the idea of every test potentially requiring code to be written.
If we have testers writing code who tests the testers tests?
If we let the developers write the tests then where is our tester independence?
After few small problems (thank you sourceforge) i have your genericfixture and the DSL adapter working, and its exactly what i wanted to accomplish, thank you anubhava.
Stuart.
Hi Anubhava,
Great work on the generic fixture!
Does anyone know if there is an already created DSL definition of many (or all) of the Selenium commands?
We have been exploring tools such as WebTest, Fitnium and now we have discovered the Generic Fixture!
We are interested in using it in conjunction with Selenium RC.
We followed your instructions and we even mapped a few Selenium commands to our DSL which was great!
We think that such a pre-made list would be very helpful and a great kick-starter as well for most of the cases.
Thanks and keep up the great work!
Dear Aristotelis,
Thanks for your great feedback. I will upload my selenium DSL definition that I use for my own testing very soon.
Thanks,
Anubhava
Hi Anubhava,
Thanks for your reply and great attitude.
I have one more question that may be of interest to more people.
Considering that the main advantage and added value is the ‘almost natural language’ in which the tests are written,
we’d like to know if it is possible to omit the
!| Generic Fixture | selenium= |
from the beginning of each table.
Is there a way to make it the ‘default’ or just write it once at the beginning of the document?
Thanks again.
Dear Aristotelis,
I have uploaded my DSL definition for Selenium API in this Blog post. This doesn’t cover Selenium API completely but it has been sufficient for my needs. Please feel free to add whatever you find missing in this DSL definition set.
Now about your suggestion about avoiding use of:
!| Generic Fixture | selenium= |
You may know that all “flow type” fixtures such as DoFixture have this capability where you specify name of your DoFixture only once on a page. I have actually thought about it many times on similar lines but stopped short of modifying “Generic Fixture” code to provide this ability. Let me try to explain why.
Underlying Fixture framework expects the name of the Java Class in the first cell of the first row of the table. That Java class is instantiated by Fitnesse framework and becomes system under test (SUT). Rest of the code in the table is interpreted by that class only. But “Generic Fixture” even though acts as SUT for underlying framework is actually is a broker that executed methods of the class name you provide to GenericFixture (for example com.thoughtworks.selenium.DefaultSelenium or as a variable selenium=
So actual SUT is Selenium not GenericFixture. At present GenericFixture supports you to change this real SUT multiple times in different table on same page for complex testing scripts.
If I make GenericFixture a “flow type” fixture then you will be able to execute methods of ONLY one class on a page such as Selenium. But in most of the real life test scripts using GenericFixture, testers use various other Java classes such as String or Decimal or BeanShell to get better assertion support. Many of my own test scripts involve hitting a database using a SQL query after posting some data from web form to verify that data has been stored in database correctly.
Sorry for the long answer but I hope you got my point. But I must say you have raised a veery valid issue. And now I have started thinking that may be give this option to end user if he/she wants “flow type” behavior or not on a SetUp page or at the top of the page. I will start researching this option as soon as I get some free time.
cheers,
Anubhava
Dear Anubhava,
Thank you for your reply, and no it was not too long, it was all needed for me to understand.
I see your dilemma (’to flow or not to flow’…) and I have an idea that may be of interest to you.
What if you define in the DSL, for each command/lemma, the fixture that it belongs to?
This way each lemma would be uniquely identified and the natural language on the test script would not be harmed.
Exampe:
| user opens the URL | % | sel= | open | {1} |
Alternatively this could be also done in groups, example:
!| DSL Adapter | sel= |
| user starts the browser | start | |
| user opens the URL | % | open | {1} |
!| DSL Adapter | myClass= |
| friendly name | % | and | %| myFriendlyFunc | {1},{2} |
| other name | % | and | %| myOhterFunc | {1},{2} |
I do not know whether these are implementable, but if they are I’d think they would be of great interest to many.
thanks,
Aristotelis.