├── ParseSauceURL.py ├── README ├── SauceRest.py ├── SeleniumFactory.py ├── sauce_ondemand_test.py └── simple_test.py /ParseSauceURL.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | class ParseSauceURL: 4 | def __init__(self, url): 5 | self.url = url 6 | 7 | self.fields = {} 8 | fields = self.url.split(':')[1][1:].split('&') 9 | for field in fields: 10 | [key, value] = field.split('=') 11 | self.fields[key] = value 12 | 13 | def getValue(self, key): 14 | if key in self.fields: 15 | return self.fields[key] 16 | else: 17 | return "" 18 | 19 | def getUserName(self): 20 | return self.getValue("username") 21 | 22 | def getAccessKey(self): 23 | return self.getValue("access-key") 24 | 25 | def getJobName(self): 26 | return self.getValue("job-name") 27 | 28 | def getOS(self): 29 | return self.getValue("os") 30 | 31 | def getBrowser(self): 32 | return self.getValue('browser') 33 | 34 | def getBrowserVersion(self): 35 | return self.getValue('browser-version') 36 | 37 | def getFirefoxProfileURL(self): 38 | return self.getValue('firefox-profile-url') 39 | 40 | def getMaxDuration(self): 41 | try: 42 | return int(self.getValue('max-duration')) 43 | except: 44 | return 0 45 | 46 | def getIdleTimeout(self): 47 | try: 48 | return int(self.getValue('idle-timeout')) 49 | except: 50 | return 0 51 | 52 | def getUserExtensionsURL(self): 53 | return self.getValue('user-extensions-url') 54 | 55 | def toJSON(self): 56 | return json.dumps(self.fields, sort_keys=False) -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | SeleniumFactory for Python 2 | --------------------------- 3 | 4 | Simple interface factory to create Selenium objects, inspired by SeleniumFactory interface 5 | from https://github.com/infradna/selenium-client-factory. The main objective is to be able to have an automatic interface to easily run tests under the Bamboo Sauce Ondemand plugin as well as local tests. The factory object reads environments variables setup by the Bamboo plugin and creates a remote Sauce OnDemand session accordingly, otherwise it creates a local selenium configuration. 6 | 7 | Simple setup: 8 | from SeleniumFactory import * 9 | 10 | For selenium 2 webDriver: 11 | webDriver = SeleniumFactory().createWebDriver() 12 | 13 | For selenium 1 RC: 14 | browser = SeleniumFactory().create() 15 | 16 | Current state of code: 17 | 18 | Please note that the code is very new and still being developed. Please look at the code and make any improvements you feel fit. 19 | 20 | -------------------------------------------------------------------------------- /SauceRest.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | import base64 4 | 5 | url = 'https://saucelabs.com/rest/%s/%s/%s' 6 | 7 | """ 8 | This class provides several helper methods to invoke the Sauce REST API. 9 | """ 10 | class SauceRest: 11 | def __init__(self, user, key): 12 | self.user = user 13 | self.key = key 14 | 15 | def buildUrl(self, version, suffix): 16 | return url %(version, self.user, suffix) 17 | 18 | """ 19 | Updates a Sauce Job with the data contained in the attributes dict 20 | """ 21 | def update(self, id, attributes): 22 | url = self.buildUrl("v1", "jobs/" + id) 23 | data = json.dumps(attributes) 24 | return self.invokePut(url, self.user, self.key, data) 25 | 26 | """ 27 | Retrieves the details for a Sauce job in JSON format 28 | """ 29 | def get(self, id): 30 | url = self.buildUrl("v1", "jobs/" + id) 31 | return self.invokeGet(url, self.user, self.key) 32 | 33 | def invokePut(self, theurl, username, password, data): 34 | request = urllib2.Request(theurl, data, {'content-type': 'application/json'}) 35 | base64string = base64.encodestring('%s:%s' % (username, password))[:-1] 36 | request.add_header("Authorization", "Basic %s" % base64string) 37 | request.get_method = lambda: 'PUT' 38 | htmlFile = urllib2.urlopen(request) 39 | return htmlFile.read() 40 | 41 | def invokeGet(self, theurl, username, password): 42 | request = urllib2.Request(theurl) 43 | base64string = base64.encodestring('%s:%s' % (username, password))[:-1] 44 | request.add_header("Authorization", "Basic %s" % base64string) 45 | htmlFile = urllib2.urlopen(request) 46 | return htmlFile.read() -------------------------------------------------------------------------------- /SeleniumFactory.py: -------------------------------------------------------------------------------- 1 | import os 2 | from selenium import webdriver 3 | from selenium import selenium 4 | 5 | from ParseSauceURL import * 6 | from SauceRest import * 7 | 8 | """ 9 | This class wraps a webdriver/selenium instance. It delegates most method calls to the underlying webdriver/selenium 10 | instance, and provides some helper methods to set the build number and job status using the Sauce REST API. 11 | 12 | It also outputs the Sauce Session ID, which will be parsed by the Jenkins/Bamboo plugins so as to associate the CI build with 13 | the Sauce job. 14 | """ 15 | class Wrapper: 16 | def __init__(self, selenium, parse): 17 | self.__dict__['selenium'] = selenium 18 | self.username = parse.getUserName() 19 | self.accessKey = parse.getAccessKey() 20 | self.jobName = parse.getJobName() 21 | 22 | def id(self): 23 | if hasattr(self.selenium, 'session_id'): 24 | return self.selenium.session_id 25 | else: 26 | return self.selenium.sessionId 27 | 28 | def dump_session_id(self): 29 | print "\rSauceOnDemandSessionID=%s job-name=%s" % (self.id(), self.jobName) 30 | 31 | def set_build_number(self, buildNumber): 32 | sauceRest = SauceRest(self.username, self.accessKey) 33 | sauceRest.update(self.id(), {'build': buildNumber}) 34 | 35 | def job_passed(self): 36 | sauceRest = SauceRest(self.username, self.accessKey) 37 | sauceRest.update(self.id(), {'passed': True}) 38 | 39 | def job_failed(self): 40 | sauceRest = SauceRest(self.username, self.accessKey) 41 | sauceRest.update(self.id(), {'passed': False}) 42 | 43 | # automatic delegation: 44 | def __getattr__(self, attr): 45 | return getattr(self.selenium, attr) 46 | 47 | def __setattr__(self, attr, value): 48 | return setattr(self.selenium, attr, value) 49 | 50 | """ 51 | Simple interface factory to create Selenium objects, inspired by the SeleniumFactory interface 52 | from https://github.com/infradna/selenium-client-factory for Java. 53 | 54 |
55 | Compared to directly initializing {@link com.thoughtworks.selenium.DefaultSelenium}, this additional indirection 56 | allows the build script or a CI server to control how you connect to the selenium. 57 | This makes it easier to run the same set of tests in different environments without 58 | modifying the test code. 59 | 60 |
61 | This is analogous to how you connect to JDBC — you normally don't directly 62 | instantiate a specific driver, and instead you do {@link DriverManager#getConnection(String)}. 63 | """ 64 | class SeleniumFactory: 65 | def __init__(self): 66 | pass 67 | 68 | """ 69 | Uses a driver specified by the 'SELENIUM_DRIVER' environment variable, 70 | and run the test against the domain specified in 'SELENIUM_URL' system property or the environment variable. 71 | If no variables exist, a local Selenium driver is created. 72 | """ 73 | def create(self): 74 | if 'SELENIUM_STARTING_URL' not in os.environ: 75 | startingUrl = "http://saucelabs.com" 76 | else: 77 | startingUrl = os.environ['SELENIUM_STARTING_URL'] 78 | 79 | if 'SELENIUM_DRIVER' in os.environ and 'SELENIUM_HOST' in os.environ and 'SELENIUM_PORT' in os.environ: 80 | parse = ParseSauceURL(os.environ["SELENIUM_DRIVER"]) 81 | driver = selenium(os.environ['SELENIUM_HOST'], os.environ['SELENIUM_PORT'], parse.toJSON(), startingUrl) 82 | driver.start() 83 | 84 | if parse.getMaxDuration() != 0: 85 | driver.set_timeout(parse.getMaxDuration()) 86 | 87 | wrapper = Wrapper(driver, parse) 88 | wrapper.dump_session_id() 89 | return wrapper 90 | else: 91 | driver = selenium("localhost", 4444, "*firefox", startingUrl) 92 | driver.start() 93 | return driver 94 | 95 | """ 96 | Uses a driver specified by the 'SELENIUM_DRIVER' system property or the environment variable, 97 | and run the test against the domain specified in 'SELENIUM_STARTING_URL' system property or the environment variable. 98 | If no variables exist, a local Selenium web driver is created. 99 | """ 100 | def createWebDriver(self): 101 | if 'SELENIUM_STARTING_URL' not in os.environ: 102 | startingUrl = "http://saucelabs.com" 103 | else: 104 | startingUrl = os.environ['SELENIUM_STARTING_URL'] 105 | 106 | if 'SELENIUM_DRIVER' in os.environ and 'SELENIUM_HOST' in os.environ and 'SELENIUM_PORT' in os.environ: 107 | parse = ParseSauceURL(os.environ["SELENIUM_DRIVER"]) 108 | 109 | desired_capabilities = {} 110 | if parse.getBrowser() == 'android': 111 | desired_capabilities = webdriver.DesiredCapabilities.ANDROID 112 | elif parse.getBrowser() == 'googlechrome': 113 | desired_capabilities = webdriver.DesiredCapabilities.CHROME 114 | elif parse.getBrowser() == 'firefox': 115 | desired_capabilities = webdriver.DesiredCapabilities.FIREFOX 116 | elif parse.getBrowser() == 'htmlunit': 117 | desired_capabilities = webdriver.DesiredCapabilities.HTMLUNIT 118 | elif parse.getBrowser() == 'iexplore': 119 | desired_capabilities = webdriver.DesiredCapabilities.INTERNETEXPLORER 120 | elif parse.getBrowser() == 'iphone': 121 | desired_capabilities = webdriver.DesiredCapabilities.IPHONE 122 | else: 123 | desired_capabilities = webdriver.DesiredCapabilities.FIREFOX 124 | 125 | desired_capabilities['version'] = parse.getBrowserVersion() 126 | 127 | if 'SELENIUM_PLATFORM' in os.environ: 128 | desired_capabilities['platform'] = os.environ['SELENIUM_PLATFORM'] 129 | else: 130 | #work around for name issues in Selenium 2 131 | if 'Windows 2003' in parse.getOS(): 132 | desired_capabilities['platform'] = 'XP' 133 | elif 'Windows 2008' in parse.getOS(): 134 | desired_capabilities['platform'] = 'VISTA' 135 | elif 'Linux' in parse.getOS(): 136 | desired_capabilities['platform'] = 'LINUX' 137 | else: 138 | desired_capabilities['platform'] = parse.getOS() 139 | 140 | desired_capabilities['name'] = parse.getJobName() 141 | 142 | command_executor="http://%s:%s@%s:%s/wd/hub"%(parse.getUserName(), parse.getAccessKey( 143 | ), os.environ['SELENIUM_HOST'], os.environ['SELENIUM_PORT']) 144 | 145 | #make sure the test doesn't run forever if if the test crashes 146 | if parse.getMaxDuration() != 0: 147 | desired_capabilities['max-duration'] = parse.getMaxDuration() 148 | desired_capabilities['command-timeout'] = parse.getMaxDuration() 149 | 150 | if parse.getIdleTimeout() != 0: 151 | desired_capabilities['idle-timeout'] = parse.getIdleTimeout() 152 | 153 | driver=webdriver.Remote(desired_capabilities=desired_capabilities, command_executor=command_executor) 154 | driver.get(startingUrl) 155 | wrapper = Wrapper(driver, parse) 156 | wrapper.dump_session_id() 157 | return wrapper 158 | 159 | else: 160 | return webdriver.Firefox() 161 | -------------------------------------------------------------------------------- /sauce_ondemand_test.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | import unittest, time, re 3 | import os 4 | import json 5 | 6 | from ParseSauceURL import * 7 | from SeleniumFactory import * 8 | 9 | """ 10 | """ 11 | class testSauceWrappers(unittest.TestCase): 12 | username = "rossco_9_9" 13 | access_key = "blah-blah-blah" 14 | def setUp(self): 15 | self.url = "sauce-ondemand:?username=%s&access-key=%s&job-name=simple test&os=Linux&browser=firefox&browser-version=7&firefox-profile-url=&idle-timeout=90&user-extensions-url=" % (self.username, self.access_key) 16 | os.environ["SELENIUM_DRIVER"] = self.url 17 | os.environ["SELENIUM_PORT"] = "80" 18 | os.environ["SELENIUM_HOST"] = "ondemand.saucelabs.com" 19 | os.environ["SELENIUM_STARTING_URL"]= "http://www.amazon.com" 20 | 21 | def retrieve_job_details(self, browser): 22 | sauceRest = SauceRest(self.username, self.access_key) 23 | result = sauceRest.get(browser.id()) 24 | data = json.loads(result) 25 | return data 26 | 27 | def test_webdriver_success(self): 28 | 29 | browser = SeleniumFactory().createWebDriver() 30 | browser.get("http://amazon.com") 31 | assert "Amazon.com" in browser.title 32 | browser.job_passed() 33 | data = self.retrieve_job_details(browser) 34 | assert data['passed'] 35 | browser.quit() 36 | 37 | def test_webdriver_failed(self): 38 | 39 | browser = SeleniumFactory().createWebDriver() 40 | browser.get("http://amazon.com") 41 | assert "Amazon.com" in browser.title 42 | browser.job_failed() 43 | data = self.retrieve_job_details(browser) 44 | assert not data['passed'] 45 | browser.quit() 46 | 47 | def test_selenium_success(self): 48 | browser = SeleniumFactory().create() 49 | browser.open("http://www.amazon.com") 50 | assert "Amazon.com" in browser.get_title() 51 | browser.job_passed() 52 | data = self.retrieve_job_details(browser) 53 | assert data['passed'] 54 | browser.stop() 55 | 56 | def test_selenium_failed(self): 57 | browser = SeleniumFactory().create() 58 | browser.open("http://www.amazon.com") 59 | assert "Amazon.com" in browser.get_title() 60 | browser.job_failed() 61 | data = self.retrieve_job_details(browser) 62 | assert not data['passed'] 63 | browser.stop() 64 | 65 | if __name__ == "__main__": 66 | unittest.main() -------------------------------------------------------------------------------- /simple_test.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | import unittest, time, re 3 | import os 4 | 5 | from ParseSauceURL import * 6 | from SeleniumFactory import * 7 | 8 | class testParseSauceURL(unittest.TestCase): 9 | def setUp(self): 10 | self.url = "sauce-ondemand:?username=foobar&access-key=1234-5678-9102-3456&job-name=simple test&os=Linux&browser=firefox&browser-version=7&firefox-profile-url=&max-duration=300&idle-timeout=90&user-extensions-url=" 11 | 12 | def test_parse(self): 13 | parse = ParseSauceURL(self.url) 14 | self.assertEqual("foobar", parse.getUserName()) 15 | self.assertEqual("1234-5678-9102-3456", parse.getAccessKey()) 16 | self.assertEqual("simple test", parse.getJobName()) 17 | self.assertEqual("Linux", parse.getOS()) 18 | self.assertEqual("firefox", parse.getBrowser()) 19 | self.assertEqual("7", parse.getBrowserVersion()) 20 | self.assertEqual("", parse.getFirefoxProfileURL()) 21 | self.assertEqual(300, parse.getMaxDuration()) 22 | self.assertEqual(90, parse.getIdleTimeout()) 23 | self.assertEqual("", parse.getUserExtensionsURL()) 24 | 25 | 26 | 27 | class testSelenium2(unittest.TestCase): 28 | def setUp(self): 29 | self.browser = SeleniumFactory().createWebDriver() 30 | 31 | def test_get(self): 32 | self.browser.get("http://amazon.com") 33 | assert "Amazon.com" in self.browser.title 34 | 35 | def tearDown(self): 36 | self.browser.quit() 37 | 38 | 39 | class testSelenium1(unittest.TestCase): 40 | def setUp(self): 41 | self.browser = SeleniumFactory().create() 42 | 43 | def test_open(self): 44 | self.browser.open("http://amazon.com") 45 | assert "Amazon.com" in self.browser.get_title() 46 | 47 | def tearDown(self): 48 | self.browser.stop() 49 | 50 | 51 | if __name__ == "__main__": 52 | unittest.main() 53 | --------------------------------------------------------------------------------