Introducing DSL Adapter for Generic Fixture

DSL (Domain Specific Language) is used to define problems in a specific domain. DSL, off late has gained a lot of focus due to its readability and ease of use. It is much more expressive than general purpose programming language especially for test authors, business people and end users.

When I published my Generic Fixture, there was a very valid concern raised by some FitNesse users that since Generic Fixture lets testers write their tests without writing any Java code and exposes user’s class level details (such as constructors and methods) to testers it might not be very appealing to customer and business folks as it is to developers. That’s when I started dwelling into this issue. Initially I thought of providing a MS Excel macro that lets testers write their test in DSL and by running that macro it will produce class level tests suited for Generic Fixture. But I abandoned that macro idea because of my unfamiliarity with VB and also to avoid hassle of an extra step for testers. (what if testers are writing tests directly on Wiki page rather than MS Excel). Finally I decided upon providing a plug-in kind of extension to Generic Fixture that will let testers define their own high level customer centric language (DSL). This DSL adapter will contain information on how to map it to Java Class level details. They just have to define this mapping once for each domain and can make it part of the SetUp page to make it available to for all the test pages.

There are many advantages of defining DSL in the test tables itself over the defining DSL inside the Java code. How powerful a DSL will be which is being created by a developer rather than the end-user himself? Doesn’t it loose the very concept of being a customer centric language? Advantage of DSL is its user friendly nature or being a customer centric language. But we all know that each customer or end user is different. What if the verbalization or syntax of DSL preferred by one user is not liked by another user. By writing DSL through source code we are saying this is the language better adapt to it because it CANNOT be changed. But my approach of defining DSL in the test tables gives you this flexibility. You can alter or customize DSL for each user and that too on the fly and at will, without ever making any source code change. Using this approach, DSL can be modified and managed directly by the customers, they can write tests in ANY LANGUAGE not just in English. That goes along with the goal of the Generic Fixture that the testers should be able to write tests without writing source code.

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:

  1. A new instance of Selenium is being created in SetUp page and being used in main page and TearDown page using a variable selenium=.
  2. 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.
  3. Test tables are not calling any of the Selenium APIs, only DSL is being used to direct Selenium run all the test steps.
  4. 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.
  5. 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 |
  6. 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} |
Advertisements

15 thoughts on “Introducing DSL Adapter for Generic Fixture

  1. Manohar says:

    Hi anubhava,

    I ready your comments. You are too good in fitnesse + selenium rc. I have some doubt while setup the fitnesse and uses of fixtures. Where we define the path for fixtures.

    If you dont mind please call me on this number :+91 9226999696 otherwise Please give me your personal number.

    Thanks
    Manohar Sonar

  2. Dear dosshouse,

    For starters I would suggest going through my Blog post: https://anubhava.wordpress.com/2008/03/09/jump-start-fitnesse-with-selenium/ for step-by-step instructions to setup Generic Fixture. I know you are not interested in Selenium but I still suggest you to please go through this Blog to understand whole setup.

    And if it still doesn’t resolve then just paste your Fitnesse code that you tried with the error you are getting and I will tell you how to resolve it.

    Thanks,
    Anubhava

  3. dosshouse says:

    Dear Anubhava,

    Thanks for your kind reply. This sounds great for me.

    In this case, my interest in on the new method definition with DSL Adaptor. I tried the example listed here, but no success. So might I ask more (I just touch Fitnesse only several weeks):
    1) How to define the Generic Fixture row? (the example only
    2) should I install special packet?

    Thanks in advance,
    -dosshouse

  4. Dear dosshouse,

    Yes absolutely! DSL feature is completely independent of Selenium. In fact on this very page if you notice my initial examples (Math and sleep) are non-Selenium Java APIs. For my personal use as well I use DSL most of time without using Selenium.

    Thanks,
    Anubhava

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s