first commit

This commit is contained in:
Ayxan
2022-05-23 00:16:32 +04:00
commit d660f2a4ca
24786 changed files with 4428337 additions and 0 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,7 @@
def pytest_addoption(parser):
parser.addoption('--integration_tests', action='store_true', dest="integration_tests",
default=False, help="enable integration tests")
def pytest_configure(config):
if not config.option.integration_tests:
setattr(config.option, 'markexpr', 'not integration_tests')

View File

@ -0,0 +1,258 @@
"""Base class for notebook tests."""
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 ModuleNotFoundError as error:
# Errors that should be immediately thrown back to caller
raise error
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'] = f'token {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())
# Patch the current loop in order to match production
# behavior
import nest_asyncio
nest_asyncio.apply()
# 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 f'http://localhost:{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 = f'/tmp/.notebook.{os.getpid()}.sock'
@classmethod
def get_bind_args(cls):
return dict(sock=cls.sock)
@classmethod
def base_url(cls):
return f'{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, \
f"Expected status {status}, got {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(['base/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,258 @@
//
// 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([
"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, 'sub ∂ir1', 'sub ∂ir 1a'))
os.makedirs(pjoin(nbdir, 'sub ∂ir2', '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',
f'nbserver-{proc.pid:d}.json')
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 = f"{username}:{access_key}@localhost:4445"
print("Connecting remote driver on Sauce Labs")
driver = Remote(desired_capabilities=capabilities,
command_executor=f"http://{hub_url}/wd/hub")
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,65 @@
import os
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,57 @@
def cell_is_deletable(nb, index):
JS = f'return Jupyter.notebook.get_cell({index}).is_deletable();'
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 = [
f'b64data = {b64data}',
'data = base64.decodebytes(b64data)',
f'display(Image(data, retina={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 = f"data:{image_fmt};base64"
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,94 @@
"""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=f"display(HTML({test_str!r}))")
non_isolated = (
f"<style>div{{color:{red};}}</style>"
f"<div id='non-isolated'>Should be red</div>")
display_ni = f"display(HTML({non_isolated!r}), metadata={{'isolated':False}})"
notebook.add_and_execute_cell(content=display_ni)
isolated = (
f"<style>div{{color:{blue};}}</style>"
f"<div id='isolated'>Should be blue</div>")
display_i = f"display(HTML({isolated!r}), metadata={{'isolated':True}})"
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 = f"""s1 = '''<svg width="1cm" height="1cm" viewBox="0 0 1000 500"><defs><style>rect {{fill:{yellow};}}; </style></defs><rect id="r1" x="200" y="100" width="600" height="300" /></svg>'''"""
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,59 @@
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=f"Restart ({menu_item!r}) after shutdown did not start kernel"
)

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 = f"{a}\n\n{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 = f"{c}\n\n{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 = f"{merged_a_b}\n\n{merged_c_d}\n\n{e}\n\n{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,43 @@
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,29 @@
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,64 @@
"""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(f"{escaped_name!r} not found in {hrefs_nonmatch!r}")
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 = f'Jupyter.notebook.rename("{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
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,468 @@
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(f"{cell_type} is not a valid cell type,use 'code', 'markdown', or 'raw'")
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 = f'Jupyter.notebook.get_cell({index}).metadata.{key} = {value}'
return self.browser.execute_script(JS)
def get_cell_type(self, index=0):
JS = f'return Jupyter.notebook.get_cell({index}).cell_type'
return self.browser.execute_script(JS)
def set_cell_input_prompt(self, index, prmpt_val):
JS = f'Jupyter.notebook.get_cell({index}).set_input_prompt({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(f"Don't know how to add cell from {value!r}")
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 = f'Jupyter.notebook.clear_output({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 = f'#{kernel_name} a'
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',
// note: there will be a 'busy' in here, too,
// but we can't guarantee which will come first
'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_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 ipykernel.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 ipykernel.comm.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 ipykernel.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)

View File

@ -0,0 +1,172 @@
"""Test the /files/ handler."""
import os
pjoin = os.path.join
import json
from nbformat import write
from nbformat.v4 import (new_notebook,
new_markdown_cell, new_code_cell,
new_output)
from notebook.utils import url_path_join
from .launchnotebook import NotebookTestBase
class FilesTest(NotebookTestBase):
def test_hidden_files(self):
not_hidden = [
'å b',
'å b/ç. d',
]
hidden = [
'.å b',
'å b/.ç d',
]
dirs = not_hidden + hidden
nbdir = self.notebook_dir
for d in dirs:
path = pjoin(nbdir, d.replace('/', os.sep))
if not os.path.exists(path):
os.mkdir(path)
with open(pjoin(path, 'foo'), 'w') as f:
f.write('foo')
with open(pjoin(path, '.foo'), 'w') as f:
f.write('.foo')
for d in not_hidden:
path = pjoin(nbdir, d.replace('/', os.sep))
r = self.request('GET', url_path_join('files', d, 'foo'))
r.raise_for_status()
self.assertEqual(r.text, 'foo')
r = self.request('GET', url_path_join('files', d, '.foo'))
self.assertEqual(r.status_code, 404)
for d in hidden:
path = pjoin(nbdir, d.replace('/', os.sep))
for foo in ('foo', '.foo'):
r = self.request('GET', url_path_join('files', d, foo))
self.assertEqual(r.status_code, 404)
self.notebook.contents_manager.allow_hidden = True
try:
for d in not_hidden:
path = pjoin(nbdir, d.replace('/', os.sep))
r = self.request('GET', url_path_join('files', d, 'foo'))
r.raise_for_status()
self.assertEqual(r.text, 'foo')
r = self.request('GET', url_path_join('files', d, '.foo'))
r.raise_for_status()
self.assertEqual(r.text, '.foo')
for d in hidden:
path = pjoin(nbdir, d.replace('/', os.sep))
for foo in ('foo', '.foo'):
r = self.request('GET', url_path_join('files', d, foo))
r.raise_for_status()
self.assertEqual(r.text, foo)
finally:
self.notebook.contents_manager.allow_hidden = False
def test_contents_manager(self):
"make sure ContentsManager returns right files (ipynb, bin, txt)."
nbdir = self.notebook_dir
nb = new_notebook(
cells=[
new_markdown_cell('Created by test ³'),
new_code_cell("print(2*6)", outputs=[
new_output("stream", text="12"),
])
]
)
with open(pjoin(nbdir, 'testnb.ipynb'), 'w',
encoding='utf-8') as f:
write(nb, f, version=4)
with open(pjoin(nbdir, 'test.bin'), 'wb') as f:
f.write(b'\xff' + os.urandom(5))
f.close()
with open(pjoin(nbdir, 'test.txt'), 'w') as f:
f.write('foobar')
f.close()
r = self.request('GET', 'files/testnb.ipynb')
self.assertEqual(r.status_code, 200)
self.assertIn('print(2*6)', r.text)
json.loads(r.text)
r = self.request('GET', 'files/test.bin')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'application/octet-stream')
self.assertEqual(r.content[:1], b'\xff')
self.assertEqual(len(r.content), 6)
r = self.request('GET', 'files/test.txt')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.headers['content-type'], 'text/plain; charset=UTF-8')
self.assertEqual(r.text, 'foobar')
def test_download(self):
nbdir = self.notebook_dir
text = 'hello'
with open(pjoin(nbdir, 'test.txt'), 'w') as f:
f.write(text)
r = self.request('GET', 'files/test.txt')
disposition = r.headers.get('Content-Disposition', '')
self.assertNotIn('attachment', disposition)
r = self.request('GET', 'files/test.txt?download=1')
disposition = r.headers.get('Content-Disposition', '')
self.assertIn('attachment', disposition)
self.assertIn("filename*=utf-8''test.txt", disposition)
def test_view_html(self):
nbdir = self.notebook_dir
html = '<div>Test test</div>'
with open(pjoin(nbdir, 'test.html'), 'w') as f:
f.write(html)
r = self.request('GET', 'view/test.html')
self.assertEqual(r.status_code, 200)
def test_old_files_redirect(self):
"""pre-2.0 'files/' prefixed links are properly redirected"""
nbdir = self.notebook_dir
os.mkdir(pjoin(nbdir, 'files'))
os.makedirs(pjoin(nbdir, 'sub', 'files'))
for prefix in ('', 'sub'):
with open(pjoin(nbdir, prefix, 'files', 'f1.txt'), 'w') as f:
f.write(prefix + '/files/f1')
with open(pjoin(nbdir, prefix, 'files', 'f2.txt'), 'w') as f:
f.write(prefix + '/files/f2')
with open(pjoin(nbdir, prefix, 'f2.txt'), 'w') as f:
f.write(prefix + '/f2')
with open(pjoin(nbdir, prefix, 'f3.txt'), 'w') as f:
f.write(prefix + '/f3')
url = url_path_join('notebooks', prefix, 'files', 'f1.txt')
r = self.request('GET', url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.text, prefix + '/files/f1')
url = url_path_join('notebooks', prefix, 'files', 'f2.txt')
r = self.request('GET', url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.text, prefix + '/files/f2')
url = url_path_join('notebooks', prefix, 'files', 'f3.txt')
r = self.request('GET', url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.text, prefix + '/f3')

View File

@ -0,0 +1,351 @@
"""Test GatewayClient"""
import os
import json
import uuid
from datetime import datetime
from io import StringIO
from unittest.mock import patch
from tornado.web import HTTPError
from tornado.httpclient import HTTPRequest, HTTPResponse
from notebook.gateway.managers import GatewayClient
from notebook.utils import maybe_future
from .launchnotebook import NotebookTestBase
def generate_kernelspec(name):
argv_stanza = ['python', '-m', 'ipykernel_launcher', '-f', '{connection_file}']
spec_stanza = {'spec': {'argv': argv_stanza, 'env': {}, 'display_name': name, 'language': 'python', 'interrupt_mode': 'signal', 'metadata': {}}}
kernelspec_stanza = {'name': name, 'spec': spec_stanza, 'resources': {}}
return kernelspec_stanza
# We'll mock up two kernelspecs - kspec_foo and kspec_bar
kernelspecs = {'default': 'kspec_foo', 'kernelspecs': {'kspec_foo': generate_kernelspec('kspec_foo'), 'kspec_bar': generate_kernelspec('kspec_bar')}}
# maintain a dictionary of expected running kernels. Key = kernel_id, Value = model.
running_kernels = dict()
def generate_model(name):
"""Generate a mocked kernel model. Caller is responsible for adding model to running_kernels dictionary."""
dt = datetime.utcnow().isoformat() + 'Z'
kernel_id = str(uuid.uuid4())
model = {'id': kernel_id, 'name': name, 'last_activity': str(dt), 'execution_state': 'idle', 'connections': 1}
return model
async def mock_gateway_request(url, **kwargs):
method = 'GET'
if kwargs['method']:
method = kwargs['method']
request = HTTPRequest(url=url, **kwargs)
endpoint = str(url)
# Fetch all kernelspecs
if endpoint.endswith('/api/kernelspecs') and method == 'GET':
response_buf = StringIO(json.dumps(kernelspecs))
response = await maybe_future(HTTPResponse(request, 200, buffer=response_buf))
return response
# Fetch named kernelspec
if endpoint.rfind('/api/kernelspecs/') >= 0 and method == 'GET':
requested_kernelspec = endpoint.rpartition('/')[2]
kspecs = kernelspecs.get('kernelspecs')
if requested_kernelspec in kspecs:
response_buf = StringIO(json.dumps(kspecs.get(requested_kernelspec)))
response = await maybe_future(HTTPResponse(request, 200, buffer=response_buf))
return response
else:
raise HTTPError(404, message=f'Kernelspec does not exist: {requested_kernelspec}')
# Create kernel
if endpoint.endswith('/api/kernels') and method == 'POST':
json_body = json.loads(kwargs['body'])
name = json_body.get('name')
env = json_body.get('env')
kspec_name = env.get('KERNEL_KSPEC_NAME')
assert name == kspec_name # Ensure that KERNEL_ env values get propagated
model = generate_model(name)
running_kernels[model.get('id')] = model # Register model as a running kernel
response_buf = StringIO(json.dumps(model))
response = await maybe_future(HTTPResponse(request, 201, buffer=response_buf))
return response
# Fetch list of running kernels
if endpoint.endswith('/api/kernels') and method == 'GET':
kernels = []
for kernel_id in running_kernels.keys():
model = running_kernels.get(kernel_id)
kernels.append(model)
response_buf = StringIO(json.dumps(kernels))
response = await maybe_future(HTTPResponse(request, 200, buffer=response_buf))
return response
# Interrupt or restart existing kernel
if endpoint.rfind('/api/kernels/') >= 0 and method == 'POST':
requested_kernel_id, sep, action = endpoint.rpartition('/api/kernels/')[2].rpartition('/')
if action == 'interrupt':
if requested_kernel_id in running_kernels:
response = await maybe_future(HTTPResponse(request, 204))
return response
else:
raise HTTPError(404, message=f'Kernel does not exist: {requested_kernel_id}')
elif action == 'restart':
if requested_kernel_id in running_kernels:
response_buf = StringIO(json.dumps(running_kernels.get(requested_kernel_id)))
response = await maybe_future(HTTPResponse(request, 204, buffer=response_buf))
return response
else:
raise HTTPError(404, message=f'Kernel does not exist: {requested_kernel_id}')
else:
raise HTTPError(404, message=f'Bad action detected: {action}')
# Shutdown existing kernel
if endpoint.rfind('/api/kernels/') >= 0 and method == 'DELETE':
requested_kernel_id = endpoint.rpartition('/')[2]
running_kernels.pop(requested_kernel_id) # Simulate shutdown by removing kernel from running set
response = await maybe_future(HTTPResponse(request, 204))
return response
# Fetch existing kernel
if endpoint.rfind('/api/kernels/') >= 0 and method == 'GET':
requested_kernel_id = endpoint.rpartition('/')[2]
if requested_kernel_id in running_kernels:
response_buf = StringIO(json.dumps(running_kernels.get(requested_kernel_id)))
response = await maybe_future(HTTPResponse(request, 200, buffer=response_buf))
return response
else:
raise HTTPError(404, message=f'Kernel does not exist: {requested_kernel_id}')
mocked_gateway = patch('notebook.gateway.managers.gateway_request', mock_gateway_request)
class TestGateway(NotebookTestBase):
mock_gateway_url = 'http://mock-gateway-server:8889'
mock_http_user = 'alice'
@classmethod
def setup_class(cls):
GatewayClient.clear_instance()
super().setup_class()
@classmethod
def teardown_class(cls):
GatewayClient.clear_instance()
super().teardown_class()
@classmethod
def get_patch_env(cls):
test_env = super().get_patch_env()
test_env.update({'JUPYTER_GATEWAY_URL': TestGateway.mock_gateway_url,
'JUPYTER_GATEWAY_CONNECT_TIMEOUT': '44.4'})
return test_env
@classmethod
def get_argv(cls):
argv = super().get_argv()
argv.extend(['--GatewayClient.request_timeout=96.0', '--GatewayClient.http_user=' + TestGateway.mock_http_user])
return argv
def setUp(self):
kwargs = dict()
GatewayClient.instance().load_connection_args(**kwargs)
super().setUp()
def test_gateway_options(self):
assert self.notebook.gateway_config.gateway_enabled == True
assert self.notebook.gateway_config.url == TestGateway.mock_gateway_url
assert self.notebook.gateway_config.http_user == TestGateway.mock_http_user
assert self.notebook.gateway_config.connect_timeout == self.notebook.gateway_config.connect_timeout
assert self.notebook.gateway_config.connect_timeout == 44.4
assert self.notebook.gateway_config.request_timeout == 96.0
assert os.environ['KERNEL_LAUNCH_TIMEOUT'] == str(96) # Ensure KLT gets set from request-timeout
def test_gateway_class_mappings(self):
# Ensure appropriate class mappings are in place.
assert self.notebook.kernel_manager_class.__name__ == 'GatewayKernelManager'
assert self.notebook.session_manager_class.__name__ == 'GatewaySessionManager'
assert self.notebook.kernel_spec_manager_class.__name__ == 'GatewayKernelSpecManager'
def test_gateway_get_kernelspecs(self):
# Validate that kernelspecs come from gateway.
with mocked_gateway:
response = self.request('GET', '/api/kernelspecs')
self.assertEqual(response.status_code, 200)
content = json.loads(response.content.decode('utf-8'))
kspecs = content.get('kernelspecs')
assert len(kspecs) == 2
assert kspecs.get('kspec_bar').get('name') == 'kspec_bar'
def test_gateway_get_named_kernelspec(self):
# Validate that a specific kernelspec can be retrieved from gateway.
with mocked_gateway:
response = self.request('GET', '/api/kernelspecs/kspec_foo')
assert response.status_code == 200
kspec_foo = json.loads(response.content.decode('utf-8'))
assert kspec_foo.get('name') == 'kspec_foo'
response = self.request('GET', '/api/kernelspecs/no_such_spec')
assert response.status_code == 404
def test_gateway_session_lifecycle(self):
# Validate session lifecycle functions; create and delete.
# create
session_id, kernel_id = self.create_session('kspec_foo')
# ensure kernel still considered running
self.assertTrue(self.is_kernel_running(kernel_id))
# interrupt
self.interrupt_kernel(kernel_id)
# ensure kernel still considered running
self.assertTrue(self.is_kernel_running(kernel_id))
# restart
self.restart_kernel(kernel_id)
# ensure kernel still considered running
self.assertTrue(self.is_kernel_running(kernel_id))
# delete
self.delete_session(session_id)
self.assertFalse(self.is_kernel_running(kernel_id))
def test_gateway_kernel_lifecycle(self):
# Validate kernel lifecycle functions; create, interrupt, restart and delete.
# create
kernel_id = self.create_kernel('kspec_bar')
# ensure kernel still considered running
self.assertTrue(self.is_kernel_running(kernel_id))
# interrupt
self.interrupt_kernel(kernel_id)
# ensure kernel still considered running
self.assertTrue(self.is_kernel_running(kernel_id))
# restart
self.restart_kernel(kernel_id)
# ensure kernel still considered running
self.assertTrue(self.is_kernel_running(kernel_id))
# delete
self.delete_kernel(kernel_id)
self.assertFalse(self.is_kernel_running(kernel_id))
def create_session(self, kernel_name):
"""Creates a session for a kernel. The session is created against the notebook server
which then uses the gateway for kernel management.
"""
with mocked_gateway:
nb_path = os.path.join(self.notebook_dir, 'testgw.ipynb')
kwargs = dict()
kwargs['json'] = {'path': nb_path, 'type': 'notebook', 'kernel': {'name': kernel_name}}
# add a KERNEL_ value to the current env and we'll ensure that that value exists in the mocked method
os.environ['KERNEL_KSPEC_NAME'] = kernel_name
# Create the kernel... (also tests get_kernel)
response = self.request('POST', '/api/sessions', **kwargs)
self.assertEqual(response.status_code, 201)
model = json.loads(response.content.decode('utf-8'))
self.assertEqual(model.get('path'), nb_path)
kernel_id = model.get('kernel').get('id')
# ensure its in the running_kernels and name matches.
running_kernel = running_kernels.get(kernel_id)
self.assertEqual(kernel_id, running_kernel.get('id'))
self.assertEqual(model.get('kernel').get('name'), running_kernel.get('name'))
session_id = model.get('id')
# restore env
os.environ.pop('KERNEL_KSPEC_NAME')
return session_id, kernel_id
def delete_session(self, session_id):
"""Deletes a session corresponding to the given session id.
"""
with mocked_gateway:
# Delete the session (and kernel)
response = self.request('DELETE', '/api/sessions/' + session_id)
self.assertEqual(response.status_code, 204)
self.assertEqual(response.reason, 'No Content')
def is_kernel_running(self, kernel_id):
"""Issues request to get the set of running kernels
"""
with mocked_gateway:
# Get list of running kernels
response = self.request('GET', '/api/kernels')
self.assertEqual(response.status_code, 200)
kernels = json.loads(response.content.decode('utf-8'))
self.assertEqual(len(kernels), len(running_kernels))
for model in kernels:
if model.get('id') == kernel_id:
return True
return False
def create_kernel(self, kernel_name):
"""Issues request to restart the given kernel
"""
with mocked_gateway:
kwargs = dict()
kwargs['json'] = {'name': kernel_name}
# add a KERNEL_ value to the current env and we'll ensure that that value exists in the mocked method
os.environ['KERNEL_KSPEC_NAME'] = kernel_name
response = self.request('POST', '/api/kernels', **kwargs)
self.assertEqual(response.status_code, 201)
model = json.loads(response.content.decode('utf-8'))
kernel_id = model.get('id')
# ensure its in the running_kernels and name matches.
running_kernel = running_kernels.get(kernel_id)
self.assertEqual(kernel_id, running_kernel.get('id'))
self.assertEqual(model.get('name'), kernel_name)
# restore env
os.environ.pop('KERNEL_KSPEC_NAME')
return kernel_id
def interrupt_kernel(self, kernel_id):
"""Issues request to interrupt the given kernel
"""
with mocked_gateway:
response = self.request('POST', '/api/kernels/' + kernel_id + '/interrupt')
self.assertEqual(response.status_code, 204)
self.assertEqual(response.reason, 'No Content')
def restart_kernel(self, kernel_id):
"""Issues request to restart the given kernel
"""
with mocked_gateway:
response = self.request('POST', '/api/kernels/' + kernel_id + '/restart')
self.assertEqual(response.status_code, 200)
model = json.loads(response.content.decode('utf-8'))
restarted_kernel_id = model.get('id')
# ensure its in the running_kernels and name matches.
running_kernel = running_kernels.get(restarted_kernel_id)
self.assertEqual(restarted_kernel_id, running_kernel.get('id'))
self.assertEqual(model.get('name'), running_kernel.get('name'))
def delete_kernel(self, kernel_id):
"""Deletes kernel corresponding to the given kernel id.
"""
with mocked_gateway:
# Delete the session (and kernel)
response = self.request('DELETE', '/api/kernels/' + kernel_id)
self.assertEqual(response.status_code, 204)
self.assertEqual(response.reason, 'No Content')

View File

@ -0,0 +1,7 @@
from notebook import i18n
def test_parse_accept_lang_header():
palh = i18n.parse_accept_lang_header
assert palh('') == []
assert palh('zh-CN,en-GB;q=0.7,en;q=0.3') == ['en', 'en_GB', 'zh', 'zh_CN']
assert palh('nl,fr;q=0') == ['nl']

View File

@ -0,0 +1,40 @@
import unittest
from unittest import mock
from notebook import log
class TestLogRequest(unittest.TestCase):
@mock.patch('notebook.log.prometheus_log_method')
def test_log_request_json(self, mock_prometheus):
headers = {'Referer': 'test'}
request = mock.Mock(
request_time=mock.Mock(return_value=1),
headers=headers,
method='GET',
remote_ip='1.2.3.4',
uri='/notebooks/foo/bar'
)
handler = mock.MagicMock(
request=request,
get_status=mock.Mock(return_value=500)
)
logger = mock.MagicMock()
log.log_request(handler, log=logger, log_json=True)
# Since the status was 500 there should be two calls to log.error,
# one with the request headers and another with the other request
# parameters.
self.assertEqual(2, logger.error.call_count)
logger.error.assert_has_calls([
mock.call("", extra=dict(props=dict(headers))),
mock.call("", extra=dict(props={
'status': handler.get_status(),
'method': request.method,
'ip': request.remote_ip,
'uri': request.uri,
'request_time': 1000.0,
'referer': headers['Referer']
}))
])
mock_prometheus.assert_called_once_with(handler)

View File

@ -0,0 +1,514 @@
"""Test installation of notebook extensions"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import glob
import os
import pytest
import sys
import tarfile
import zipfile
from io import BytesIO, StringIO
from os.path import basename, join as pjoin
from traitlets.tests.utils import check_help_all_output
from unittest import TestCase
from unittest.mock import patch
from ipython_genutils import py3compat
from ipython_genutils.tempdir import TemporaryDirectory
from notebook import nbextensions
from notebook.nbextensions import (install_nbextension, check_nbextension,
enable_nbextension, disable_nbextension,
install_nbextension_python, uninstall_nbextension_python,
enable_nbextension_python, disable_nbextension_python, _get_config_dir,
validate_nbextension, validate_nbextension_python
)
from notebook.config_manager import BaseJSONConfigManager
def touch(file_name, mtime=None):
"""ensure a file exists, and set its modification time
returns the modification time of the file
"""
open(file_name, 'a').close()
# set explicit mtime
if mtime:
atime = os.stat(file_name).st_atime
os.utime(file_name, (atime, mtime))
return os.stat(file_name).st_mtime
def test_help_output():
check_help_all_output('notebook.nbextensions')
check_help_all_output('notebook.nbextensions', ['enable'])
check_help_all_output('notebook.nbextensions', ['disable'])
check_help_all_output('notebook.nbextensions', ['install'])
check_help_all_output('notebook.nbextensions', ['uninstall'])
class TestInstallNBExtension(TestCase):
def tempdir(self):
td = TemporaryDirectory()
self.tempdirs.append(td)
return py3compat.cast_unicode(td.name)
def setUp(self):
# Any TemporaryDirectory objects appended to this list will be cleaned
# up at the end of the test run.
self.tempdirs = []
@self.addCleanup
def cleanup_tempdirs():
for d in self.tempdirs:
d.cleanup()
self.src = self.tempdir()
self.files = files = [
pjoin('ƒile'),
pjoin('∂ir', 'ƒile1'),
pjoin('∂ir', '∂ir2', 'ƒile2'),
]
for file_name in files:
fullpath = os.path.join(self.src, file_name)
parent = os.path.dirname(fullpath)
if not os.path.exists(parent):
os.makedirs(parent)
touch(fullpath)
self.test_dir = self.tempdir()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
self.system_data_dir = os.path.join(self.test_dir, 'system_data')
self.system_path = [self.system_data_dir]
self.system_nbext = os.path.join(self.system_data_dir, 'nbextensions')
# Patch out os.environ so that tests are isolated from the real OS
# environment.
self.patch_env = patch.dict('os.environ', {
'JUPYTER_CONFIG_DIR': self.config_dir,
'JUPYTER_DATA_DIR': self.data_dir,
})
self.patch_env.start()
self.addCleanup(self.patch_env.stop)
# Patch out the system path os that we consistently use our own
# temporary directory instead.
self.patch_system_path = patch.object(
nbextensions, 'SYSTEM_JUPYTER_PATH', self.system_path
)
self.patch_system_path.start()
self.addCleanup(self.patch_system_path.stop)
def assert_dir_exists(self, path):
if not os.path.exists(path):
do_exist = os.listdir(os.path.dirname(path))
self.fail(f"{path} should exist (found {do_exist})")
def assert_not_dir_exists(self, path):
if os.path.exists(path):
self.fail(f"{path} should not exist")
def assert_installed(self, relative_path, user=False):
if user:
nbext = pjoin(self.data_dir, 'nbextensions')
else:
nbext = self.system_nbext
self.assert_dir_exists(
pjoin(nbext, relative_path)
)
def assert_not_installed(self, relative_path, user=False):
if user:
nbext = pjoin(self.data_dir, 'nbextensions')
else:
nbext = self.system_nbext
self.assert_not_dir_exists(
pjoin(nbext, relative_path)
)
def test_create_data_dir(self):
"""install_nbextension when data_dir doesn't exist"""
with TemporaryDirectory() as td:
data_dir = os.path.join(td, self.data_dir)
with patch.dict('os.environ', {
'JUPYTER_DATA_DIR': data_dir,
}):
install_nbextension(self.src, user=True)
self.assert_dir_exists(data_dir)
for file_name in self.files:
self.assert_installed(
pjoin(basename(self.src), file_name),
user=True,
)
def test_create_nbextensions_user(self):
with TemporaryDirectory() as td:
install_nbextension(self.src, user=True)
self.assert_installed(
pjoin(basename(self.src), 'ƒile'),
user=True
)
def test_create_nbextensions_system(self):
with TemporaryDirectory() as td:
self.system_nbext = pjoin(td, 'nbextensions')
with patch.object(nbextensions, 'SYSTEM_JUPYTER_PATH', [td]):
install_nbextension(self.src, user=False)
self.assert_installed(
pjoin(basename(self.src), 'ƒile'),
user=False
)
def test_single_file(self):
file_name = self.files[0]
install_nbextension(pjoin(self.src, file_name))
self.assert_installed(file_name)
def test_single_dir(self):
d = '∂ir'
install_nbextension(pjoin(self.src, d))
self.assert_installed(self.files[-1])
def test_single_dir_trailing_slash(self):
d = '∂ir/'
install_nbextension(pjoin(self.src, d))
self.assert_installed(self.files[-1])
if os.name == 'nt':
d = '∂ir\\'
install_nbextension(pjoin(self.src, d))
self.assert_installed(self.files[-1])
def test_destination_file(self):
file_name = self.files[0]
install_nbextension(pjoin(self.src, file_name), destination = 'ƒiledest')
self.assert_installed('ƒiledest')
def test_destination_dir(self):
d = '∂ir'
install_nbextension(pjoin(self.src, d), destination = 'ƒiledest2')
self.assert_installed(pjoin('ƒiledest2', '∂ir2', 'ƒile2'))
def test_install_nbextension(self):
with self.assertRaises(TypeError):
install_nbextension(glob.glob(pjoin(self.src, '*')))
def test_overwrite_file(self):
with TemporaryDirectory() as d:
fname = 'ƒ.js'
src = pjoin(d, fname)
with open(src, 'w') as f:
f.write('first')
mtime = touch(src)
dest = pjoin(self.system_nbext, fname)
install_nbextension(src)
with open(src, 'w') as f:
f.write('overwrite')
mtime = touch(src, mtime - 100)
install_nbextension(src, overwrite=True)
with open(dest) as f:
self.assertEqual(f.read(), 'overwrite')
def test_overwrite_dir(self):
with TemporaryDirectory() as src:
base = basename(src)
fname = 'ƒ.js'
touch(pjoin(src, fname))
install_nbextension(src)
self.assert_installed(pjoin(base, fname))
os.remove(pjoin(src, fname))
fname2 = '∂.js'
touch(pjoin(src, fname2))
install_nbextension(src, overwrite=True)
self.assert_installed(pjoin(base, fname2))
self.assert_not_installed(pjoin(base, fname))
def test_update_file(self):
with TemporaryDirectory() as d:
fname = 'ƒ.js'
src = pjoin(d, fname)
with open(src, 'w') as f:
f.write('first')
mtime = touch(src)
install_nbextension(src)
self.assert_installed(fname)
dest = pjoin(self.system_nbext, fname)
os.stat(dest).st_mtime
with open(src, 'w') as f:
f.write('overwrite')
touch(src, mtime + 10)
install_nbextension(src)
with open(dest) as f:
self.assertEqual(f.read(), 'overwrite')
def test_skip_old_file(self):
with TemporaryDirectory() as d:
fname = 'ƒ.js'
src = pjoin(d, fname)
mtime = touch(src)
install_nbextension(src)
self.assert_installed(fname)
dest = pjoin(self.system_nbext, fname)
old_mtime = os.stat(dest).st_mtime
mtime = touch(src, mtime - 100)
install_nbextension(src)
new_mtime = os.stat(dest).st_mtime
self.assertEqual(new_mtime, old_mtime)
def test_quiet(self):
stdout = StringIO()
stderr = StringIO()
with patch.object(sys, 'stdout', stdout), \
patch.object(sys, 'stderr', stderr):
install_nbextension(self.src)
self.assertEqual(stdout.getvalue(), '')
self.assertEqual(stderr.getvalue(), '')
def test_install_zip(self):
path = pjoin(self.src, "myjsext.zip")
with zipfile.ZipFile(path, 'w') as f:
f.writestr("a.js", b"b();")
f.writestr("foo/a.js", b"foo();")
install_nbextension(path)
self.assert_installed("a.js")
self.assert_installed(pjoin("foo", "a.js"))
def test_install_tar(self):
def _add_file(f, fname, buf):
info = tarfile.TarInfo(fname)
info.size = len(buf)
f.addfile(info, BytesIO(buf))
for i,ext in enumerate((".tar.gz", ".tgz", ".tar.bz2")):
path = pjoin(self.src, "myjsext" + ext)
with tarfile.open(path, 'w') as f:
_add_file(f, f"b{i}.js", b"b();")
_add_file(f, f"foo/b{i}.js", b"foo();")
install_nbextension(path)
self.assert_installed(f"b{i}.js")
self.assert_installed(pjoin("foo", f"b{i}.js"))
def test_install_url(self):
def fake_urlretrieve(url, dest):
touch(dest)
save_urlretrieve = nbextensions.urlretrieve
nbextensions.urlretrieve = fake_urlretrieve
try:
install_nbextension("http://example.com/path/to/foo.js")
self.assert_installed("foo.js")
install_nbextension("https://example.com/path/to/another/bar.js")
self.assert_installed("bar.js")
install_nbextension("https://example.com/path/to/another/bar.js",
destination = 'foobar.js')
self.assert_installed("foobar.js")
finally:
nbextensions.urlretrieve = save_urlretrieve
def test_check_nbextension(self):
with TemporaryDirectory() as d:
f = 'ƒ.js'
src = pjoin(d, f)
touch(src)
install_nbextension(src, user=True)
assert check_nbextension(f, user=True)
assert check_nbextension([f], user=True)
assert not check_nbextension([f, pjoin('dne', f)], user=True)
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_install_symlink(self):
with TemporaryDirectory() as d:
f = 'ƒ.js'
src = pjoin(d, f)
touch(src)
install_nbextension(src, symlink=True)
dest = pjoin(self.system_nbext, f)
assert os.path.islink(dest)
link = os.readlink(dest)
self.assertEqual(link, src)
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_overwrite_broken_symlink(self):
with TemporaryDirectory() as d:
f = 'ƒ.js'
f2 = 'ƒ2.js'
src = pjoin(d, f)
src2 = pjoin(d, f2)
touch(src)
install_nbextension(src, symlink=True)
os.rename(src, src2)
install_nbextension(src2, symlink=True, overwrite=True, destination=f)
dest = pjoin(self.system_nbext, f)
assert os.path.islink(dest)
link = os.readlink(dest)
self.assertEqual(link, src2)
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_install_symlink_destination(self):
with TemporaryDirectory() as d:
f = 'ƒ.js'
flink = 'ƒlink.js'
src = pjoin(d, f)
touch(src)
install_nbextension(src, symlink=True, destination=flink)
dest = pjoin(self.system_nbext, flink)
assert os.path.islink(dest)
link = os.readlink(dest)
self.assertEqual(link, src)
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_install_symlink_bad(self):
with self.assertRaises(ValueError):
install_nbextension("http://example.com/foo.js", symlink=True)
with TemporaryDirectory() as d:
zf = 'ƒ.zip'
zsrc = pjoin(d, zf)
with zipfile.ZipFile(zsrc, 'w') as z:
z.writestr("a.js", b"b();")
with self.assertRaises(ValueError):
install_nbextension(zsrc, symlink=True)
def test_install_destination_bad(self):
with TemporaryDirectory() as d:
zf = 'ƒ.zip'
zsrc = pjoin(d, zf)
with zipfile.ZipFile(zsrc, 'w') as z:
z.writestr("a.js", b"b();")
with self.assertRaises(ValueError):
install_nbextension(zsrc, destination='foo')
def test_nbextension_enable(self):
with TemporaryDirectory() as d:
f = 'ƒ.js'
src = pjoin(d, f)
touch(src)
install_nbextension(src, user=True)
enable_nbextension(section='notebook', require='ƒ')
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('notebook').get('load_extensions', {}).get('ƒ', False)
assert enabled
def test_nbextension_disable(self):
self.test_nbextension_enable()
disable_nbextension(section='notebook', require='ƒ')
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('notebook').get('load_extensions', {}).get('ƒ', False)
assert not enabled
def _mock_extension_spec_meta(self, section='notebook'):
return {
'section': section,
'src': 'mockextension',
'dest': '_mockdestination',
'require': '_mockdestination/index'
}
def _inject_mock_extension(self, section='notebook'):
outer_file = __file__
meta = self._mock_extension_spec_meta(section)
class mock():
__file__ = outer_file
@staticmethod
def _jupyter_nbextension_paths():
return [meta]
import sys
sys.modules['mockextension'] = mock
def test_nbextensionpy_files(self):
self._inject_mock_extension()
install_nbextension_python('mockextension')
assert check_nbextension('_mockdestination/index.js')
assert check_nbextension(['_mockdestination/index.js'])
def test_nbextensionpy_user_files(self):
self._inject_mock_extension()
install_nbextension_python('mockextension', user=True)
assert check_nbextension('_mockdestination/index.js', user=True)
assert check_nbextension(['_mockdestination/index.js'], user=True)
def test_nbextensionpy_uninstall_files(self):
self._inject_mock_extension()
install_nbextension_python('mockextension', user=True)
uninstall_nbextension_python('mockextension', user=True)
assert not check_nbextension('_mockdestination/index.js')
assert not check_nbextension(['_mockdestination/index.js'])
def test_nbextensionpy_enable(self):
self._inject_mock_extension('notebook')
install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('notebook').get('load_extensions', {}).get('_mockdestination/index', False)
assert enabled
def test_nbextensionpy_disable(self):
self._inject_mock_extension('notebook')
install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
disable_nbextension_python('mockextension', user=True)
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
cm = BaseJSONConfigManager(config_dir=config_dir)
enabled = cm.get('notebook').get('load_extensions', {}).get('_mockdestination/index', False)
assert not enabled
def test_nbextensionpy_validate(self):
self._inject_mock_extension('notebook')
paths = install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
meta = self._mock_extension_spec_meta()
warnings = validate_nbextension_python(meta, paths[0])
self.assertEqual([], warnings, warnings)
def test_nbextensionpy_validate_bad(self):
# Break the metadata (correct file will still be copied)
self._inject_mock_extension('notebook')
paths = install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
meta = self._mock_extension_spec_meta()
meta.update(require="bad-require")
warnings = validate_nbextension_python(meta, paths[0])
self.assertNotEqual([], warnings, warnings)
def test_nbextension_validate(self):
# Break the metadata (correct file will still be copied)
self._inject_mock_extension('notebook')
install_nbextension_python('mockextension', user=True)
enable_nbextension_python('mockextension')
warnings = validate_nbextension("_mockdestination/index")
self.assertEqual([], warnings, warnings)
def test_nbextension_validate_bad(self):
warnings = validate_nbextension("this-doesn't-exist")
self.assertNotEqual([], warnings, warnings)

View File

@ -0,0 +1,231 @@
"""Test NotebookApp"""
import getpass
import logging
import os
import re
import sys
from tempfile import NamedTemporaryFile
from unittest.mock import patch
import pytest
from traitlets.tests.utils import check_help_all_output
from jupyter_core.application import NoStart
from ipython_genutils.tempdir import TemporaryDirectory
from traitlets import TraitError
from notebook import notebookapp, __version__
from notebook.auth.security import passwd_check
NotebookApp = notebookapp.NotebookApp
from .launchnotebook import NotebookTestBase, UNIXSocketNotebookTestBase
def test_help_output():
"""ipython notebook --help-all works"""
check_help_all_output('notebook')
def test_server_info_file():
td = TemporaryDirectory()
nbapp = NotebookApp(runtime_dir=td.name, log=logging.getLogger())
def get_servers():
return list(notebookapp.list_running_servers(nbapp.runtime_dir))
nbapp.initialize(argv=[])
nbapp.write_server_info_file()
servers = get_servers()
assert len(servers) == 1
assert servers[0]['port'] == nbapp.port
assert servers[0]['url'] == nbapp.connection_url
nbapp.remove_server_info_file()
assert get_servers() == []
# The ENOENT error should be silenced.
nbapp.remove_server_info_file()
def test_nb_dir():
with TemporaryDirectory() as td:
app = NotebookApp(notebook_dir=td)
assert app.notebook_dir == td
def test_no_create_nb_dir():
with TemporaryDirectory() as td:
nbdir = os.path.join(td, 'notebooks')
app = NotebookApp()
with pytest.raises(TraitError):
app.notebook_dir = nbdir
def test_missing_nb_dir():
with TemporaryDirectory() as td:
nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
app = NotebookApp()
with pytest.raises(TraitError):
app.notebook_dir = nbdir
def test_invalid_nb_dir():
with NamedTemporaryFile() as tf:
app = NotebookApp()
with pytest.raises(TraitError):
app.notebook_dir = tf
def test_nb_dir_with_slash():
with TemporaryDirectory(suffix="_slash" + os.sep) as td:
app = NotebookApp(notebook_dir=td)
assert not app.notebook_dir.endswith(os.sep)
def test_nb_dir_root():
root = os.path.abspath(os.sep) # gets the right value on Windows, Posix
app = NotebookApp(notebook_dir=root)
assert app.notebook_dir == root
def test_generate_config():
with TemporaryDirectory() as td:
app = NotebookApp(config_dir=td)
app.initialize(['--generate-config', '--allow-root'])
with pytest.raises(NoStart):
app.start()
assert os.path.exists(os.path.join(td, 'jupyter_notebook_config.py'))
#test if the version testin function works
@pytest.mark.parametrize(
'version', [
'4.1.0.b1',
'4.1.b1',
'4.2',
'X.y.z',
'1.2.3.dev1.post2',
]
)
def test_pep440_bad_version(version):
with pytest.raises(ValueError):
raise_on_bad_version(version)
@pytest.mark.parametrize(
'version', [
'4.1.1',
'4.2.1b3',
]
)
def test_pep440_good_version(version):
raise_on_bad_version(version)
pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
def raise_on_bad_version(version):
if not pep440re.match(version):
raise ValueError("Versions String does apparently not match Pep 440 specification, "
"which might lead to sdist and wheel being seen as 2 different release. "
"E.g: do not use dots for beta/alpha/rc markers.")
def test_current_version():
raise_on_bad_version(__version__)
def test_notebook_password():
password = 'secret'
with TemporaryDirectory() as td:
with patch.dict('os.environ', {
'JUPYTER_CONFIG_DIR': td,
}), patch.object(getpass, 'getpass', return_value=password):
app = notebookapp.NotebookPasswordApp(log_level=logging.ERROR)
app.initialize([])
app.start()
nb = NotebookApp()
nb.load_config_file()
assert nb.password != ''
passwd_check(nb.password, password)
class StopAppTest(notebookapp.NbserverStopApp):
"""For testing the logic of NbserverStopApp."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.servers_shut_down = []
def shutdown_server(self, server):
self.servers_shut_down.append(server)
return True
def test_notebook_stop():
def list_running_servers(runtime_dir):
for port in range(100, 110):
yield {
'pid': 1000 + port,
'port': port,
'base_url': '/',
'hostname': 'localhost',
'notebook_dir': '/',
'secure': False,
'token': '',
'password': False,
'url': f'http://localhost:{port}',
}
mock_servers = patch('notebook.notebookapp.list_running_servers', list_running_servers)
# test stop with a match
with mock_servers:
app = StopAppTest()
app.initialize(['105'])
app.start()
assert len(app.servers_shut_down) == 1
assert app.servers_shut_down[0]['port'] == 105
# test no match
with mock_servers, patch('os.kill') as os_kill:
app = StopAppTest()
app.initialize(['999'])
with pytest.raises(SystemExit) as exc:
app.start()
assert exc.value.code == 1
assert len(app.servers_shut_down) == 0
class NotebookAppTests(NotebookTestBase):
def test_list_running_servers(self):
servers = list(notebookapp.list_running_servers())
assert len(servers) >= 1
assert self.port in {info['port'] for info in servers}
def test_log_json_default(self):
self.assertFalse(self.notebook.log_json)
def test_validate_log_json(self):
self.assertFalse(self.notebook._validate_log_json(dict(value=False)))
# UNIX sockets aren't available on Windows.
if not sys.platform.startswith('win'):
class NotebookUnixSocketTests(UNIXSocketNotebookTestBase):
def test_run(self):
self.fetch_url(self.base_url() + 'api/contents')
def test_list_running_sock_servers(self):
servers = list(notebookapp.list_running_servers())
assert len(servers) >= 1
assert self.sock in {info['sock'] for info in servers}
class NotebookAppJSONLoggingTests(NotebookTestBase):
"""Tests for when json logging is enabled."""
@classmethod
def setup_class(cls):
super().setup_class()
try:
import json_logging
cls.json_logging_available = True
except ImportError:
cls.json_logging_available = False
@classmethod
def get_patch_env(cls):
test_env = super().get_patch_env()
test_env.update({'JUPYTER_ENABLE_JSON_LOGGING': 'true'})
return test_env
def test_log_json_enabled(self):
self.assertTrue(self.notebook._default_log_json())
def test_validate_log_json(self):
self.assertEqual(
self.notebook._validate_log_json(dict(value=True)),
self.json_logging_available)

View File

@ -0,0 +1,170 @@
import os
import stat
import subprocess
import sys
import time
import pytest
from notebook import DEFAULT_NOTEBOOK_PORT
from .launchnotebook import UNIXSocketNotebookTestBase
from ..utils import urlencode_unix_socket, urlencode_unix_socket_path
pytestmark = pytest.mark.integration_tests
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_shutdown_sock_server_integration():
sock = UNIXSocketNotebookTestBase.sock
url = urlencode_unix_socket(sock).encode()
encoded_sock_path = urlencode_unix_socket_path(sock)
p = subprocess.Popen(
['jupyter-notebook', f'--sock={sock}', '--sock-mode=0700'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
complete = False
for line in iter(p.stderr.readline, b''):
print(line.decode())
if url in line:
complete = True
break
assert complete, 'did not find socket URL in stdout when launching notebook'
assert encoded_sock_path.encode() in subprocess.check_output(['jupyter-notebook', 'list'])
# Ensure umask is properly applied.
assert stat.S_IMODE(os.lstat(sock).st_mode) == 0o700
try:
subprocess.check_output(['jupyter-notebook', 'stop'], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
assert 'There is currently no server running on' in e.output.decode()
else:
raise AssertionError('expected stop command to fail due to target mis-match')
assert encoded_sock_path.encode() in subprocess.check_output(['jupyter-notebook', 'list'])
subprocess.check_output(['jupyter-notebook', 'stop', sock])
assert encoded_sock_path.encode() not in subprocess.check_output(['jupyter-notebook', 'list'])
p.wait()
def test_sock_server_validate_sockmode_type():
try:
subprocess.check_output(
['jupyter-notebook', '--sock=/tmp/nonexistent', '--sock-mode=badbadbad'],
stderr=subprocess.STDOUT
)
except subprocess.CalledProcessError as e:
assert 'badbadbad' in e.output.decode()
else:
raise AssertionError('expected execution to fail due to validation of --sock-mode param')
def test_sock_server_validate_sockmode_accessible():
try:
subprocess.check_output(
['jupyter-notebook', '--sock=/tmp/nonexistent', '--sock-mode=0444'],
stderr=subprocess.STDOUT
)
except subprocess.CalledProcessError as e:
assert '0444' in e.output.decode()
else:
raise AssertionError('expected execution to fail due to validation of --sock-mode param')
def _ensure_stopped(check_msg='There are no running servers'):
try:
subprocess.check_output(
['jupyter-notebook', 'stop'],
stderr=subprocess.STDOUT
)
except subprocess.CalledProcessError as e:
assert check_msg in e.output.decode()
else:
raise AssertionError('expected all servers to be stopped')
@pytest.mark.skipif(not bool(os.environ.get('RUN_NB_INTEGRATION_TESTS', False)), reason="for local testing")
def test_stop_multi_integration():
"""Tests lifecycle behavior for mixed-mode server types w/ default ports.
Mostly suitable for local dev testing due to reliance on default port binding.
"""
TEST_PORT = '9797'
MSG_TMPL = 'Shutting down server on {}...'
_ensure_stopped()
# Default port.
p1 = subprocess.Popen(
['jupyter-notebook', '--no-browser']
)
# Unix socket.
sock = UNIXSocketNotebookTestBase.sock
p2 = subprocess.Popen(
['jupyter-notebook', f'--sock={sock}']
)
# Specified port
p3 = subprocess.Popen(
['jupyter-notebook', '--no-browser', f'--port={TEST_PORT}']
)
time.sleep(3)
assert MSG_TMPL.format(DEFAULT_NOTEBOOK_PORT) in subprocess.check_output(
['jupyter-notebook', 'stop']
).decode()
_ensure_stopped('There is currently no server running on 8888')
assert MSG_TMPL.format(sock) in subprocess.check_output(
['jupyter-notebook', 'stop', sock]
).decode()
assert MSG_TMPL.format(TEST_PORT) in subprocess.check_output(
['jupyter-notebook', 'stop', TEST_PORT]
).decode()
_ensure_stopped()
p1.wait()
p2.wait()
p3.wait()
@pytest.mark.skipif(sys.platform == "win32", reason="do not run on windows")
def test_launch_socket_collision():
"""Tests UNIX socket in-use detection for lifecycle correctness."""
sock = UNIXSocketNotebookTestBase.sock
check_msg = f'socket {sock} is already in use'
_ensure_stopped()
# Start a server.
cmd = ['jupyter-notebook', f'--sock={sock}']
p1 = subprocess.Popen(cmd)
time.sleep(3)
# Try to start a server bound to the same UNIX socket.
try:
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
assert check_msg in e.output.decode()
else:
raise AssertionError(f'expected error, instead got {e.output.decode()}')
# Stop the background server, ensure it's stopped and wait on the process to exit.
subprocess.check_call(['jupyter-notebook', 'stop', sock])
_ensure_stopped()
p1.wait()

View File

@ -0,0 +1,48 @@
import re
from notebook.base.handlers import path_regex
from notebook.utils import url_path_join
from .launchnotebook import NotebookTestBase
# build regexps that tornado uses:
path_pat = re.compile(f'^/x{path_regex}$')
def test_path_regex():
for path in (
'/x',
'/x/',
'/x/foo',
'/x/foo.ipynb',
'/x/foo/bar',
'/x/foo/bar.txt',
):
assert re.match(path_pat, path)
def test_path_regex_bad():
for path in (
'/xfoo',
'/xfoo/',
'/xfoo/bar',
'/xfoo/bar/',
'/x/foo/bar/',
'/x//foo',
'/y',
'/y/x/foo',
):
assert not re.match(path_pat, path)
class RedirectTestCase(NotebookTestBase):
def test_trailing_slash(self):
for uri, expected in (
("/notebooks/mynotebook/", "/notebooks/mynotebook"),
("////foo///", "/foo"),
("//example.com/", "/example.com"),
("/has/param/?hasparam=true", "/has/param?hasparam=true"),
):
r = self.request("GET", uri, allow_redirects=False)
print(uri, expected)
assert r.status_code == 302
assert "Location" in r.headers
assert r.headers["Location"] == url_path_join(self.url_prefix, expected)

View File

@ -0,0 +1,24 @@
"""Test serialize/deserialize messages with buffers"""
import os
from jupyter_client.session import Session
from ..base.zmqhandlers import (
serialize_binary_message,
deserialize_binary_message,
)
def test_serialize_binary():
s = Session()
msg = s.msg('data_pub', content={'a': 'b'})
msg['buffers'] = [ memoryview(os.urandom(3)) for i in range(3) ]
bmsg = serialize_binary_message(msg)
assert isinstance(bmsg, bytes)
def test_deserialize_binary():
s = Session()
msg = s.msg('data_pub', content={'a': 'b'})
msg['buffers'] = [ memoryview(os.urandom(2)) for i in range(3) ]
bmsg = serialize_binary_message(msg)
msg2 = deserialize_binary_message(bmsg)
assert msg2 == msg

View File

@ -0,0 +1,196 @@
import os
import site
import sys
from unittest import TestCase
from unittest.mock import patch
from ipython_genutils.tempdir import TemporaryDirectory
from ipython_genutils import py3compat
from notebook.config_manager import BaseJSONConfigManager
from traitlets.tests.utils import check_help_all_output
from jupyter_core import paths
from notebook.serverextensions import toggle_serverextension_python
from notebook import nbextensions, extensions
from notebook.notebookapp import NotebookApp
from notebook.nbextensions import _get_config_dir
from types import SimpleNamespace
from collections import OrderedDict
def test_help_output():
check_help_all_output('notebook.serverextensions')
check_help_all_output('notebook.serverextensions', ['enable'])
check_help_all_output('notebook.serverextensions', ['disable'])
check_help_all_output('notebook.serverextensions', ['install'])
check_help_all_output('notebook.serverextensions', ['uninstall'])
outer_file = __file__
class MockExtensionModule:
__file__ = outer_file
@staticmethod
def _jupyter_server_extension_paths():
return [{
'module': '_mockdestination/index'
}]
loaded = False
def load_jupyter_server_extension(self, app):
self.loaded = True
class MockEnvTestCase(TestCase):
def tempdir(self):
td = TemporaryDirectory()
self.tempdirs.append(td)
return py3compat.cast_unicode(td.name)
def setUp(self):
self.tempdirs = []
self._mock_extensions = []
self.test_dir = self.tempdir()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
self.system_data_dir = os.path.join(self.test_dir, 'system_data')
self.system_config_dir = os.path.join(self.test_dir, 'system_config')
self.system_path = [self.system_data_dir]
self.system_config_path = [self.system_config_dir]
self.patches = []
p = patch.dict('os.environ', {
'JUPYTER_CONFIG_DIR': self.config_dir,
'JUPYTER_DATA_DIR': self.data_dir,
})
self.patches.append(p)
for mod in (paths, nbextensions):
p = patch.object(mod,
'SYSTEM_JUPYTER_PATH', self.system_path)
self.patches.append(p)
p = patch.object(mod,
'ENV_JUPYTER_PATH', [])
self.patches.append(p)
for mod in (paths, extensions):
p = patch.object(mod,
'SYSTEM_CONFIG_PATH', self.system_config_path)
self.patches.append(p)
p = patch.object(mod,
'ENV_CONFIG_PATH', [])
self.patches.append(p)
# avoid adding the user site to the config paths with jupyter-core >= 4.9
# https://github.com/jupyter/jupyter_core/pull/242
p = patch.object(site,
'ENABLE_USER_SITE', False)
self.patches.append(p)
for p in self.patches:
p.start()
self.addCleanup(p.stop)
# verify our patches
self.assertEqual(paths.jupyter_config_path(), [self.config_dir] + self.system_config_path)
self.assertEqual(extensions._get_config_dir(user=False), self.system_config_dir)
self.assertEqual(paths.jupyter_path(), [self.data_dir] + self.system_path)
def tearDown(self):
for modulename in self._mock_extensions:
sys.modules.pop(modulename)
def _inject_mock_extension(self, modulename='mockextension'):
sys.modules[modulename] = ext = MockExtensionModule()
self._mock_extensions.append(modulename)
return ext
class TestInstallServerExtension(MockEnvTestCase):
def _get_config(self, user=True):
cm = BaseJSONConfigManager(config_dir=_get_config_dir(user))
data = cm.get("jupyter_notebook_config")
return data.get("NotebookApp", {}).get("nbserver_extensions", {})
def test_enable(self):
self._inject_mock_extension()
toggle_serverextension_python('mockextension', True)
config = self._get_config()
assert config['mockextension']
def test_disable(self):
self._inject_mock_extension()
toggle_serverextension_python('mockextension', True)
toggle_serverextension_python('mockextension', False)
config = self._get_config()
assert not config['mockextension']
def test_merge_config(self):
# enabled at sys level
mock_sys = self._inject_mock_extension('mockext_sys')
# enabled at sys, disabled at user
mock_both = self._inject_mock_extension('mockext_both')
# enabled at user
mock_user = self._inject_mock_extension('mockext_user')
# enabled at Python
mock_py = self._inject_mock_extension('mockext_py')
toggle_serverextension_python('mockext_sys', enabled=True, user=False)
toggle_serverextension_python('mockext_user', enabled=True, user=True)
toggle_serverextension_python('mockext_both', enabled=True, user=False)
toggle_serverextension_python('mockext_both', enabled=False, user=True)
app = NotebookApp(nbserver_extensions={'mockext_py': True})
app.init_server_extension_config()
app.init_server_extensions()
assert mock_user.loaded
assert mock_sys.loaded
assert mock_py.loaded
assert not mock_both.loaded
class TestOrderedServerExtension(MockEnvTestCase):
"""
Test that Server Extensions are loaded _in order_
"""
def setUp(self):
super().setUp()
mockextension1 = SimpleNamespace()
mockextension2 = SimpleNamespace()
def load_jupyter_server_extension(obj):
obj.mockI = True
obj.mock_shared = 'I'
mockextension1.load_jupyter_server_extension = load_jupyter_server_extension
def load_jupyter_server_extension(obj):
obj.mockII = True
obj.mock_shared = 'II'
mockextension2.load_jupyter_server_extension = load_jupyter_server_extension
sys.modules['mockextension2'] = mockextension2
sys.modules['mockextension1'] = mockextension1
def tearDown(self):
super().tearDown()
del sys.modules['mockextension2']
del sys.modules['mockextension1']
def test_load_ordered(self):
app = NotebookApp()
app.nbserver_extensions = OrderedDict([('mockextension2',True),('mockextension1',True)])
app.init_server_extensions()
assert app.mockII is True, "Mock II should have been loaded"
assert app.mockI is True, "Mock I should have been loaded"
assert app.mock_shared == 'II', "Mock II should be loaded after Mock I"

View File

@ -0,0 +1,80 @@
import pytest
from traitlets import HasTraits, TraitError
from traitlets.utils.importstring import import_item
from notebook.traittypes import (
InstanceFromClasses,
TypeFromClasses
)
from notebook.services.contents.largefilemanager import LargeFileManager
class DummyClass:
"""Dummy class for testing Instance"""
class DummyInt(int):
"""Dummy class for testing types."""
class Thing(HasTraits):
a = InstanceFromClasses(
default_value=2,
klasses=[
int,
str,
DummyClass,
]
)
b = TypeFromClasses(
default_value=None,
allow_none=True,
klasses=[
DummyClass,
int,
'notebook.services.contents.manager.ContentsManager'
]
)
class TestInstanceFromClasses:
@pytest.mark.parametrize(
'value',
[1, 'test', DummyClass()]
)
def test_good_values(self, value):
thing = Thing(a=value)
assert thing.a == value
@pytest.mark.parametrize(
'value',
[2.4, object()]
)
def test_bad_values(self, value):
with pytest.raises(TraitError) as e:
thing = Thing(a=value)
class TestTypeFromClasses:
@pytest.mark.parametrize(
'value',
[DummyClass, DummyInt, LargeFileManager,
'notebook.services.contents.manager.ContentsManager']
)
def test_good_values(self, value):
thing = Thing(b=value)
if isinstance(value, str):
value = import_item(value)
assert thing.b == value
@pytest.mark.parametrize(
'value',
[float, object]
)
def test_bad_values(self, value):
with pytest.raises(TraitError) as e:
thing = Thing(b=value)

View File

@ -0,0 +1,94 @@
"""Test HTML utils"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import ctypes
import os
import sys
import pytest
from traitlets.tests.utils import check_help_all_output
from notebook.utils import url_escape, url_unescape, is_hidden, is_file_hidden
from ipython_genutils.py3compat import cast_unicode
from ipython_genutils.tempdir import TemporaryDirectory
def test_help_output():
"""jupyter notebook --help-all works"""
# FIXME: will be notebook
check_help_all_output('notebook')
def test_url_escape():
# changes path or notebook name with special characters to url encoding
# these tests specifically encode paths with spaces
path = url_escape('/this is a test/for spaces/')
assert path == '/this%20is%20a%20test/for%20spaces/'
path = url_escape('notebook with space.ipynb')
assert path == 'notebook%20with%20space.ipynb'
path = url_escape('/path with a/notebook and space.ipynb')
assert path == '/path%20with%20a/notebook%20and%20space.ipynb'
path = url_escape('/ !@$#%^&* / test %^ notebook @#$ name.ipynb')
assert path == '/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb'
def test_url_unescape():
# decodes a url string to a plain string
# these tests decode paths with spaces
path = url_unescape('/this%20is%20a%20test/for%20spaces/')
assert path == '/this is a test/for spaces/'
path = url_unescape('notebook%20with%20space.ipynb')
assert path == 'notebook with space.ipynb'
path = url_unescape('/path%20with%20a/notebook%20and%20space.ipynb')
assert path == '/path with a/notebook and space.ipynb'
path = url_unescape(
'/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb')
assert path == '/ !@$#%^&* / test %^ notebook @#$ name.ipynb'
def test_is_hidden():
with TemporaryDirectory() as root:
subdir1 = os.path.join(root, 'subdir')
os.makedirs(subdir1)
assert is_hidden(subdir1, root) == False
assert is_file_hidden(subdir1) == False
subdir2 = os.path.join(root, '.subdir2')
os.makedirs(subdir2)
assert is_hidden(subdir2, root) == True
assert is_file_hidden(subdir2) == True
# root dir is always visible
assert is_hidden(subdir2, subdir2) == False
subdir34 = os.path.join(root, 'subdir3', '.subdir4')
os.makedirs(subdir34)
assert is_hidden(subdir34, root) == True
assert is_hidden(subdir34) == True
subdir56 = os.path.join(root, '.subdir5', 'subdir6')
os.makedirs(subdir56)
assert is_hidden(subdir56, root) == True
assert is_hidden(subdir56) == True
assert is_file_hidden(subdir56) == False
assert is_file_hidden(subdir56, os.stat(subdir56)) == False
@pytest.mark.skipif(sys.platform != "win32", reason="run on windows only")
def test_is_hidden_win32():
with TemporaryDirectory() as root:
root = cast_unicode(root)
subdir1 = os.path.join(root, 'subdir')
os.makedirs(subdir1)
assert not is_hidden(subdir1, root)
r = ctypes.windll.kernel32.SetFileAttributesW(subdir1, 0x02)
print(r)
assert is_hidden(subdir1, root)
assert is_file_hidden(subdir1)

View File

@ -0,0 +1,889 @@
//
// Utility functions for the HTML notebook's CasperJS tests.
//
casper.get_notebook_server = function () {
// Get the URL of a notebook server on which to run tests.
var port = casper.cli.get("port");
port = (typeof port === 'undefined') ? '8888' : port;
return casper.cli.get("url") || ('http://127.0.0.1:' + port);
};
// casper.thenClick doesn't seem to trigger click events properly
casper.thenClick = function (selector) {
return this.thenEvaluate(function(selector) {
var el = $(selector);
if (el.length === 0) {
console.error("Missing element!", selector)
}
el.click();
}, {selector: selector})
}
casper.open_new_notebook = function () {
// Create and open a new notebook.
var baseUrl = this.get_notebook_server();
this.start(baseUrl);
this.waitFor(this.page_loaded);
this.waitForSelector('#kernel-python2 a, #kernel-python3 a');
this.thenClick('#kernel-python2 a, #kernel-python3 a');
this.waitForPopup('');
this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
this.then(function () {
this.open(this.popups[0].url);
});
this.waitFor(this.page_loaded);
// Hook the log and error methods of the console, forcing them to
// serialize their arguments before printing. This allows the
// Objects to cross into the phantom/slimer regime for display.
this.thenEvaluate(function(){
var serialize_arguments = function(f, context) {
return function() {
var pretty_arguments = [];
for (var i = 0; i < arguments.length; i++) {
var value = arguments[i];
if (value instanceof Object) {
var name = value.name || 'Object';
// Print a JSON string representation of the object.
// If we don't do this, [Object object] gets printed
// by casper, which is useless. The long regular
// expression reduces the verbosity of the JSON.
pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ')
.replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n')
.replace(/\n(\s+)?\n/g, '\n'));
} else {
pretty_arguments.push(value);
}
}
f.apply(context, pretty_arguments);
};
};
console.log = serialize_arguments(console.log, console);
console.error = serialize_arguments(console.error, console);
});
// Make sure the kernel has started
this.waitFor(this.kernel_running);
// track the IPython busy/idle state
this.thenEvaluate(function () {
require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
events.on('kernel_idle.Kernel',function () {
IPython._status = 'idle';
});
events.on('kernel_busy.Kernel',function () {
IPython._status = 'busy';
});
});
});
};
casper.page_loaded = function() {
// Return whether or not the page has been loaded.
return this.evaluate(function() {
return typeof IPython !== "undefined" &&
IPython.page !== undefined;
});
};
casper.kernel_running = function() {
// Return whether or not the kernel is running.
return this.evaluate(function() {
return IPython &&
IPython.notebook &&
IPython.notebook.kernel &&
IPython.notebook.kernel.is_connected();
});
};
casper.kernel_disconnected = function() {
return this.evaluate(function() {
return IPython.notebook.kernel.is_fully_disconnected();
});
};
casper.wait_for_kernel_ready = function () {
this.waitFor(this.kernel_running);
this.thenEvaluate(function () {
IPython._kernel_ready = false;
IPython.notebook.kernel.kernel_info(
function () {
IPython._kernel_ready = true;
});
});
this.waitFor(function () {
return this.evaluate(function () {
return IPython._kernel_ready;
});
});
};
casper.shutdown_current_kernel = function () {
// Shut down the current notebook's kernel.
this.thenEvaluate(function() {
IPython.notebook.session.delete();
});
// We close the page right after this so we need to give it time to complete.
this.wait(1000);
};
casper.delete_current_notebook = function () {
// Delete created notebook.
// For some unknown reason, this doesn't work?!?
this.thenEvaluate(function() {
IPython.notebook.delete();
});
};
casper.wait_for_busy = function () {
// Waits for the notebook to enter a busy state.
this.waitFor(function () {
return this.evaluate(function () {
return IPython._status == 'busy';
});
});
};
casper.wait_for_idle = function () {
// Waits for the notebook to idle.
this.waitFor(function () {
return this.evaluate(function () {
return IPython._status == 'idle';
});
});
};
casper.wait_for_output = function (cell_num, out_num) {
// wait for the nth output in a given cell
this.wait_for_idle();
out_num = out_num || 0;
this.then(function() {
this.waitFor(function (c, o) {
return this.evaluate(function get_output(c, o) {
var cell = IPython.notebook.get_cell(c);
return cell.output_area.outputs.length > o;
},
// pass parameter from the test suite js to the browser code js
{c : cell_num, o : out_num});
},
function then() { },
function timeout() {
this.echo("wait_for_output timed out on cell "+cell_num+", waiting for "+out_num+" outputs .");
var pn = this.evaluate(function get_prompt(c) {
return (IPython.notebook.get_cell(c)|| {'input_prompt_number':'no cell'}).input_prompt_number;
});
this.echo("cell prompt was :'"+pn+"'.");
});
});
};
casper.wait_for_widget = function (widget_info) {
// wait for a widget msg que to reach 0
//
// Parameters
// ----------
// widget_info : object
// Object which contains info related to the widget. The model_id property
// is used to identify the widget.
// Clear the results of a previous query, if they exist. Make sure a
// dictionary exists to store the async results in.
this.thenEvaluate(function(model_id) {
if (window.pending_msgs === undefined) {
window.pending_msgs = {};
} else {
window.pending_msgs[model_id] = -1;
}
}, {model_id: widget_info.model_id});
// Wait for the pending messages to be 0.
this.waitFor(function () {
var pending = this.evaluate(function (model_id) {
// Get the model. Once the model is had, store it's pending_msgs
// count in the window's dictionary.
IPython.notebook.kernel.widget_manager.get_model(model_id)
.then(function(model) {
window.pending_msgs[model_id] = model.pending_msgs;
});
// Return the pending_msgs result.
return window.pending_msgs[model_id];
}, {model_id: widget_info.model_id});
if (pending === 0) {
return true;
} else {
return false;
}
});
};
casper.cell_has_outputs = function (cell_num) {
var result = casper.evaluate(function (c) {
var cell = IPython.notebook.get_cell(c);
return cell.output_area.outputs.length;
},
{c : cell_num});
return result > 0;
};
casper.get_output_cell = function (cell_num, out_num, message) {
messsge = message+': ' ||'no category :'
// return an output of a given cell
out_num = out_num || 0;
var result = casper.evaluate(function (c, o) {
var cell = IPython.notebook.get_cell(c);
return cell.output_area.outputs[o];
},
{c : cell_num, o : out_num});
if (!result) {
var num_outputs = casper.evaluate(function (c) {
var cell = IPython.notebook.get_cell(c);
return cell.output_area.outputs.length;
},
{c : cell_num});
this.test.assertTrue(false,
message+"Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
);
} else {
return result;
}
};
casper.get_cells_length = function () {
// return the number of cells in the notebook
var result = casper.evaluate(function () {
return IPython.notebook.get_cells().length;
});
return result;
};
casper.set_cell_text = function(index, text){
// Set the text content of a cell.
this.evaluate(function (index, text) {
var cell = IPython.notebook.get_cell(index);
cell.set_text(text);
}, index, text);
};
casper.get_cell_text = function(index){
// Get the text content of a cell.
return this.evaluate(function (index) {
var cell = IPython.notebook.get_cell(index);
return cell.get_text();
}, index);
};
casper.insert_cell_at_bottom = function(cell_type){
// Inserts a cell at the bottom of the notebook
// Returns the new cell's index.
return this.evaluate(function (cell_type) {
var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
return IPython.notebook.find_cell_index(cell);
}, cell_type);
};
casper.append_cell = function(text, cell_type) {
// Insert a cell at the bottom of the notebook and set the cells text.
// Returns the new cell's index.
var index = this.insert_cell_at_bottom(cell_type);
if (text !== undefined) {
this.set_cell_text(index, text);
}
return index;
};
casper.execute_cell = function(index, expect_failure){
// Asynchronously executes a cell by index.
// Returns the cell's index.
if (expect_failure === undefined) expect_failure = false;
var that = this;
this.then(function(){
that.evaluate(function (index) {
var cell = IPython.notebook.get_cell(index);
cell.execute();
}, index);
});
this.wait_for_idle();
this.then(function () {
var error = that.evaluate(function (index) {
var cell = IPython.notebook.get_cell(index);
var outputs = cell.output_area.outputs;
for (var i = 0; i < outputs.length; i++) {
if (outputs[i].output_type == 'error') {
return outputs[i];
}
}
return false;
}, index);
if (error === null) {
this.test.fail("Failed to check for error output");
}
if (expect_failure && error === false) {
this.test.fail("Expected error while running cell");
} else if (!expect_failure && error !== false) {
this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
}
});
return index;
};
casper.execute_cell_then = function(index, then_callback, expect_failure) {
// Synchronously executes a cell by index.
// Optionally accepts a then_callback parameter. then_callback will get called
// when the cell has finished executing.
// Returns the cell's index.
var return_val = this.execute_cell(index, expect_failure);
this.wait_for_idle();
var that = this;
this.then(function(){
if (then_callback!==undefined) {
then_callback.apply(that, [index]);
}
});
return return_val;
};
casper.append_cell_execute_then = function(text, then_callback, expect_failure) {
// Append a code cell and execute it, optionally calling a then_callback
var c = this.append_cell(text);
return this.execute_cell_then(c, then_callback, expect_failure);
};
casper.assert_output_equals = function(text, output_text, message) {
// Append a code cell with the text, then assert the output is equal to output_text
this.append_cell_execute_then(text, function(index) {
this.test.assertEquals(this.get_output_cell(index).text.trim(), output_text, message);
});
};
casper.wait_for_element = function(index, selector){
// Utility function that allows us to easily wait for an element
// within a cell. Uses JQuery selector to look for the element.
var that = this;
this.waitFor(function() {
return that.cell_element_exists(index, selector);
});
};
casper.cell_element_exists = function(index, selector){
// Utility function that allows us to easily check if an element exists
// within a cell. Uses JQuery selector to look for the element.
return casper.evaluate(function (index, selector) {
var $cell = IPython.notebook.get_cell(index).element;
return $cell.find(selector).length > 0;
}, index, selector);
};
casper.cell_element_function = function(index, selector, function_name, function_args){
// Utility function that allows us to execute a jQuery function on an
// element within a cell.
return casper.evaluate(function (index, selector, function_name, function_args) {
var $cell = IPython.notebook.get_cell(index).element;
var $el = $cell.find(selector);
return $el[function_name].apply($el, function_args);
}, index, selector, function_name, function_args);
};
casper.validate_notebook_state = function(message, mode, cell_index) {
// Validate the entire dual mode state of the notebook. Make sure no more than
// one cell is selected, focused, in edit mode, etc...
// General tests.
this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
message + '; keyboard and notebook modes match');
// Is the selected cell the only cell that is selected?
if (cell_index!==undefined) {
this.test.assert(this.is_only_cell_selected(cell_index),
message + '; expecting cell ' + cell_index + ' to be the only cell selected. Got selected cell(s):'+
(function(){
return casper.evaluate(function(){
return IPython.notebook.get_selected_cells_indices();
})
})()
);
}
// Mode specific tests.
if (mode==='command') {
// Are the notebook and keyboard manager in command mode?
this.test.assertEquals(this.get_keyboard_mode(), 'command',
message + '; in command mode');
// Make sure there isn't a single cell in edit mode.
this.test.assert(this.is_only_cell_edit(null),
message + '; all cells in command mode');
this.test.assert(this.is_cell_editor_focused(null),
message + '; no cell editors are focused while in command mode');
} else if (mode==='edit') {
// Are the notebook and keyboard manager in edit mode?
this.test.assertEquals(this.get_keyboard_mode(), 'edit',
message + '; in edit mode');
if (cell_index!==undefined) {
// Is the specified cell the only cell in edit mode?
this.test.assert(this.is_only_cell_edit(cell_index),
message + '; cell ' + cell_index + ' is the only cell in edit mode '+ this.cells_modes());
// Is the specified cell the only cell with a focused code mirror?
this.test.assert(this.is_cell_editor_focused(cell_index),
message + '; cell ' + cell_index + '\'s editor is appropriately focused');
}
} else {
this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
}
};
casper.select_cell = function(index, moveanchor) {
// Select a cell in the notebook.
this.evaluate(function (i, moveanchor) {
IPython.notebook.select(i, moveanchor);
}, {i: index, moveanchor: moveanchor});
};
casper.select_cells = function(index, bound, moveanchor) {
// Select a block of cells in the notebook.
// like Python range, selects [index,bound)
this.evaluate(function (i, n, moveanchor) {
Jupyter.notebook.select(i, moveanchor);
Jupyter.notebook.extend_selection_by(n);
}, {i: index, n: (bound - index - 1), moveanchor: moveanchor});
};
casper.click_cell_editor = function(index) {
// Emulate a click on a cell's editor.
// Code Mirror does not play nicely with emulated brower events.
// Instead of trying to emulate a click, here we run code similar to
// the code used in Code Mirror that handles the mousedown event on a
// region of codemirror that the user can focus.
this.evaluate(function (i) {
var cm = IPython.notebook.get_cell(i).code_mirror;
if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input)){
cm.display.input.focus();
}
}, {i: index});
};
casper.set_cell_editor_cursor = function(index, line_index, char_index) {
// Set the Code Mirror instance cursor's location.
this.evaluate(function (i, l, c) {
IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
}, {i: index, l: line_index, c: char_index});
};
casper.focus_notebook = function() {
// Focus the notebook div.
this.evaluate(function (){
$('#notebook').focus();
}, {});
};
casper.trigger_keydown = function() {
// Emulate a keydown in the notebook.
for (var i = 0; i < arguments.length; i++) {
this.evaluate(function (k) {
var element = $(document);
var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
element.trigger(event);
}, {k: arguments[i]});
}
};
casper.get_keyboard_mode = function() {
// Get the mode of the keyboard manager.
return this.evaluate(function() {
return IPython.keyboard_manager.mode;
}, {});
};
casper.get_notebook_mode = function() {
// Get the mode of the notebook.
return this.evaluate(function() {
return IPython.notebook.mode;
}, {});
};
casper.get_cell = function(index) {
// Get a single cell.
//
// Note: Handles to DOM elements stored in the cell will be useless once in
// CasperJS context.
return this.evaluate(function(i) {
var cell = IPython.notebook.get_cell(i);
if (cell) {
return cell;
}
return null;
}, {i : index});
};
casper.is_cell_editor_focused = function(index) {
// Make sure a cell's editor is the only editor focused on the page.
return this.evaluate(function(i) {
var focused_textarea = $('#notebook .CodeMirror-focused textarea');
if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
if (i === null) {
return focused_textarea.length === 0;
} else {
var cell = IPython.notebook.get_cell(i);
if (cell) {
return cell.code_mirror.getInputField() == focused_textarea[0];
}
}
return false;
}, {i : index});
};
casper.is_only_cell_selected = function(index) {
// Check if a cell is the only cell selected.
// Pass null as the index to check if no cells are selected.
return this.is_only_cell_on(index, 'selected', 'unselected');
};
casper.is_only_cell_edit = function(index) {
// Check if a cell is the only cell in edit mode.
// Pass null as the index to check if all of the cells are in command mode.
var cells_length = this.get_cells_length();
for (var j = 0; j < cells_length; j++) {
if (j === index) {
if (!this.cell_mode_is(j, 'edit')) {
return false;
}
} else {
if (this.cell_mode_is(j, 'edit')) {
return false;
}
}
}
return true;
};
casper.is_only_cell_on = function(i, on_class, off_class) {
// Check if a cell is the only cell with the `on_class` DOM class applied to it.
// All of the other cells are checked for the `off_class` DOM class.
// Pass null as the index to check if all of the cells have the `off_class`.
var cells_length = this.get_cells_length();
for (var j = 0; j < cells_length; j++) {
if (j === i) {
if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
return false;
}
} else {
if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
return false;
}
}
}
return true;
};
casper.cells_modes = function(){
return this.evaluate(function(){
return IPython.notebook.get_cells().map(function(x,c){return x.mode})
}, {});
};
casper.cell_mode_is = function(index, mode) {
// Check if a cell is in a specific mode
return this.evaluate(function(i, m) {
var cell = IPython.notebook.get_cell(i);
if (cell) {
return cell.mode === m;
}
return false;
}, {i : index, m: mode});
};
casper.cell_has_class = function(index, classes) {
// Check if a cell has a class.
return this.evaluate(function(i, c) {
var cell = IPython.notebook.get_cell(i);
if (cell) {
return cell.element.hasClass(c);
}
return false;
}, {i : index, c: classes});
};
casper.is_cell_rendered = function (index) {
return this.evaluate(function(i) {
return !!IPython.notebook.get_cell(i).rendered;
}, {i:index});
};
casper.assert_colors_equal = function (hex_color, local_color, msg) {
// Tests to see if two colors are equal.
//
// Parameters
// hex_color: string
// Hexadecimal color code, with or without preceding hash character.
// local_color: string
// Local color representation. Can either be hexadecimal (default for
// phantom) or rgb (default for slimer).
// Remove parentheses, hashes, semi-colons, and space characters.
hex_color = hex_color.replace(/[\(\); #]/, '');
local_color = local_color.replace(/[\(\); #]/, '');
// If the local color is rgb, clean it up and replace
if (local_color.substr(0,3).toLowerCase() == 'rgb') {
var components = local_color.substr(3).split(',');
local_color = '';
for (var i = 0; i < components.length; i++) {
var part = parseInt(components[i]).toString(16);
while (part.length < 2) part = '0' + part;
local_color += part;
}
}
this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
};
casper.notebook_test = function(test) {
// Wrap a notebook test to reduce boilerplate.
this.open_new_notebook();
// Echo whether or not we are running this test using SlimerJS
if (this.evaluate(function(){
return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
})) {
console.log('This test is running in SlimerJS.');
this.slimerjs = true;
}
// Make sure to remove the onbeforeunload callback. This callback is
// responsible for the "Are you sure you want to quit?" type messages.
// PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
this.then(function(){
this.evaluate(function(){
window.onbeforeunload = function(){};
});
});
this.then(test);
// Kill the kernel and delete the notebook.
this.shutdown_current_kernel();
// This is still broken but shouldn't be a problem for now.
// this.delete_current_notebook();
// This is required to clean up the page we just finished with. If we don't call this
// casperjs will leak file descriptors of all the open WebSockets in that page. We
// have to set this.page=null so that next time casper.start runs, it will create a
// new page from scratch.
this.then(function () {
this.page.close();
this.page = null;
});
// Run the browser automation.
this.run(function() {
this.test.done();
});
};
casper.wait_for_dashboard = function () {
// Wait for the dashboard list to load.
casper.waitForSelector('.list_item');
};
/**
* Open the dashboard page
* @param {bool} use_start - If true, will use casper.start(), otherwise
* casper.open(). You should only set it to true if the dashboard
* is the first URL to be opened in the test, because calling
* casper.start() multiple times causes casper to skip subsequent then()
*/
casper.open_dashboard = function (use_start) {
if (use_start === undefined) {
use_start = false;
}
// Start casper by opening the dashboard page.
var baseUrl = this.get_notebook_server();
if (use_start) {
this.start(baseUrl);
} else {
this.open(baseUrl);
}
this.waitFor(this.page_loaded);
this.wait_for_dashboard();
};
casper.dashboard_test = function (test) {
// Open the dashboard page and run a test.
this.open_dashboard(true);
this.then(test);
this.then(function () {
this.page.close();
this.page = null;
});
// Run the browser automation.
this.run(function() {
this.test.done();
});
};
// note that this will only work for UNIQUE events -- if you want to
// listen for the same event twice, this will not work!
casper.event_test = function (name, events, action, timeout) {
// set up handlers to listen for each of the events
this.thenEvaluate(function (events) {
var make_handler = function (event) {
return function () {
IPython._events_triggered.push(event);
IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
delete IPython._event_handlers[event];
};
};
IPython._event_handlers = {};
IPython._events_triggered = [];
for (var i=0; i < events.length; i++) {
IPython._event_handlers[events[i]] = make_handler(events[i]);
IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
}
}, [events]);
// execute the requested action
this.then(action);
// wait for all the events to be triggered
this.waitFor(function () {
return this.evaluate(function (events) {
return IPython._events_triggered.length >= events.length;
}, [events]);
}, undefined, undefined, timeout);
// test that the events were triggered in the proper order
this.then(function () {
var triggered = this.evaluate(function () {
return IPython._events_triggered;
});
var handlers = this.evaluate(function () {
return Object.keys(IPython._event_handlers);
});
this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
for (var i=0; i < events.length; i++) {
this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
}
});
// turn off any remaining event listeners
this.thenEvaluate(function () {
for (var event in IPython._event_handlers) {
IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
delete IPython._event_handlers[event];
}
});
};
casper.options.waitTimeout=10000;
casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
this.echo("Timeout for " + casper.get_notebook_server());
this.echo("Is the notebook server running?");
});
casper.print_log = function () {
// Pass `console.log` calls from page JS to casper.
this.on('remote.message', function(msg) {
this.echo('Remote message caught: ' + msg);
});
};
casper.on("page.error", function onError(msg, trace) {
// show errors in the browser
this.echo("Page Error");
this.echo(" Message: " + msg.split('\n').join('\n '));
this.echo(" Call stack:");
var local_path = this.get_notebook_server();
for (var i = 0; i < trace.length; i++) {
var frame = trace[i];
var file = frame.file;
// shorten common phantomjs evaluate url
// this will have a different value on slimerjs
if (file === "phantomjs://webpage.evaluate()") {
file = "evaluate";
}
// remove the version tag from the path
file = file.replace(/(\?v=[0-9abcdef]+)/, '');
// remove the local address from the beginning of the path
if (file.indexOf(local_path) === 0) {
file = file.substr(local_path.length);
}
var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
this.echo(" line " + frame.line + " of " + file + frame_text);
}
});
casper.capture_log = function () {
// show captured errors
var captured_log = [];
var seen_errors = 0;
this.on('remote.message', function(msg) {
captured_log.push(msg);
});
var that = this;
this.test.on("test.done", function (result) {
// test.done runs per-file,
// but suiteResults is per-suite (directory)
var current_errors;
if (this.suiteResults) {
// casper 1.1 has suiteResults
current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
} else {
// casper 1.0 has testResults instead
current_errors = this.testResults.failed;
}
if (current_errors > seen_errors && captured_log.length > 0) {
casper.echo("\nCaptured console.log:");
for (var i = 0; i < captured_log.length; i++) {
var output = String(captured_log[i]).split('\n');
for (var j = 0; j < output.length; j++) {
casper.echo(" " + output[j]);
}
}
}
seen_errors = current_errors;
captured_log = [];
});
};
casper.interact = function() {
// Start an interactive Javascript console.
var system = require('system');
system.stdout.writeLine('JS interactive console.');
system.stdout.writeLine('Type `exit` to quit.');
function read_line() {
system.stdout.writeLine('JS: ');
var line = system.stdin.readLine();
return line;
}
var input = read_line();
while (input.trim() != 'exit') {
var output = this.evaluate(function(code) {
return String(eval(code));
}, {code: input});
system.stdout.writeLine('\nOut: ' + output);
input = read_line();
}
};
casper.capture_log();