Sunday, 18 July 2010

Selenium Grid Part 3 - Integrating Selenium Python tests with Hudson and having them run in parallel

In part 3 of the my Selenium Grid series I will be looking at using Python as the scripting language to write the tests. I am borrowing the ideas from Mozilla QA's testing of their sites. Their SVN server has some Python base Selenium scripts (SUMO SVN and AMO SVN). I saw an interesting video of them talk about Mozilla talking about their use of Selenium at a Conference.

Firstly down Selenium RC and from the zip file copy /selenium-python-client-driver-1.0.1/selenium.py and place that in your test directory. This is the library file for allowing Python to talk to the Selenium Grid Hub. Another file that you will need is XML Test Runner.  This XML Test Runner is an extension to Python's built in Unit Testing that will output jUnit style XML reports that Hudson can read and display the results for.

The test suite I am setting out below will run the tests in parallel and has provisions to do more in the future. It can be set up in the future to run different sets of tests, run all the tests in different browsers and has some early examples of external configuration and common functions.

So in my Selenium job in Hudson I changed the execute shell to be: ./Swrunner.py and said that the published jUnit test results are *.xml. I also had to chmod the working directory to give the Hudson user permission to write out the jUnit results out to this directory. One other change I made was to stop using the Selenium Grid Hudson plugin as that seem to have issues with running more tests than there were available nodes, so I fixed this by like in Part 1 of just running the Hub from the command line and that seems to work well.

You can either see the scripts below to download them in a zip file.

selenium.py - from Selenium RC zip file

SWconfig.py:
'''
Created on 17 July 2010
@author: Dave

These are connection details

Idea from: http://viewvc.svn.mozilla.org/vc/projects/sumo/tests/frontend/python_tests/vars.py?view=markup
'''

class connectionParameters:
    server = '172.24.128.200'
    port = 4444
    baseurl = 'http://shihadwiki.com'
    page_load_timeout = 600000
    browser = '*firefox'

SWfunctions.py:
'''
Created on 17 July 2010
@author: Dave

These are some generic function examples for running ShihadWiki.com
'''

     
def openSite(sel, page_load_timeout):
    sel.open("/wiki/Main_Page")
    sel.wait_for_page_to_load(page_load_timeout)
    
def clickMenu(sel, link, page_load_timeout):
    sel.click('//li[@id=\'n-%s\']/a' % (link))
    sel.wait_for_page_to_load(page_load_timeout)

SWrunner.py:
#!/usr/bin/python
'''
Created on 17 July 2010
@author: Dave

This is the test runner to run all the tests

Idea from: http://viewvc.svn.mozilla.org/vc/projects/sumo/tests/frontend/python_tests/suite_sumo.py?view=markup
Idea from: http://saucelabs.com/blog/index.php/2009/09/running-your-selenium-tests-in-parallel-python/
'''

import SWfunctions
from SWconfig import connectionParameters
from SWtestList import testList
import sys
from subprocess import Popen


args = sys.argv

type = None

if len(args) < 2:
    type = 'smoke'
else:
    type = args[1]

tests = []    

if type == 'smoke':
    tests = testList.smoke_list
elif type == 'full':
    tests = testList.full_list

browsers = []
browsers.append('*firefox')

processes = []

for browser in browsers:
    vars = connectionParameters
    for test in tests:
        processes.append(Popen('python %s %s %s %s %s %s' % (test, vars.server, vars.port, browser, vars.baseurl, vars.page_load_timeout), shell = True))
        
for process in processes:
    process.wait()

SWtestList.py:
'''
Created on 17 July 2010
@author: Dave

These are the tests to execute

Idea from: http://viewvc.svn.mozilla.org/vc/projects/sumo/tests/frontend/python_tests/tests.py?view=markup
'''

class testList():

    smoke_list = []
    #smoke_list.append(file-name.py)
    smoke_list.append('test-gigs.py')
    smoke_list.append('test-songs.py')
    smoke_list.append('test-discography.py')   
    
    full_list = []
    full_list.extend(smoke_list)

test-discography.py:
from selenium import selenium
import unittest, time, re
import sys
import SWfunctions
from  xmlrunner import *

host = None
port = None
broswer = None
baseurl = None
page_load_timeout = None

class test_discography(unittest.TestCase):
    
    def setUp(self):
        self.verificationErrors = []
        self.selenium = selenium(host, port, broswer, baseurl)        
        self.selenium.start()
        self.selenium.set_timeout(page_load_timeout)
        
    
    def test_discography(self):
        sel = self.selenium
        SWfunctions.openSite(sel, page_load_timeout)
        SWfunctions.clickMenu(sel, 'Discography', page_load_timeout)
        self.assertEqual("Discography - Shihad Wiki", sel.get_title())
        try: self.failUnless(sel.is_text_present("Discogarphy"))
        except AssertionError, e: self.verificationErrors.append(str(e))
    
    def tearDown(self):
        self.selenium.stop()
        self.assertEqual([], self.verificationErrors)

if __name__ == '__main__':
    args = sys.argv
    host = args[1]
    port  = args[2]
    broswer = args[3]
    baseurl = args[4]
    page_load_timeout = args[5]
    suite = unittest.TestSuite()
    suite.addTest(test_discography('test_discography'))
    #suite.addTest(test_songs.test_songs)
    #unittest.TextTestRunner().run(suite)
    runner = XMLTestRunner(file('results_test_discography_%s.xml' % (broswer), "w"))
    runner.run(suite)

test-gigs.py
from selenium import selenium
import unittest, time, re
import sys
import SWfunctions
from  xmlrunner import *

host = None
port = None
broswer = None
baseurl = None
page_load_timeout = None

class test_gigs(unittest.TestCase):
    
    def setUp(self):
        self.verificationErrors = []
        self.selenium = selenium(host, port, broswer, baseurl)        
        self.selenium.start()
        self.selenium.set_timeout(page_load_timeout)
        
    
    def test_gigs(self):
        sel = self.selenium
        SWfunctions.openSite(sel, page_load_timeout)
        SWfunctions.clickMenu(sel, 'Gigs', page_load_timeout)
        self.assertEqual("Gigs - Shihad Wiki", sel.get_title())
        try: self.failUnless(sel.is_text_present("Gigs"))
        except AssertionError, e: self.verificationErrors.append(str(e))
    
    def tearDown(self):
        self.selenium.stop()
        self.assertEqual([], self.verificationErrors)

if __name__ == '__main__':
    args = sys.argv
    host = args[1]
    port  = args[2]
    broswer = args[3]
    baseurl = args[4]
    page_load_timeout = args[5]
    suite = unittest.TestSuite()
    suite.addTest(test_gigs('test_gigs'))
    #unittest.TextTestRunner().run(suite)
    #runner = xmlrunner.XmlTestRunner(sys.stdout)
    runner = XMLTestRunner(file('results_test_gigs_%s.xml' % (broswer), "w"))
    runner.run(suite)

test-songs.py
from selenium import selenium
import unittest, time, re
import sys
import SWfunctions
from  xmlrunner import *

host = None
port = None
broswer = None
baseurl = None
page_load_timeout = None

class test_songs(unittest.TestCase):
    
    def setUp(self):
        self.verificationErrors = []
        self.selenium = selenium(host, port, broswer, baseurl)        
        self.selenium.start()
        self.selenium.set_timeout(page_load_timeout)
        
    
    def test_songs(self):
        sel = self.selenium
        SWfunctions.openSite(sel, page_load_timeout)
        SWfunctions.clickMenu(sel, 'Songs', page_load_timeout)
        self.assertEqual("Category:Songs - Shihad Wiki", sel.get_title())
        try: self.failUnless(sel.is_text_present("Songs"))
        except AssertionError, e: self.verificationErrors.append(str(e))
    
    def tearDown(self):
        self.selenium.stop()
        self.assertEqual([], self.verificationErrors)

if __name__ == '__main__':
    args = sys.argv
    host = args[1]
    port  = args[2]
    broswer = args[3]
    baseurl = args[4]
    page_load_timeout = args[5]
    suite = unittest.TestSuite()
    suite.addTest(test_songs('test_songs'))
    #suite.addTest(test_songs.test_songs)
    #unittest.TextTestRunner().run(suite)
    runner = XMLTestRunner(file('results_test_songs_%s.xml' % (broswer), "w"))
    runner.run(suite)

xmlrunner.py - from http://www.rittau.org/python/


The Series:

Upcoming ideas for the series:
  • Putting the test suite in SVN and running from there

2 comments:

Preston said...

wow. i am impressed. keep it up.

SMERSH009X said...

Great Posts!!

I've been having a hard time getting the steps to work on Windows, but I was able to resolve one of the issues:

Traceback (most recent call last):
File "test-songs.py", line 45, in
runner = XMLTestRunner(file('results_test_songs_%s.xml' % (broswer), "w"))
IOError: [Errno 22] invalid mode ('w') or filename: 'results_test_songs_*firefox.xml'

Windows doesn't like the "*" in the name, so to fix this you have to go to each file that is having the error, such as test-songs.py , and change from
runner = XMLTestRunner(file('results_test_songs_%s.xml' % (broswer), "w"))

to:
runner = XMLTestRunner(file('results_test_songs_%s.xml' % (broswer[1:-1]), "w"))

I will post my updates on other issues I faced as well.