Uploaded Test files

This commit is contained in:
Batuhan Berk Başoğlu 2020-11-12 11:05:57 -05:00
parent f584ad9d97
commit 2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions

View file

@ -0,0 +1,58 @@
casper.notebook_test(function () {
this.on('remote.callback', function(data){
if(data.error_expected){
that.test.assertEquals(
data.error,
true,
"!highlight: " + data.provided + " errors"
);
}else{
that.test.assertEquals(
data.observed,
data.expected,
"highlight: " + data.provided + " as " + data.expected
);
}
});
var that = this;
// syntax highlighting
[
{to: "gfm"},
{to: "python"},
{to: "ipython"},
{to: "ipythongfm"},
{to: "text/x-markdown", from: [".md"]},
{to: "text/x-python", from: [".py", "Python"]},
{to: "application/json", from: ["json", "JSON"]},
{to: "text/x-ruby", from: [".rb", "ruby", "Ruby"]},
{to: "application/ld+json", from: ["json-ld", "JSON-LD"]},
{from: [".pyc"], error: true},
{from: ["../"], error: true},
{from: ["//"], error: true},
].map(function (mode) {
(mode.from || []).concat(mode.to || []).map(function(from){
casper.evaluate(function(from, expected, error_expected){
IPython.utils.requireCodeMirrorMode(from, function(observed){
window.callPhantom({
provided: from,
expected: expected,
observed: observed,
error_expected: error_expected
});
}, function(error){
window.callPhantom({
provided: from,
expected: expected,
error: true,
error_expected: error_expected
});
});
}, {
from: from,
expected: mode.to,
error_expected: mode.error
});
});
});
});

View file

@ -0,0 +1,95 @@
var normalized_shortcuts = [
'ctrl-shift-m',
'alt-meta-p',
];
var to_normalize = [
['shift-%', 'shift-5'],
['ShiFT-MeTa-CtRl-AlT-m', 'alt-ctrl-meta-shift-m'],
];
var unshifted = "` 1 2 3 4 5 6 7 8 9 0 - = q w e r t y u i o p [ ] \\ a s d f g h j k l ; ' z x c v b n m , . /";
// shifted = '~ ! @ # $ % ^ & * ( ) _ + Q W E R T Y U I O P { } | A S D F G H J K L : " Z X C V B N M < > ?';
var ambiguous_expect = function(ch){
// `-` is ambiguous in shortcut context as a separator, so map it to `minus`
if(ch === '-'){
return 'minus';
}
return ch;
};
casper.notebook_test(function () {
var that = this;
this.then(function () {
this.each(unshifted.split(' '), function (self, item) {
var result = this.evaluate(function (sc) {
var e = IPython.keyboard.shortcut_to_event(sc);
var sc2 = IPython.keyboard.event_to_shortcut(e);
return sc2;
}, item);
this.test.assertEquals(result, ambiguous_expect(item), 'Shortcut to event roundtrip: '+item);
});
});
this.then(function () {
this.each(to_normalize, function (self, item) {
var result = this.evaluate(function (pair) {
return IPython.keyboard.normalize_shortcut(pair[0]);
}, item);
this.test.assertEquals(result, item[1], 'Normalize shortcut: '+item[0]);
});
});
this.then(function () {
this.each(normalized_shortcuts, function (self, item) {
var result = this.evaluate(function (sc) {
var e = IPython.keyboard.shortcut_to_event(sc);
var sc2 = IPython.keyboard.event_to_shortcut(e);
return sc2;
}, item);
this.test.assertEquals(result, item, 'Shortcut to event roundtrip: '+item);
});
});
this.then(function(){
var shortcuts_test = {
'i,e,e,e,e,e' : '[[5E]]',
'i,d,d,q,d' : '[[TEST1]]',
'i,d,d' : '[[TEST1 WILL BE SHADOWED]]',
'i,d,k' : '[[SHOULD SHADOW TEST2]]',
'i,d,k,r,q' : '[[TEST2 NOT SHADOWED]]',
';,up,down,up,down,left,right,left,right,b,a' : '[[KONAMI]]',
'ctrl-x,meta-c,meta-b,u,t,t,e,r,f,l,y' : '[[XKCD]]'
};
that.msgs = [];
that.on('remote.message', function(msg) {
that.msgs.push(msg);
});
that.evaluate(function (obj) {
for(var k in obj){
if ({}.hasOwnProperty.call(obj, k)) {
IPython.keyboard_manager.command_shortcuts.add_shortcut(k, function(){console.log(obj[k]);});
}
}
}, shortcuts_test);
var longer_first = false;
var longer_last = false;
for(var m in that.msgs){
if ({}.hasOwnProperty.call(that.msgs, m)) {
longer_first = longer_first||(that.msgs[m].match(/you are overriting/)!= null);
longer_last = longer_last ||(that.msgs[m].match(/will be shadowed/) != null);
}
}
this.test.assert(longer_first, 'no warning if registering shorter shortcut');
this.test.assert(longer_last , 'no warning if registering longer shortcut');
});
});

View file

@ -0,0 +1,45 @@
//
// Miscellaneous javascript tests
//
casper.notebook_test(function () {
var jsver = this.evaluate(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text('import notebook; print(notebook.__version__)');
cell.execute();
return IPython.version;
});
this.wait_for_output(0);
// refactor this into just a get_output(0)
this.then(function () {
var result = this.get_output_cell(0);
this.test.assertEquals(result.text.trim(), jsver, 'IPython.version in JS matches server-side.');
});
// verify that requirejs loads the same CodeCell prototype at runtime as build time
this.thenEvaluate(function () {
require(['notebook/js/codecell'], function (codecell) {
codecell.CodeCell.prototype.test = function () {
return 'ok';
}
window._waitForMe = true;
})
})
this.waitFor(function () {
return this.evaluate(function () {
return window._waitForMe;
});
})
this.then(function () {
var result = this.evaluate(function () {
var cell = Jupyter.notebook.get_cell(0);
return cell.test();
});
this.test.assertEquals(result, 'ok', "runtime-requirejs loads the same modules")
})
});

View file

@ -0,0 +1,57 @@
safe_tests = [
"<p>Hi there</p>",
'<h1 class="foo">Hi There!</h1>',
'<a data-cite="foo">citation</a>',
'<div><span>Hi There</span></div>',
];
unsafe_tests = [
"<script>alert(999);</script>",
'<a onmouseover="alert(999)">999</a>',
'<a onmouseover=alert(999)>999</a>',
'<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
'<IMG SRC=# onmouseover="alert(999)">',
'<<SCRIPT>alert(999);//<</SCRIPT>',
'<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >',
'<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">',
'<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert(999);">',
'<IFRAME SRC="javascript:alert(999);"></IFRAME>',
'<IFRAME SRC=# onmouseover="alert(document.cookie)"></IFRAME>',
'<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>',
// CSS is scrubbed
'<style src="http://untrusted/style.css"></style>',
'<style>div#notebook { background-color: alert-red; }</style>',
'<div style="background-color: alert-red;"></div>',
];
var truncate = function (s, n) {
// truncate a string with an ellipsis
if (s.length > n) {
return s.substr(0, n-3) + '...';
} else {
return s;
}
};
casper.notebook_test(function () {
this.each(safe_tests, function (self, item) {
var sanitized = self.evaluate(function (item) {
return IPython.security.sanitize_html(item);
}, item);
// string equality may be too strict, but it works for now
this.test.assertEquals(sanitized, item, "Safe: '" + truncate(item, 32) + "'");
});
this.each(unsafe_tests, function (self, item) {
var sanitized = self.evaluate(function (item) {
return IPython.security.sanitize_html(item);
}, item);
this.test.assertNotEquals(sanitized, item,
"Sanitized: '" + truncate(item, 32) +
"' => '" + truncate(sanitized, 32) + "'"
);
this.test.assertEquals(sanitized.indexOf("alert"), -1, "alert removed");
});
});

View file

@ -0,0 +1,93 @@
casper.notebook_test(function () {
// Test fixConsole
// Note, \u001b is the unicode notation of octal \033 which is not officially in js
var input = [
"\u001b[0m[\u001b[0minfo\u001b[0m] \u001b[0mtext\u001b[0m",
"\u001b[0m[\u001b[33mwarn\u001b[0m] \u001b[0m\tmore text\u001b[0m",
"\u001b[0m[\u001b[33mwarn\u001b[0m] \u001b[0m https://some/url/to/a/file.ext\u001b[0m",
"\u001b[0m[\u001b[31merror\u001b[0m] \u001b[0m\u001b[0m",
"\u001b[0m[\u001b[31merror\u001b[0m] \u001b[0m\teven more text\u001b[0m",
"\u001b[?25hBuilding wheels for collected packages: scipy",
"\x1b[38;5;28;01mtry\x1b[39;00m",
"\u001b[0m[\u001b[31merror\u001b[0m] \u001b[0m\t\tand more more text\u001b[0m",
"normal\x1b[43myellowbg\x1b[35mmagentafg\x1b[1mbold\x1b[49mdefaultbg\x1b[39mdefaultfg\x1b[22mnormal",
].join("\n");
var output = [
"[info] text",
'[<span class="ansi-yellow-fg">warn</span>] \tmore text',
'[<span class="ansi-yellow-fg">warn</span>] https://some/url/to/a/file.ext',
'[<span class="ansi-red-fg">error</span>] ',
'[<span class="ansi-red-fg">error</span>] \teven more text',
"Building wheels for collected packages: scipy",
'<span class="ansi-bold" style="color: rgb(0,135,0)">try</span>',
'[<span class="ansi-red-fg">error</span>] \t\tand more more text',
'normal<span class="ansi-yellow-bg">yellowbg</span><span class="ansi-magenta-fg ansi-yellow-bg">magentafg</span><span class="ansi-magenta-intense-fg ansi-yellow-bg ansi-bold">bold</span><span class="ansi-magenta-intense-fg ansi-bold">defaultbg</span><span class="ansi-bold">defaultfg</span>normal',
].join("\n");
var result = this.evaluate(function (input) {
return IPython.utils.fixConsole(input);
}, input);
this.test.assertEquals(result, output, "IPython.utils.fixConsole() handles [0m correctly");
// Test fixOverwrittenChars
var overwriting_test_cases = [
{input: "ABC\rDEF", result: "DEF"},
{input: "ABC\r\nDEF", result: "ABC\nDEF"},
{input: "123\b456", result: "12456"},
{input: "123\n\b456", result: "123\n\b456"},
{input: "\b456", result: "\b456"}
];
var that = this;
overwriting_test_cases.forEach(function(testcase){
var result = that.evaluate(function (input) {
return IPython.utils.fixOverwrittenChars(input);
}, testcase.input);
that.test.assertEquals(result, testcase.result, "Overwriting characters processed");
});
var input = [
'hasrn\r\n',
'hasn\n',
'\n',
'abcdef\r',
'hello\n',
'ab3\r',
'x2\r\r',
'1\r',
].join('');
var output = [
'hasrn\n',
'hasn\n',
'\n',
'hellof\n',
'123\r'
].join('');
var result = this.evaluate(function (input) {
return IPython.utils.fixCarriageReturn(input);
}, input);
this.test.assertEquals(result, output, "IPython.utils.fixCarriageReturns works");
// Test load_extensions
this.thenEvaluate(function() {
define('nbextensions/a', [], function() { window.a = true; });
define('nbextensions/c', [], function() { window.c = true; });
require(['base/js/utils'], function(utils) {
utils.load_extensions('a', 'b', 'c');
});
}).then(function() {
this.waitFor(function() {
return this.evaluate(function() { return window.a; });
});
this.waitFor(function() {
return this.evaluate(function() { return window.a; });
});
});
});

View file

@ -0,0 +1,251 @@
"""Base class for notebook tests."""
from __future__ import print_function
from binascii import hexlify
from contextlib import contextmanager
import errno
import os
import sys
from threading import Thread, Event
import time
from unittest import TestCase
pjoin = os.path.join
from unittest.mock import patch
import requests
from tornado.ioloop import IOLoop
import zmq
import jupyter_core.paths
from traitlets.config import Config
from ..notebookapp import NotebookApp, urlencode_unix_socket
from ..utils import url_path_join
from ipython_genutils.tempdir import TemporaryDirectory
MAX_WAITTIME = 30 # seconds to wait for notebook server to start
POLL_INTERVAL = 0.1 # time between attempts
# TimeoutError is a builtin on Python 3. This can be removed when we stop
# supporting Python 2.
class TimeoutError(Exception):
pass
class NotebookTestBase(TestCase):
"""A base class for tests that need a running notebook.
This create some empty config and runtime directories
and then starts the notebook server with them.
"""
port = 12341
config = None
# run with a base URL that would be escaped,
# to test that we don't double-escape URLs
url_prefix = '/a%40b/'
@classmethod
def wait_until_alive(cls):
"""Wait for the server to be alive"""
url = cls.base_url() + 'api/contents'
for _ in range(int(MAX_WAITTIME/POLL_INTERVAL)):
try:
cls.fetch_url(url)
except Exception as e:
if not cls.notebook_thread.is_alive():
raise RuntimeError("The notebook server failed to start") from e
time.sleep(POLL_INTERVAL)
else:
return
raise TimeoutError("The notebook server didn't start up correctly.")
@classmethod
def wait_until_dead(cls):
"""Wait for the server process to terminate after shutdown"""
cls.notebook_thread.join(timeout=MAX_WAITTIME)
if cls.notebook_thread.is_alive():
raise TimeoutError("Undead notebook server")
@classmethod
def auth_headers(cls):
headers = {}
if cls.token:
headers['Authorization'] = 'token %s' % cls.token
return headers
@staticmethod
def fetch_url(url):
return requests.get(url)
@classmethod
def request(cls, verb, path, **kwargs):
"""Send a request to my server
with authentication and everything.
"""
headers = kwargs.setdefault('headers', {})
headers.update(cls.auth_headers())
response = requests.request(verb,
url_path_join(cls.base_url(), path),
**kwargs)
return response
@classmethod
def get_patch_env(cls):
return {
'HOME': cls.home_dir,
'PYTHONPATH': os.pathsep.join(sys.path),
'IPYTHONDIR': pjoin(cls.home_dir, '.ipython'),
'JUPYTER_NO_CONFIG': '1', # needed in the future
'JUPYTER_CONFIG_DIR' : cls.config_dir,
'JUPYTER_DATA_DIR' : cls.data_dir,
'JUPYTER_RUNTIME_DIR': cls.runtime_dir,
}
@classmethod
def get_argv(cls):
return []
@classmethod
def get_bind_args(cls):
return dict(port=cls.port)
@classmethod
def setup_class(cls):
cls.tmp_dir = TemporaryDirectory()
def tmp(*parts):
path = os.path.join(cls.tmp_dir.name, *parts)
try:
os.makedirs(path)
except OSError as e:
if e.errno != errno.EEXIST:
raise
return path
cls.home_dir = tmp('home')
data_dir = cls.data_dir = tmp('data')
config_dir = cls.config_dir = tmp('config')
runtime_dir = cls.runtime_dir = tmp('runtime')
cls.notebook_dir = tmp('notebooks')
cls.env_patch = patch.dict('os.environ', cls.get_patch_env())
cls.env_patch.start()
# Patch systemwide & user-wide data & config directories, to isolate
# the tests from oddities of the local setup. But leave Python env
# locations alone, so data files for e.g. nbconvert are accessible.
# If this isolation isn't sufficient, you may need to run the tests in
# a virtualenv or conda env.
cls.path_patch = patch.multiple(
jupyter_core.paths,
SYSTEM_JUPYTER_PATH=[tmp('share', 'jupyter')],
SYSTEM_CONFIG_PATH=[tmp('etc', 'jupyter')],
)
cls.path_patch.start()
config = cls.config or Config()
config.NotebookNotary.db_file = ':memory:'
cls.token = hexlify(os.urandom(4)).decode('ascii')
started = Event()
def start_thread():
try:
bind_args = cls.get_bind_args()
app = cls.notebook = NotebookApp(
port_retries=0,
open_browser=False,
config_dir=cls.config_dir,
data_dir=cls.data_dir,
runtime_dir=cls.runtime_dir,
notebook_dir=cls.notebook_dir,
base_url=cls.url_prefix,
config=config,
allow_root=True,
token=cls.token,
**bind_args
)
if 'asyncio' in sys.modules:
app._init_asyncio_patch()
import asyncio
asyncio.set_event_loop(asyncio.new_event_loop())
# don't register signal handler during tests
app.init_signal = lambda : None
# clear log handlers and propagate to root for nose to capture it
# needs to be redone after initialize, which reconfigures logging
app.log.propagate = True
app.log.handlers = []
app.initialize(argv=cls.get_argv())
app.log.propagate = True
app.log.handlers = []
loop = IOLoop.current()
loop.add_callback(started.set)
app.start()
finally:
# set the event, so failure to start doesn't cause a hang
started.set()
app.session_manager.close()
cls.notebook_thread = Thread(target=start_thread)
cls.notebook_thread.daemon = True
cls.notebook_thread.start()
started.wait()
cls.wait_until_alive()
@classmethod
def teardown_class(cls):
cls.notebook.stop()
cls.wait_until_dead()
cls.env_patch.stop()
cls.path_patch.stop()
cls.tmp_dir.cleanup()
# cleanup global zmq Context, to ensure we aren't leaving dangling sockets
def cleanup_zmq():
zmq.Context.instance().term()
t = Thread(target=cleanup_zmq)
t.daemon = True
t.start()
t.join(5) # give it a few seconds to clean up (this should be immediate)
# if term never returned, there's zmq stuff still open somewhere, so shout about it.
if t.is_alive():
raise RuntimeError("Failed to teardown zmq Context, open sockets likely left lying around.")
@classmethod
def base_url(cls):
return 'http://localhost:%i%s' % (cls.port, cls.url_prefix)
class UNIXSocketNotebookTestBase(NotebookTestBase):
# Rely on `/tmp` to avoid any Linux socket length max buffer
# issues. Key on PID for process-wise concurrency.
sock = '/tmp/.notebook.%i.sock' % os.getpid()
@classmethod
def get_bind_args(cls):
return dict(sock=cls.sock)
@classmethod
def base_url(cls):
return '%s%s' % (urlencode_unix_socket(cls.sock), cls.url_prefix)
@staticmethod
def fetch_url(url):
# Lazily import so it is not required at the module level
if os.name != 'nt':
import requests_unixsocket
with requests_unixsocket.monkeypatch():
return requests.get(url)
@contextmanager
def assert_http_error(status, msg=None):
try:
yield
except requests.HTTPError as e:
real_status = e.response.status_code
assert real_status == status, \
"Expected status %d, got %d" % (status, real_status)
if msg:
assert msg in str(e), e
else:
assert False, "Expected HTTP error status"

View file

@ -0,0 +1 @@
console.log('z');

View file

@ -0,0 +1,192 @@
//
// Test cell attachments
//
var fs = require('fs');
casper.notebook_test(function () {
// -- Test the Edit->Insert Image menu to insert new attachments
"use strict";
casper.test.info("Testing attachments insertion through the menuitem");
this.viewport(1024, 768);
// Click on menuitem
var selector = '#insert_image > a';
this.waitForSelector(selector);
this.thenEvaluate(function(sel) {
Jupyter.notebook.to_markdown();
var cell = Jupyter.notebook.get_selected_cell();
cell.set_text("");
cell.unrender();
$(sel).click();
}, selector);
// Wait for the dialog to be shown
this.waitUntilVisible(".modal-body");
this.wait(200);
// Select the image file to insert
// For some reason, this doesn't seem to work in a reliable way in
// phantomjs. So we manually set the input's files attribute
//this.page.uploadFile('.modal-body input[name=file]', 'test.png')
this.then(function() {
var fname = 'notebook/tests/_testdata/black_square_22.png';
if (!fs.exists(fname)) {
this.test.fail(
" does not exist, are you running the tests " +
"from the root directory ? "
);
}
this.fill('form#insert-image-form', {'file': fname});
});
// Validate and render the markdown cell
this.thenClick('#btn_ok');
this.thenEvaluate(function() {
Jupyter.notebook.get_cell(0).render();
});
this.wait(300);
// Check that an <img> tag has been inserted and that it contains the
// image
this.then(function() {
var img = this.evaluate(function() {
var cell = Jupyter.notebook.get_cell(0);
var img = $("div.text_cell_render").find("img");
return {
src: img.attr("src"),
width: img.width(),
height: img.height(),
};
});
this.test.assertType(img, "object", "Image('image/png')");
this.test.assertEquals(img.src.split(',')[0],
"data:image/png;base64",
"Image data-uri prefix");
this.test.assertEquals(img.width, 2, "width == 2");
this.test.assertEquals(img.height, 2, "height == 2");
});
//this.then(function() {
//this.capture('test.png');
//});
// -- Use the Edit->Copy/Paste Cell Attachments menu items
selector = '#copy_cell_attachments > a';
this.waitForSelector(selector);
this.thenClick(selector);
// append a new cell
this.append_cell('', 'markdown');
this.thenEvaluate(function() {
Jupyter.notebook.select_next();
});
// and paste the attachments into it
selector = '#paste_cell_attachments > a';
this.waitForSelector(selector);
this.thenClick(selector);
// check that the new cell has attachments
this.then(function() {
var cell_attachments = this.evaluate(function() {
return Jupyter.notebook.get_selected_cell().attachments;
});
var orig_cell_attachments = this.evaluate(function() {
return Jupyter.notebook.get_cell(0).attachments;
});
// Check that the two cells have the same attachments
this.test.assertEquals(cell_attachments, orig_cell_attachments,
"pasted attachments ok");
});
// copy/paste cell includes attachments
selector = '#copy_cell > a';
this.waitForSelector(selector);
this.thenClick(selector);
selector = '#paste_cell_below > a';
this.waitForSelector(selector);
this.thenClick(selector);
// check that the new cell has attachments
this.then(function() {
var cell_attachments = this.evaluate(function() {
return Jupyter.notebook.get_selected_cell().attachments;
});
var orig_cell_attachments = this.evaluate(function() {
return Jupyter.notebook.get_cell(0).attachments;
});
// Check that the two cells have the same attachments
this.test.assertEquals(cell_attachments, orig_cell_attachments,
"pasted cell has attachments");
});
var nbname = 'attachments_test.ipynb';
this.thenEvaluate(function(nbname) {
Jupyter.notebook.set_notebook_name(nbname);
}, {nbname:nbname});
// -- Save the notebook. This should cause garbage collection for the
// second cell (since we just pasted the attachments but there is no
// markdown referencing them)
this.thenEvaluate(function(nbname) {
Jupyter._checkpoint_created = false;
require(['base/js/events'], function (events) {
events.on('checkpoint_created.Notebook', function (evt, data) {
Jupyter._checkpoint_created = true;
});
});
Jupyter.notebook.save_checkpoint();
}, {nbname:nbname});
this.waitFor(function () {
return this.evaluate(function(){
return Jupyter._checkpoint_created;
});
});
this.then(function(){
this.open_dashboard();
});
this.then(function(){
var notebook_url = this.evaluate(function(nbname){
var escaped_name = encodeURIComponent(nbname);
var return_this_thing = null;
$("a.item_link").map(function (i,a) {
if (a.href.indexOf(escaped_name) >= 0) {
return_this_thing = a.href;
return;
}
});
return return_this_thing;
}, {nbname:nbname});
this.test.assertNotEquals(notebook_url, null, "Escaped URL in notebook list");
// open the notebook
this.open(notebook_url);
});
// wait for the notebook
this.waitFor(this.kernel_running);
this.waitFor(function() {
return this.evaluate(function () {
return Jupyter && Jupyter.notebook && true;
});
});
this.then(function() {
var cell0 = this.evaluate(function() {
return Jupyter.notebook.get_cell(0);
});
var cell1 = this.evaluate(function() {
return Jupyter.notebook.get_cell(1);
});
this.test.assert('black_square_22.png' in cell0.attachments,
'cell0 has kept its attachments');
this.test.assertEquals(Object.keys(cell1.attachments).length, 0,
'cell1 attachments have been garbage collected');
});
});

View file

@ -0,0 +1,192 @@
//
// Various output tests
//
casper.notebook_test(function () {
function get_outputs(cell_idx) {
var outputs_json = casper.evaluate(function (cell_idx) {
var cell = Jupyter.notebook.get_cell(cell_idx);
return JSON.stringify(cell.output_area.outputs);
}, {cell_idx: cell_idx});
return JSON.parse(outputs_json);
}
this.thenEvaluate(function () {
Jupyter.notebook.insert_cell_at_index("code", 0);
var cell = Jupyter.notebook.get_cell(0);
cell.set_text([
"ip = get_ipython()",
"from IPython.display import display",
"def display_with_id(obj, display_id, update=False, execute_result=False):",
" iopub = ip.kernel.iopub_socket",
" session = get_ipython().kernel.session",
" data, md = ip.display_formatter.format(obj)",
" transient = {'display_id': display_id}",
" content = {'data': data, 'metadata': md, 'transient': transient}",
" if execute_result:",
" msg_type = 'execute_result'",
" content['execution_count'] = ip.execution_count",
" else:",
" msg_type = 'update_display_data' if update else 'display_data'",
" session.send(iopub, msg_type, content, parent=ip.parent_header)",
"",
].join('\n'));
cell.execute();
});
this.thenEvaluate(function () {
Jupyter.notebook.insert_cell_at_index("code", 1);
var cell = Jupyter.notebook.get_cell(1);
cell.set_text([
"display('above')",
"display_with_id(1, 'here')",
"display('below')",
].join('\n'));
cell.execute();
});
this.wait_for_output(1);
this.wait_for_idle()
this.then(function () {
var outputs = get_outputs(1);
this.test.assertEquals(outputs.length, 3, 'cell 1 has the right number of outputs');
this.test.assertEquals(outputs[1].transient.display_id, 'here', 'has transient display_id');
this.test.assertEquals(outputs[1].data['text/plain'], '1', 'display_with_id produces output');
});
this.thenEvaluate(function () {
Jupyter.notebook.insert_cell_at_index("code", 2);
var cell = Jupyter.notebook.get_cell(2);
cell.set_text([
"display_with_id(2, 'here')",
"display_with_id(3, 'there')",
"display_with_id(4, 'here')",
].join('\n'));
cell.execute();
});
this.wait_for_output(2);
this.wait_for_idle();
this.then(function () {
var outputs1 = get_outputs(1);
this.test.assertEquals(outputs1[1].data['text/plain'], '4', '');
this.test.assertEquals(outputs1.length, 3, 'cell 1 still has the right number of outputs');
var outputs2 = get_outputs(2);
this.test.assertEquals(outputs2.length, 3, 'cell 2 has the right number of outputs');
this.test.assertEquals(outputs2[0].transient.display_id, 'here', 'check display id 0');
this.test.assertEquals(outputs2[0].data['text/plain'], '4', 'output[2][0]');
this.test.assertEquals(outputs2[1].transient.display_id, 'there', 'display id 1');
this.test.assertEquals(outputs2[1].data['text/plain'], '3', 'output[2][1]');
this.test.assertEquals(outputs2[2].transient.display_id, 'here', 'display id 2');
this.test.assertEquals(outputs2[2].data['text/plain'], '4', 'output[2][2]');
});
this.then(function () {
this.echo("Test output callback overrides work with display ids");
});
this.thenEvaluate(function () {
Jupyter.notebook.insert_cell_at_index("code", 3);
var cell = Jupyter.notebook.get_cell(3);
cell.set_text([
"display_with_id(5, 'here')",
"display_with_id(6, 'here', update=True)",
].join('\n'));
cell.execute();
var kernel = IPython.notebook.kernel;
var msg_id = cell.last_msg_id;
var callback_id = 'mycallbackid'
cell.iopub_messages = [];
var add_msg = function(msg) {
msg.content.output_type = msg.msg_type;
cell.iopub_messages.push(msg.content);
};
kernel.set_callbacks_for_msg(callback_id, {
iopub: {
output: add_msg,
clear_output: add_msg,
}
}, false);
kernel.output_callback_overrides_push(msg_id, callback_id);
});
this.waitFor(function () {
return this.evaluate(function () {
var cell = IPython.notebook.get_cell(3);
return cell.iopub_messages.length >= 2;
});
});
this.wait_for_idle();
this.then(function () {
var returned = this.evaluate(function () {
var cell = IPython.notebook.get_cell(3);
return [cell.output_area.outputs, cell.iopub_messages];
});
var cell_results = returned[0];
var callback_results = returned[1];
this.test.assertEquals(cell_results.length, 0, "correct number of cell outputs");
this.test.assertEquals(callback_results.length, 2, "correct number of callback outputs");
this.test.assertEquals(callback_results[0].output_type, 'display_data', 'check output_type 0');
this.test.assertEquals(callback_results[0].transient.display_id, 'here', 'check display id 0');
this.test.assertEquals(callback_results[0].data['text/plain'], '5', 'value');
this.test.assertEquals(callback_results[1].output_type, 'update_display_data', 'check output_type 1');
this.test.assertEquals(callback_results[1].transient.display_id, 'here', 'display id 1');
this.test.assertEquals(callback_results[1].data['text/plain'], '6', 'value');
});
this.thenEvaluate(function () {
Jupyter.notebook.insert_cell_at_index("code", 4);
var cell = Jupyter.notebook.get_cell(4);
cell.set_text([
"display_with_id(7, 'here')",
"display_with_id(8, 'here', update=True)",
"display_with_id(9, 'result', execute_result=True)"
].join('\n'));
cell.execute();
Jupyter.notebook.insert_cell_at_index("code", 5);
var cell = Jupyter.notebook.get_cell(5);
cell.set_text([
"display_with_id(10, 'result', update=True)",
"1",
].join('\n'));
cell.execute();
});
this.wait_for_output(4);
this.wait_for_output(5);
this.wait_for_idle();
this.then(function () {
var returned = JSON.parse(this.evaluate(function () {
var cell3 = Jupyter.notebook.get_cell(3);
var cell4 = Jupyter.notebook.get_cell(4);
return JSON.stringify([cell4.output_area.outputs, cell3.iopub_messages]);
}));
var cell_results = returned[0];
var callback_results = returned[1];
this.test.assertEquals(cell_results.length, 2, "correct number of cell outputs");
this.test.assertEquals(callback_results.length, 4, "correct number of callback outputs");
this.test.assertEquals(callback_results[0].output_type, 'display_data', 'check output_type 0');
this.test.assertEquals(callback_results[0].transient.display_id, 'here', 'check display id 0');
this.test.assertEquals(callback_results[0].data['text/plain'], '5', 'value');
this.test.assertEquals(callback_results[1].output_type, 'update_display_data', 'check output_type 1');
this.test.assertEquals(callback_results[1].transient.display_id, 'here', 'display id 1');
this.test.assertEquals(callback_results[1].data['text/plain'], '6', 'value');
this.test.assertEquals(callback_results[2].output_type, 'display_data', 'check output_type 2');
this.test.assertEquals(callback_results[2].transient.display_id, 'here', 'check display id 2');
this.test.assertEquals(callback_results[2].data['text/plain'], '7', 'value');
this.test.assertEquals(callback_results[3].output_type, 'update_display_data', 'check output_type 3');
this.test.assertEquals(callback_results[3].transient.display_id, 'here', 'display id 3');
this.test.assertEquals(callback_results[3].data['text/plain'], '8', 'value');
this.test.assertEquals(cell_results[1].data['text/plain'], '10', 'update execute_result')
});
});

View file

@ -0,0 +1,116 @@
// Test the notebook dual mode feature.
// Test
casper.notebook_test(function () {
var a = 'print("a")';
var index = this.append_cell(a);
this.execute_cell_then(index);
var b = 'print("b")';
index = this.append_cell(b);
this.execute_cell_then(index);
var c = 'print("c")';
index = this.append_cell(c);
this.execute_cell_then(index);
this.then(function () {
if (this.slimerjs) {
// When running in xvfb, the Slimer window doesn't always have focus
// immediately. By clicking on a new element on the page we can force
// it to gain focus.
this.click_cell_editor(1);
this.click_cell_editor(0);
}
this.validate_notebook_state('initial state', 'edit', 0);
this.trigger_keydown('esc');
this.validate_notebook_state('esc', 'command', 0);
this.trigger_keydown('down');
this.validate_notebook_state('down', 'command', 1);
this.trigger_keydown('enter');
this.validate_notebook_state('enter', 'edit', 1);
this.trigger_keydown('j');
this.validate_notebook_state('j in edit mode', 'edit', 1);
this.trigger_keydown('esc');
this.validate_notebook_state('esc', 'command', 1);
this.trigger_keydown('j');
this.validate_notebook_state('j in command mode', 'command', 2);
this.click_cell_editor(0);
this.validate_notebook_state('click cell 0', 'edit', 0);
this.click_cell_editor(3);
this.validate_notebook_state('click cell 3', 'edit', 3);
this.trigger_keydown('esc');
this.validate_notebook_state('esc', 'command', 3);
// Open keyboard help
this.evaluate(function(){
$('#keyboard_shortcuts a').click();
}, {});
});
// Wait for the dialog to fade in completely.
this.waitForSelector('div.modal', function() {
this.evaluate(function(){
IPython.modal_shown = false;
$('div.modal').on('shown.bs.modal', function (){
IPython.modal_shown = true;
});
$('div.modal').on('hidden.bs.modal', function (){
IPython.modal_shown = false;
});
});
});
this.waitFor(function () {
return this.evaluate(function(){
return IPython.modal_shown;
});
},
function() {
this.trigger_keydown('k');
this.validate_notebook_state('k in command mode while keyboard help is up', 'command', 3);
// Close keyboard help
this.evaluate(function(){
$('div.modal-footer button.btn-default').click();
}, {});
});
// Wait for the dialog to fade out completely.
this.waitFor(function () {
return this.evaluate(function(){
return !IPython.modal_shown;
});
},
function() {
this.trigger_keydown('k');
this.validate_notebook_state('k in command mode', 'command', 2);
this.click_cell_editor(0);
this.validate_notebook_state('click cell 0', 'edit', 0);
this.focus_notebook();
this.validate_notebook_state('focus #notebook', 'command', 0);
this.click_cell_editor(0);
this.validate_notebook_state('click cell 0', 'edit', 0);
this.focus_notebook();
this.validate_notebook_state('focus #notebook', 'command', 0);
this.click_cell_editor(3);
this.validate_notebook_state('click cell 3', 'edit', 3);
// Cell deletion
this.trigger_keydown('esc', 'd', 'd');
this.test.assertEquals(this.get_cells_length(), 3, 'dd actually deletes a cell');
this.validate_notebook_state('dd', 'command', 2);
// Make sure that if the time between d presses is too long, nothing gets removed.
this.trigger_keydown('d');
});
this.wait(1000);
this.then(function () {
this.trigger_keydown('d');
this.test.assertEquals(this.get_cells_length(), 3, "d, 1 second wait, d doesn't delete a cell");
this.validate_notebook_state('d, 1 second wait, d', 'command', 2);
});
});

View file

@ -0,0 +1,198 @@
// Test
casper.notebook_test(function () {
var a = 'ab\n\ncd';
var b = 'print("b")';
var c = 'print("c")';
var d = '"d"';
var e = '"e"';
var f = '"f"';
var g = '"g"';
var N = 7;
var that = this;
var cell_is_mergeable = function (index) {
// Get the mergeable status of a cell.
return that.evaluate(function (index) {
var cell = IPython.notebook.get_cell(index);
return cell.is_mergeable();
}, index);
};
var cell_is_splittable = function (index) {
// Get the splittable status of a cell.
return that.evaluate(function (index) {
var cell = IPython.notebook.get_cell(index);
return cell.is_splittable();
}, index);
};
var close_dialog = function () {
this.evaluate(function(){
$('div.modal-footer button.btn-default').click();
}, {});
};
this.then(function () {
// Split and merge cells
this.select_cell(0);
this.trigger_keydown('a', 'enter'); // Create cell above and enter edit mode.
this.validate_notebook_state('a, enter', 'edit', 0);
this.set_cell_text(0, 'abcd');
this.set_cell_editor_cursor(0, 0, 2);
this.test.assertEquals(this.get_cell_text(0), 'abcd', 'Verify that cell 0 has the new contents.');
this.trigger_keydown('ctrl-shift--'); // Split
this.test.assertEquals(this.get_cell_text(0), 'ab', 'split; Verify that cell 0 has the first half.');
this.test.assertEquals(this.get_cell_text(1), 'cd', 'split; Verify that cell 1 has the second half.');
this.validate_notebook_state('split', 'edit', 1);
this.select_cell(0); // Move up to cell 0
this.evaluate(function() { IPython.notebook.extend_selection_by(1);});
this.trigger_keydown('shift-m'); // Merge
this.validate_notebook_state('merge', 'command', 0);
this.test.assertEquals(this.get_cell_text(0), a, 'merge; Verify that cell 0 has the merged contents.');
});
// add some more cells and test splitting/merging when a cell is not deletable
this.then(function () {
this.append_cell(b);
this.append_cell(c);
this.append_cell(d);
this.append_cell(e);
this.append_cell(f);
this.append_cell(g);
});
this.thenEvaluate(function() {
IPython.notebook.get_cell(1).metadata.deletable = false;
});
// Check that merge/split status are correct
this.then(function () {
this.test.assert(cell_is_splittable(0), 'Cell 0 is splittable');
this.test.assert(cell_is_mergeable(0), 'Cell 0 is mergeable');
this.test.assert(!cell_is_splittable(1), 'Cell 1 is not splittable');
this.test.assert(!cell_is_mergeable(1), 'Cell 1 is not mergeable');
this.test.assert(cell_is_splittable(2), 'Cell 2 is splittable');
this.test.assert(cell_is_mergeable(2), 'Cell 2 is mergeable');
});
// Try to merge cell 0 above, nothing should happen
this.then(function () {
this.select_cell(0);
});
this.thenEvaluate(function() {
IPython.notebook.merge_cell_above();
});
this.then(function() {
this.test.assertEquals(this.get_cells_length(), N, 'Merge cell 0 above: There are still '+N+' cells');
this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 0 above: Cell 0 is unchanged');
this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 0 above: Cell 1 is unchanged');
this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 0 above: Cell 2 is unchanged');
this.validate_notebook_state('merge up', 'command', 0);
});
// Try to merge cell 0 below with cell 1, should not work, as 1 is locked
this.then(function () {
this.trigger_keydown('esc');
this.select_cell(0);
this.select_cell(1,false);
this.trigger_keydown('shift-m');
this.trigger_keydown('esc');
this.test.assertEquals(this.get_cells_length(), N, 'Merge cell 0 down: There are still '+N+' cells');
this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 0 down: Cell 0 is unchanged');
this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 0 down: Cell 1 is unchanged');
this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 0 down: Cell 2 is unchanged');
this.validate_notebook_state('merge 0 with 1', 'command', 1);
});
// Try to merge cell 1 above with cell 0
this.then(function () {
this.select_cell(1);
});
this.thenEvaluate(function () {
IPython.notebook.merge_cell_above();
});
this.then(function () {
this.test.assertEquals(this.get_cells_length(), N, 'Merge cell 1 up: There are still '+N+' cells');
this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 1 up: Cell 0 is unchanged');
this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 1 up: Cell 1 is unchanged');
this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 1 up: Cell 2 is unchanged');
this.validate_notebook_state('merge up', 'command', 1);
});
// Try to split cell 1
this.then(function () {
this.select_cell(1);
this.trigger_keydown('enter');
this.set_cell_editor_cursor(1, 0, 2);
this.trigger_keydown('ctrl-shift--'); // Split
this.test.assertEquals(this.get_cells_length(), N, 'Split cell 1: There are still '+N+' cells');
this.test.assertEquals(this.get_cell_text(0), a, 'Split cell 1: Cell 0 is unchanged');
this.test.assertEquals(this.get_cell_text(1), b, 'Split cell 1: Cell 1 is unchanged');
this.test.assertEquals(this.get_cell_text(2), c, 'Split cell 1: Cell 2 is unchanged');
this.validate_notebook_state('ctrl-shift--', 'edit', 1);
});
// Try to merge cell 1 down, should fail, as 1 is locked
this.then(function () {
this.trigger_keydown('esc');
this.select_cell(1);
this.select_cell(2, false); // extend selection
this.trigger_keydown('shift-m');
this.trigger_keydown('esc');
this.test.assertEquals(this.get_cells_length(), N, 'Merge cell 1 down: There are still '+N+' cells');
this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 1 down: Cell 0 is unchanged');
this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 1 down: Cell 1 is unchanged');
this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 1 down: Cell 2 is unchanged');
this.validate_notebook_state('Merge 1 with 2', 'command', 2);
});
// Try to merge cell 2 above with cell 1, should fail, 1 is locked
this.then(function () {
this.select_cell(2);
});
this.thenEvaluate(function () {
IPython.notebook.merge_cell_above();
});
this.then(function () {
this.test.assertEquals(this.get_cells_length(), N, 'Merge cell 2 up: There are still '+N+' cells');
this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 2 up: Cell 0 is unchanged');
this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 2 up: Cell 1 is unchanged');
this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 2 up: Cell 2 is unchanged');
this.validate_notebook_state('merge up', 'command', 2);
});
this.then(function () {
this.trigger_keydown('esc');
this.select_cell(3);
this.select_cell(4, false); // extend selection
this.trigger_keydown('shift-m');
this.trigger_keydown('esc');
this.test.assertEquals(this.get_cells_length(), N-1 , 'Merge cell 3 with 4: There are now '+(N-1)+' cells');
this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 3 with 4: Cell 0 is unchanged');
this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 3 with 4: Cell 1 is unchanged');
this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 3 with 4: Cell 2 is unchanged');
this.test.assertEquals(this.get_cell_text(3), d+'\n\n'+e, 'Merge cell 3 with 4: Cell 3 is merged');
this.test.assertEquals(this.get_cell_text(4), f, 'Merge cell 3 with 4: Cell 5 is now cell 4');
this.test.assertEquals(this.get_cell_text(5), g, 'Merge cell 3 with 4: Cell 6 is now cell 5');
this.validate_notebook_state('actual merge', 'command', 3);
});
this.then(function () {
this.trigger_keydown('esc');
this.select_cell(4);
// shift-m on single selection does nothing.
this.trigger_keydown('shift-m');
this.trigger_keydown('esc');
this.test.assertEquals(this.get_cells_length(), N-2 , 'Merge cell 4 with 5: There are now '+(N-2)+' cells');
this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 4 with 5: Cell 0 is unchanged');
this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 4 with 5: Cell 1 is unchanged');
this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 4 with 5: Cell 2 is unchanged');
this.test.assertEquals(this.get_cell_text(3), d+'\n\n'+e, 'Merge cell 4 with 5: Cell 3 is unchanged');
this.test.assertEquals(this.get_cell_text(4), f+'\n\n'+g, 'Merge cell 4 with 5: Cell 4 and 5 are merged');
this.validate_notebook_state('merge on single cell merge with below', 'command', 4);
});
});

View file

@ -0,0 +1,178 @@
//
// Test that the correct cells are executed when there are marked cells.
//
casper.notebook_test(function () {
var that = this;
var assert_outputs = function (expected, msg_prefix) {
var msg, i;
msg_prefix = "(assert_outputs) "+(msg_prefix || 'no prefix')+": ";
for (i = 0; i < that.get_cells_length(); i++) {
if (expected[i] === undefined) {
msg = msg_prefix + 'cell ' + i + ' not executed';
that.test.assertFalse(that.cell_has_outputs(i), msg);
} else {
msg = msg_prefix + 'cell ' + i + ' executed';
var out = (that.get_output_cell(i, undefined, msg_prefix)||{test:'<no cells>'}).text
that.test.assertEquals(out, expected[i], msg + ', out is: '+out);
}
}
};
this.then(function () {
this.set_cell_text(0, 'print("a")');
this.append_cell('print("b")');
this.append_cell('print("c")');
this.append_cell('print("d")');
this.test.assertEquals(this.get_cells_length(), 4, "correct number of cells");
});
this.then(function () {
this.select_cell(1);
this.select_cell(2, false);
});
this.then(function () {
this.evaluate(function () {
IPython.notebook.clear_all_output();
});
})
this.then(function(){
this.select_cell(1);
this.validate_notebook_state('before execute 1', 'command', 1);
this.select_cell(1);
this.select_cell(2, false);
this.trigger_keydown('ctrl-enter');
});
this.wait_for_output(1);
this.wait_for_output(2);
this.then(function () {
assert_outputs([undefined, 'b\n', 'c\n', undefined], 'run selected 1');
this.validate_notebook_state('run selected cells 1', 'command', 2);
});
// execute and insert below when there are selected cells
this.then(function () {
this.evaluate(function () {
IPython.notebook.clear_all_output();
});
this.select_cell(1);
this.validate_notebook_state('before execute 2', 'command', 1);
this.evaluate(function () {
$("#run_cell_insert_below").click();
});
});
this.wait_for_output(1);
this.then(function () {
assert_outputs([undefined, 'b\n', undefined, undefined , undefined],'run selected cells 2');
this.validate_notebook_state('run selected cells 2', 'edit', 2);
});
// check that it doesn't affect run all above
this.then(function () {
this.evaluate(function () {
IPython.notebook.clear_all_output();
});
this.select_cell(1);
this.validate_notebook_state('before execute 3', 'command', 1);
this.evaluate(function () {
$("#run_all_cells_above").click();
});
});
this.wait_for_output(0);
this.then(function () {
assert_outputs(['a\n', undefined, undefined, undefined],'run cells above');
this.validate_notebook_state('run cells above', 'command', 0);
});
// check that it doesn't affect run all below
this.then(function () {
this.evaluate(function () {
IPython.notebook.clear_all_output();
});
this.select_cell(1);
this.validate_notebook_state('before execute 4', 'command', 1);
this.evaluate(function () {
$("#run_all_cells_below").click();
});
});
this.wait_for_output(1);
this.wait_for_output(2);
this.wait_for_output(3);
this.then(function () {
assert_outputs([undefined, 'b\n', undefined, 'c\n', 'd\n'],'run cells below');
this.validate_notebook_state('run cells below', 'command', 4);
});
// check that it doesn't affect run all
this.then(function () {
this.evaluate(function () {
IPython.notebook.clear_all_output();
});
this.select_cell(1);
this.validate_notebook_state('before execute 5', 'command', 1);
this.evaluate(function () {
$("#run_all_cells").click();
});
});
this.wait_for_output(0);
this.wait_for_output(1);
this.wait_for_output(2);
this.wait_for_output(3);
this.then(function () {
assert_outputs(['a\n', 'b\n', undefined, 'c\n', 'd\n'],'run all cells');
this.validate_notebook_state('run all cells', 'command', 4);
});
this.then(function(){
this.set_cell_text(0, 'print("x")');
this.set_cell_text(1, 'print("y")');
this.select_cell(0);
this.select_cell(1, false);
this.trigger_keydown('alt-enter');
});
this.wait_for_output(0);
this.wait_for_output(1);
this.then(function () {
assert_outputs(['x\n', 'y\n', undefined, undefined, 'c\n', 'd\n'],'run selection and insert below');
this.validate_notebook_state('run selection insert below', 'edit', 2);
});
this.then(function(){
this.set_cell_text(1, 'print("z")');
this.set_cell_text(2, 'print("a")');
this.select_cell(1);
this.select_cell(2, false);
this.evaluate(function () {
$("#run_cell_select_below").click();
});
});
this.wait_for_output(1);
this.wait_for_output(2);
this.then(function () {
assert_outputs(['x\n', 'z\n', 'a\n', undefined, 'c\n', 'd\n'],'run selection and select below');
this.validate_notebook_state('run selection select below', 'command', 3);
});
});

View file

@ -0,0 +1,23 @@
//
// Test robustness about JS injection in different place
//
// This assume malicious document arrive to the frontend.
//
casper.notebook_test(function () {
var messages = [];
this.on('remote.alert', function (msg) {
messages.push(msg);
});
this.evaluate(function () {
var cell = IPython.notebook.get_cell(0);
var json = cell.toJSON();
json.execution_count = "<script> alert('hello from input prompts !')</script>";
cell.fromJSON(json);
});
this.then(function () {
this.test.assert(messages.length == 0, "Captured log message from script tag injection !");
});
});

View file

@ -0,0 +1,64 @@
//
// Test that a Markdown cell is rendered to HTML.
//
casper.notebook_test(function () {
"use strict";
var text = 'multi\nline';
this.evaluate(function (text) {
var cell = IPython.notebook.insert_cell_at_index('markdown', 0);
cell.set_text(text);
}, {text: text});
// Test markdown code blocks
function mathjax_render_test(input_string, result, message){
casper.thenEvaluate(function (text){
window._test_result = null;
require(['notebook/js/mathjaxutils'],function(mathjaxutils){
window._test_result = mathjaxutils.remove_math(text);
});
}, {text: input_string});
casper.waitFor(function() {
return casper.evaluate(function(){
return window._test_result!==null;
});
});
casper.then(function(){
var return_val = casper.evaluate(function(){
var blah = window._test_result;
delete window._test_result;
return blah;
});
this.test.assertEquals(return_val[0], result[0], message+" markdown");
this.test.assertEquals(return_val[1].length, result[1].length, message+" math instance count");
for(var i=0; i<return_val[1].length; i++){
this.test.assertEquals(return_val[1][i], result[1][i], message+" math instance "+i);
};
});
};
var input_string_1 = 'x \\\\(a_{0}+ b_{T}\\\\) y \\\\(a_{0}+ b_{T}\\\\) z';
var expected_result_1 = ['x @@0@@ y @@1@@ z', ['\\\\(a_{0}+ b_{T}\\\\)','\\\\(a_{0}+ b_{T}\\\\)']];
var message_1 = "multiple inline(LaTeX style) with underscores";
var input_string_2 = 'x \\\\[a_{0}+ b_{T}\\\\] y \\\\[a_{0}+ b_{T}\\\\] z';
var expected_result_2 = ['x @@0@@ y @@1@@ z', ['\\\\[a_{0}+ b_{T}\\\\]','\\\\[a_{0}+ b_{T}\\\\]']];
var message_2 = "multiple equation (LaTeX style) with underscores";
var input_string_3 = 'x $a_{0}+ b_{T}$ y $a_{0}+ b_{T}$ z';
var expected_result_3 = ['x @@0@@ y @@1@@ z',['$a_{0}+ b_{T}$','$a_{0}+ b_{T}$']];
var message_3 = "multiple inline(TeX style) with underscores";
var input_string_4 = 'x $$a_{0}+ b_{T}$$ y $$a_{0}+ b_{T}$$ z';
var expected_result_4 = ['x @@0@@ y @@1@@ z', ['$$a_{0}+ b_{T}$$','$$a_{0}+ b_{T}$$']];
var message_4 = "multiple equation(TeX style) with underscores";
var input_string_5 = 'x \\begin{equation}a_{0}+ b_{T}\\end{equation} y \\begin{equation}a_{0}+ b_{T}\\end{equation} z';
var expected_result_5 = ['x @@0@@ y @@1@@ z',['\\begin{equation}a_{0}+ b_{T}\\end{equation}','\\begin{equation}a_{0}+ b_{T}\\end{equation}']];
var message_5 = "multiple equations with underscores";
mathjax_render_test(input_string_1, expected_result_1, message_1);
mathjax_render_test(input_string_2, expected_result_2, message_2);
mathjax_render_test(input_string_3, expected_result_3, message_3);
mathjax_render_test(input_string_4, expected_result_4, message_4);
mathjax_render_test(input_string_5, expected_result_5, message_5);
});

View file

@ -0,0 +1,259 @@
//
// Various output tests
//
casper.notebook_test(function () {
this.compare_outputs = function(results, expected) {
for (var i = 0; i < results.length; i++) {
var r = results[i];
var ex = expected[i];
this.test.assertEquals(r.output_type, ex.output_type, "output " + i + " = " + r.output_type);
if (r.output_type === 'stream') {
this.test.assertEquals(r.name, ex.name, "stream " + i + " = " + r.name);
this.test.assertEquals(r.text, ex.text, "content " + i);
}
}
}
this.test_coalesced_output = function (msg, code, expected) {
this.then(function () {
this.echo("Test coalesced output: " + msg);
});
this.thenEvaluate(function (code) {
IPython.notebook.insert_cell_at_index("code", 0);
var cell = IPython.notebook.get_cell(0);
cell.set_text(code);
cell.execute();
}, {code: code});
this.wait_for_output(0);
this.then(function () {
var results = this.evaluate(function () {
var cell = IPython.notebook.get_cell(0);
return cell.output_area.outputs;
});
this.test.assertEquals(results.length, expected.length, "correct number of outputs");
this.compare_outputs(results, expected);
});
};
this.thenEvaluate(function () {
IPython.notebook.insert_cell_at_index("code", 0);
var cell = IPython.notebook.get_cell(0);
cell.set_text([
"from __future__ import print_function",
"import sys",
"from IPython.display import display, clear_output"
].join("\n")
);
cell.execute();
});
this.test_coalesced_output("stdout", [
"print(1)",
"sys.stdout.flush()",
"print(2)",
"sys.stdout.flush()",
"print(3)"
].join("\n"), [{
output_type: "stream",
name: "stdout",
text: "1\n2\n3\n"
}]
);
this.test_coalesced_output("stdout+sdterr", [
"print(1)",
"sys.stdout.flush()",
"print(2)",
"print(3, file=sys.stderr)"
].join("\n"), [{
output_type: "stream",
name: "stdout",
text: "1\n2\n"
},{
output_type: "stream",
name: "stderr",
text: "3\n"
}]
);
this.test_coalesced_output("display splits streams", [
"print(1)",
"sys.stdout.flush()",
"display(2)",
"print(3)"
].join("\n"), [{
output_type: "stream",
name: "stdout",
text: "1\n"
},{
output_type: "display_data",
},{
output_type: "stream",
name: "stdout",
text: "3\n"
}]
);
this.test_coalesced_output("test nested svg", [
'from IPython.display import SVG',
'nested_svg="""',
'<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" >',
' <svg x="0">',
' <rect x="10" y="10" height="80" width="80" style="fill: #0000ff"/>',
' </svg>',
' <svg x="100">',
' <rect x="10" y="10" height="80" width="80" style="fill: #00cc00"/>',
' </svg>',
'</svg>"""',
'SVG(nested_svg)'
].join("\n"), [{
output_type: "execute_result",
data: {
"text/plain" : "<IPython.core.display.SVG object>",
"image/svg+xml": [
'<svg height="200" width="100" xmlns="http://www.w3.org/2000/svg">',
' <svg x="0">',
' <rect height="80" style="fill: #0000ff" width="80" x="10" y="10"/>',
' </svg>',
' <svg x="100">',
' <rect height="80" style="fill: #00cc00" width="80" x="10" y="10"/>',
' </svg>',
'</svg>'].join("\n")
},
}]
);
this.then(function () {
this.echo("Test output callback overrides");
});
this.thenEvaluate(function () {
IPython.notebook.insert_cell_at_index("code", 0);
var cell = IPython.notebook.get_cell(0);
cell.set_text(["print(1)",
"sys.stdout.flush()",
"print(2)",
"sys.stdout.flush()",
"print(3, file=sys.stderr)",
"sys.stdout.flush()",
"display(2)",
"clear_output()",
"sys.stdout.flush()",
"print('remove handler')",
"sys.stdout.flush()",
"print('back to cell')",
"sys.stdout.flush()",
].join('\n'));
cell.execute();
var kernel = IPython.notebook.kernel;
var msg_id = cell.last_msg_id;
var callback_id = 'mycallbackid'
cell.iopub_messages = [];
var add_msg = function(msg) {
if (msg.content.text==="remove handler\n") {
kernel.output_callback_overrides_pop(msg_id);
}
msg.content.output_type = msg.msg_type;
cell.iopub_messages.push(msg.content);
};
kernel.set_callbacks_for_msg(callback_id, {
iopub: {
output: add_msg,
clear_output: add_msg,
}
}, false);
kernel.output_callback_overrides_push(msg_id, callback_id);
});
this.wait_for_idle();
this.then(function () {
var expected_callback = [{
output_type: "stream",
name: "stdout",
text: "1\n"
}, {
output_type: "stream",
name: "stdout",
text: "2\n"
}, {
output_type: "stream",
name: "stderr",
text: "3\n"
},{
output_type: "display_data",
},{
output_type: "clear_output",
},{
output_type: "stream",
name: "stdout",
text: "remove handler\n"
},]
var expected_cell = [{
output_type: "stream",
name: "stdout",
text: "back to cell\n"
}]
var returned = this.evaluate(function () {
var cell = IPython.notebook.get_cell(0);
return [cell.output_area.outputs, cell.iopub_messages];
});
var cell_results = returned[0];
var callback_results = returned[1];
this.test.assertEquals(cell_results.length, expected_cell.length, "correct number of cell outputs");
this.test.assertEquals(callback_results.length, expected_callback.length, "correct number of callback outputs");
this.compare_outputs(cell_results, expected_cell);
this.compare_outputs(callback_results, expected_callback);
});
this.then(function () {
this.echo("Test output callback overrides get execute_results messages too");
});
this.thenEvaluate(function () {
IPython.notebook.insert_cell_at_index("code", 0);
var cell = IPython.notebook.get_cell(0);
cell.set_text("'end'");
cell.execute();
var kernel = IPython.notebook.kernel;
var msg_id = cell.last_msg_id;
var callback_id = 'mycallbackid2'
cell.iopub_messages = [];
var add_msg = function(msg) {
msg.content.output_type = msg.msg_type;
cell.iopub_messages.push(msg.content);
};
kernel.set_callbacks_for_msg(callback_id, {
iopub: {
output: add_msg,
clear_output: add_msg,
}
}, false);
kernel.output_callback_overrides_push(msg_id, callback_id);
});
this.wait_for_idle();
this.then(function () {
var expected_callback = [{
output_type: "execute_result",
data: {
"text/plain" : "'end'"
}
}];
var expected_cell = [];
var returned = this.evaluate(function () {
var cell = IPython.notebook.get_cell(0);
return [cell.output_area.outputs, cell.iopub_messages];
});
var cell_results = returned[0];
var callback_results = returned[1];
this.test.assertEquals(cell_results.length, expected_cell.length, "correct number of cell outputs");
this.test.assertEquals(callback_results.length, expected_callback.length, "correct number of callback outputs");
this.compare_outputs(callback_results, expected_callback);
});
});

View file

@ -0,0 +1,254 @@
// Test opening a rich notebook, saving it, and reopening it again.
//
//toJSON fromJSON toJSON and do a string comparison
// this is just a copy of OutputArea.mime_mape_r in IPython/html/static/notebook/js/outputarea.js
mime = {
"text" : "text/plain",
"html" : "text/html",
"svg" : "image/svg+xml",
"png" : "image/png",
"jpeg" : "image/jpeg",
"latex" : "text/latex",
"json" : "application/json",
"javascript" : "application/javascript",
};
var black_dot_jpeg="u\"\"\"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDACodICUgGiolIiUvLSoyP2lEPzo6P4FcYUxpmYagnpaG\nk5GovfLNqLPltZGT0v/V5fr/////o8v///////L/////2wBDAS0vLz83P3xERHz/rpOu////////\n////////////////////////////////////////////////////////////wgARCAABAAEDAREA\nAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAABP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEA\nAhADEAAAARn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAEFAn//xAAUEQEAAAAAAAAAAAAA\nAAAAAAAA/9oACAEDAQE/AX//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/AX//xAAUEAEA\nAAAAAAAAAAAAAAAAAAAA/9oACAEBAAY/An//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nIX//2gAMAwEAAgADAAAAEB//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/EH//xAAUEQEA\nAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/EH//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nEH//2Q==\"\"\"";
var black_dot_png = 'u\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QA\\niAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AA\\nAAACAAHiIbwzAAAAAElFTkSuQmCC\"';
var svg = "\"<svg width='1cm' height='1cm' viewBox='0 0 1000 500'><defs><style>rect {fill:red;}; </style></defs><rect id='r1' x='200' y='100' width='600' height='300' /></svg>\"";
// helper function to ensure that the short_name is found in the toJSON
// representation, while the original in-memory cell retains its long mimetype
// name, and that fromJSON also gets its long mimetype name
function assert_has(short_name, json, result, result2) {
var long_name = mime[short_name];
this.test.assertFalse(json[0].data.hasOwnProperty(short_name),
"toJSON() representation doesn't use " + short_name);
this.test.assertTrue(json[0].data.hasOwnProperty(long_name),
'toJSON() representation uses ' + long_name);
this.test.assertTrue(result.data.hasOwnProperty(long_name),
'toJSON() original embedded JSON keeps ' + long_name);
this.test.assertTrue(result2.data.hasOwnProperty(long_name),
'fromJSON() embedded ' + short_name + ' gets mime key ' + long_name);
}
// helper function for checkout that the first two cells have a particular
// output_type (either 'execute_result' or 'display_data'), and checks the to/fromJSON
// for a set of mimetype keys, ensuring the old short names ('javascript', 'text',
// 'png', etc) are not used.
function check_output_area(output_type, keys) {
this.wait_for_output(0);
var json = this.evaluate(function() {
var json = IPython.notebook.get_cell(0).output_area.toJSON();
// appended cell will initially be empty, let's add some output
IPython.notebook.get_cell(1).output_area.fromJSON(json);
return json;
});
// The evaluate call above happens asynchronously: wait for cell[1] to have output
this.wait_for_output(1);
var result = this.get_output_cell(0);
var result2 = this.get_output_cell(1);
this.test.assertEquals(result.output_type, output_type,
'testing ' + output_type + ' for ' + keys.join(' and '));
for (var idx in keys) {
assert_has.apply(this, [keys[idx], json, result, result2]);
}
}
// helper function to clear the first two cells, set the text of and execute
// the first one
function clear_and_execute(that, code) {
that.evaluate(function() {
IPython.notebook.get_cell(0).clear_output();
IPython.notebook.get_cell(1).clear_output();
});
that.then(function () {
that.set_cell_text(0, code);
that.execute_cell(0);
that.wait_for_idle();
});
}
casper.notebook_test(function () {
this.evaluate(function () {
var cell = IPython.notebook.get_cell(0);
// "we have to make messes to find out who we are"
cell.set_text([
"%%javascript",
"IPython.notebook.insert_cell_below('code')"
].join('\n')
);
});
this.execute_cell_then(0, function () {
var result = this.get_output_cell(0);
var num_cells = this.get_cells_length();
this.test.assertEquals(num_cells, 2, '%%javascript magic works');
this.test.assertTrue(result.data.hasOwnProperty('application/javascript'),
'testing JS embedded with mime key');
});
//this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
this.then(function () {
clear_and_execute(this, [
"%%javascript",
"var a=5;"
].join('\n'));
});
this.then(function () {
check_output_area.apply(this, ['display_data', ['javascript']]);
});
this.then(function() {
clear_and_execute(this, '%lsmagic');
});
this.then(function () {
check_output_area.apply(this, ['execute_result', ['text', 'json']]);
});
this.then(function() {
clear_and_execute(this,
"x = %lsmagic\nfrom IPython.display import display; display(x)");
});
this.then(function ( ) {
check_output_area.apply(this, ['display_data', ['text', 'json']]);
});
this.then(function() {
clear_and_execute(this,
"from IPython.display import Latex; Latex('$X^2$')");
});
this.then(function ( ) {
check_output_area.apply(this, ['execute_result', ['text', 'latex']]);
});
this.then(function() {
clear_and_execute(this,
"from IPython.display import Latex, display; display(Latex('$X^2$'))");
});
this.then(function ( ) {
check_output_area.apply(this, ['display_data', ['text', 'latex']]);
});
this.then(function() {
clear_and_execute(this,
"from IPython.display import HTML; HTML('<b>it works!</b>')");
});
this.then(function ( ) {
check_output_area.apply(this, ['execute_result', ['text', 'html']]);
});
this.then(function() {
clear_and_execute(this,
"from base64 import b64decode;" +
"black_dot_png = b64decode(" + black_dot_png + ");" +
"black_dot_jpeg = b64decode(" + black_dot_jpeg + ")"
);
});
this.then(function() {
clear_and_execute(this,
"from IPython.display import HTML, display; display(HTML('<b>it works!</b>'))");
});
this.then(function ( ) {
check_output_area.apply(this, ['display_data', ['text', 'html']]);
});
this.then(function() {
clear_and_execute(this,
"from IPython.display import Image; Image(black_dot_png)");
});
this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
this.then(function ( ) {
check_output_area.apply(this, ['execute_result', ['text', 'png']]);
});
this.then(function() {
clear_and_execute(this,
"from IPython.display import Image, display; display(Image(black_dot_png))");
});
this.then(function ( ) {
check_output_area.apply(this, ['display_data', ['text', 'png']]);
});
this.then(function() {
clear_and_execute(this,
"from IPython.display import Image; Image(black_dot_jpeg, format='jpeg')");
});
this.then(function ( ) {
check_output_area.apply(this, ['execute_result', ['text', 'jpeg']]);
});
this.then(function() {
clear_and_execute(this,
"from IPython.display import Image, display; display(Image(black_dot_jpeg, format='jpeg'))");
});
this.then(function ( ) {
check_output_area.apply(this, ['display_data', ['text', 'jpeg']]);
});
this.then(function() {
clear_and_execute(this,
"from IPython.core.display import SVG; SVG(" + svg + ")");
});
this.then(function ( ) {
check_output_area.apply(this, ['execute_result', ['text', 'svg']]);
});
this.then(function() {
clear_and_execute(this,
"from IPython.core.display import SVG, display; display(SVG(" + svg + "))");
});
this.then(function ( ) {
check_output_area.apply(this, ['display_data', ['text', 'svg']]);
});
this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
this.then(function() {
clear_and_execute(this, [
"from IPython.core.formatters import HTMLFormatter",
"x = HTMLFormatter()",
"x.format_type = 'text/superfancymimetype'",
"get_ipython().display_formatter.formatters['text/superfancymimetype'] = x",
"from IPython.display import HTML, display",
'display(HTML("yo"))',
"HTML('hello')"].join('\n')
);
});
this.wait_for_output(0, 1);
this.then(function () {
var long_name = 'text/superfancymimetype';
var result = this.get_output_cell(0);
this.test.assertTrue(result.data.hasOwnProperty(long_name),
'display_data custom mimetype ' + long_name);
result = this.get_output_cell(0, 1);
this.test.assertTrue(result.data.hasOwnProperty(long_name),
'execute_result custom mimetype ' + long_name);
});
});

View file

@ -0,0 +1,32 @@
//
// Test validation in append_output
//
// Invalid output data is stripped and logged.
//
casper.notebook_test(function () {
// this.printLog();
var messages = [];
this.on('remote.message', function (msg) {
messages.push(msg);
});
this.evaluate(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text( "dp = get_ipython().display_pub\n" +
"dp.publish({'text/plain' : '5', 'image/png' : 5})"
);
cell.execute();
});
this.wait_for_output(0);
this.on('remote.message', function () {});
this.then(function () {
var output = this.get_output_cell(0);
this.test.assert(messages.length > 0, "Captured log message");
this.test.assertEquals(messages[messages.length-1].substr(0,26), "Invalid type for image/png", "Logged Invalid type message");
this.test.assertEquals(output.data['image/png'], undefined, "Non-string png data was stripped");
this.test.assertEquals(output.data['text/plain'], '5', "text data is fine");
});
});

View file

@ -0,0 +1,123 @@
//
// Various tagging
//
casper.notebook_test(function () {
function get_tag_metadata () {
return casper.evaluate(function () {
return Jupyter.notebook.get_cell(0).metadata.tags;
});
}
function get_tag_elements () {
return casper.evaluate(function () {
var cell = Jupyter.notebook.get_cell(0);
return $.map(
cell.element.find('.cell-tag'),
function (el) {
return $(el.childNodes[0]).text();
}
);
})
}
// wait for cell toolbar item to be present (necessary?)
this.waitFor(function () {
return this.evaluate(function() {
return $('#menu-cell-toolbar-submenu')
.find('[data-name=Tags]')
.length;
});
});
this.then(function () {
var tag_items = this.evaluate(function() {
return $('#menu-cell-toolbar-submenu')
.find('[data-name=Tags]')
.length;
});
this.test.assertEquals(
tag_items,
1,
"Tag cell toolbar item is present");
})
// activate tags toolbar via menubar
this.thenEvaluate(function () {
$('#menu-cell-toolbar-submenu')
.find('[data-name=Tags]')
.find('a')
.click();
});
// wait for one tag container
this.waitForSelector('.tags_button_container');
this.then(function () {
var elements = this.evaluate(function () {
var cell = Jupyter.notebook.get_cell(0);
var tag_input = cell.element
.find('.tags-input input');
return tag_input.length;
})
this.test.assertEquals(
elements,
1,
"tags-input element exists");
})
// apply some tags
this.thenEvaluate(function () {
var cell = Jupyter.notebook.get_cell(0);
var tag_input = cell.element
.find('.tags-input input');
// add some tags separated by commas and spaces,
// including duplicates
tag_input.val('tag1, tüg2 tåg3,tag4,,,tag5 tag1');
cell.element
.find('.tags-input button')
.click();
});
var all_tags = ['tag1', 'tüg2', 'tåg3', 'tag4', 'tag5'];
// verify that tags are applied
this.then(function () {
var tags = get_tag_metadata();
this.test.assertEquals(
tags,
all_tags,
"tags have been applied to metadata"
);
var tag_elements = get_tag_elements();
this.test.assertEquals(
tag_elements,
all_tags,
"tags elements have been added"
);
});
// remove first tag by clicking 'X'
this.thenEvaluate(function () {
var cell = Jupyter.notebook.get_cell(0);
var X = cell.element
.find('.tag-container .remove-tag-btn')
.first();
X.click();
});
this.then(function () {
var expected_tags = all_tags.slice(1);
var tags = get_tag_metadata();
this.test.assertEquals(
tags,
expected_tags,
"clicking X removes tags from metadata"
);
var tag_elements = get_tag_elements();
this.test.assertEquals(
tag_elements,
expected_tags,
"clicking X removes tags from UI"
);
})
});

View file

@ -0,0 +1,143 @@
import json
import nbformat
from nbformat.v4 import new_notebook, new_code_cell
import os
import pytest
import requests
from subprocess import Popen
import sys
from tempfile import mkstemp
from testpath.tempdir import TemporaryDirectory
import time
from urllib.parse import urljoin
from selenium.webdriver import Firefox, Remote, Chrome
from .utils import Notebook
pjoin = os.path.join
def _wait_for_server(proc, info_file_path):
"""Wait 30 seconds for the notebook server to start"""
for i in range(300):
if proc.poll() is not None:
raise RuntimeError("Notebook server failed to start")
if os.path.exists(info_file_path):
try:
with open(info_file_path) as f:
return json.load(f)
except ValueError:
# If the server is halfway through writing the file, we may
# get invalid JSON; it should be ready next iteration.
pass
time.sleep(0.1)
raise RuntimeError("Didn't find %s in 30 seconds", info_file_path)
@pytest.fixture(scope='session')
def notebook_server():
info = {}
with TemporaryDirectory() as td:
nbdir = info['nbdir'] = pjoin(td, 'notebooks')
os.makedirs(pjoin(nbdir, u'sub ∂ir1', u'sub ∂ir 1a'))
os.makedirs(pjoin(nbdir, u'sub ∂ir2', u'sub ∂ir 1b'))
info['extra_env'] = {
'JUPYTER_CONFIG_DIR': pjoin(td, 'jupyter_config'),
'JUPYTER_RUNTIME_DIR': pjoin(td, 'jupyter_runtime'),
'IPYTHONDIR': pjoin(td, 'ipython'),
}
env = os.environ.copy()
env.update(info['extra_env'])
command = [sys.executable, '-m', 'notebook',
'--no-browser',
'--notebook-dir', nbdir,
# run with a base URL that would be escaped,
# to test that we don't double-escape URLs
'--NotebookApp.base_url=/a@b/',
]
print("command=", command)
proc = info['popen'] = Popen(command, cwd=nbdir, env=env)
info_file_path = pjoin(td, 'jupyter_runtime',
'nbserver-%i.json' % proc.pid)
info.update(_wait_for_server(proc, info_file_path))
print("Notebook server info:", info)
yield info
# Shut the server down
requests.post(urljoin(info['url'], 'api/shutdown'),
headers={'Authorization': 'token '+info['token']})
def make_sauce_driver():
"""This function helps travis create a driver on Sauce Labs.
This function will err if used without specifying the variables expected
in that context.
"""
username = os.environ["SAUCE_USERNAME"]
access_key = os.environ["SAUCE_ACCESS_KEY"]
capabilities = {
"tunnel-identifier": os.environ["TRAVIS_JOB_NUMBER"],
"build": os.environ["TRAVIS_BUILD_NUMBER"],
"tags": [os.environ['TRAVIS_PYTHON_VERSION'], 'CI'],
"platform": "Windows 10",
"browserName": os.environ['JUPYTER_TEST_BROWSER'],
"version": "latest",
}
if capabilities['browserName'] == 'firefox':
# Attempt to work around issue where browser loses authentication
capabilities['version'] = '57.0'
hub_url = "%s:%s@localhost:4445" % (username, access_key)
print("Connecting remote driver on Sauce Labs")
driver = Remote(desired_capabilities=capabilities,
command_executor="http://%s/wd/hub" % hub_url)
return driver
@pytest.fixture(scope='session')
def selenium_driver():
if os.environ.get('SAUCE_USERNAME'):
driver = make_sauce_driver()
elif os.environ.get('JUPYTER_TEST_BROWSER') == 'chrome':
driver = Chrome()
else:
driver = Firefox()
yield driver
# Teardown
driver.quit()
@pytest.fixture(scope='module')
def authenticated_browser(selenium_driver, notebook_server):
selenium_driver.jupyter_server_info = notebook_server
selenium_driver.get("{url}?token={token}".format(**notebook_server))
return selenium_driver
@pytest.fixture
def notebook(authenticated_browser):
tree_wh = authenticated_browser.current_window_handle
yield Notebook.new_notebook(authenticated_browser)
authenticated_browser.switch_to.window(tree_wh)
@pytest.fixture
def prefill_notebook(selenium_driver, notebook_server):
def inner(cells):
cells = [new_code_cell(c) if isinstance(c, str) else c
for c in cells]
nb = new_notebook(cells=cells)
fd, path = mkstemp(dir=notebook_server['nbdir'], suffix='.ipynb')
with open(fd, 'w', encoding='utf-8') as f:
nbformat.write(nb, f)
fname = os.path.basename(path)
selenium_driver.get(
"{url}notebooks/{}?token={token}".format(fname, **notebook_server)
)
return Notebook(selenium_driver)
return inner

View file

@ -0,0 +1,53 @@
"""Utilities for driving Selenium interactively to develop tests.
These are not used in the tests themselves - rather, the developer writing tests
can use them to experiment with Selenium.
"""
from selenium.webdriver import Firefox
from notebook.tests.selenium.utils import Notebook
from notebook.notebookapp import list_running_servers
class NoServerError(Exception):
def __init__(self, message):
self.message = message
def quick_driver(lab=False):
"""Quickly create a selenium driver pointing at an active noteboook server.
Usage example:
from notebook.tests.selenium.quick_selenium import quick_driver
driver = quick_driver
Note: you need to manually close the driver that opens with driver.quit()
"""
try:
server = list(list_running_servers())[0]
except IndexError as e:
raise NoServerError('You need a server running before you can run '
'this command') from e
driver = Firefox()
auth_url = '{url}?token={token}'.format(**server)
driver.get(auth_url)
# If this redirects us to a lab page and we don't want that;
# then we need to redirect ourselves to the classic notebook view
if driver.current_url.endswith('/lab') and not lab:
driver.get(driver.current_url.rstrip('lab')+'tree')
return driver
def quick_notebook():
"""Quickly create a new classic notebook in a selenium driver
Usage example:
from notebook.tests.selenium.quick_selenium import quick_notebook
nb = quick_notebook()
Note: you need to manually close the driver that opens with nb.browser.quit()
"""
return Notebook.new_notebook(quick_driver())

View file

@ -0,0 +1,50 @@
"""Tests buffering of execution requests."""
from .utils import wait_for_selector
def wait_for_cell_text_output(notebook, index):
cell = notebook.cells[index]
output = wait_for_selector(cell, ".output_text", single=True)
return output.text
def wait_for_kernel_ready(notebook):
wait_for_selector(notebook.browser, ".kernel_idle_icon")
def test_kernels_buffer_without_conn(prefill_notebook):
"""Test that execution request made while disconnected is buffered."""
notebook = prefill_notebook(["print(1 + 2)"])
wait_for_kernel_ready(notebook)
notebook.browser.execute_script("IPython.notebook.kernel.stop_channels();")
notebook.execute_cell(0)
notebook.browser.execute_script("IPython.notebook.kernel.reconnect();")
wait_for_kernel_ready(notebook)
assert wait_for_cell_text_output(notebook, 0) == "3"
def test_buffered_cells_execute_in_order(prefill_notebook):
"""Test that buffered requests execute in order."""
notebook = prefill_notebook(['', 'k=1', 'k+=1', 'k*=3', 'print(k)'])
# Repeated execution of cell queued up in the kernel executes
# each execution request in order.
wait_for_kernel_ready(notebook)
notebook.browser.execute_script("IPython.notebook.kernel.stop_channels();")
# k == 1
notebook.execute_cell(1)
# k == 2
notebook.execute_cell(2)
# k == 6
notebook.execute_cell(3)
# k == 7
notebook.execute_cell(2)
notebook.execute_cell(4)
notebook.browser.execute_script("IPython.notebook.kernel.reconnect();")
wait_for_kernel_ready(notebook)
# Check that current value of k is 7
assert wait_for_cell_text_output(notebook, 4) == "7"

View file

@ -0,0 +1,27 @@
"""Tests clipboard by copying, cutting and pasting multiple cells"""
from selenium.webdriver.common.keys import Keys
from .utils import wait_for_selector, wait_for_xpath
def test_clipboard_multiselect(prefill_notebook):
notebook = prefill_notebook(['', '1', '2', '3', '4', '5a', '6b', '7c', '8d'])
assert notebook.get_cells_contents() == ['', '1', '2', '3', '4', '5a', '6b', '7c', '8d']
# Select the first 3 cells with value and replace the last 3
[notebook.body.send_keys(Keys.UP) for i in range(8)]
notebook.select_cell_range(1, 3)
notebook.body.send_keys("c")
notebook.select_cell_range(6, 8)
wait_for_xpath(notebook.browser, '//a[text()="Edit"]', single=True).click()
wait_for_selector(notebook.browser, '#paste_cell_replace', single=True).click()
assert notebook.get_cells_contents() == ['', '1', '2', '3', '4', '5a', '1', '2', '3']
# Select the last four cells, cut them and paste them below the first cell
notebook.select_cell_range(5, 8)
wait_for_selector(notebook.browser, '.fa-cut.fa', single=True).click()
for i in range(8):
notebook.body.send_keys(Keys.UP)
notebook.body.send_keys("v")
assert notebook.get_cells_contents() == ['', '5a', '1', '2', '3', '1', '2', '3', '4']

View file

@ -0,0 +1,73 @@
import os
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from notebook.utils import url_path_join
from notebook.tests.selenium.utils import wait_for_selector
pjoin = os.path.join
class PageError(Exception):
"""Error for an action being incompatible with the current jupyter web page.
"""
def __init__(self, message):
self.message = message
def url_in_tree(browser, url=None):
if url is None:
url = browser.current_url
tree_url = url_path_join(browser.jupyter_server_info['url'], 'tree')
return url.startswith(tree_url)
def get_list_items(browser):
"""Gets list items from a directory listing page
Raises PageError if not in directory listing page (url has tree in it)
"""
if not url_in_tree(browser):
raise PageError("You are not in the notebook's file tree view."
"This function can only be used the file tree context.")
# we need to make sure that at least one item link loads
wait_for_selector(browser, '.item_link')
return [{
'link': a.get_attribute('href'),
'label': a.find_element_by_class_name('item_name').text,
'element': a,
} for a in browser.find_elements_by_class_name('item_link')]
def only_dir_links(browser):
"""Return only links that point at other directories in the tree
"""
items = get_list_items(browser)
return [i for i in items
if url_in_tree(browser, i['link']) and i['label'] != '..']
def test_items(authenticated_browser):
visited_dict = {}
# Going down the tree to collect links
while True:
wait_for_selector(authenticated_browser, '.item_link')
current_url = authenticated_browser.current_url
items = visited_dict[current_url] = only_dir_links(authenticated_browser)
try:
item = items[0]
item["element"].click()
assert authenticated_browser.current_url == item['link']
except IndexError:
break
# Going back up the tree while we still have unvisited links
while visited_dict:
current_items = only_dir_links(authenticated_browser)
current_items_links = [item["link"] for item in current_items]
stored_items = visited_dict.pop(authenticated_browser.current_url)
stored_items_links = [item["link"] for item in stored_items]
assert stored_items_links == current_items_links
authenticated_browser.back()

View file

@ -0,0 +1,58 @@
def cell_is_deletable(nb, index):
JS = 'return Jupyter.notebook.get_cell({}).is_deletable();'.format(index)
return nb.browser.execute_script(JS)
def remove_all_cells(notebook):
for i in range(len(notebook.cells)):
notebook.delete_cell(0)
INITIAL_CELLS = ['print("a")', 'print("b")', 'print("c")']
def test_delete_cells(prefill_notebook):
a, b, c = INITIAL_CELLS
notebook = prefill_notebook(INITIAL_CELLS)
# Validate initial state
assert notebook.get_cells_contents() == [a, b, c]
for cell in range(0, 3):
assert cell_is_deletable(notebook, cell)
notebook.set_cell_metadata(0, 'deletable', 'false')
notebook.set_cell_metadata(1, 'deletable', 0
)
assert not cell_is_deletable(notebook, 0)
assert cell_is_deletable(notebook, 1)
assert cell_is_deletable(notebook, 2)
# Try to delete cell a (should not be deleted)
notebook.delete_cell(0)
assert notebook.get_cells_contents() == [a, b, c]
# Try to delete cell b (should succeed)
notebook.delete_cell(1)
assert notebook.get_cells_contents() == [a, c]
# Try to delete cell c (should succeed)
notebook.delete_cell(1)
assert notebook.get_cells_contents() == [a]
# Change the deletable state of cell a
notebook.set_cell_metadata(0, 'deletable', 'true')
# Try to delete cell a (should succeed)
notebook.delete_cell(0)
assert len(notebook.cells) == 1 # it contains an empty cell
# Make sure copied cells are deletable
notebook.edit_cell(index=0, content=a)
notebook.set_cell_metadata(0, 'deletable', 'false')
assert not cell_is_deletable(notebook, 0)
notebook.to_command_mode()
notebook.current_cell.send_keys('cv')
assert len(notebook.cells) == 2
assert cell_is_deletable(notebook, 1)
notebook.set_cell_metadata(0, 'deletable', 'true') # to perform below test, remove all the cells
remove_all_cells(notebook)
assert len(notebook.cells) == 1 # notebook should create one automatically on empty notebook

View file

@ -0,0 +1,65 @@
"""Test display of images
The effect of shape metadata is validated, using Image(retina=True)
"""
from .utils import wait_for_tag
# 2x2 black square in b64 jpeg and png
b64_image_data = {
"image/png" : b'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAC0lEQVR4nGNgQAYAAA4AAamRc7EA\\nAAAASUVORK5CYII',
"image/jpeg" : b'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a\nHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy\nMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAACAAIDASIA\nAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA\nAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3\nODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm\np6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA\nAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx\nBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK\nU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3\nuLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5/ooo\noAoo2Qoo'
}
def imports(notebook):
commands = [
'import base64',
'from IPython.display import display, Image',
]
notebook.edit_cell(index=0, content="\n".join(commands))
notebook.execute_cell(0)
def validate_img(notebook, cell_index, image_fmt, retina):
"""Validate that image renders as expected."""
b64data = b64_image_data[image_fmt]
commands = [
'b64data = %s' % b64data,
'data = base64.decodebytes(b64data)',
'display(Image(data, retina=%s))' % retina
]
notebook.append("\n".join(commands))
notebook.execute_cell(cell_index)
# Find the image element that was just displayed
wait_for_tag(notebook.cells[cell_index], "img", single=True)
img_element = notebook.cells[cell_index].find_element_by_tag_name("img")
src = img_element.get_attribute("src")
prefix = src.split(',')[0]
expected_prefix = "data:%s;base64" % image_fmt
assert prefix == expected_prefix
expected_size = 1 if retina else 2
assert img_element.size["width"] == expected_size
assert img_element.size["height"] == expected_size
assert img_element.get_attribute("width") == str(expected_size)
assert img_element.get_attribute("height") == str(expected_size)
def test_display_image(notebook):
imports(notebook)
# PNG, non-retina
validate_img(notebook, 1, "image/png", False)
# PNG, retina display
validate_img(notebook, 2, "image/png", True)
# JPEG, non-retina
validate_img(notebook, 3, "image/jpeg", False)
# JPEG, retina display
validate_img(notebook, 4, "image/jpeg", True)

View file

@ -0,0 +1,96 @@
"""Test display isolation.
An object whose metadata contains an "isolated" tag must be isolated
from the rest of the document.
"""
from .utils import wait_for_tag
def test_display_isolation(notebook):
import_ln = "from IPython.core.display import HTML, SVG, display, display_svg"
notebook.edit_cell(index=0, content=import_ln)
notebook.execute_cell(notebook.current_cell)
try:
isolated_html(notebook)
isolated_svg(notebook)
finally:
# Ensure we switch from iframe back to default content even if test fails
notebook.browser.switch_to.default_content()
def isolated_html(notebook):
"""Test HTML display isolation.
HTML styling rendered without isolation will affect the whole
document, whereas styling applied with isolation will affect only
the local display object.
"""
red = 'rgb(255, 0, 0)'
blue = 'rgb(0, 0, 255)'
test_str = "<div id='test'>Should turn red from non-isolation</div>"
notebook.add_and_execute_cell(content="display(HTML(%r))" % test_str)
non_isolated = (
"<style>div{color:%s;}</style>" % red +
"<div id='non-isolated'>Should be red</div>")
display_ni = "display(HTML(%r), metadata={'isolated':False})" % (
non_isolated)
notebook.add_and_execute_cell(content=display_ni)
isolated = (
"<style>div{color:%s;}</style>" % blue +
"<div id='isolated'>Should be blue</div>")
display_i = "display(HTML(%r), metadata={'isolated':True})" % (
isolated)
notebook.add_and_execute_cell(content=display_i)
iframe = wait_for_tag(notebook.browser, "iframe", single=True)
# The non-isolated div will be in the body
non_isolated_div = notebook.body.find_element_by_id("non-isolated")
assert non_isolated_div.value_of_css_property("color") == red
# The non-isolated styling will have affected the output of other cells
test_div = notebook.body.find_element_by_id("test")
assert test_div.value_of_css_property("color") == red
# The isolated div will be in an iframe, only that element will be blue
notebook.browser.switch_to.frame(iframe)
isolated_div = notebook.browser.find_element_by_id("isolated")
assert isolated_div.value_of_css_property("color") == blue
notebook.browser.switch_to.default_content()
# Clean up the html test cells
for i in range(1, len(notebook.cells)):
notebook.delete_cell(1)
def isolated_svg(notebook):
"""Test that multiple isolated SVGs have different scopes.
Asserts that there no CSS leaks between two isolated SVGs.
"""
yellow = "rgb(255, 255, 0)"
black = "rgb(0, 0, 0)"
svg_1_str = """s1 = '''<svg width="1cm" height="1cm" viewBox="0 0 1000 500"><defs><style>rect {fill:%s;}; </style></defs><rect id="r1" x="200" y="100" width="600" height="300" /></svg>'''""" % yellow
svg_2_str = """s2 = '''<svg width="1cm" height="1cm" viewBox="0 0 1000 500"><rect id="r2" x="200" y="100" width="600" height="300" /></svg>'''"""
notebook.add_and_execute_cell(content=svg_1_str)
notebook.add_and_execute_cell(content=svg_2_str)
notebook.add_and_execute_cell(
content="display_svg(SVG(s1), metadata=dict(isolated=True))")
notebook.add_and_execute_cell(
content="display_svg(SVG(s2), metadata=dict(isolated=True))")
iframes = wait_for_tag(notebook.browser, "iframe", wait_for_n=2)
# The first rectangle will be red
notebook.browser.switch_to.frame(iframes[0])
isolated_svg_1 = notebook.browser.find_element_by_id('r1')
assert isolated_svg_1.value_of_css_property("fill") == yellow
notebook.browser.switch_to.default_content()
# The second rectangle will be black
notebook.browser.switch_to.frame(iframes[1])
isolated_svg_2 = notebook.browser.find_element_by_id('r2')
assert isolated_svg_2.value_of_css_property("fill") == black
# Clean up the svg test cells
for i in range(1, len(notebook.cells)):
notebook.delete_cell(1)

View file

@ -0,0 +1,103 @@
"""Tests arrow keys on both command and edit mode"""
from selenium.webdriver.common.keys import Keys
def test_dualmode_arrows(notebook):
# Tests in command mode.
# Setting up the cells to test the keys to move up.
notebook.to_command_mode()
[notebook.body.send_keys("b") for i in range(3)]
# Use both "k" and up arrow keys to moving up and enter a value.
# Once located on the top cell, use the up arrow keys to prove the top cell is still selected.
notebook.body.send_keys("k")
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys("2")
notebook.to_command_mode()
notebook.body.send_keys(Keys.UP)
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys("1")
notebook.to_command_mode()
notebook.body.send_keys("k")
notebook.body.send_keys(Keys.UP)
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys("0")
notebook.to_command_mode()
assert notebook.get_cells_contents() == ["0", "1", "2", ""]
# Use the "k" key on the top cell as well
notebook.body.send_keys("k")
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys(" edit #1")
notebook.to_command_mode()
assert notebook.get_cells_contents() == ["0 edit #1", "1", "2", ""]
# Setting up the cells to test the keys to move down
[notebook.body.send_keys("j") for i in range(3)]
[notebook.body.send_keys("a") for i in range(2)]
notebook.body.send_keys("k")
# Use both "j" key and down arrow keys to moving down and enter a value.
# Once located on the bottom cell, use the down arrow key to prove the bottom cell is still selected.
notebook.body.send_keys(Keys.DOWN)
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys("3")
notebook.to_command_mode()
notebook.body.send_keys("j")
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys("4")
notebook.to_command_mode()
notebook.body.send_keys("j")
notebook.body.send_keys(Keys.DOWN)
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys("5")
notebook.to_command_mode()
assert notebook.get_cells_contents() == ["0 edit #1", "1", "2", "3", "4", "5"]
# Use the "j" key on the top cell as well
notebook.body.send_keys("j")
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys(" edit #1")
notebook.to_command_mode()
assert notebook.get_cells_contents() == ["0 edit #1", "1", "2", "3", "4", "5 edit #1"]
# On the bottom cell, use both left and right arrow keys to prove the bottom cell is still selected.
notebook.body.send_keys(Keys.LEFT)
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys(", #2")
notebook.to_command_mode()
assert notebook.get_cells_contents() == ["0 edit #1", "1", "2", "3", "4", "5 edit #1, #2"]
notebook.body.send_keys(Keys.RIGHT)
notebook.body.send_keys(Keys.ENTER)
notebook.body.send_keys(" and #3")
notebook.to_command_mode()
assert notebook.get_cells_contents() == ["0 edit #1", "1", "2", "3", "4", "5 edit #1, #2 and #3"]
# Tests in edit mode.
# First, erase the previous content and then setup the cells to test the keys to move up.
[notebook.browser.find_element_by_class_name("fa-cut.fa").click() for i in range(6)]
[notebook.body.send_keys("b") for i in range(2)]
notebook.body.send_keys("a")
notebook.body.send_keys(Keys.ENTER)
# Use the up arrow key to move down and enter a value.
# We will use the left arrow key to move one char to the left since moving up on last character only moves selector to the first one.
# Once located on the top cell, use the up arrow key to prove the top cell is still selected.
notebook.body.send_keys(Keys.UP)
notebook.body.send_keys("1")
notebook.body.send_keys(Keys.LEFT)
[notebook.body.send_keys(Keys.UP) for i in range(2)]
notebook.body.send_keys("0")
# Use the down arrow key to move down and enter a value.
# We will use the right arrow key to move one char to the right since moving down puts selector to the last character.
# Once located on the bottom cell, use the down arrow key to prove the bottom cell is still selected.
notebook.body.send_keys(Keys.DOWN)
notebook.body.send_keys(Keys.RIGHT)
notebook.body.send_keys(Keys.DOWN)
notebook.body.send_keys("2")
[notebook.body.send_keys(Keys.DOWN) for i in range(2)]
notebook.body.send_keys("3")
notebook.to_command_mode()
assert notebook.get_cells_contents() == ["0", "1", "2", "3"]

View file

@ -0,0 +1,58 @@
"""Test keyboard shortcuts that change the cell's mode."""
def test_dualmode_cellmode(notebook):
def get_cell_cm_mode(index):
code_mirror_mode = notebook.browser.execute_script(
"return Jupyter.notebook.get_cell(%s).code_mirror.getMode().name;"%index)
return code_mirror_mode
index = 0
a = 'hello\nmulti\nline'
notebook.edit_cell(index=index, content=a)
"""check for the default cell type"""
notebook.to_command_mode()
notebook.body.send_keys("r")
assert notebook.get_cell_type(index) == 'raw'
assert get_cell_cm_mode(index) == 'null'
"""check cell type after changing to markdown"""
notebook.body.send_keys("1")
assert notebook.get_cell_type(index) == 'markdown'
assert notebook.get_cell_contents(index) == '# ' + a
assert get_cell_cm_mode(index) == 'ipythongfm'
notebook.body.send_keys("2")
assert notebook.get_cell_type(index) == 'markdown'
assert notebook.get_cell_contents(index) == '## ' + a
notebook.body.send_keys("3")
assert notebook.get_cell_type(index) == 'markdown'
assert notebook.get_cell_contents(index) == '### ' + a
notebook.body.send_keys("4")
assert notebook.get_cell_type(index) == 'markdown'
assert notebook.get_cell_contents(index) == '#### ' + a
notebook.body.send_keys("5")
assert notebook.get_cell_type(index) == 'markdown'
assert notebook.get_cell_contents(index) == '##### ' + a
notebook.body.send_keys("6")
assert notebook.get_cell_type(index) == 'markdown'
assert notebook.get_cell_contents(index) == '###### ' + a
notebook.body.send_keys("m")
assert notebook.get_cell_type(index) == 'markdown'
assert notebook.get_cell_contents(index) == '###### ' + a
notebook.body.send_keys("y")
assert notebook.get_cell_type(index) == 'code'
assert notebook.get_cell_contents(index) == '###### ' + a
assert get_cell_cm_mode(index) == 'ipython'
notebook.body.send_keys("1")
assert notebook.get_cell_type(index) == 'markdown'
assert notebook.get_cell_contents(index) == '# ' + a

View file

@ -0,0 +1,54 @@
"""Test"""
from .utils import shift, validate_dualmode_state
INITIAL_CELLS = ['', 'print("a")', 'print("b")', 'print("c")']
def test_dualmode_clipboard(prefill_notebook):
notebook = prefill_notebook(INITIAL_CELLS)
_, a, b, c = INITIAL_CELLS
for i in range(1, 4):
notebook.execute_cell(i)
#Copy/past/cut
num_cells = len(notebook.cells)
assert notebook.get_cell_contents(1) == a #Cell 1 is a
notebook.focus_cell(1)
notebook.body.send_keys("x") #Cut
validate_dualmode_state(notebook, 'command', 1)
assert notebook.get_cell_contents(1) == b #Cell 2 is now where cell 1 was
assert len(notebook.cells) == num_cells-1 #A cell was removed
notebook.focus_cell(2)
notebook.body.send_keys("v") #Paste
validate_dualmode_state(notebook, 'command', 3)
assert notebook.get_cell_contents(3) == a #Cell 3 has the cut contents
assert len(notebook.cells) == num_cells #A cell was added
notebook.body.send_keys("v") #Paste
validate_dualmode_state(notebook, 'command', 4)
assert notebook.get_cell_contents(4) == a #Cell a has the cut contents
assert len(notebook.cells) == num_cells+1 #A cell was added
notebook.focus_cell(1)
notebook.body.send_keys("c") #Copy
validate_dualmode_state(notebook, 'command', 1)
assert notebook.get_cell_contents(1) == b #Cell 1 is b
notebook.focus_cell(2)
notebook.body.send_keys("c") #Copy
validate_dualmode_state(notebook, 'command', 2)
assert notebook.get_cell_contents(2) == c #Cell 2 is c
notebook.focus_cell(4)
notebook.body.send_keys("v") #Paste
validate_dualmode_state(notebook, 'command', 5)
assert notebook.get_cell_contents(2) == c #Cell 2 has the copied contents
assert notebook.get_cell_contents(5) == c #Cell 5 has the copied contents
assert len(notebook.cells) == num_cells+2 #A cell was added
notebook.focus_cell(0)
shift(notebook.browser, 'v') #Paste
validate_dualmode_state(notebook, 'command', 0)
assert notebook.get_cell_contents(0) == c #Cell 0 has the copied contents
assert len(notebook.cells) == num_cells+3 #A cell was added

View file

@ -0,0 +1,74 @@
''' Test keyboard invoked execution '''
from selenium.webdriver.common.keys import Keys
from .utils import shift, cmdtrl, alt, validate_dualmode_state
INITIAL_CELLS = ['', 'print("a")', 'print("b")', 'print("c")']
def test_dualmode_execute(prefill_notebook):
notebook = prefill_notebook(INITIAL_CELLS)
for i in range(1, 4):
notebook.execute_cell(i)
#shift-enter
#last cell in notebook
base_index = 3
notebook.focus_cell(base_index)
shift(notebook.browser, Keys.ENTER) #creates one cell
validate_dualmode_state(notebook, 'edit', base_index + 1)
#Not last cell in notebook & starts in edit mode
notebook.focus_cell(base_index)
notebook.body.send_keys(Keys.ENTER) #Enter edit mode
validate_dualmode_state(notebook, 'edit', base_index)
shift(notebook.browser, Keys.ENTER) #creates one cell
validate_dualmode_state(notebook, 'command', base_index + 1)
#Starts in command mode
notebook.body.send_keys('k')
validate_dualmode_state(notebook, 'command', base_index)
shift(notebook.browser, Keys.ENTER) #creates one cell
validate_dualmode_state(notebook, 'command', base_index + 1)
#Ctrl-enter
#Last cell in notebook
base_index += 1
cmdtrl(notebook.browser, Keys.ENTER)
validate_dualmode_state(notebook, 'command', base_index)
#Not last cell in notebook & stats in edit mode
notebook.focus_cell(base_index - 1)
notebook.body.send_keys(Keys.ENTER) #Enter edit mode
validate_dualmode_state(notebook, 'edit', base_index - 1)
cmdtrl(notebook.browser, Keys.ENTER)
#Starts in command mode
notebook.body.send_keys('j')
validate_dualmode_state(notebook, 'command', base_index)
cmdtrl(notebook.browser, Keys.ENTER)
validate_dualmode_state(notebook, 'command', base_index)
#Alt-enter
#Last cell in notebook
alt(notebook.browser, Keys.ENTER)
validate_dualmode_state(notebook, 'edit', base_index + 1)
#Not last cell in notebook &starts in edit mode
notebook.focus_cell(base_index)
notebook.body.send_keys(Keys.ENTER) #Enter edit mode
validate_dualmode_state(notebook, 'edit', base_index)
alt(notebook.browser, Keys.ENTER)
validate_dualmode_state(notebook, 'edit', base_index + 1)
#starts in command mode
notebook.body.send_keys(Keys.ESCAPE, 'k')
validate_dualmode_state(notebook, 'command', base_index)
alt(notebook.browser, Keys.ENTER)
validate_dualmode_state(notebook, 'edit', base_index + 1)
#Notebook will now have 8 cells, the index of the last cell will be 7
assert len(notebook) == 8 #Cells where added
notebook.focus_cell(7)
validate_dualmode_state(notebook, 'command', 7)

View file

@ -0,0 +1,51 @@
from selenium.webdriver.common.keys import Keys
from .utils import shift
INITIAL_CELLS = ['print("a")', 'print("b")', 'print("c")']
def test_insert_cell(prefill_notebook):
notebook = prefill_notebook(INITIAL_CELLS)
notebook.to_command_mode()
notebook.focus_cell(2)
notebook.convert_cell_type(2, "markdown")
# insert code cell above
notebook.current_cell.send_keys("a")
assert notebook.get_cell_contents(2) == ""
assert notebook.get_cell_type(2) == "code"
assert len(notebook.cells) == 4
# insert code cell below
notebook.current_cell.send_keys("b")
assert notebook.get_cell_contents(2) == ""
assert notebook.get_cell_contents(3) == ""
assert notebook.get_cell_type(3) == "code"
assert len(notebook.cells) == 5
notebook.edit_cell(index=1, content="cell1")
notebook.focus_cell(1)
notebook.current_cell.send_keys("a")
assert notebook.get_cell_contents(1) == ""
assert notebook.get_cell_contents(2) == "cell1"
notebook.edit_cell(index=1, content='cell1')
notebook.edit_cell(index=2, content='cell2')
notebook.edit_cell(index=3, content='cell3')
notebook.focus_cell(2)
notebook.current_cell.send_keys("b")
assert notebook.get_cell_contents(1) == "cell1"
assert notebook.get_cell_contents(2) == "cell2"
assert notebook.get_cell_contents(3) == ""
assert notebook.get_cell_contents(4) == "cell3"
# insert above multiple selected cells
notebook.focus_cell(1)
shift(notebook.browser, Keys.DOWN)
notebook.current_cell.send_keys('a')
# insert below multiple selected cells
notebook.focus_cell(2)
shift(notebook.browser, Keys.DOWN)
notebook.current_cell.send_keys('b')
assert notebook.get_cells_contents()[1:5] == ["", "cell1", "cell2", ""]

View file

@ -0,0 +1,53 @@
'''Test'''
from selenium.webdriver.common.keys import Keys
from .utils import cmdtrl, shift, validate_dualmode_state
def test_dualmode_markdown(notebook):
def is_cell_rendered(index):
JS = 'return !!IPython.notebook.get_cell(%s).rendered;'%index
return notebook.browser.execute_script(JS)
a = 'print("a")'
index = 1
notebook.append(a)
#Markdown rendering / unrendering
notebook.focus_cell(index)
validate_dualmode_state(notebook, 'command', index)
notebook.body.send_keys("m")
assert notebook.get_cell_type(index) == 'markdown'
assert not is_cell_rendered(index) #cell is not rendered
notebook.body.send_keys(Keys.ENTER)#cell is unrendered
assert not is_cell_rendered(index) #cell is not rendered
validate_dualmode_state(notebook, 'edit', index)
cmdtrl(notebook.browser, Keys.ENTER)
assert is_cell_rendered(index) #cell is rendered with crtl+enter
validate_dualmode_state(notebook, 'command', index)
notebook.body.send_keys(Keys.ENTER)#cell is unrendered
assert not is_cell_rendered(index) #cell is not rendered
notebook.focus_cell(index - 1)
assert not is_cell_rendered(index) #Select index-1; cell index is still not rendered
validate_dualmode_state(notebook, 'command', index - 1)
notebook.focus_cell(index)
validate_dualmode_state(notebook, 'command', index)
cmdtrl(notebook.browser, Keys.ENTER)
assert is_cell_rendered(index)#Cell is rendered
notebook.focus_cell(index - 1)
validate_dualmode_state(notebook, 'command', index - 1)
shift(notebook.browser, Keys.ENTER)
validate_dualmode_state(notebook, 'command', index)
assert is_cell_rendered(index)#Cell is rendered
shift(notebook.browser, Keys.ENTER)
validate_dualmode_state(notebook, 'edit', index + 1)
assert is_cell_rendered(index)#Cell is rendered

View file

@ -0,0 +1,66 @@
from selenium.webdriver.common.keys import Keys
from .utils import shift, cmdtrl
def test_execute_code(notebook):
browser = notebook.browser
def clear_outputs():
return notebook.browser.execute_script(
"Jupyter.notebook.clear_all_output();")
# Execute cell with Javascript API
notebook.edit_cell(index=0, content='a=10; print(a)')
browser.execute_script("Jupyter.notebook.get_cell(0).execute();")
outputs = notebook.wait_for_cell_output(0)
assert outputs[0].text == '10'
# Execute cell with Shift-Enter
notebook.edit_cell(index=0, content='a=11; print(a)')
clear_outputs()
shift(notebook.browser, Keys.ENTER)
outputs = notebook.wait_for_cell_output(0)
assert outputs[0].text == '11'
notebook.delete_cell(index=1)
# Execute cell with Ctrl-Enter
notebook.edit_cell(index=0, content='a=12; print(a)')
clear_outputs()
cmdtrl(notebook.browser, Keys.ENTER)
outputs = notebook.wait_for_cell_output(0)
assert outputs[0].text == '12'
# Execute cell with toolbar button
notebook.edit_cell(index=0, content='a=13; print(a)')
clear_outputs()
notebook.browser.find_element_by_css_selector(
"button[data-jupyter-action='jupyter-notebook:run-cell-and-select-next']").click()
outputs = notebook.wait_for_cell_output(0)
assert outputs[0].text == '13'
# Set up two cells to test stopping on error
notebook.edit_cell(index=0, content='raise IOError')
notebook.edit_cell(index=1, content='a=14; print(a)')
# Default behaviour: stop on error
clear_outputs()
browser.execute_script("""
var cell0 = Jupyter.notebook.get_cell(0);
var cell1 = Jupyter.notebook.get_cell(1);
cell0.execute();
cell1.execute();
""")
outputs = notebook.wait_for_cell_output(0)
assert notebook.get_cell_output(1) == []
# Execute a cell with stop_on_error=false
clear_outputs()
browser.execute_script("""
var cell0 = Jupyter.notebook.get_cell(0);
var cell1 = Jupyter.notebook.get_cell(1);
cell0.execute(false);
cell1.execute();
""")
outputs = notebook.wait_for_cell_output(1)
assert outputs[0].text == '14'

View file

@ -0,0 +1,16 @@
INITIAL_CELLS = ["hello", "hellohello", "abc", "ello"]
def test_find_and_replace(prefill_notebook):
""" test find and replace on all the cells """
notebook = prefill_notebook(INITIAL_CELLS)
find_str = "ello"
replace_str = "foo"
# replace the strings
notebook.find_and_replace(index=0, find_txt=find_str, replace_txt=replace_str)
# check content of the cells
assert notebook.get_cells_contents() == [
s.replace(find_str, replace_str) for s in INITIAL_CELLS
]

View file

@ -0,0 +1,36 @@
from .utils import wait_for_selector
def interrupt_from_menu(notebook):
# Click interrupt button in kernel menu
notebook.browser.find_element_by_id('kernellink').click()
wait_for_selector(notebook.browser, '#int_kernel', single=True).click()
def interrupt_from_keyboard(notebook):
notebook.body.send_keys("ii")
def test_interrupt(notebook):
""" Test the interrupt function using both the button in the Kernel menu and the keyboard shortcut "ii"
Having trouble accessing the Interrupt message when execution is halted. I am assuming that the
message does not lie in the "outputs" field of the cell's JSON object. Using a timeout work-around for
test with an infinite loop. We know the interrupt function is working if this test passes.
Hope this is a good start.
"""
text = ('import time\n'
'for x in range(3):\n'
' time.sleep(1)')
notebook.edit_cell(index=0, content=text)
for interrupt_method in (interrupt_from_menu, interrupt_from_keyboard):
notebook.clear_cell_output(0)
notebook.to_command_mode()
notebook.execute_cell(0)
interrupt_method(notebook)
# Wait for an output to appear
output = wait_for_selector(notebook.browser, '.output_subarea', single=True)
assert 'KeyboardInterrupt' in output.text

View file

@ -0,0 +1,60 @@
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from notebook.tests.selenium.utils import wait_for_selector
restart_selectors = [
'#restart_kernel', '#restart_clear_output', '#restart_run_all'
]
notify_interaction = '#notification_kernel > span'
shutdown_selector = '#shutdown_kernel'
confirm_selector = '.btn-danger'
cancel_selector = ".modal-footer button:first-of-type"
def test_cancel_restart_or_shutdown(notebook):
"""Click each of the restart options, then cancel the confirmation dialog"""
browser = notebook.browser
kernel_menu = browser.find_element_by_id('kernellink')
for menu_item in restart_selectors + [shutdown_selector]:
kernel_menu.click()
wait_for_selector(browser, menu_item, visible=True, single=True).click()
wait_for_selector(browser, cancel_selector, visible=True, single=True).click()
WebDriverWait(browser, 3).until(
EC.invisibility_of_element((By.CSS_SELECTOR, '.modal-backdrop'))
)
assert notebook.is_kernel_running()
def test_menu_items(notebook):
browser = notebook.browser
kernel_menu = browser.find_element_by_id('kernellink')
for menu_item in restart_selectors:
# Shutdown
kernel_menu.click()
wait_for_selector(browser, shutdown_selector, visible=True, single=True).click()
# Confirm shutdown
wait_for_selector(browser, confirm_selector, visible=True, single=True).click()
WebDriverWait(browser, 3).until(
lambda b: not notebook.is_kernel_running(),
message="Kernel did not shut down as expected"
)
# Restart
# Selenium can't click the menu while a modal dialog is fading out
WebDriverWait(browser, 3).until(
EC.invisibility_of_element((By.CSS_SELECTOR, '.modal-backdrop'))
)
kernel_menu.click()
wait_for_selector(browser, menu_item, visible=True, single=True).click()
WebDriverWait(browser, 10).until(
lambda b: notebook.is_kernel_running(),
message="Restart (%r) after shutdown did not start kernel" % menu_item
)

View file

@ -0,0 +1,41 @@
from nbformat.v4 import new_markdown_cell
def get_rendered_contents(nb):
cl = ["text_cell", "render"]
rendered_cells = [cell.find_element_by_class_name("text_cell_render")
for cell in nb.cells
if all([c in cell.get_attribute("class") for c in cl])]
return [x.get_attribute('innerHTML').strip()
for x in rendered_cells
if x is not None]
def test_markdown_cell(prefill_notebook):
nb = prefill_notebook([new_markdown_cell(md) for md in [
'# Foo', '**Bar**', '*Baz*', '```\nx = 1\n```', '```aaaa\nx = 1\n```',
'```python\ns = "$"\nt = "$"\n```'
]])
assert get_rendered_contents(nb) == [
'<h1 id="Foo">Foo<a class="anchor-link" href="#Foo">¶</a></h1>',
'<p><strong>Bar</strong></p>',
'<p><em>Baz</em></p>',
'<pre><code>x = 1</code></pre>',
'<pre><code class="cm-s-ipython language-aaaa">x = 1</code></pre>',
'<pre><code class="cm-s-ipython language-python">' +
'<span class="cm-variable">s</span> <span class="cm-operator">=</span> <span class="cm-string">"$"</span>\n' +
'<span class="cm-variable">t</span> <span class="cm-operator">=</span> <span class="cm-string">"$"</span></code></pre>'
]
def test_markdown_headings(notebook):
lst = list([1, 2, 3, 4, 5, 6, 2, 1])
for i in lst:
notebook.add_markdown_cell()
cell_text = notebook.browser.execute_script(f"""
var cell = IPython.notebook.get_cell(1);
cell.set_heading_level({i});
cell.get_text();
""")
assert notebook.get_cell_contents(1) == "#" * i + " "
notebook.delete_cell(1)

View file

@ -0,0 +1,36 @@
"""Tests the merge cell api."""
INITIAL_CELLS = [
"foo = 5",
"bar = 10",
"baz = 15",
"print(foo)",
"print(bar)",
"print(baz)",
]
def test_merge_cells(prefill_notebook):
notebook = prefill_notebook(INITIAL_CELLS)
a, b, c, d, e, f = INITIAL_CELLS
# Before merging, there are 6 separate cells
assert notebook.get_cells_contents() == [a, b, c, d, e, f]
# Focus on the second cell and merge it with the cell above
notebook.focus_cell(1)
notebook.browser.execute_script("Jupyter.notebook.merge_cell_above();")
merged_a_b = "%s\n\n%s" % (a, b)
assert notebook.get_cells_contents() == [merged_a_b, c, d, e, f]
# Focus on the second cell and merge it with the cell below
notebook.focus_cell(1)
notebook.browser.execute_script("Jupyter.notebook.merge_cell_below();")
merged_c_d = "%s\n\n%s" % (c, d)
assert notebook.get_cells_contents() == [merged_a_b, merged_c_d, e, f]
# Merge everything down to a single cell with selected cells
notebook.select_cell_range(0,3)
notebook.browser.execute_script("Jupyter.notebook.merge_selected_cells();")
merged_all = "%s\n\n%s\n\n%s\n\n%s" % (merged_a_b, merged_c_d, e, f)
assert notebook.get_cells_contents() == [merged_all]

View file

@ -0,0 +1,47 @@
INITIAL_CELLS = ['1', '2', '3', '4', '5', '6']
def test_move_multiselection(prefill_notebook):
notebook = prefill_notebook(INITIAL_CELLS)
def assert_oder(pre_message, expected_state):
for i in range(len(expected_state)):
assert expected_state[i] == notebook.get_cell_contents(i), f"{pre_message}: Verify that cell {i} has for content: {expected_state[i]} found: {notebook.get_cell_contents(i)}"
# Select 3 first cells
notebook.select_cell_range(0, 2)
notebook.browser.execute_script(
"Jupyter.notebook.move_selection_up();"
)
# Should not move up at top
assert_oder('move up at top', ['1', '2', '3', '4', '5','6'])
# We do not need to reselect, move/up down should keep the selection.
notebook.browser.execute_script(
"Jupyter.notebook.move_selection_down();"
)
notebook.browser.execute_script(
"Jupyter.notebook.move_selection_down();"
)
notebook.browser.execute_script(
"Jupyter.notebook.move_selection_down();"
)
# 3 times down should move the 3 selected cells to the bottom
assert_oder("move down to bottom", ['4', '5', '6', '1', '2', '3'])
notebook.browser.execute_script(
"Jupyter.notebook.move_selection_down();"
)
# They can't go any futher
assert_oder("move down to bottom", ['4', '5', '6', '1', '2', '3'])
notebook.browser.execute_script(
"Jupyter.notebook.move_selection_up();"
)
notebook.browser.execute_script(
"Jupyter.notebook.move_selection_up();"
)
notebook.browser.execute_script(
"Jupyter.notebook.move_selection_up();"
)
# Bring them back on top
assert_oder('move up at top', ['1', '2', '3', '4', '5','6'])

View file

@ -0,0 +1,63 @@
INITIAL_CELLS = ['print("a")', 'print("b")', 'print("c")']
def test_multiselect(prefill_notebook):
notebook = prefill_notebook(INITIAL_CELLS)
def extend_selection_by(delta):
notebook.browser.execute_script(
"Jupyter.notebook.extend_selection_by(arguments[0]);", delta)
def n_selected_cells():
return notebook.browser.execute_script(
"return Jupyter.notebook.get_selected_cells().length;")
notebook.focus_cell(0)
assert n_selected_cells() == 1
# Check that only one cell is selected according to CSS classes as well
selected_css = notebook.browser.find_elements_by_css_selector(
'.cell.jupyter-soft-selected, .cell.selected')
assert len(selected_css) == 1
# Extend the selection down one
extend_selection_by(1)
assert n_selected_cells() == 2
# Contract the selection up one
extend_selection_by(-1)
assert n_selected_cells() == 1
# Extend the selection up one
notebook.focus_cell(1)
extend_selection_by(-1)
assert n_selected_cells() == 2
# Convert selected cells to Markdown
notebook.browser.execute_script("Jupyter.notebook.cells_to_markdown();")
cell_types = notebook.browser.execute_script(
"return Jupyter.notebook.get_cells().map(c => c.cell_type)")
assert cell_types == ['markdown', 'markdown', 'code']
# One cell left selected after conversion
assert n_selected_cells() == 1
# Convert selected cells to raw
notebook.focus_cell(1)
extend_selection_by(1)
assert n_selected_cells() == 2
notebook.browser.execute_script("Jupyter.notebook.cells_to_raw();")
cell_types = notebook.browser.execute_script(
"return Jupyter.notebook.get_cells().map(c => c.cell_type)")
assert cell_types == ['markdown', 'raw', 'raw']
# One cell left selected after conversion
assert n_selected_cells() == 1
# Convert selected cells to code
notebook.focus_cell(0)
extend_selection_by(2)
assert n_selected_cells() == 3
notebook.browser.execute_script("Jupyter.notebook.cells_to_code();")
cell_types = notebook.browser.execute_script(
"return Jupyter.notebook.get_cells().map(c => c.cell_type)")
assert cell_types == ['code'] * 3
# One cell left selected after conversion
assert n_selected_cells() == 1

View file

@ -0,0 +1,44 @@
INITIAL_CELLS = ['print("a")', 'print("b")', 'print("c")']
def test_multiselect_toggle(prefill_notebook):
notebook = prefill_notebook(INITIAL_CELLS)
def extend_selection_by(delta):
notebook.browser.execute_script(
"Jupyter.notebook.extend_selection_by(arguments[0]);", delta)
def n_selected_cells():
return notebook.browser.execute_script(
"return Jupyter.notebook.get_selected_cells().length;")
def select_cells():
notebook.focus_cell(0)
extend_selection_by(2)
# Test that cells, which start off not collapsed, are collapsed after
# calling the multiselected cell toggle.
select_cells()
assert n_selected_cells() == 3
notebook.browser.execute_script("Jupyter.notebook.execute_selected_cells();")
select_cells()
notebook.browser.execute_script("Jupyter.notebook.toggle_cells_outputs();")
cell_output_states = notebook.browser.execute_script(
"return Jupyter.notebook.get_cells().map(c => c.collapsed)")
assert cell_output_states == [False] * 3, "ensure that all cells are not collapsed"
# Test that cells, which start off not scrolled are scrolled after
# calling the multiselected scroll toggle.
select_cells()
assert n_selected_cells() == 3
notebook.browser.execute_script("Jupyter.notebook.toggle_cells_outputs_scroll();")
cell_scrolled_states = notebook.browser.execute_script(
"return Jupyter.notebook.get_cells().map(c => c.output_area.scroll_state)")
assert all(cell_scrolled_states), "ensure that all have scrolling enabled"
# Test that cells, which start off not cleared are cleared after
# calling the multiselected scroll toggle.
select_cells()
assert n_selected_cells() == 3
notebook.browser.execute_script("Jupyter.notebook.clear_cells_outputs();")
cell_outputs_cleared = notebook.browser.execute_script(
"return Jupyter.notebook.get_cells().map(c => c.output_area.element.html())")
assert cell_outputs_cleared == [""] * 3, "ensure that all cells are cleared"

View file

@ -0,0 +1,103 @@
"""
Test the notification area and widgets
"""
import pytest
from .utils import wait_for_selector, wait_for_script_to_return_true
def get_widget(notebook, name):
return notebook.browser.execute_script(
f"return IPython.notification_area.get_widget('{name}') !== undefined"
)
def widget(notebook, name):
return notebook.browser.execute_script(
f"return IPython.notification_area.widget('{name}') !== undefined"
)
def new_notification_widget(notebook, name):
return notebook.browser.execute_script(
f"return IPython.notification_area.new_notification_widget('{name}') !== undefined"
)
def widget_has_class(notebook, name, class_name):
return notebook.browser.execute_script(
f"""
var w = IPython.notification_area.get_widget('{name}');
return w.element.hasClass('{class_name}');
"""
)
def widget_message(notebook, name):
return notebook.browser.execute_script(
f"""
var w = IPython.notification_area.get_widget('{name}');
return w.get_message();
"""
)
def test_notification(notebook):
# check that existing widgets are there
assert get_widget(notebook, "kernel") and widget(notebook, "kernel"),\
"The kernel notification widget exists"
assert get_widget(notebook, "notebook") and widget(notebook, "notebook"),\
"The notebook notification widget exists"
# try getting a non-existent widget
with pytest.raises(Exception):
get_widget(notebook, "foo")
# try creating a non-existent widget
assert widget(notebook, "bar"), "widget: new widget is created"
# try creating a widget that already exists
with pytest.raises(Exception):
new_notification_widget(notebook, "kernel")
# test creating 'info', 'warning' and 'danger' messages
for level in ("info", "warning", "danger"):
notebook.browser.execute_script(f"""
var tnw = IPython.notification_area.widget('test');
tnw.{level}('test {level}');
""")
wait_for_selector(notebook.browser, "#notification_test", visible=True)
assert widget_has_class(notebook, "test", level), f"{level}: class is correct"
assert widget_message(notebook, "test") == f"test {level}", f"{level}: message is correct"
# test message timeout
notebook.browser.execute_script("""
var tnw = IPython.notification_area.widget('test');
tnw.set_message('test timeout', 1000);
""")
wait_for_selector(notebook.browser, "#notification_test", visible=True)
assert widget_message(notebook, "test") == "test timeout", "timeout: message is correct"
wait_for_selector(notebook.browser, "#notification_test", obscures=True)
assert widget_message(notebook, "test") == "", "timeout: message was cleared"
# test click callback
notebook.browser.execute_script("""
var tnw = IPython.notification_area.widget('test');
tnw._clicked = false;
tnw.set_message('test click', undefined, function () {
tnw._clicked = true;
return true;
});
""")
wait_for_selector(notebook.browser, "#notification_test", visible=True)
assert widget_message(notebook, "test") == "test click", "callback: message is correct"
notebook.browser.find_element_by_id("notification_test").click()
wait_for_script_to_return_true(notebook.browser,
'return IPython.notification_area.widget("test")._clicked;')
wait_for_selector(notebook.browser, "#notification_test", obscures=True)
assert widget_message(notebook, "test") == "", "callback: message was cleared"

View file

@ -0,0 +1,30 @@
def test_prompt_numbers(prefill_notebook):
notebook = prefill_notebook(['print("a")'])
def get_prompt():
return (
notebook.cells[0].find_element_by_class_name('input')
.find_element_by_class_name('input_prompt')
.get_attribute('innerHTML').strip()
)
def set_prompt(value):
notebook.set_cell_input_prompt(0, value)
assert get_prompt() == "<bdi>In</bdi>&nbsp;[&nbsp;]:"
set_prompt(2)
assert get_prompt() == "<bdi>In</bdi>&nbsp;[2]:"
set_prompt(0)
assert get_prompt() == "<bdi>In</bdi>&nbsp;[0]:"
set_prompt("'*'")
assert get_prompt() == "<bdi>In</bdi>&nbsp;[*]:"
set_prompt("undefined")
assert get_prompt() == "<bdi>In</bdi>&nbsp;[&nbsp;]:"
set_prompt("null")
assert get_prompt() == "<bdi>In</bdi>&nbsp;[&nbsp;]:"

View file

@ -0,0 +1,65 @@
"""Test saving a notebook with escaped characters
"""
from urllib.parse import quote
from .utils import wait_for_selector
promise_js = """
var done = arguments[arguments.length - 1];
%s.then(
data => { done(["success", data]); },
error => { done(["error", error]); }
);
"""
def execute_promise(js, browser):
state, data = browser.execute_async_script(promise_js % js)
if state == 'success':
return data
raise Exception(data)
def test_save(notebook):
# don't use unicode with ambiguous composed/decomposed normalization
# because the filesystem may use a different normalization than literals.
# This causes no actual problems, but will break string comparison.
nbname = "has#hash and space and unicø∂e.ipynb"
escaped_name = quote(nbname)
notebook.edit_cell(index=0, content="s = '??'")
notebook.browser.execute_script("Jupyter.notebook.set_notebook_name(arguments[0])", nbname)
model = execute_promise("Jupyter.notebook.save_notebook()", notebook.browser)
assert model['name'] == nbname
current_name = notebook.browser.execute_script("return Jupyter.notebook.notebook_name")
assert current_name == nbname
current_path = notebook.browser.execute_script("return Jupyter.notebook.notebook_path")
assert current_path == nbname
displayed_name = notebook.browser.find_element_by_id('notebook_name').text
assert displayed_name + '.ipynb' == nbname
execute_promise("Jupyter.notebook.save_checkpoint()", notebook.browser)
checkpoints = notebook.browser.execute_script("return Jupyter.notebook.checkpoints")
assert len(checkpoints) == 1
notebook.browser.find_element_by_css_selector('#ipython_notebook a').click()
hrefs_nonmatch = []
for link in wait_for_selector(notebook.browser, 'a.item_link'):
href = link.get_attribute('href')
if escaped_name in href:
print("Opening", href)
notebook.browser.get(href)
wait_for_selector(notebook.browser, '.cell')
break
hrefs_nonmatch.append(href)
else:
raise AssertionError("{!r} not found in {!r}"
.format(escaped_name, hrefs_nonmatch))
current_name = notebook.browser.execute_script("return Jupyter.notebook.notebook_name")
assert current_name == nbname

View file

@ -0,0 +1,40 @@
from notebook.tests.selenium.utils import wait_for_selector
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
def wait_for_rename(browser, nbname, timeout=10):
wait = WebDriverWait(browser, timeout)
def notebook_renamed(browser):
elem = browser.find_element_by_id('notebook_name')
current_name = browser.execute_script('return arguments[0].innerText', elem)
return current_name == nbname
return wait.until(notebook_renamed)
def save_as(nb):
JS = 'Jupyter.notebook.save_notebook_as()'
return nb.browser.execute_script(JS)
def get_notebook_name(nb):
JS = 'return Jupyter.notebook.notebook_name'
return nb.browser.execute_script(JS)
def set_notebook_name(nb, name):
JS = 'Jupyter.notebook.rename("{}")'.format(name)
nb.browser.execute_script(JS)
def test_save_notebook_as(notebook):
# Set a name for comparison later
set_notebook_name(notebook, name="nb1.ipynb")
wait_for_rename(notebook.browser, "nb1")
assert get_notebook_name(notebook) == "nb1.ipynb"
# Wait for Save As modal, save
save_as(notebook)
wait_for_selector(notebook.browser, '.save-message')
inp = notebook.browser.find_element_by_xpath('//input[@data-testid="save-as"]')
inp.send_keys('new_notebook.ipynb')
inp.send_keys(Keys.RETURN)
wait_for_rename(notebook.browser, "new_notebook")
# Test that the name changed
assert get_notebook_name(notebook) == "new_notebook.ipynb"
# Test that address bar was updated (TODO: get the base url)
assert "new_notebook.ipynb" in notebook.browser.current_url

View file

@ -0,0 +1,80 @@
from notebook.tests.selenium.utils import wait_for_selector, Notebook
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
promise_js = """
var done = arguments[arguments.length - 1];
(%s).then(
data => { done(["success", data]); },
error => { done(["error", error]); }
);
"""
def execute_promise(js, browser):
state, data = browser.execute_async_script(promise_js % js)
if state == 'success':
return data
raise Exception(data)
def wait_for_rename(browser, nbname, timeout=10):
wait = WebDriverWait(browser, timeout)
def notebook_renamed(browser):
elem = browser.find_element_by_id('notebook_name')
current_name = browser.execute_script('return arguments[0].innerText', elem)
return current_name == nbname
return wait.until(notebook_renamed)
def save_as(nb):
JS = 'Jupyter.notebook.save_notebook_as()'
return nb.browser.execute_script(JS)
def get_notebook_name(nb):
JS = 'return Jupyter.notebook.notebook_name'
return nb.browser.execute_script(JS)
def refresh_notebook(nb):
nb.browser.refresh()
nb.__init__(nb.browser)
def test_save_readonly_notebook_as(notebook):
# Make notebook read-only
notebook.edit_cell(index=0, content='import os\nimport stat\nos.chmod("'
+ notebook.browser.current_url.split('?')[0].split('/')[-1] + '", stat.S_IREAD)\nprint(0)')
notebook.browser.execute_script("Jupyter.notebook.get_cell(0).execute();")
notebook.wait_for_cell_output(0)
refresh_notebook(notebook)
# Test that the notebook is read-only
assert notebook.browser.execute_script('return Jupyter.notebook.writable') == False
# Add some content
test_content_0 = "print('a simple')\nprint('test script')"
notebook.edit_cell(index=0, content=test_content_0)
# Wait for Save As modal, save
save_as(notebook)
wait_for_selector(notebook.browser, '.save-message')
inp = notebook.browser.find_element_by_xpath('//input[@data-testid="save-as"]')
inp.send_keys('writable_notebook.ipynb')
inp.send_keys(Keys.RETURN)
wait_for_rename(notebook.browser, "writable_notebook")
# Test that the name changed
assert get_notebook_name(notebook) == "writable_notebook.ipynb"
# Test that address bar was updated (TODO: get the base url)
assert "writable_notebook.ipynb" in notebook.browser.current_url
# Test that it is no longer read-only
assert notebook.browser.execute_script('return Jupyter.notebook.writable') == True
# Add some more content
test_content_1 = "print('a second simple')\nprint('script to test save feature')"
notebook.add_and_execute_cell(content=test_content_1)
# and save the notebook
execute_promise("Jupyter.notebook.save_notebook()", notebook.browser)
# Test that it still contains the content
assert notebook.get_cell_contents(index=0) == test_content_0
assert notebook.get_cell_contents(index=1) == test_content_1
# even after a refresh
refresh_notebook(notebook)
assert notebook.get_cell_contents(index=0) == test_content_0
assert notebook.get_cell_contents(index=1) == test_content_1

View file

@ -0,0 +1,15 @@
"""Tests shutdown of the Kernel."""
from .utils import wait_for_selector, wait_for_xpath
def test_shutdown(notebook):
notebook.edit_cell(content="print(21)")
wait_for_xpath(notebook.browser, '//a[text()="Kernel"]', single=True).click()
wait_for_selector(notebook.browser, '#shutdown_kernel', single=True).click()
wait_for_selector(notebook.browser, '.btn.btn-default.btn-sm.btn-danger', single=True).click()
#Wait until all shutdown modal elements disappear before trying to execute the cell
wait_for_xpath(notebook.browser, "//div[contains(@class,'modal')]", obscures=True)
notebook.execute_cell(0)
assert not notebook.is_kernel_running()
assert len(notebook.get_cell_output()) == 0

View file

@ -0,0 +1,92 @@
from selenium.webdriver.common.keys import Keys
from .utils import shift
def undelete(nb):
nb.browser.execute_script('Jupyter.notebook.undelete_cell();')
INITIAL_CELLS = ['print("a")', 'print("b")', 'print("c")', 'print("d")']
def test_undelete_cells(prefill_notebook):
notebook = prefill_notebook(INITIAL_CELLS)
a, b, c, d = INITIAL_CELLS
# Verify initial state
assert notebook.get_cells_contents() == [a, b, c, d]
# Delete cells [1, 2]
notebook.focus_cell(1)
shift(notebook.browser, Keys.DOWN)
notebook.current_cell.send_keys('dd')
assert notebook.get_cells_contents() == [a, d]
# Delete new cell 1 (which contains d)
notebook.focus_cell(1)
notebook.current_cell.send_keys('dd')
assert notebook.get_cells_contents() == [a]
# Undelete d
undelete(notebook)
assert notebook.get_cells_contents() == [a, d]
# Undelete b, c
undelete(notebook)
assert notebook.get_cells_contents() == [a, b, c, d]
# Nothing more to undelete
undelete(notebook)
assert notebook.get_cells_contents() == [a, b, c, d]
# Delete first two cells and restore
notebook.focus_cell(0)
shift(notebook.browser, Keys.DOWN)
notebook.current_cell.send_keys('dd')
assert notebook.get_cells_contents() == [c, d]
undelete(notebook)
assert notebook.get_cells_contents() == [a, b, c, d]
# Delete last two cells and restore
notebook.focus_cell(-1)
shift(notebook.browser, Keys.UP)
notebook.current_cell.send_keys('dd')
assert notebook.get_cells_contents() == [a, b]
undelete(notebook)
assert notebook.get_cells_contents() == [a, b, c, d]
# Merge cells [1, 2], restore the deleted one
bc = b + "\n\n" + c
notebook.focus_cell(1)
shift(notebook.browser, 'j')
shift(notebook.browser, 'm')
assert notebook.get_cells_contents() == [a, bc, d]
undelete(notebook)
assert notebook.get_cells_contents() == [a, bc, c, d]
# Merge cells [2, 3], restore the deleted one
cd = c + "\n\n" + d
notebook.focus_cell(-1)
shift(notebook.browser, 'k')
shift(notebook.browser, 'm')
assert notebook.get_cells_contents() == [a, bc, cd]
undelete(notebook)
assert notebook.get_cells_contents() == [a, bc, cd, d]
# Reset contents to [a, b, c, d] --------------------------------------
notebook.edit_cell(index=1, content=b)
notebook.edit_cell(index=2, content=c)
assert notebook.get_cells_contents() == [a, b, c, d]
# Merge cell below, restore the deleted one
ab = a + "\n\n" + b
notebook.focus_cell(0)
notebook.browser.execute_script("Jupyter.notebook.merge_cell_below();")
assert notebook.get_cells_contents() == [ab, c, d]
undelete(notebook)
assert notebook.get_cells_contents() == [ab, b, c, d]
# Merge cell above, restore the deleted one
cd = c + "\n\n" + d
notebook.focus_cell(-1)
notebook.browser.execute_script("Jupyter.notebook.merge_cell_above();")
assert notebook.get_cells_contents() == [ab, b, cd]
undelete(notebook)
assert notebook.get_cells_contents() == [ab, b, c, cd]

View file

@ -0,0 +1,469 @@
import os
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.remote.webelement import WebElement
from contextlib import contextmanager
pjoin = os.path.join
def wait_for_selector(driver, selector, timeout=10, visible=False, single=False, wait_for_n=1, obscures=False):
if wait_for_n > 1:
return _wait_for_multiple(
driver, By.CSS_SELECTOR, selector, timeout, wait_for_n, visible)
return _wait_for(driver, By.CSS_SELECTOR, selector, timeout, visible, single, obscures)
def wait_for_tag(driver, tag, timeout=10, visible=False, single=False, wait_for_n=1, obscures=False):
if wait_for_n > 1:
return _wait_for_multiple(
driver, By.TAG_NAME, tag, timeout, wait_for_n, visible)
return _wait_for(driver, By.TAG_NAME, tag, timeout, visible, single, obscures)
def wait_for_xpath(driver, xpath, timeout=10, visible=False, single=False, wait_for_n=1, obscures=False):
if wait_for_n > 1:
return _wait_for_multiple(
driver, By.XPATH, xpath, timeout, wait_for_n, visible)
return _wait_for(driver, By.XPATH, xpath, timeout, visible, single, obscures)
def wait_for_script_to_return_true(driver, script, timeout=10):
WebDriverWait(driver, timeout).until(lambda d: d.execute_script(script))
def _wait_for(driver, locator_type, locator, timeout=10, visible=False, single=False, obscures=False):
"""Waits `timeout` seconds for the specified condition to be met. Condition is
met if any matching element is found. Returns located element(s) when found.
Args:
driver: Selenium web driver instance
locator_type: type of locator (e.g. By.CSS_SELECTOR or By.TAG_NAME)
locator: name of tag, class, etc. to wait for
timeout: how long to wait for presence/visibility of element
visible: if True, require that element is not only present, but visible
single: if True, return a single element, otherwise return a list of matching
elements
obscures: if True, waits until the element becomes invisible
"""
wait = WebDriverWait(driver, timeout)
if obscures:
conditional = EC.invisibility_of_element_located
elif single:
if visible:
conditional = EC.visibility_of_element_located
else:
conditional = EC.presence_of_element_located
else:
if visible:
conditional = EC.visibility_of_all_elements_located
else:
conditional = EC.presence_of_all_elements_located
return wait.until(conditional((locator_type, locator)))
def _wait_for_multiple(driver, locator_type, locator, timeout, wait_for_n, visible=False):
"""Waits until `wait_for_n` matching elements to be present (or visible).
Returns located elements when found.
Args:
driver: Selenium web driver instance
locator_type: type of locator (e.g. By.CSS_SELECTOR or By.TAG_NAME)
locator: name of tag, class, etc. to wait for
timeout: how long to wait for presence/visibility of element
wait_for_n: wait until this number of matching elements are present/visible
visible: if True, require that elements are not only present, but visible
"""
wait = WebDriverWait(driver, timeout)
def multiple_found(driver):
elements = driver.find_elements(locator_type, locator)
if visible:
elements = [e for e in elements if e.is_displayed()]
if len(elements) < wait_for_n:
return False
return elements
return wait.until(multiple_found)
class CellTypeError(ValueError):
def __init__(self, message=""):
self.message = message
class Notebook:
def __init__(self, browser):
self.browser = browser
self._wait_for_start()
self.disable_autosave_and_onbeforeunload()
def __len__(self):
return len(self.cells)
def __getitem__(self, key):
return self.cells[key]
def __setitem__(self, key, item):
if isinstance(key, int):
self.edit_cell(index=key, content=item, render=False)
# TODO: re-add slicing support, handle general python slicing behaviour
# includes: overwriting the entire self.cells object if you do
# self[:] = []
# elif isinstance(key, slice):
# indices = (self.index(cell) for cell in self[key])
# for k, v in zip(indices, item):
# self.edit_cell(index=k, content=v, render=False)
def __iter__(self):
return (cell for cell in self.cells)
def _wait_for_start(self):
"""Wait until the notebook interface is loaded and the kernel started"""
wait_for_selector(self.browser, '.cell')
WebDriverWait(self.browser, 10).until(
lambda drvr: self.is_kernel_running()
)
@property
def body(self):
return self.browser.find_element_by_tag_name("body")
@property
def cells(self):
"""Gets all cells once they are visible.
"""
return self.browser.find_elements_by_class_name("cell")
@property
def current_index(self):
return self.index(self.current_cell)
def index(self, cell):
return self.cells.index(cell)
def disable_autosave_and_onbeforeunload(self):
"""Disable request to save before closing window and autosave.
This is most easily done by using js directly.
"""
self.browser.execute_script("window.onbeforeunload = null;")
self.browser.execute_script("Jupyter.notebook.set_autosave_interval(0)")
def to_command_mode(self):
"""Changes us into command mode on currently focused cell
"""
self.body.send_keys(Keys.ESCAPE)
self.browser.execute_script("return Jupyter.notebook.handle_command_mode("
"Jupyter.notebook.get_cell("
"Jupyter.notebook.get_edit_index()))")
def focus_cell(self, index=0):
cell = self.cells[index]
cell.click()
self.to_command_mode()
self.current_cell = cell
def select_cell_range(self, initial_index=0, final_index=0):
self.focus_cell(initial_index)
self.to_command_mode()
for i in range(final_index - initial_index):
shift(self.browser, 'j')
def find_and_replace(self, index=0, find_txt='', replace_txt=''):
self.focus_cell(index)
self.to_command_mode()
self.body.send_keys('f')
wait_for_selector(self.browser, "#find-and-replace", single=True)
self.browser.find_element_by_id("findreplace_allcells_btn").click()
self.browser.find_element_by_id("findreplace_find_inp").send_keys(find_txt)
self.browser.find_element_by_id("findreplace_replace_inp").send_keys(replace_txt)
self.browser.find_element_by_id("findreplace_replaceall_btn").click()
def convert_cell_type(self, index=0, cell_type="code"):
# TODO add check to see if it is already present
self.focus_cell(index)
cell = self.cells[index]
if cell_type == "markdown":
self.current_cell.send_keys("m")
elif cell_type == "raw":
self.current_cell.send_keys("r")
elif cell_type == "code":
self.current_cell.send_keys("y")
else:
raise CellTypeError(("{} is not a valid cell type,"
"use 'code', 'markdown', or 'raw'").format(cell_type))
self.wait_for_stale_cell(cell)
self.focus_cell(index)
return self.current_cell
def wait_for_stale_cell(self, cell):
""" This is needed to switch a cell's mode and refocus it, or to render it.
Warning: there is currently no way to do this when changing between
markdown and raw cells.
"""
wait = WebDriverWait(self.browser, 10)
element = wait.until(EC.staleness_of(cell))
def wait_for_element_availability(self, element):
_wait_for(self.browser, By.CLASS_NAME, element, visible=True)
def get_cells_contents(self):
JS = 'return Jupyter.notebook.get_cells().map(function(c) {return c.get_text();})'
return self.browser.execute_script(JS)
def get_cell_contents(self, index=0, selector='div .CodeMirror-code'):
return self.cells[index].find_element_by_css_selector(selector).text
def get_cell_output(self, index=0, output='output_subarea'):
return self.cells[index].find_elements_by_class_name(output)
def wait_for_cell_output(self, index=0, timeout=10):
return WebDriverWait(self.browser, timeout).until(
lambda b: self.get_cell_output(index)
)
def set_cell_metadata(self, index, key, value):
JS = 'Jupyter.notebook.get_cell({}).metadata.{} = {}'.format(index, key, value)
return self.browser.execute_script(JS)
def get_cell_type(self, index=0):
JS = 'return Jupyter.notebook.get_cell({}).cell_type'.format(index)
return self.browser.execute_script(JS)
def set_cell_input_prompt(self, index, prmpt_val):
JS = 'Jupyter.notebook.get_cell({}).set_input_prompt({})'.format(index, prmpt_val)
self.browser.execute_script(JS)
def edit_cell(self, cell=None, index=0, content="", render=False):
"""Set the contents of a cell to *content*, by cell object or by index
"""
if cell is not None:
index = self.index(cell)
self.focus_cell(index)
# Select & delete anything already in the cell
self.current_cell.send_keys(Keys.ENTER)
cmdtrl(self.browser, 'a')
self.current_cell.send_keys(Keys.DELETE)
for line_no, line in enumerate(content.splitlines()):
if line_no != 0:
self.current_cell.send_keys(Keys.ENTER, "\n")
self.current_cell.send_keys(Keys.ENTER, line)
if render:
self.execute_cell(self.current_index)
def execute_cell(self, cell_or_index=None):
if isinstance(cell_or_index, int):
index = cell_or_index
elif isinstance(cell_or_index, WebElement):
index = self.index(cell_or_index)
else:
raise TypeError("execute_cell only accepts a WebElement or an int")
self.focus_cell(index)
self.current_cell.send_keys(Keys.CONTROL, Keys.ENTER)
def add_cell(self, index=-1, cell_type="code", content=""):
self.focus_cell(index)
self.current_cell.send_keys("b")
new_index = index + 1 if index >= 0 else index
if content:
self.edit_cell(index=index, content=content)
if cell_type != 'code':
self.convert_cell_type(index=new_index, cell_type=cell_type)
def add_and_execute_cell(self, index=-1, cell_type="code", content=""):
self.add_cell(index=index, cell_type=cell_type, content=content)
self.execute_cell(index)
def delete_cell(self, index):
self.focus_cell(index)
self.to_command_mode()
self.current_cell.send_keys('dd')
def add_markdown_cell(self, index=-1, content="", render=True):
self.add_cell(index, cell_type="markdown")
self.edit_cell(index=index, content=content, render=render)
def append(self, *values, cell_type="code"):
for i, value in enumerate(values):
if isinstance(value, str):
self.add_cell(cell_type=cell_type,
content=value)
else:
raise TypeError("Don't know how to add cell from %r" % value)
def extend(self, values):
self.append(*values)
def run_all(self):
for cell in self:
self.execute_cell(cell)
def trigger_keydown(self, keys):
trigger_keystrokes(self.body, keys)
def is_kernel_running(self):
return self.browser.execute_script(
"return Jupyter.notebook.kernel && Jupyter.notebook.kernel.is_connected()"
)
def clear_cell_output(self, index):
JS = 'Jupyter.notebook.clear_output({})'.format(index)
self.browser.execute_script(JS)
@classmethod
def new_notebook(cls, browser, kernel_name='kernel-python3'):
with new_window(browser):
select_kernel(browser, kernel_name=kernel_name)
return cls(browser)
def select_kernel(browser, kernel_name='kernel-python3'):
"""Clicks the "new" button and selects a kernel from the options.
"""
wait = WebDriverWait(browser, 10)
new_button = wait.until(EC.element_to_be_clickable((By.ID, "new-dropdown-button")))
new_button.click()
kernel_selector = '#{} a'.format(kernel_name)
kernel = wait_for_selector(browser, kernel_selector, single=True)
kernel.click()
@contextmanager
def new_window(browser):
"""Contextmanager for switching to & waiting for a window created.
This context manager gives you the ability to create a new window inside
the created context and it will switch you to that new window.
Usage example:
from notebook.tests.selenium.utils import new_window, Notebook
# something that creates a browser object
with new_window(browser):
select_kernel(browser, kernel_name=kernel_name)
nb = Notebook(browser)
"""
initial_window_handles = browser.window_handles
yield
new_window_handles = [window for window in browser.window_handles
if window not in initial_window_handles]
if not new_window_handles:
raise Exception("No new windows opened during context")
browser.switch_to.window(new_window_handles[0])
def shift(browser, k):
"""Send key combination Shift+(k)"""
trigger_keystrokes(browser, "shift-%s"%k)
def cmdtrl(browser, k):
"""Send key combination Ctrl+(k) or Command+(k) for MacOS"""
trigger_keystrokes(browser, "command-%s"%k) if os.uname()[0] == "Darwin" else trigger_keystrokes(browser, "control-%s"%k)
def alt(browser, k):
"""Send key combination Alt+(k)"""
trigger_keystrokes(browser, 'alt-%s'%k)
def trigger_keystrokes(browser, *keys):
""" Send the keys in sequence to the browser.
Handles following key combinations
1. with modifiers eg. 'control-alt-a', 'shift-c'
2. just modifiers eg. 'alt', 'esc'
3. non-modifiers eg. 'abc'
Modifiers : http://seleniumhq.github.io/selenium/docs/api/py/webdriver/selenium.webdriver.common.keys.html
"""
for each_key_combination in keys:
keys = each_key_combination.split('-')
if len(keys) > 1: # key has modifiers eg. control, alt, shift
modifiers_keys = [getattr(Keys, x.upper()) for x in keys[:-1]]
ac = ActionChains(browser)
for i in modifiers_keys: ac = ac.key_down(i)
ac.send_keys(keys[-1])
for i in modifiers_keys[::-1]: ac = ac.key_up(i)
ac.perform()
else: # single key stroke. Check if modifier eg. "up"
browser.send_keys(getattr(Keys, keys[0].upper(), keys[0]))
def validate_dualmode_state(notebook, mode, index):
'''Validate the entire dual mode state of the notebook.
Checks if the specified cell is selected, and the mode and keyboard mode are the same.
Depending on the mode given:
Command: Checks that no cells are in focus or in edit mode.
Edit: Checks that only the specified cell is in focus and in edit mode.
'''
def is_only_cell_edit(index):
JS = 'return Jupyter.notebook.get_cells().map(function(c) {return c.mode;})'
cells_mode = notebook.browser.execute_script(JS)
#None of the cells are in edit mode
if index is None:
for mode in cells_mode:
if mode == 'edit':
return False
return True
#Only the index cell is on edit mode
for i, mode in enumerate(cells_mode):
if i == index:
if mode != 'edit':
return False
else:
if mode == 'edit':
return False
return True
def is_focused_on(index):
JS = "return $('#notebook .CodeMirror-focused textarea').length;"
focused_cells = notebook.browser.execute_script(JS)
if index is None:
return focused_cells == 0
if focused_cells != 1: #only one cell is focused
return False
JS = "return $('#notebook .CodeMirror-focused textarea')[0];"
focused_cell = notebook.browser.execute_script(JS)
JS = "return IPython.notebook.get_cell(%s).code_mirror.getInputField()"%index
cell = notebook.browser.execute_script(JS)
return focused_cell == cell
#general test
JS = "return IPython.keyboard_manager.mode;"
keyboard_mode = notebook.browser.execute_script(JS)
JS = "return IPython.notebook.mode;"
notebook_mode = notebook.browser.execute_script(JS)
#validate selected cell
JS = "return Jupyter.notebook.get_selected_cells_indices();"
cell_index = notebook.browser.execute_script(JS)
assert cell_index == [index] #only the index cell is selected
if mode != 'command' and mode != 'edit':
raise Exception('An unknown mode was send: mode = "%s"'%mode) #An unknown mode is send
#validate mode
assert mode == keyboard_mode #keyboard mode is correct
if mode == 'command':
assert is_focused_on(None) #no focused cells
assert is_only_cell_edit(None) #no cells in edit mode
elif mode == 'edit':
assert is_focused_on(index) #The specified cell is focused
assert is_only_cell_edit(index) #The specified cell is the only one in edit mode

View file

@ -0,0 +1,325 @@
//
// Kernel tests
//
casper.notebook_test(function () {
// test that the kernel is running
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel is running');
});
// test list
this.thenEvaluate(function () {
IPython._kernels = null;
IPython.notebook.kernel.list(function (data) {
IPython._kernels = data;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._kernels !== null;
});
});
this.then(function () {
var num_kernels = this.evaluate(function () {
return IPython._kernels.length;
});
this.test.assertEquals(num_kernels, 1, 'one kernel running');
});
// test get_info
var kernel_info = this.evaluate(function () {
return {
name: IPython.notebook.kernel.name,
id: IPython.notebook.kernel.id
};
});
this.thenEvaluate(function () {
IPython._kernel_info = null;
IPython.notebook.kernel.get_info(function (data) {
IPython._kernel_info = data;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._kernel_info !== null;
});
});
this.then(function () {
var new_kernel_info = this.evaluate(function () {
return IPython._kernel_info;
});
this.test.assertEquals(kernel_info.name, new_kernel_info.name, 'kernel: name correct');
this.test.assertEquals(kernel_info.id, new_kernel_info.id, 'kernel: id correct');
});
// test interrupt
this.thenEvaluate(function () {
IPython._interrupted = false;
IPython.notebook.kernel.interrupt(function () {
IPython._interrupted = true;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._interrupted;
});
});
this.then(function () {
var interrupted = this.evaluate(function () {
return IPython._interrupted;
});
this.test.assert(interrupted, 'kernel was interrupted');
});
// test restart
this.thenEvaluate(function () {
IPython.notebook.kernel.restart();
});
this.waitFor(this.kernel_disconnected);
this.wait_for_kernel_ready();
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel restarted');
});
// test reconnect
this.thenEvaluate(function () {
IPython.notebook.kernel.stop_channels();
});
this.waitFor(this.kernel_disconnected);
this.thenEvaluate(function () {
IPython.notebook.kernel.reconnect();
});
this.wait_for_kernel_ready();
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel reconnected');
});
// test kernel_info_request
this.evaluate(function () {
IPython.notebook.kernel.kernel_info(
function(msg){
IPython._kernel_info_response = msg;
});
});
this.waitFor(
function () {
return this.evaluate(function(){
return IPython._kernel_info_response;
});
});
this.then(function () {
var kernel_info_response = this.evaluate(function(){
return IPython._kernel_info_response;
});
this.test.assertTrue( kernel_info_response.msg_type === 'kernel_info_reply', 'Kernel info request return kernel_info_reply');
this.test.assertTrue( kernel_info_response.content !== undefined, 'Kernel_info_reply is not undefined');
});
// test kill
this.thenEvaluate(function () {
IPython.notebook.kernel.kill();
});
this.waitFor(this.kernel_disconnected);
this.then(function () {
this.test.assert(!this.kernel_running(), 'kernel is not running');
});
// test start
var url, base_url;
this.then(function () {
base_url = this.evaluate(function () {
return IPython.notebook.base_url;
});
url = this.evaluate(function () {
return IPython.notebook.kernel.start();
});
});
this.then(function () {
this.test.assertEquals(url, base_url + "api/kernels", "start url is correct");
});
this.wait_for_kernel_ready();
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel is running');
});
// test start with parameters
this.thenEvaluate(function () {
IPython.notebook.kernel.kill();
});
this.waitFor(this.kernel_disconnected);
this.then(function () {
url = this.evaluate(function () {
return IPython.notebook.kernel.start({foo: "bar"});
});
});
this.then(function () {
this.test.assertEquals(url, base_url + "api/kernels?foo=bar", "start url with params is correct");
});
this.wait_for_kernel_ready();
this.then(function () {
this.test.assert(this.kernel_running(), 'kernel is running');
});
// check for events in kill/start cycle
this.event_test(
'kill/start',
[
'kernel_killed.Kernel',
'kernel_starting.Kernel',
'kernel_created.Kernel',
'kernel_connected.Kernel',
'kernel_ready.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel.kill();
});
this.waitFor(this.kernel_disconnected);
this.thenEvaluate(function () {
IPython.notebook.kernel.start();
});
}
);
// wait for any last idle/busy messages to be handled
this.wait_for_kernel_ready();
// check for events in disconnect/connect cycle
this.event_test(
'reconnect',
[
'kernel_reconnecting.Kernel',
'kernel_connected.Kernel',
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel.stop_channels();
IPython.notebook.kernel.reconnect(1);
});
}
);
// wait for any last idle/busy messages to be handled
this.wait_for_kernel_ready();
// check for events in the restart cycle
this.event_test(
'restart',
[
'kernel_restarting.Kernel',
'kernel_created.Kernel',
'kernel_connected.Kernel',
'kernel_ready.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel.restart();
});
}
);
// wait for any last idle/busy messages to be handled
this.wait_for_kernel_ready();
// check for events in the interrupt cycle
this.event_test(
'interrupt',
[
'kernel_interrupting.Kernel',
'kernel_busy.Kernel',
'kernel_idle.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel.interrupt();
});
}
);
this.wait_for_kernel_ready();
// check for events after ws close
this.event_test(
'ws_closed_ok',
[
'kernel_disconnected.Kernel',
'kernel_reconnecting.Kernel',
'kernel_connected.Kernel',
'kernel_busy.Kernel',
'kernel_idle.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel._ws_closed("", false);
});
}
);
// wait for any last idle/busy messages to be handled
this.wait_for_kernel_ready();
// check for events after ws close (error)
this.event_test(
'ws_closed_error',
[
'kernel_disconnected.Kernel',
'kernel_connection_failed.Kernel',
'kernel_reconnecting.Kernel',
'kernel_connected.Kernel',
'kernel_busy.Kernel',
'kernel_idle.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.kernel._ws_closed("", true);
});
}
);
// wait for any last idle/busy messages to be handled
this.wait_for_kernel_ready();
// start the kernel back up
this.thenEvaluate(function () {
IPython.notebook.kernel.restart();
});
this.waitFor(this.kernel_running);
this.wait_for_kernel_ready();
// test handling of autorestarting messages
this.event_test(
'autorestarting',
[
'kernel_restarting.Kernel',
'kernel_autorestarting.Kernel',
],
function () {
this.thenEvaluate(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text('import os\n' + 'os._exit(1)');
cell.execute();
});
}
);
this.wait_for_kernel_ready();
// test handling of failed restart
this.event_test(
'failed_restart',
[
'kernel_restarting.Kernel',
'kernel_autorestarting.Kernel',
'kernel_dead.Kernel'
],
function () {
this.thenEvaluate(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text("import os\n" +
"from IPython.kernel.connect import get_connection_file\n" +
"with open(get_connection_file(), 'w') as f:\n" +
" f.write('garbage')\n" +
"os._exit(1)");
cell.execute();
});
},
// need an extra-long timeout, because it needs to try
// restarting the kernel 5 times!
20000
);
});

View file

@ -0,0 +1,123 @@
//
// Test binary messages on websockets.
// Only works on slimer for now, due to old websocket impl in phantomjs.
//
casper.notebook_test(function () {
// create EchoBuffers target on js-side.
// it just captures and echos comm messages.
this.then(function () {
var success = this.evaluate(function () {
IPython._msgs = [];
var EchoBuffers = function(comm) {
this.comm = comm;
this.comm.on_msg($.proxy(this.on_msg, this));
};
EchoBuffers.prototype.on_msg = function (msg) {
IPython._msgs.push(msg);
this.comm.send(msg.content.data, {}, {}, msg.buffers);
};
IPython.notebook.kernel.comm_manager.register_target("echo", function (comm) {
return new EchoBuffers(comm);
});
return true;
});
this.test.assertEquals(success, true, "Created echo comm target");
});
// Create a similar comm that captures messages Python-side
this.then(function () {
var index = this.append_cell([
"import os",
"from IPython.kernel.comm import Comm",
"comm = Comm(target_name='echo')",
"msgs = []",
"def on_msg(msg):",
" msgs.append(msg)",
"comm.on_msg(on_msg)"
].join('\n'), 'code');
this.execute_cell(index);
});
// send a message with binary data
this.then(function () {
var index = this.append_cell([
"buffers = [b'\\xFF\\x00', b'\\x00\\x01\\x02']",
"comm.send(data='message 0', buffers=buffers)",
"comm.send(data='message 1')",
"comm.send(data='message 2', buffers=buffers)",
].join('\n'), 'code');
this.execute_cell(index);
});
// wait for capture
this.waitFor(function () {
return this.evaluate(function () {
return IPython._msgs.length >= 3;
});
});
// validate captured buffers js-side
this.then(function () {
var msgs = this.evaluate(function () {
return IPython._msgs;
});
this.test.assertEquals(msgs.length, 3, "Captured three comm messages");
// check the messages came in the right order
this.test.assertEquals(msgs[0].content.data, "message 0", "message 0 processed first");
this.test.assertEquals(msgs[0].buffers.length, 2, "comm message 0 has two buffers");
this.test.assertEquals(msgs[1].content.data, "message 1", "message 1 processed second");
this.test.assertEquals(msgs[1].buffers.length, 0, "comm message 1 has no buffers");
this.test.assertEquals(msgs[2].content.data, "message 2", "message 2 processed third");
this.test.assertEquals(msgs[2].buffers.length, 2, "comm message 2 has two buffers");
// extract attributes to test in evaluate,
// because the raw DataViews can't be passed across
var buf_info = function (message, index) {
var buf = IPython._msgs[message].buffers[index];
var data = {};
data.byteLength = buf.byteLength;
data.bytes = [];
for (var i = 0; i < data.byteLength; i++) {
data.bytes.push(buf.getUint8(i));
}
return data;
};
var msgs_with_buffers = [0, 2];
for (var i = 0; i < msgs_with_buffers.length; i++) {
msg_index = msgs_with_buffers[i];
buf0 = this.evaluate(buf_info, msg_index, 0);
buf1 = this.evaluate(buf_info, msg_index, 1);
this.test.assertEquals(buf0.byteLength, 2, 'buf[0] has correct size in message '+msg_index);
this.test.assertEquals(buf0.bytes, [255, 0], 'buf[0] has correct bytes in message '+msg_index);
this.test.assertEquals(buf1.byteLength, 3, 'buf[1] has correct size in message '+msg_index);
this.test.assertEquals(buf1.bytes, [0, 1, 2], 'buf[1] has correct bytes in message '+msg_index);
}
});
// validate captured buffers Python-side
this.then(function () {
var index = this.append_cell([
"assert len(msgs) == 3, len(msgs)",
"bufs = msgs[0]['buffers']",
"assert len(bufs) == len(buffers), bufs",
"assert bufs[0].tobytes() == buffers[0], bufs[0]",
"assert bufs[1].tobytes() == buffers[1], bufs[1]",
"1",
].join('\n'), 'code');
this.execute_cell(index);
this.wait_for_output(index);
this.then(function () {
var out = this.get_output_cell(index);
this.test.assertEquals(out.data['text/plain'], '1', "Python received buffers");
});
});
});

View file

@ -0,0 +1,186 @@
//
// Tests for the Session object
//
casper.notebook_test(function () {
var that = this;
var get_info = function () {
return that.evaluate(function () {
return JSON.parse(JSON.stringify(IPython.notebook.session._get_model()));
});
};
// test that the kernel is running
this.then(function () {
this.test.assert(this.kernel_running(), 'session: kernel is running');
});
// test list
this.thenEvaluate(function () {
IPython._sessions = null;
IPython.notebook.session.list(function (data) {
IPython._sessions = data;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._sessions !== null;
});
});
this.then(function () {
var num_sessions = this.evaluate(function () {
return IPython._sessions.length;
});
this.test.assertEquals(num_sessions, 1, 'one session running');
});
// test get_info
var session_info = get_info();
this.thenEvaluate(function () {
IPython._session_info = null;
IPython.notebook.session.get_info(function (data) {
IPython._session_info = data;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._session_info !== null;
});
});
this.then(function () {
var new_session_info = this.evaluate(function () {
return IPython._session_info;
});
this.test.assertEquals(session_info.name, new_session_info.name, 'session: notebook name correct');
this.test.assertEquals(session_info.path, new_session_info.path, 'session: notebook path correct');
this.test.assertEquals(session_info.kernel.name, new_session_info.kernel.name, 'session: kernel name correct');
this.test.assertEquals(session_info.kernel.id, new_session_info.kernel.id, 'session: kernel id correct');
});
// test rename_notebook
//
// TODO: the PATCH request isn't supported by phantom, so this test always
// fails, see https://github.com/ariya/phantomjs/issues/11384
// when this is fixed we can properly run this test
//
// this.thenEvaluate(function () {
// IPython._renamed = false;
// IPython.notebook.session.rename_notebook(
// "foo",
// "bar",
// function (data) {
// IPython._renamed = true;
// }
// );
// });
// this.waitFor(function () {
// return this.evaluate(function () {
// return IPython._renamed;
// });
// });
// this.then(function () {
// var info = get_info();
// this.test.assertEquals(info.notebook.name, "foo", "notebook was renamed");
// this.test.assertEquals(info.notebook.path, "bar", "notebook path was changed");
// });
// test delete
this.thenEvaluate(function () {
IPython.notebook.session.delete();
});
this.waitFor(this.kernel_disconnected);
this.then(function () {
this.test.assert(!this.kernel_running(), 'session deletes kernel');
});
// check for events when starting the session
this.event_test(
'start_session',
[
'kernel_created.Session',
'kernel_connected.Kernel',
'kernel_ready.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.session.start();
});
}
);
this.wait_for_kernel_ready();
// check for events when killing the session
this.event_test(
'delete_session',
['kernel_killed.Session'],
function () {
this.thenEvaluate(function () {
IPython.notebook.session.delete();
});
}
);
this.thenEvaluate( function() {IPython.notebook.session.start()});
this.wait_for_kernel_ready();
// check for events when restarting the session
this.event_test(
'restart_session',
[
'kernel_killed.Session',
'kernel_created.Session',
'kernel_connected.Kernel',
'kernel_ready.Kernel'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.session.restart();
});
}
);
this.wait_for_kernel_ready();
// test handling of failed restart
this.event_test(
'failed_restart',
[
'kernel_restarting.Kernel',
'kernel_autorestarting.Kernel',
'kernel_killed.Session',
'kernel_dead.Kernel',
],
function () {
this.thenEvaluate(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text("import os\n" +
"from IPython.kernel.connect import get_connection_file\n" +
"with open(get_connection_file(), 'w') as f:\n" +
" f.write('garbage')\n" +
"os._exit(1)");
cell.execute();
});
},
// need an extra-long timeout, because it needs to try
// restarting the kernel 5 times!
20000
);
this.thenEvaluate( function() {IPython.notebook.session.start()});
this.wait_for_kernel_ready();
// check for events when starting a nonexistent kernel
this.event_test(
'bad_start_session',
[
'kernel_killed.Session',
'kernel_dead.Session'
],
function () {
this.thenEvaluate(function () {
IPython.notebook.session.restart({kernel_name: 'foo'});
});
}
);
});

View file

@ -0,0 +1,57 @@
import json
import os
import shutil
import tempfile
from notebook.config_manager import BaseJSONConfigManager
def test_json():
tmpdir = tempfile.mkdtemp()
try:
root_data = dict(a=1, x=2, nest={'a':1, 'x':2})
with open(os.path.join(tmpdir, 'foo.json'), 'w') as f:
json.dump(root_data, f)
# also make a foo.d/ directory with multiple json files
os.makedirs(os.path.join(tmpdir, 'foo.d'))
with open(os.path.join(tmpdir, 'foo.d', 'a.json'), 'w') as f:
json.dump(dict(a=2, b=1, nest={'a':2, 'b':1}), f)
with open(os.path.join(tmpdir, 'foo.d', 'b.json'), 'w') as f:
json.dump(dict(a=3, b=2, c=3, nest={'a':3, 'b':2, 'c':3}, only_in_b={'x':1}), f)
manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=False)
data = manager.get('foo')
assert 'a' in data
assert 'x' in data
assert 'b' not in data
assert 'c' not in data
assert data['a'] == 1
assert 'x' in data['nest']
# if we write it out, it also shouldn't pick up the subdirectory
manager.set('foo', data)
data = manager.get('foo')
assert data == root_data
manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=True)
data = manager.get('foo')
assert 'a' in data
assert 'b' in data
assert 'c' in data
# files should be read in order foo.d/a.json foo.d/b.json foo.json
assert data['a'] == 1
assert data['b'] == 2
assert data['c'] == 3
assert data['nest']['a'] == 1
assert data['nest']['b'] == 2
assert data['nest']['c'] == 3
assert data['nest']['x'] == 2
# when writing out, we don't want foo.d/*.json data to be included in the root foo.json
manager.set('foo', data)
manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=False)
data = manager.get('foo')
assert data == root_data
finally:
shutil.rmtree(tmpdir)

Some files were not shown because too many files have changed in this diff Show more