diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js
index dad860936..22287ce0a 100644
--- a/IPython/html/tests/util.js
+++ b/IPython/html/tests/util.js
@@ -1,7 +1,6 @@
//
// Utility functions for the HTML notebook's CasperJS tests.
//
-
casper.get_notebook_server = function () {
// Get the URL of a notebook server on which to run tests.
port = casper.cli.get("port");
@@ -32,6 +31,15 @@ casper.open_new_notebook = function () {
IPython._status = 'busy';
});
});
+
+ // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
+ // sure the notebook has actually been loaded into the IPython namespace
+ // before running any tests.
+ this.waitFor(function() {
+ return this.evaluate(function () {
+ return IPython.notebook;
+ });
+ });
};
casper.kernel_running = function kernel_running() {
@@ -411,9 +419,30 @@ casper.cell_has_class = function(index, classes) {
casper.notebook_test = function(test) {
// Wrap a notebook test to reduce boilerplate.
+ //
+ // If you want to read parameters from the commandline, use the following
+ // (i.e. value=):
+ // if (casper.cli.options.value) {
+ // casper.exit(1);
+ // }
this.open_new_notebook();
- this.then(test);
+ // Echo whether or not we are running this test using SlimerJS
+ if (this.evaluate(function(){
+ return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
+ })) { console.log('This test is running in SlimerJS.'); }
+
+ // Make sure to remove the onbeforeunload callback. This callback is
+ // responsable for the "Are you sure you want to quit?" type messages.
+ // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
+ this.then(function(){
+ this.evaluate(function(){
+ window.onbeforeunload = function(){};
+ });
+ });
+
+ this.then(test);
+
// Kill the kernel and delete the notebook.
this.shutdown_current_kernel();
// This is still broken but shouldn't be a problem for now.
diff --git a/IPython/html/tests/widgets/widget.js b/IPython/html/tests/widgets/widget.js
index 3067ab606..5eb8d5b76 100644
--- a/IPython/html/tests/widgets/widget.js
+++ b/IPython/html/tests/widgets/widget.js
@@ -1,9 +1,15 @@
var xor = function (a, b) {return !a ^ !b;};
-var isArray = function (a) {return toString.call(a) === "[object Array]" || toString.call(a) === "[object RuntimeArray]";};
+var isArray = function (a) {
+ try {
+ return Object.toString.call(a) === "[object Array]" || Object.toString.call(a) === "[object RuntimeArray]";
+ } catch (e) {
+ return Array.isArray(a);
+ }
+};
var recursive_compare = function(a, b) {
// Recursively compare two objects.
var same = true;
- same = same && !xor(a instanceof Object, b instanceof Object);
+ same = same && !xor(a instanceof Object || typeof a == 'object', b instanceof Object || typeof b == 'object');
same = same && !xor(isArray(a), isArray(b));
if (same) {
diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py
index 957b8a395..bb03e3884 100644
--- a/IPython/testing/iptest.py
+++ b/IPython/testing/iptest.py
@@ -148,6 +148,8 @@ have['jinja2'] = test_for('jinja2')
have['requests'] = test_for('requests')
have['sphinx'] = test_for('sphinx')
have['casperjs'] = is_cmd_found('casperjs')
+have['phantomjs'] = is_cmd_found('phantomjs')
+have['slimerjs'] = is_cmd_found('slimerjs')
min_zmq = (2,1,11)
diff --git a/IPython/testing/iptestcontroller.py b/IPython/testing/iptestcontroller.py
index ee3e0e730..5b48b0a05 100644
--- a/IPython/testing/iptestcontroller.py
+++ b/IPython/testing/iptestcontroller.py
@@ -20,12 +20,14 @@ import signal
import sys
import subprocess
import time
+import re
from .iptest import have, test_group_names as py_test_group_names, test_sections, StreamCapturer
from IPython.utils.path import compress_user
from IPython.utils.py3compat import bytes_to_str
from IPython.utils.sysinfo import get_sys_info
from IPython.utils.tempdir import TemporaryDirectory
+from IPython.nbconvert.filters.ansi import strip_ansi
try:
# Python >= 3.3
@@ -214,15 +216,17 @@ def all_js_groups():
class JSController(TestController):
"""Run CasperJS tests """
requirements = ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3']
- def __init__(self, section, enabled=True):
+ def __init__(self, section, enabled=True, engine='phantomjs'):
"""Create new test runner."""
TestController.__init__(self)
+ self.engine = engine
self.section = section
self.enabled = enabled
+ self.slimer_failure = re.compile('^FAIL.*', flags=re.MULTILINE)
js_test_dir = get_js_test_dir()
includes = '--includes=' + os.path.join(js_test_dir,'util.js')
test_cases = os.path.join(js_test_dir, self.section[len(js_prefix):])
- self.cmd = ['casperjs', 'test', includes, test_cases]
+ self.cmd = ['casperjs', 'test', includes, test_cases, '--engine=%s' % self.engine]
def setup(self):
self.ipydir = TemporaryDirectory()
@@ -240,13 +244,31 @@ class JSController(TestController):
else:
# don't launch tests if the server didn't start
self.cmd = [sys.executable, '-c', 'raise SystemExit(1)']
+
+ def launch(self, buffer_output):
+ # If the engine is SlimerJS, we need to buffer the output because
+ # SlimerJS does not support exit codes, therefor CasperJS always returns
+ # 0 which is a false positive.
+ buffer_output = (self.engine == 'slimerjs') or buffer_output
+ super(JSController, self).launch(buffer_output=buffer_output)
+
+ def wait(self, *pargs, **kwargs):
+ """Wait for the JSController to finish"""
+ ret = super(JSController, self).wait(*pargs, **kwargs)
+ # If this is a SlimerJS controller, echo the captured output.
+ if self.engine == 'slimerjs':
+ print(self.stdout)
+ # Return True if a failure occured.
+ return self.slimer_failure.search(strip_ansi(self.stdout))
+ else:
+ return ret
def print_extra_info(self):
print("Running tests with notebook directory %r" % self.nbdir.name)
@property
def will_run(self):
- return self.enabled and all(have[a] for a in self.requirements)
+ return self.enabled and all(have[a] for a in self.requirements + [self.engine])
def _init_server(self):
"Start the notebook server in a separate process"
@@ -281,7 +303,7 @@ class JSController(TestController):
# get invalid JSON; it should be ready next iteration.
pass
else:
- return
+ return
time.sleep(0.1)
print("Notebook server-info file never arrived: %s" % self.server_info_file,
file=sys.stderr
@@ -350,7 +372,8 @@ def prepare_controllers(options):
else:
js_testgroups = all_js_groups()
- c_js = [JSController(name) for name in js_testgroups]
+ engine = 'phantomjs' if have['phantomjs'] and not options.slimerjs else 'slimerjs'
+ c_js = [JSController(name, engine=engine) for name in js_testgroups]
c_py = [PyTestController(name, options) for name in py_testgroups]
controllers = c_py + c_js
@@ -455,6 +478,9 @@ def run_iptestall(options):
Include slow tests, like IPython.parallel. By default, these tests aren't
run.
+ slimerjs : bool
+ Use slimerjs if it's installed instead of phantomjs for casperjs tests.
+
xunit : bool
Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
@@ -581,6 +607,8 @@ argparser.add_argument('testgroups', nargs='*',
'all tests.')
argparser.add_argument('--all', action='store_true',
help='Include slow tests not run by default.')
+argparser.add_argument('--slimerjs', action='store_true',
+ help="Use slimerjs if it's installed instead of phantomjs for casperjs tests.")
argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
help='Run test sections in parallel. This starts as many '
'processes as you have cores, or you can specify a number.')