Tuesday, September 14, 2010

Prepare objects repository using Selenium UI-Element feature

In my previous post we have recorded simple test case to ensure that new entity object can be created via web application. As you observed using original objects description defined by Selenium IDE represents test scenario in not human readable format. Also, it is hard to maintain. Therefore we will use UI-elements. From this post such objects as Pagesets and UI Elements to be specified before we can start using this feature in our test cases.

Pagesets shorthand

I’ve looked thru the application and determine next pages to work with: allPages (it contains objects related to any page), mainPage (there is the only page in the application that any user may have access to with no authentication) and areaPage. This page includes shorthand for all objects related to area entity.

applMap.addPageset({
    name: 'allPages'
    , description: 'all appl.ua pages'
    , pathRegexp: '.*'
});

 
applMap.addPageset({
    name: 'areaPages'
    , description: 'all pages for Areas'
    , pathRegexp: 'member\.php\?lang=(eng|ukr)&op=' + page_ids['Areas']
});

 
applMap.addPageset({
    name: 'mainPages'
    , description: 'home pages for a user who is not logged in'
    , pathRegexp: 'appl/(eng|ukr)/home'
});
Regular expression in areaPages description includes some list (pairs of possible page id with corresponding name). With this list it is easy to make such object descriptions. For instance, choosing areas page you do not need to know exact id value of this page, just select ‘Areas’ item from the list. page_ids list represents key/value pairs. Below you may find an example of this list:

var page_ids = {
    'Dashboard': 'page_dashboard'
    , 'Areas': 'list_areas'
    …
    , 'Activities': 'list_activity'
};

In shorthand for mainPages above you may see the regular expression contains ‘(eng|ukr)’ value. It means we make this object in the repository language independent. It means that the page will be discovered regardless locale selected at the moment.

So, meaningful pages are specified. Remaining pages can be covered the same way. The only thing left is to add object descriptions for certain entities and link them with pages from the set.

UI-Elements shorthand

I will describe objects to be used in this blog below (like credentials, menu items, table elements, popup dialog box etc). Others objects can be covered using similar approach. The following description shows how to specify credentials.

  • Credentials:

applMap.addElement('mainPages', {
    name: 'credentials'
    , description: 'Credentials properties'
    , args: [
        {
            name: 'credential_name'
            , description: 'the name of the credential'
            , defaultValues: keys(credential_ids)
        }
    ]
    , getLocator: function(args) {
        var topic = credential_ids[args.credential_name];
        return "//input[@name='" + topic + "']";
    }
});

In this example credential_ids there is nothing else but set of key/value. Wit this approach you can make object description language independent.

var credential_ids = {
    'User': 'username'
    , 'Password': 'password'
    , 'OK': 'imageField'
}


Login page

  • Menu items:

applMap.addElement('allPages', {
    name: 'menuitem'
    , description: 'Manage menuitem'
    , args: [
        {
            name: 'submenu'
            , description: 'the name of the submenu'
            , defaultValues: keys(menuitem_ids)
        }
    ]
    , getLocator: function(args) {
        var topic = menuitem_ids[args.submenu];
        return "//td[@id='menuItem_" + topic + "']/table/tbody/tr/td[2]/table/tbody/tr/td[1]";
    }
});

where menuitem_ids is:

var menuitems = [
    'Dashboard',
    'Manage',
    …
    'Language',
    'Help',
    'Logout'
];

Menu items

  • Table elements:

Table consists of several objects such as cells, column headers, filters etc. Cell intended to hold respective values. Clicking on the headers sort order can be specified for elements in the table. With filters search criteria can be determined. If some filters are not empty – all rows with values in corresponding columns that matched the criteria to be shown.

  • Cells:


applMap.addElement('allPages', {
    name: 'gridbox_cell'
    , description: 'A cell in the common gridbox'
    , args: [
        {
            name: 'row_index'
            , description: 'the index of the row'
            , defaultValues: range(2, 20)
        }
        , {
            name: 'column_index'
            , description: 'the index of the column'
            , defaultValues: range(1, 20)
        }
    ]
    , getLocator: function(args) {
        var xpath = "//div[@id='gridboxObjectBuffer']/table/tbody/tr[" + (args.row_index || 1) + "]/td[" + (args.column_index || 1) + "]";
        return xpath;
    }
});

This description is parameterized. Every cell can be defined using row and column ids.

  • Column headers:

Concerning column headers - two ways on how to get certain headers are shown below. First one implemented the way using column name while the second one – its id. You are free to use any of these ways depending on your needs. I admit that the second way has an issue – respective description is language dependent. It works fine for one language only. If you are going to test some application on different locales – this description should be updated in order to cover this case. Let’s keep it as is since it is minor, and respective solution has already been described above. I’m just adding this to the outstanding issues list.

applMap.addElement('allPages', {
    name: 'gridbox_header_by_index'
    , description: 'Column in the header of the common gridbox'
    , args: [
        {
            name: 'column_index'
            , description: "the index of the column in the gridbox header"
            , defaultValues: range(1, 20)
        }
    ]
    , getLocator: function(args) {
        var xpath = "//div[@id='gridboxHeader']/table/tbody/tr/td[1]/table/tbody/tr[2]/td[" + (args.column_index || 1) + "]/div";
        return xpath;
    }
});

applMap.addElement('allPages', {
    name: 'gridbox_header_by_name'
    , description: 'Column in the header of the common gridbox'
    , args: [
        {
            name: 'column_name'
            , description: "the index of the column in the gridbox header"
            , defaultValues: headers
        }
    ]
    , getLocator: function(args) {
        var xpath = "//div[@id='gridboxHeader']/table/tbody/tr/td[1]/table/tbody/tr/td/div[contains(text(),'" + args.column_name + "')]";
        return xpath;
    }
});

Filter description is nothing else but an object related to every column and can be reached by its id:

applMap.addElement('allPages', {
    name: 'gridbox_filter_criteria'
    , description: 'Filter criteria in the header of the common gridbox'
    , args: [
        {
            name: 'column_index'
            , description: "Text area for the filter criteria for the column in the gridbox header"
            , defaultValues: range(2, 20)
        }
    ]
    , getLocator: function(args) {
        var xpath = "//div[@id='gridboxHeader']/table/tbody/tr/td[1]/table/tbody/tr[3]/td[" + (args.column_index || 1) + "]/input";
        return xpath;
    }
});

Area entity objects list

  • Dialog box:


Any entity object can be registered/updated via respective popup dialog box. Area entity has such dialog window as well as others entities. This box includes primary and auxiliary properties (in our particular case located on General and Customer places tabs correspondingly). Also, there few button located on this box.

applMap.addElement('areaPages', {
    name: 'tabs_in_dialog_box'
    , description: "Tabs located in dialog box"
    , args: [
        {
            name: 'name'
            , description: 'the name of the tab'
            , defaultValues: keys(tab_ids)
        }
    ]
    , getLocator: function(args) {
        var xpath = "//div[@id='a_tabbar']/div/div[1]/div/div[" + tab_ids[args.name] + "]/div[3]/div";
        return xpath;
    }
});

applMap.addElement('areaPages', {
    name: 'dialog_box_area_property'
    , description: 'Filter criteria in the header of the dialog box'
    , args: [
        {
            name: 'property_name'
            , description: "Property for selected area in respective dialog box"
            , defaultValues: keys(area_property_ids)
        }
    ]
    , getLocator: function(args) {
        var xpath = "" + area_property_ids[args.property_name] + "";
        return xpath;
    }
});

Using the description above you may reach any property on General tab (i.e. Area name, Description and Other information). area_property_ids set of key/value lists possible items:

var area_property_ids = {
    'Area name': 'def'
    , 'Description': 'DESCRIPTION'
    , 'Other information': 'OTHERINFO'
}

applMap.addElement('areaPages', {
    name: 'buttons_in_dialog_box'
    , description: "Buttons located in dialog box appeared on top for an object"
    , args: [
        {
            name: 'name'
            , description: 'the name of the button'
            , defaultValues: [
                'Submit'
                , 'Close'
                , 'Check'
                , 'Check all'
                , 'Uncheck'
                , 'Uncheck all'
            ]
        }
    ]
    , getLocator: function(args) {
        var xpath = "//input[@type='button' and @value='" + args.name + "']";
        return xpath;
    }
});

var tab_ids = {
    'General': '1'
    , 'Customer places': '2'
}

It seems all objects required to record our simple scenario have been described using this wonderful UI-Element locator plug-in for Selenium IDE. Without this plug-in it will be too hard to make the automation readable and maintainable.


Dialog box for area entity object


  • Buttons in the dialog box:

Oh, forgot about one more object – this is that button which calls dialog box to register new area object. Here it is:

applMap.addElement('areaPages', {
    name: 'buttons_under_menu'
    , description: "Buttons located under main menu on top common grid"
    , args: [
        {
            name: 'name'
            , description: 'the name of the button'
            , defaultValues: button_names
        }
    ]
    , getLocator: function(args) {
        var xpath = "//td/table[contains(@title,'" + args.name + "')]";
        return xpath;
    }
});

var button_names = [
    'Add new area (Ins)'
    , 'Edit (Enter)'
    …
];

Initially, I was going to describe object repository and update simple test case in scope of one post. However as I can see it became to long. So it would be better to divide the material on two separate posts. Please see the updated test case in my later post.

Monday, September 6, 2010

Record simple test case in Selenium IDE

In scope of this post I will record a simple test case. It is first draft, first attempt to receive something that we can use in the web testing. Initially I do not suspect to have it represented in human readable format and not repeatable until objects repository is implemented. From first look the test application is nothing else but manage tool, including entities with respective relations. And simple test case can check if a user is able to create new object in the application. Also, it has some kind of authentication. Simple scenario I’m proposing to start with consists of the following steps:
  • Open home page of the application;
  • Log into the application;
  • Open objects list for area entity;
  • Create new item;
  • Log out from the application.
Most of these steps are complex from logic point of view (divided on several elementary actions). For instance login action consists of 3 steps like specify user name, specify user password and click on a button to login. To see objects list first you need to click on item from the main menu, second choose menu item in sub menu. Then objects list is shown. To create new item you have to click on respective button. After that wait for dialog box is opened, then specify meaningful properties, submit changes etc.

The main idea of this scenario is to receive set of steps to work with. So, let’s begin. First, open our test application in FireFox and Selenium IDE. Then, ensure that Selenium IDE is recording (red round button in the top right side is turned on). Then, step by step repeat the scenario above. Afterwards, at the picture below you may see the result I received on my PC:


Let’s review each action in details:

  1. First action from the list is open command. With this command we started from home page of the application;
  2. Then we filled user name value;
  3. Action that fills password value is not recorded for some reason. Let’s postpone with it for awhile. I suggest set low priority for the issues like this and move them to some "outstanding issues" list. And resolve them later;
  4. Next action clicks on a button that makes login operation. It clicks on the object known as imageField;
  5. selectFrame command is nothing else but just wait until dialog is opened. In this dialog all properties for new object to be specified;
  6. Next 3 type commands fill properties for the object (like name, description and other information) in opened dialog box;
  7. Last command from the list clicks Submit button on the dialog box. After that the dialog is closed, new object appeared in the objects list;
  8. At the very end I clicked on Logout button. Unfortunately. It has not been recorded as well.
Eventually, we received first draft of the test case with the scenario above. As you may see this is not human reader and not repeatable as expected from the very beginning:)

Tuesday, August 24, 2010

Setup test environment

This article will not be too long because installation of this framework is not time consuming task and easy enough. In general, the only information I used to prepare the environment was documentation located at their official site.

The installation by itself consists of 3 separate areas: Selenium Core, Selenium-RC and Selenium IDE. I downloaded most recent stable data (selenium-core-1.0.1.zip, selenium-remote-control-1.0.3.zip and selenium-ide-1.0.7.xpi correspondingly) from the Downloads page.
 
With Selenium Core and Selenium-RC I had no problems at all. For Selenium Core I copied core folder under Tomcat 6 (as it described in install-readme.txt from selenium-core-1.0.1.zip). Selenium-RC was configured basing on the article from the following page. There are two ways described on how to configure this component. One way is for Eclipse. Another one is for IntelliJ. You are free to use any way you want, but personally I prefer the second.
 
With Selenium IDE I have had few issues to resolve. One of the key requirements defined for the whole activity is that UI-Element should be included to. I red an article about this extension and found it very useful. It gives many benefits and corresponds to what we call "good programming practice". In UI-Element Locator article from extensions list I downloaded selenium-ide-ui-current.zip and tried to install it as Selenium IDE with UI Element Locator included to. Unfortunately, I have got some errors. From the article I found that the archive currently included is not up to date because it is based on some earlier release and the component should be rebuilt using most recent sources. Jumpstart: See UI-Element In Action article from the blog describes everything you need to get actual Selenium IDE with the feature included. However it is not enough to make it working. Unfortunately, current version of FireFox browser does not support this version of Selenium IDE (at least on the moment I was writing this post). Therefore, to solve this problem FireFox should be downgraded up to 3.5.*. After these modifications I’ve got working environment.

Eventually, we are ready to start recording our first test case. Also, please note, the application is being tested should be installed and configured in the environment as well;)

Wednesday, August 18, 2010

Planning our activity

Any activity begins from a plan. So let’s do so to have a clear understanding of how it should look like and which requirements it should achieve. Here is a brief list and preliminary order of steps we will go closely with. Also, I assume it is to be updated (corrected) in the future once we have any changes in the direction.
  1. Setup test environment;
  2. Record simple test case in Selenium IDE (excluding check and verification points);
  3. Prepare objects repository using Selenium UI-Element feature and update the test case accordingly;
  4. Specify basic scenario and define objects to be used in respective use case;
  5. Wrap up commands that represent a logic action into separate procedure. Update the test case accordingly;
  6. Make the test case repeatable;
  7. Migrate the test case to Selenium RC and make it workable;
  8. Run the test case included into a test suite from command line;
  9. Check an ability to run the test case under different browsers (such as FireFox, IE, Chrome);
  10. Add output control mechanism (insert check and verification points);
  11. Integrate the automated testing into continuous integration;
  12. Add meaningful test case in order to perform sanity testing.
Using this plan we'll try to cover meaningful areas of what the automation consists of. You may see that there are no verifications made at the very beginning. It is made intentionally. I have some thoughts about which I explain later.

Monday, August 9, 2010

Introduction

It is a secret to nobody that the success of the application depends largely on the quality of it. In other words: if it is fairly stable and its behavior is predictable - people will use it in their work, and will do this with gusto. Many people would agree that automated testing - an integral part of a successful product. That is why we all are wondering and trying to make this process as much convenient as possible and useful to work with.

As a guinea-pig, I took an application, developed by our programmers. One of the few weaknesses of this product is pour automation of the testing process or absent at all:). That is why my choice fell on it. I have been testing software last 6 years, and during that time managed to get acquainted with some products, which help to QA and Test-engineers very much in our arduous labor. Saying “arduous labor” I meant automated testing. Here are some of them: QTP, Rational Robot, Jmeter.

So, here is a task – implement automated testing for the application. A few weeks ago I was thinking about tool that would be useful for this activity, and came to the conclusion that the most suitable candidate for this is Selenium. Here are several reasons of it:
  • It is open-sourced tool;
  • More or less stable;
  • And the most important. I am new in the Selenium, therefore it is a good challenge to me:).

Eventually, we got to the main. There was an idea not only to setup the testing process from the very beginning but also to describe the whole process (including success and failures) and make it accessible on the public curt. I think it may be interesting for many people, especially for those who just started using Selenium suite. Also, feedback, comments, suggestions will be very much appreciated. Any suggestions are welcomed.

To be continued. Better details you will find in the future posts.