Skip to content

Instantly share code, notes, and snippets.

@maffoo
Created August 20, 2012 21:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maffoo/3408177 to your computer and use it in GitHub Desktop.
Save maffoo/3408177 to your computer and use it in GitHub Desktop.
Javascript Test Runner for JSTD + Sauce Connect + PhantomJS
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