Created
August 20, 2012 21:44
-
-
Save maffoo/3408177 to your computer and use it in GitHub Desktop.
Javascript Test Runner for JSTD + Sauce Connect + PhantomJS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import contextlib, optparse, os, subprocess, sys, tempfile, time | |
from selenium import webdriver | |
# sauce labs browser and os names | |
# see https://saucelabs.com/docs/ondemand/browsers | |
SAUCE_BROWSER = { | |
'chrome': webdriver.DesiredCapabilities.CHROME, | |
'firefox': webdriver.DesiredCapabilities.FIREFOX, | |
'ie': webdriver.DesiredCapabilities.INTERNETEXPLORER, | |
'opera': webdriver.DesiredCapabilities.OPERA | |
} | |
SAUCE_OS = { | |
'xp': 'Windows 2003', | |
'winxp': 'Windows 2003', | |
'win2003': 'Windows 2003', | |
'vista': 'Windows 2008', | |
'win2008': 'Windows 2008', | |
'linux': 'Linux' | |
} | |
class Server(object): | |
"""Base class for servers that run external processes in a with-statement""" | |
def __exit__(self, exc_type, exc_val, exc_tb): | |
if self._process: | |
try: | |
print "shutting down {0}...".format(self) | |
self._process.terminate() | |
self._process.wait() | |
self._process = None | |
except Exception: | |
pass | |
class JstdServer(Server): | |
def __init__(self, host, port, jar, run): | |
self.host = host | |
self.port = port | |
self.jar = jar | |
self.run = run | |
self.url = "http://{host}:{port}".format(host=host, port=port) | |
self.capture_url = self.url + "/capture" | |
self._process = None | |
def __enter__(self): | |
"""Run a managed jstd server in the context of a with-statement""" | |
if not self.run: | |
return self | |
if self._process: | |
raise Exception('already running') | |
args = ["java", "-jar", self.jar, "--port", str(self.port)] | |
print "starting jstd..." | |
print " ".join(args) | |
self._process = subprocess.Popen(args) | |
time.sleep(20) # TODO: actually check to see that the server is up | |
return self | |
def run_tests(self, config, tests, output_dir): | |
"""Run tests on all captured browsers using the given config file and outputting to the specified results directory""" | |
args = ["java", "-jar", self.jar, "--server", self.url, "--config", config, "--tests", tests, "--testOutput", output_dir] | |
subprocess.check_call(args) | |
class SauceServer(Server): | |
def __init__(self, user, key, host, port, jar, run): | |
self.user = user | |
self.key = key | |
self.host = host | |
self.port = port | |
self.jar = jar | |
self.run = run | |
self.url = "http://{user}:{key}@{host}:{port}/wd/hub".format(user=user, key=key, host=host, port=port) | |
self._process = None | |
def __enter__(self): | |
"""Run a managed Sauce Connect instance in the context of a with-statement""" | |
if not self.run: | |
return self | |
if self._process: | |
raise Exception('already running') | |
args = ["java", "-jar", self.jar, "-P", str(self.port), self.user, self.key] | |
print "starting sauce connect..." | |
print " ".join(args) | |
self._process = subprocess.Popen(args) | |
time.sleep(60) # TODO: actually check to see that the connection has been established | |
return self | |
@contextlib.contextmanager | |
def PhantomBrowser(capture_url, phantom_cmd): | |
"""Run a local phantomjs browser""" | |
# write the capture script to a temporary file | |
capture_script = """ | |
var system = require('system'); | |
var url = system.args[1]; | |
var page = require('webpage').create(); | |
page.open(url, function(status) { | |
console.log(status); | |
console.log('captured at: ' + url); | |
}); | |
""" | |
fid, filename = tempfile.mkstemp(suffix='.js', text=True) | |
os.write(fid, capture_script) | |
os.close(fid) | |
args = [phantom_cmd, filename, capture_url] | |
print "starting phantomjs..." | |
print " ".join(args) | |
process = subprocess.Popen(args) | |
time.sleep(5) # TODO: actually check to see that the connection has been established | |
try: | |
yield | |
finally: | |
print "shutting down phantomjs..." | |
process.terminate() | |
process.wait() | |
os.remove(filename) | |
@contextlib.contextmanager | |
def SauceBrowser(description, executor, video=False, screenshots=False): | |
"""Run a remote web browser on sauce labs""" | |
browser, version, os = description.split('-') | |
caps = SAUCE_BROWSER[browser.lower()].copy() | |
caps['version'] = version if version.lower() != 'latest' else '' | |
caps['platform'] = SAUCE_OS[os.lower()] | |
caps['name'] = description | |
caps['record-video'] = video | |
caps['record-screenshots'] = screenshots | |
print "launching browser:" | |
print " capabilities:", caps | |
driver = webdriver.Remote(desired_capabilities=caps, command_executor=executor) | |
driver.implicitly_wait(30) | |
try: | |
yield driver | |
finally: | |
driver.quit() | |
if __name__ == '__main__': | |
parser = optparse.OptionParser(usage='usage: %prog [options]') | |
# jstd options | |
parser.set_defaults( | |
jstd_run=False, | |
jstd_host=os.environ.get("JSTD_HOST", "localhost"), | |
jstd_port=int(os.environ.get("JSTD_PORT", 7777)), | |
jstd_tests="all", | |
jstd_jar=os.environ.get("JSTD_JAR", None) | |
) | |
parser.add_option('--jstd-run', action='store_true', dest='jstd_run') | |
parser.add_option('--jstd-host', action='store', type='string', dest='jstd_host') | |
parser.add_option('--jstd-port', action='store', type='int', dest='jstd_port') | |
parser.add_option('--jstd-jar', action='store', type='string', dest='jstd_jar') | |
parser.add_option('--jstd-config', action='store', type='string', dest='jstd_config') | |
parser.add_option('--jstd-tests', action='store', type='string', dest='jstd_tests') | |
parser.add_option('--jstd-output', action='store', type='string', dest='jstd_output') | |
# sauce connect options | |
parser.set_defaults( | |
sauce_run=False, | |
sauce_host=os.environ.get("SAUCE_HOST", "localhost"), | |
sauce_port=int(os.environ.get("SAUCE_PORT", 4445)), | |
sauce_user=os.environ.get("SAUCE_USER", ""), | |
sauce_key=os.environ.get("SAUCE_KEY", ""), | |
sauce_jar=os.environ.get("SAUCE_JAR", None), | |
sauce_video=False, | |
sauce_screenshots=False | |
) | |
parser.add_option('--sauce-run', action='store_true', dest='sauce_run') | |
parser.add_option('--sauce-host', action='store', type='string', dest='sauce_host') | |
parser.add_option('--sauce-port', action='store', type='int', dest='sauce_port') | |
parser.add_option('--sauce-user', action='store', type='string', dest='sauce_user') | |
parser.add_option('--sauce-key', action='store', type='string', dest='sauce_key') | |
parser.add_option('--sauce-jar', action='store', type='string', dest='sauce_jar') | |
parser.add_option('--sauce-video', action='store_true', dest='sauce_video') | |
parser.add_option('--sauce-screenshots', action='store_true', dest='sauce_screenshots') | |
# phantomjs options | |
parser.add_option('--phantom-cmd', action='store', dest='phantom_cmd', default='phantomjs') | |
o, args = parser.parse_args() | |
# jstd and sauce servers | |
jstd = JstdServer(o.jstd_host, o.jstd_port, o.jstd_jar, o.jstd_run) | |
sauce = SauceServer(o.sauce_user, o.sauce_key, o.sauce_host, o.sauce_port, o.sauce_jar, o.sauce_run) | |
# positional args give list of browsers to run | |
browsers = [] | |
for arg in args: | |
if arg.lower() == 'phantom' or arg.lower() == 'phantomjs': | |
browser = PhantomBrowser(jstd.capture_url, o.phantom_cmd) | |
else: | |
browser = SauceBrowser(arg, sauce.url, o.sauce_video, o.sauce_screenshots) | |
browsers.append(browser) | |
with contextlib.nested(jstd, sauce): | |
with contextlib.nested(*browsers) as browsers: | |
print "capturing browsers..." | |
for browser in browsers: | |
if hasattr(browser, 'get'): | |
print "capturing browser {0}...".format(browser) | |
browser.get(jstd.capture_url) | |
print "running tests from {config}...".format(config=o.jstd_config) | |
jstd.run_tests(o.jstd_config, o.jstd_tests, o.jstd_output) | |
print "done with tests." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment