Uploaded Test files

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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