Skip to content
stanislawbartkowski edited this page Apr 8, 2015 · 1 revision

Add-on to BoaTester supporting selenium based test of web applications.

Introduction

Selenium: http://seleniumhq.org/ is grown-up and well known framework for testing web applications. During my work with that framework I came to the conclusion that test steps written as sequence of python statement is not a good idea and decided to create a simple extension to the selenium which replaces this part of test with simple text file.

General idea

Test steps

The purpose is to replace sequence of python statements like:

        self.assertTrue(sel.is_element_present("id=ACCEPT"),"Element not found")
        self.assertTrue(sel.is_element_present("xpath=//option[@value='hotel']"),"Element not found")
        sel.click("id=ACCEPT")
        sel.type("PASSWORD", "osoba")
        sel.type("LOGINNAME, "osoba")
        sel.click("id=ACCEPT")
        self.assertTrue(sel.is_element_present("xpath=//td[@class='user']//div[text()='osoba']"),"Element not found"

with something like (lines from text file):

[testcase1]
waitFor : #ACCEPT
waitFor : xpath=//option[@value='hotel']|20
-- Test Case 1
-- Click on 'Accept' and error 'Login name is empty' is expected
click : #ACCEPT
mouseOver : #PASSWORD
-- Test Case 2
-- enter osoba and osoba as login name and password and click 'Accept'
-- login should be done and 'osoba' login name should appeat at the top panel
type : #LOGINNAME|osoba
type : #PASSWORD|osoba
click : #ACCEPT
waitFor : xpath=//td[@class='user']//div[text()='osoba']

and python statement:

   runtest['testcase']

Aliases

Some elements (for instance button) are used more than once. So aliases are necessary in order to avoid typing exact element selector (for instance "id=ACCEPT") and use alias name (#ACCEPT) instead. No problem with simple selector identifier but in case of complicated identifier ("xpath=//div2[text='aaa']//td ...") it could save time and lower the risk of inproper typing. Also in case of changing the element identifiers - very likely after changing the screen layout - it is enough to change only the alias definition thus leaving the test steps related to this element unchanged.

Common elements

Very often test cases are sharing some common test elements and definitions. For instance:

Test Case 1:

Login to the application
Run test scenario 1

Test Case 2:

Login to the application
Run test scenario 2

In order to avoid duplicating the same login sequence it is necessary to create a simple 'common' resource containing reusable elements.

Keep it simple

The purpose is to simplify only one aspect of running selenium test in python. The purpose is not to cover all selenium or to create another test framework. So there is no automation for starting selenium server, opening and starting selenium main object. The test case can be also partially automated by this extension, it is possible to run one fragment of test steps by this automation and the rest by the regular python module.

Only a subset of selenium command is covered. But it is very simple to extend this subset by adding more commands.

Details, SeleniumHelper file

General information

The resource used by SeleniumHelper is simple text file divided into different section identified by name tag.

The file structure

The general structure is as follows:

[alias]
.. alial section ..
[test name 1]
.. test step for 'test name1' 
[test name 2]
.. test steps for 'test name 2'
[.....]
etc

Blank lines are ignored. Comment is a line starting with --. For example:

[test1]
-- Test Case scenario:
-- Test steps:
-- Login as U/P person/tosecret
-- Verification:
-- Login passed
type : #LOGINNAME|person
..

Alias section

Everything between alias and the next section or the end of file. Only one alias section is allowed or there could be no any alias section at all.

[alias]
alias id1 : alias definition 1
alias id2 : alias definition 2
....
end of file or the next section

Definition of one alias

alias1 : definition of alias

Example:

[alias]
ACCEPT : id=ACCEPT
PASSWORD : PASSWORD
LOGINNAME : LOGINNAME

[test]
...

Here we have three aliases: ACCEPT with definition 'id=ACCEPT', PASSWORD with defintion 'PASSWORD' and LOGINNAME as 'LOGINNAME'.

Trailing spaces for identifier and definition are removed. So

ACCEPT : id=ACCEPT
 ACCEPT   :id=ACCEPT

ACCEPT   :    id=ACCEPT

describe the same alias.

Test steps section

General

Everything from name to the next section or to the end of file. Test name cannot be 'alias'. Test steps contain series of lines describing one test step. Steps are executed in top-bottom order. In case of an error or verification failure exception is raised. Verification failure raises 'unittest' failure : (http://docs.python.org/library/unittest.html)

[test name 1]
actionid : parameter1|parameter2| ... | parametern
.....
next section or the end of file

Test step line

actionid : parameter1|parameter2| ... | parametern

Test step name and zero, one or more parameters separated by | character.

For instance:

waitFor : xpath=//option[@value='hotel']|20

Test step name is waitFor and it is used with two parameters xpath=//option[@value='hotel']" and20

Important: spaces are significant. In this example it space at the beginning of the first parameter does not make any difference but in case of typing form values it could make difference.

List of actions

Important: By 'element selector' I mean the way of locating elements in the html page using method described in Selenium doc: http://seleniumhq.org/docs/04_selenese_commands.html#locating-elements

Name Description Parameter 1 Parameter 2
type Types a text into html element Element selector Text to be typed in (warning: trailing spaces are significant
click Click an element Element selector
sleep Wait for a number of seconds Number of seconds
waitFor Waits for an element on the screen. Failure if element not found and time - number of seconds expires Number of seconds (default: 5 second)
debug Does nothing, placeholder for debug breakpoint
verEqual Verification, compares contents of element with text given, failures test if not equal Element selector Text to be compared with (warning: trailing spaces are significant
isPresent Verifies if element is on the screen, failures test otherwise Element selector
mouseOver Positions mouse on the element Element selector
call Call another piece of test. This way some tests can be reusable The name of test to call.
waitForNot Opposite to waitFor. Waits until the element on the screen disappears Number of seconds (default: 5)
select Selecting in 'select' tag. For some reason 'click' does not work Selector for 'select' tag Value to be selected

How to use

Test case directory structure

http://code.google.com/p/boatester/wiki/GettingStarted#How_tests_are_organized

TestResource
  test.properties
  selenium.file
  { common SeleniumHelper file }
  Test1
    test.properties
    selenium.file
    { local SeleniumHelper file}

test.properties file (global and local) should contain selFile property. The value is the name of SeleniumHelper file (global or local}

...
selFile=selenium.file
...

It is not necessary to have both local and global SeleniumHelper file defined.

How to run selenium based test

Initialization

http://seleniumhq.org/docs/05_selenium_rc.html#chapter05-reference

Selenium server should be started manually outside BoaTester.

Declaration

There are two ways to run selenium base test: automatically and using 'python' starter

Run selenium test case automatically

This test is launched if in the 'test.properties' file 'selFile' property is present and 'testcase' property is absent. But in order to initialize 'selenium' object additional properties should be defined (parameters for constructor http://selenium.googlecode.com/svn/trunk/docs/api/py/selenium/selenium.selenium.html).

Property Value
host 'host' constructor parameter
port integer, 'port' constructor parameter
browser 'browserStartCommand'
http 'browserURL'

Example (pay attention to the lack of 'testcase' property)

[defaults]
selfile=selenium.file
host=localhost
port=4444
browser=*firefox
http=http://localhost:8080/TomcatJPAApplication/

Important: 'host','port',browser' and 'http' can be places in common 'test.properties' file. This way it can be shared between all test cases.

Use 'python' test case activator

http://code.google.com/p/boatester/source/browse/trunk/TestBoaHarness/src/TestResource/Test6/

It is a definition of python test case.

test.properties

[defaults]
descr=Test custom test case
testcase=Test6Case
selFile=selenium.file

It is also possible to read python test case file from another location. Directory is defined by the parameter 'dirtestcase'. If this parameter is provided then 'testcase' is read from directory 'dirtestcase' - not from test case resource directory. It makes sense if several test cases are using the same python test case file and differs only by scenario defined in 'selenium.file'. Example: test.properties

[defaults]
descr=Test custom test case
testcase=CommonTestCase
dirtestcase=dirtestcase=%(globresdir)s
selFile=selenium.file

This python test case CommonTestCase is read from 'dirtestcase' directory - although the selenium file is read from test case resource directory

Test case code

Test6Case

import unittest
import Test6Case
import TestCaseHelper
import os
from selenium import selenium
import SeleniumHelper


def injectParam(param,tepar) :
    Test6Case.param = param
    Test6Case.tepar = tepar

class TestSuite(unittest.TestCase):

    def setUp(self):
       self.selenium = selenium("localhost", 4444, "*firefox", "http://hotelnajavie.appspot.com/")
       self.selenium.start()
       self.seHelper = SeleniumHelper.SeleniumHelper(self.selenium)
       self.seHelper.setParam(self,Test6Case.param,Test6Case.tepar)

    def testCase1(self):
       self.selenium.open("")
       self.seHelper.runTest('test')


    def tearDown(self) :
        pass

It is a standard testunit test case. This test is run open source project.

http://hotelnajavie.appspot.com/

setUp method. Initialize and start selenium object following the selenium doc. Then intialize and pass parameters to SeleniumHelper objet.

testCase method. Open selenium and run test by launching one command. That is all !

Test example

Pay attention that test name test is the same as parameter to 'runTest' command.

http://code.google.com/p/boatester/source/browse/trunk/TestBoaHarness/src/TestResource/Test6/selenium.file

Additional info

As we can see the SeleniumHelper only automates the part of selenium testing, it not replaces the framework. It is not a problem to run a part of the test case using that automation than continue using standard python code. We can use this automation more than one in the test. So it is quite reasonable to call it a "Helper" - it really helps with the test preparation.

API description

SeleniumHelper

class SeleniumHelper.SeleniumHelper

Constructor

Reference to selenium object

setParam

Should be called once before running any test. Three parameters: Reference to unitest object, TestParam and OneTestParam object

runTest

Run the test. One parameter: test name. Test can be defined in the global or local SeleniumHelper file. In any error has occurred or verification failed the exception is raised.

How to extend set of actions or write custom actions

Extend SeleniumHelper.seleniumTypeContext

Write own class being the extension of SeleniumHelper.seleniumTypeContext object.

Overload 'do' method

The 'do' method can make usage of the following attributes.

Attribute Description
self. cparam List of actual parameters. All parameter are string
self.se selenium object
self.tcase unittest.TestCase class containing this test
self.param TestParam container
tepar OneTestParam container

Example:

class seleniumType(seleniumTypeContext) :
    """ Action class for 'type' action 
    First parameter - element selector
    Second parameter - string to be typed in
    """
    def do(self):
        self.se.type(self.cparam[0], self.cparam[1])

Register your action

Run 'registerAction' method of 'SeleniumHelper.SeleniumHelper' class

registerAction method.

  • key : action name
  • action : reference to action object
  • nof : maximum number of parameter (default 1)
  • defa : list of default values used if number of actual parameter is less than maximum number (default none)
  he.registerAction('type',seleniumType(),2)

Hints and tips

Wait at the beginning

You have to wait until dialog or windows is opened. It is difficult to tell how much time it needs.

Probably will not work:

[alias]
TOMAINBUTTON:id=tomain
GLOBINT:globint
[start]
waitFor:#TOMAINBUTTON
type:#GLOBINT|987
verEqual:#GLOBINT|987

The reason is that selenium start immediately typing value regardless if dialog is formed already or not

Will work:

[start]
waitFor:#TOMAINBUTTON
waitFor:#BLOBINT
sleep:1
type:#GLOBINT|987
verEqual:#GLOBINT|987

Check boxes

Change check boxes by clicking

[alias]
TOMAINBUTTON:id=tomain
CHECKBOOL:globbool
[start]
waitFor:#TOMAINBUTTON
waitFor:#CHECKBOOL
................
click:#CHECKBOOL

Verify check box by on/off value

click:#CHECKBOOL
verEqual:#CHECKBOOL|off