mirror of
https://github.com/aykhans/AzSuicideDataVisualization.git
synced 2025-07-03 14:49:07 +00:00
first commit
This commit is contained in:
143
.venv/Lib/site-packages/notebook/tests/selenium/conftest.py
Normal file
143
.venv/Lib/site-packages/notebook/tests/selenium/conftest.py
Normal 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
|
@ -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())
|
@ -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"
|
@ -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']
|
@ -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()
|
||||
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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"]
|
@ -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
|
@ -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
|
@ -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)
|
@ -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", ""]
|
@ -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
|
@ -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'
|
||||
|
@ -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
|
||||
]
|
@ -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
|
@ -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"
|
||||
)
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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'])
|
@ -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
|
@ -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"
|
@ -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"
|
@ -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> [ ]:"
|
||||
|
||||
set_prompt(2)
|
||||
assert get_prompt() == "<bdi>In</bdi> [2]:"
|
||||
|
||||
set_prompt(0)
|
||||
assert get_prompt() == "<bdi>In</bdi> [0]:"
|
||||
|
||||
set_prompt("'*'")
|
||||
assert get_prompt() == "<bdi>In</bdi> [*]:"
|
||||
|
||||
set_prompt("undefined")
|
||||
assert get_prompt() == "<bdi>In</bdi> [ ]:"
|
||||
|
||||
set_prompt("null")
|
||||
assert get_prompt() == "<bdi>In</bdi> [ ]:"
|
64
.venv/Lib/site-packages/notebook/tests/selenium/test_save.py
Normal file
64
.venv/Lib/site-packages/notebook/tests/selenium/test_save.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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]
|
468
.venv/Lib/site-packages/notebook/tests/selenium/utils.py
Normal file
468
.venv/Lib/site-packages/notebook/tests/selenium/utils.py
Normal 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
|
Reference in New Issue
Block a user