From 9d4cf94d28302a13fd2c27d79a3ca48b35227dd0 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Tue, 20 Mar 2018 10:33:56 -0700 Subject: [PATCH 01/40] add utils module and wait_for_selector --- notebook/tests/selenium/utils.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 notebook/tests/selenium/utils.py diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py new file mode 100644 index 0000000000..5289ffe99f --- /dev/null +++ b/notebook/tests/selenium/utils.py @@ -0,0 +1,16 @@ +import os + +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 + +pjoin = os.path.join + +def wait_for_selector(browser, selector, timeout=10, visible=False): + wait = WebDriverWait(browser, timeout) + if not visible: + return wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, selector))) + else: + return wait.until(EC.visibility_of_all_element_located((By.CSS_SELECTOR, selector))) + From 7c659f59635866beeceb2dc4520dbaf631a00d95 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Tue, 20 Mar 2018 10:35:58 -0700 Subject: [PATCH 02/40] add quick_selenium script for quickly starting up a selenium interactively --- notebook/tests/selenium/quick_selenium.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 notebook/tests/selenium/quick_selenium.py diff --git a/notebook/tests/selenium/quick_selenium.py b/notebook/tests/selenium/quick_selenium.py new file mode 100644 index 0000000000..6eff52bd3d --- /dev/null +++ b/notebook/tests/selenium/quick_selenium.py @@ -0,0 +1,16 @@ +from selenium.webdriver import Firefox +from notebook.notebookapp import list_running_servers + + +def quick_driver(): + """Quickly create a selenium driver pointing at an active noteboook server. + + """ + try: + server = list(list_running_servers())[0] + except IndexError as e: + e.message = 'You need a server running before you can run this command' + driver = Firefox() + auth_url = f'{server["url"]}?token={server["token"]}' + driver.get(auth_url) + return driver From 0994db204ac59130114d0b9071ef8e36a6b55aa0 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Tue, 20 Mar 2018 12:44:44 -0700 Subject: [PATCH 03/40] Introduce Notebook class and give usage example in quick_selenium --- notebook/tests/selenium/quick_selenium.py | 6 ++++++ notebook/tests/selenium/utils.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/notebook/tests/selenium/quick_selenium.py b/notebook/tests/selenium/quick_selenium.py index 6eff52bd3d..faf8cd8740 100644 --- a/notebook/tests/selenium/quick_selenium.py +++ b/notebook/tests/selenium/quick_selenium.py @@ -5,6 +5,12 @@ def quick_driver(): """Quickly create a selenium driver pointing at an active noteboook server. + Usage example + + from inside the selenium test directory: + + import quick_selenium, test_markdown, utils + nb = utils.Notebook(test_markdown.notebook(quick_selenium.quick_driver())) """ try: server = list(list_running_servers())[0] diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 5289ffe99f..9e5366a05f 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -14,3 +14,7 @@ def wait_for_selector(browser, selector, timeout=10, visible=False): else: return wait.until(EC.visibility_of_all_element_located((By.CSS_SELECTOR, selector))) +class Notebook: + + def __init__(self, browser): + self.browser = browser From f9dd7c16e7c792d2ddca5a256801de6656bf8ccf Mon Sep 17 00:00:00 2001 From: M Pacer Date: Tue, 20 Mar 2018 12:48:05 -0700 Subject: [PATCH 04/40] add basic cell functionality to Notebook class --- notebook/tests/selenium/utils.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 9e5366a05f..0ed7bdc487 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -18,3 +18,25 @@ class Notebook: def __init__(self, browser): self.browser = browser + @property + def cells(self): + return self.browser.find_elements_by_class_name("cell") + + def to_command_mode(self): + """Changes us into command mode on currently focused cell + """ + self.browser.switch_to.active_element.send_keys(Keys.ESCAPE) + self.browser.execute_script("return Jupyter.notebook.handle_command_mode(" + "Jupyter.notebook.get_cell(" + "Jupyter.notebook.get_edit_index()))") + + @property + def current_cell_index(self): + return self.cells.index(self.current_cell) + + def focus_cell(self, index=0, edit=False): + cell = self.cells[index] + cell.click() + if not edit: + self.to_command_mode() + self.current_cell = cell From c9a850492d23b476b4aef2ab8741c81d72be9f31 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Tue, 20 Mar 2018 15:07:35 -0700 Subject: [PATCH 05/40] add body property --- notebook/tests/selenium/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 0ed7bdc487..168eaca185 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -18,6 +18,11 @@ class Notebook: def __init__(self, browser): self.browser = browser + + @property + def body(self): + return self.browser.find_element_by_tag_name("body") + @property def cells(self): return self.browser.find_elements_by_class_name("cell") From 3d3086e98afe1182e245ea5300cfc5e21c9b059e Mon Sep 17 00:00:00 2001 From: M Pacer Date: Tue, 20 Mar 2018 15:09:16 -0700 Subject: [PATCH 06/40] add edit_cell and execute_cell methods; remove focus_cell edit parameter --- notebook/tests/selenium/utils.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 168eaca185..61345b49bd 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -39,9 +39,25 @@ def to_command_mode(self): def current_cell_index(self): return self.cells.index(self.current_cell) - def focus_cell(self, index=0, edit=False): + def focus_cell(self, index=0): cell = self.cells[index] cell.click() - if not edit: - self.to_command_mode() + self.to_command_mode() self.current_cell = cell + def edit_cell(self, cell=None, index=0, content="", render=True): + if cell is None: + cell = self.cells[index] + else: + index = self.cells.index(cell) + self.focus_cell(index) + + 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_cell_index) + + def execute_cell(self, index=0): + self.focus_cell(index) + self.current_cell.send_keys(Keys.CONTROL, Keys.ENTER) From 4b6a0a7c4498b63b586141963fa500c8e69c4176 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Tue, 20 Mar 2018 15:10:17 -0700 Subject: [PATCH 07/40] Add CellTypeError and convert_cell_type and wait_for_stale_cell methods --- notebook/tests/selenium/utils.py | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 61345b49bd..9182d08b12 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -14,6 +14,12 @@ def wait_for_selector(browser, selector, timeout=10, visible=False): else: return wait.until(EC.visibility_of_all_element_located((By.CSS_SELECTOR, selector))) + +class CellTypeError(ValueError): + + def __init__(self, message=""): + self.message = message + class Notebook: def __init__(self, browser): @@ -44,6 +50,34 @@ def focus_cell(self, index=0): cell.click() self.to_command_mode() self.current_cell = cell + + 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 edit_cell(self, cell=None, index=0, content="", render=True): if cell is None: cell = self.cells[index] From 2b7f5494982cb7adf1c58415bb74b18b93511801 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Tue, 20 Mar 2018 15:12:05 -0700 Subject: [PATCH 08/40] Add add_cell and add_markdown_cell methods --- notebook/tests/selenium/utils.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 9182d08b12..b5d403aaa7 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -95,3 +95,13 @@ def edit_cell(self, cell=None, index=0, content="", render=True): def execute_cell(self, index=0): self.focus_cell(index) self.current_cell.send_keys(Keys.CONTROL, Keys.ENTER) + + def add_cell(self, index=-1, cell_type="code"): + self.focus_cell(index) + self.current_cell.send_keys("a") + new_index = index + 1 if index >= 0 else index + self.convert_cell_type(index=new_index, cell_type=cell_type) + + 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) From e2243d6f60c917a41fc548dc095a4ff9845da44e Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 12:03:57 -0700 Subject: [PATCH 09/40] add method that allows you to close the notebook without confirming --- notebook/tests/selenium/utils.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index b5d403aaa7..9aad3fbf8e 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -14,7 +14,7 @@ def wait_for_selector(browser, selector, timeout=10, visible=False): else: return wait.until(EC.visibility_of_all_element_located((By.CSS_SELECTOR, selector))) - + class CellTypeError(ValueError): def __init__(self, message=""): @@ -24,15 +24,28 @@ class Notebook: def __init__(self, browser): self.browser = browser - + self.remove_safety_check() + @property def body(self): return self.browser.find_element_by_tag_name("body") - + @property def cells(self): return self.browser.find_elements_by_class_name("cell") + + @property + def current_cell_index(self): + return self.cells.index(self.current_cell) + + def remove_safety_check(self): + """Disable request to save before closing window. + + This is most easily done by using js directly. + """ + self.browser.execute_script("window.onbeforeunload = null;") + def to_command_mode(self): """Changes us into command mode on currently focused cell """ @@ -41,10 +54,6 @@ def to_command_mode(self): "Jupyter.notebook.get_cell(" "Jupyter.notebook.get_edit_index()))") - @property - def current_cell_index(self): - return self.cells.index(self.current_cell) - def focus_cell(self, index=0): cell = self.cells[index] cell.click() @@ -71,20 +80,20 @@ def convert_cell_type(self, index=0, cell_type="code"): 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 edit_cell(self, cell=None, index=0, content="", render=True): if cell is None: cell = self.cells[index] else: index = self.cells.index(cell) self.focus_cell(index) - + for line_no, line in enumerate(content.splitlines()): if line_no != 0: self.current_cell.send_keys(Keys.ENTER, "\n") From 86ae16289094eee8df7a15da9446224429533cee Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 12:07:15 -0700 Subject: [PATCH 10/40] add first markdown test and a notebook fixture for new notebook --- notebook/tests/selenium/test_markdown.py | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 notebook/tests/selenium/test_markdown.py diff --git a/notebook/tests/selenium/test_markdown.py b/notebook/tests/selenium/test_markdown.py new file mode 100644 index 0000000000..6c09d9cef0 --- /dev/null +++ b/notebook/tests/selenium/test_markdown.py @@ -0,0 +1,37 @@ +import os + +import pytest +from selenium.webdriver.common.keys import Keys + +from .utils import wait_for_selector, Notebook + +pjoin = os.path.join + + +@pytest.fixture(scope='module') +def notebook(authenticated_browser): + b = authenticated_browser + new_button = b.find_element_by_id('new-buttons') + new_button.click() + kernel_selector = '#kernel-python3 a' + kernel_list = wait_for_selector(b, kernel_selector) + kernel_list[0].click() + window_handle_list = b.window_handles + window_handle_list.remove(b.current_window_handle) + b.switch_to_window(window_handle_list[0]) + kernel_list = wait_for_selector(b, ".cell") + b.execute_script("Jupyter.notebook.set_autosave_interval(0)") + return Notebook(b) + + +def test_markdown_cell(notebook): + nb = notebook + cell = nb.cells[0] + nb.convert_cell_type(cell_type="markdown") + assert nb.current_cell != cell + nb.edit_cell(index=0, content="# Foo") + nb.wait_for_stale_cell(cell) + rendered_cells = nb.browser.find_elements_by_class_name('text_cell_render') + outputs = [x.get_attribute('innerHTML') for x in rendered_cells] + expected = '

Foo

' + assert outputs[0].strip() == expected From bf39dec1ef4517f302bb2e7e3e4ea036a3c87d7d Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 12:09:44 -0700 Subject: [PATCH 11/40] move sauce driver logic into isolated function, simplify selenium_driver --- notebook/tests/selenium/conftest.py | 46 ++++++++++++++++++----------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/notebook/tests/selenium/conftest.py b/notebook/tests/selenium/conftest.py index d7227b12b0..8c2f2d800f 100644 --- a/notebook/tests/selenium/conftest.py +++ b/notebook/tests/selenium/conftest.py @@ -63,26 +63,38 @@ def notebook_server(): 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'): - 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) + driver = make_sauce_driver() elif os.environ.get('JUPYTER_TEST_BROWSER') == 'chrome': driver = Chrome() else: From e0ed2c475f01c108e9f714a1998d81d0b41bff8e Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 12:10:30 -0700 Subject: [PATCH 12/40] make authenticated browser module scope fixture for permission reasons --- notebook/tests/selenium/conftest.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/notebook/tests/selenium/conftest.py b/notebook/tests/selenium/conftest.py index 8c2f2d800f..232b5bc4af 100644 --- a/notebook/tests/selenium/conftest.py +++ b/notebook/tests/selenium/conftest.py @@ -12,6 +12,7 @@ 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): @@ -28,6 +29,7 @@ def _wait_for_server(proc, info_file_path): time.sleep(0.1) raise RuntimeError("Didn't find %s in 30 seconds", info_file_path) + @pytest.fixture(scope='session') def notebook_server(): info = {} @@ -50,10 +52,11 @@ def notebook_server(): # 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_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) @@ -105,7 +108,8 @@ def selenium_driver(): # Teardown driver.quit() -@pytest.fixture + +@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)) From c220215aa82ebf1a94aa9af6901fb05d54af80bc Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 12:10:52 -0700 Subject: [PATCH 13/40] use wait_for_selector from utils module in test_dashboard_nav --- notebook/tests/selenium/test_dashboard_nav.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/notebook/tests/selenium/test_dashboard_nav.py b/notebook/tests/selenium/test_dashboard_nav.py index 9b588c75e4..8e09979025 100644 --- a/notebook/tests/selenium/test_dashboard_nav.py +++ b/notebook/tests/selenium/test_dashboard_nav.py @@ -5,6 +5,7 @@ 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 @@ -40,7 +41,6 @@ def get_list_items(browser): '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 @@ -49,12 +49,6 @@ def only_dir_links(browser): return [i for i in items if url_in_tree(browser, i['link']) and i['label'] != '..'] - -def wait_for_selector(browser, selector, timeout=10): - wait = WebDriverWait(browser, timeout) - return wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, selector))) - - def test_items(authenticated_browser): visited_dict = {} # Going down the tree to collect links From 3092800a469c77bf90f45af455c83f8df8f0c01d Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 14:41:44 -0700 Subject: [PATCH 14/40] add wait_for_selector to the cells property and to_command_mode --- notebook/tests/selenium/utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 9aad3fbf8e..949ff97768 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -32,6 +32,10 @@ def body(self): @property def cells(self): + """Gets all cells once they are visible. + + """ + wait_for_selector(self.browser, ".cell") return self.browser.find_elements_by_class_name("cell") @@ -48,8 +52,9 @@ def remove_safety_check(self): def to_command_mode(self): """Changes us into command mode on currently focused cell + """ - self.browser.switch_to.active_element.send_keys(Keys.ESCAPE) + self.cells[0].send_keys(Keys.ESCAPE) self.browser.execute_script("return Jupyter.notebook.handle_command_mode(" "Jupyter.notebook.get_cell(" "Jupyter.notebook.get_edit_index()))") From 3615d4af7c5d0b7ec7bffff7a66043516586893f Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 17:40:24 -0700 Subject: [PATCH 15/40] enhance wait_for_selector to handle returning single elements --- notebook/tests/selenium/utils.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 949ff97768..35632d4796 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -7,12 +7,20 @@ pjoin = os.path.join -def wait_for_selector(browser, selector, timeout=10, visible=False): + +def wait_for_selector(browser, selector, timeout=10, visible=False, single=False): wait = WebDriverWait(browser, timeout) - if not visible: - return wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, selector))) + if single: + if visible: + conditional = EC.visibility_of_element_located + else: + conditional = EC.presence_of_element_located else: - return wait.until(EC.visibility_of_all_element_located((By.CSS_SELECTOR, selector))) + if visible: + conditional = EC.visibility_of_all_elements_located + else: + conditional = EC.presence_of_all_elements_located + return wait.until(conditional((By.CSS_SELECTOR, selector))) class CellTypeError(ValueError): From d634f1d0a1ee26bafeed9bf8176c3ef184ed5f0c Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 17:42:37 -0700 Subject: [PATCH 16/40] add new_window contextmanager for creating, switching, & waiting on new page --- notebook/tests/selenium/utils.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 35632d4796..1aa67d1378 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -5,6 +5,8 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC +from contextlib import contextmanager + pjoin = os.path.join @@ -127,3 +129,15 @@ def add_cell(self, index=-1, cell_type="code"): 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) +@contextmanager +def new_window(browser, selector=None): + """Creates new window, switches you to that window, waits for selector if set. + + """ + initial_window_handles = browser.window_handles + yield + new_window_handle = next(window for window in browser.window_handles + if window not in initial_window_handles) + browser.switch_to_window(new_window_handle) + if selector is not None: + wait_for_selector(browser, selector) From a112ab6d2a72b1437590e6b9f115efd4e3fb6527 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 17:44:56 -0700 Subject: [PATCH 17/40] add select_kernel function for clicking "new" & selecting a kernel --- notebook/tests/selenium/utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 1aa67d1378..9054f70194 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -129,6 +129,17 @@ def add_cell(self, index=-1, cell_type="code"): 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 select_kernel(browser, kernel_name='kernel-python3'): + """Clicks the "new" button and selects a kernel from the options. + """ + new_button = wait_for_selector(browser, "#new-buttons", single=True) + 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, selector=None): """Creates new window, switches you to that window, waits for selector if set. From ebef7bae4ca6d027502f27f4afa6543a47639961 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 17:46:06 -0700 Subject: [PATCH 18/40] create new_notebook classmethod creating/switching to new Notebook page --- notebook/tests/selenium/test_markdown.py | 13 +------------ notebook/tests/selenium/utils.py | 9 +++++++++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/notebook/tests/selenium/test_markdown.py b/notebook/tests/selenium/test_markdown.py index 6c09d9cef0..c109bf7b5c 100644 --- a/notebook/tests/selenium/test_markdown.py +++ b/notebook/tests/selenium/test_markdown.py @@ -10,18 +10,7 @@ @pytest.fixture(scope='module') def notebook(authenticated_browser): - b = authenticated_browser - new_button = b.find_element_by_id('new-buttons') - new_button.click() - kernel_selector = '#kernel-python3 a' - kernel_list = wait_for_selector(b, kernel_selector) - kernel_list[0].click() - window_handle_list = b.window_handles - window_handle_list.remove(b.current_window_handle) - b.switch_to_window(window_handle_list[0]) - kernel_list = wait_for_selector(b, ".cell") - b.execute_script("Jupyter.notebook.set_autosave_interval(0)") - return Notebook(b) + return Notebook.new_notebook(authenticated_browser) def test_markdown_cell(notebook): diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 9054f70194..07f1b1d74f 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -130,6 +130,15 @@ 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) + + @classmethod + def new_notebook(cls, browser, kernel_name='kernel-python3'): + # initial_window_handles = browser.window_handles + with new_window(browser, selector=".cell"): + select_kernel(browser, kernel_name=kernel_name) + browser.execute_script("Jupyter.notebook.set_autosave_interval(0)") + return cls(browser) + def select_kernel(browser, kernel_name='kernel-python3'): """Clicks the "new" button and selects a kernel from the options. From 5d19785e87191be457c82d78fd70f7f06f64eb91 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 19:34:45 -0700 Subject: [PATCH 19/40] small changes to naming things --- notebook/tests/selenium/utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 07f1b1d74f..2ddcf664f0 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -50,8 +50,11 @@ def cells(self): @property - def current_cell_index(self): - return self.cells.index(self.current_cell) + def current_index(self): + return self.index(self.current_cell) + + def index(self, cell): + return self.cells.index(cell) def remove_safety_check(self): """Disable request to save before closing window. @@ -64,7 +67,7 @@ def to_command_mode(self): """Changes us into command mode on currently focused cell """ - self.cells[0].send_keys(Keys.ESCAPE) + 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()))") @@ -114,9 +117,9 @@ def edit_cell(self, cell=None, index=0, content="", render=True): self.current_cell.send_keys(Keys.ENTER, "\n") self.current_cell.send_keys(Keys.ENTER, line) if render: - self.execute_cell(self.current_cell_index) def execute_cell(self, index=0): + self.execute_cell(self.current_index) self.focus_cell(index) self.current_cell.send_keys(Keys.CONTROL, Keys.ENTER) From 3571f1604f36343e438611036b45d01f7dd3f039 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Thu, 22 Mar 2018 19:40:16 -0700 Subject: [PATCH 20/40] introduce cell __iter__, & __setitem__/__getitem__ for cells & indices __getitem__ also accepts slices also introduces __len__ --- notebook/tests/selenium/utils.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 2ddcf664f0..1d3a9866f2 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -4,6 +4,7 @@ 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 @@ -30,11 +31,33 @@ class CellTypeError(ValueError): def __init__(self, message=""): self.message = message + class Notebook: def __init__(self, browser): self.browser = browser self.remove_safety_check() + + def __len__(self): + return len(self.cells) + + def __getitem__(self, key): + if isinstance(key, (int, slice)): + value = self.cells[key] + elif isinstance(key, WebElement): + value = self.index(key) + return value + + def __setitem__(self, key, item): + if isinstance(key, int): + self.edit_cell(index=key, content=item, render=False) + 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) @property def body(self): From e183fc1627684b5e42315d73c9e29194d4224b2a Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 08:33:41 -0700 Subject: [PATCH 21/40] enrich signature for execute_cell to accept both index and cell directly --- notebook/tests/selenium/utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 1d3a9866f2..621f110d66 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -140,9 +140,15 @@ def edit_cell(self, cell=None, index=0, content="", render=True): self.current_cell.send_keys(Keys.ENTER, "\n") self.current_cell.send_keys(Keys.ENTER, line) if render: - - def execute_cell(self, index=0): 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[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) From c16dac22ca2ab9e017e33a0b968b3a9cb32c04b0 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 08:33:47 -0700 Subject: [PATCH 22/40] use new rich container __*__ methods to make adding & executing nicer --- notebook/tests/selenium/test_markdown.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/notebook/tests/selenium/test_markdown.py b/notebook/tests/selenium/test_markdown.py index c109bf7b5c..053942e772 100644 --- a/notebook/tests/selenium/test_markdown.py +++ b/notebook/tests/selenium/test_markdown.py @@ -15,11 +15,10 @@ def notebook(authenticated_browser): def test_markdown_cell(notebook): nb = notebook - cell = nb.cells[0] - nb.convert_cell_type(cell_type="markdown") - assert nb.current_cell != cell - nb.edit_cell(index=0, content="# Foo") - nb.wait_for_stale_cell(cell) + nb[:] = ["# Foo"] + nb.convert_cell_type(0, cell_type="markdown") + for cell in nb: + nb.execute_cell(cell) rendered_cells = nb.browser.find_elements_by_class_name('text_cell_render') outputs = [x.get_attribute('innerHTML') for x in rendered_cells] expected = '

Foo

' From e9971a97aacf63bbe939be558363d23115887500 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 10:58:36 -0700 Subject: [PATCH 23/40] simplify __getitem__ __setitem__ to handle only ints/slices and ints also adds todo to add slice support back to __setitem__ --- notebook/tests/selenium/utils.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 621f110d66..c25658f90c 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -42,19 +42,16 @@ def __len__(self): return len(self.cells) def __getitem__(self, key): - if isinstance(key, (int, slice)): - value = self.cells[key] - elif isinstance(key, WebElement): - value = self.index(key) - return value + return self.cells[key] def __setitem__(self, key, item): if isinstance(key, int): self.edit_cell(index=key, content=item, render=False) - 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) + # TODO: readd slicing support, handle general python slicing behaviour + # 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) From bf4868dfc49b9d892f40a1b1d1ac0d7e2e694862 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 10:58:58 -0700 Subject: [PATCH 24/40] remove wait_for_selector call inside self.cells property --- notebook/tests/selenium/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index c25658f90c..34179555f2 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -65,10 +65,8 @@ def cells(self): """Gets all cells once they are visible. """ - wait_for_selector(self.browser, ".cell") return self.browser.find_elements_by_class_name("cell") - @property def current_index(self): return self.index(self.current_cell) From 02e0ac38a483fb4a045c9122bb49a5daf194426e Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:01:57 -0700 Subject: [PATCH 25/40] fix add_cell logic, add content param, edit_cell default not render --- notebook/tests/selenium/utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 34179555f2..68e4e1d2bf 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -123,11 +123,9 @@ def wait_for_stale_cell(self, cell): wait = WebDriverWait(self.browser, 10) element = wait.until(EC.staleness_of(cell)) - def edit_cell(self, cell=None, index=0, content="", render=True): - if cell is None: - cell = self.cells[index] - else: - index = self.cells.index(cell) + def edit_cell(self, cell=None, index=0, content="", render=False): + if cell is not None: + index = self.index(cell) self.focus_cell(index) for line_no, line in enumerate(content.splitlines()): @@ -147,10 +145,12 @@ def execute_cell(self, cell_or_index = None): self.focus_cell(index) self.current_cell.send_keys(Keys.CONTROL, Keys.ENTER) - def add_cell(self, index=-1, cell_type="code"): + def add_cell(self, index=-1, cell_type="code", content=""): self.focus_cell(index) - self.current_cell.send_keys("a") + self.current_cell.send_keys("b") new_index = index + 1 if index >= 0 else index + if content: + self.edit_cell(index=index, content=content) self.convert_cell_type(index=new_index, cell_type=cell_type) def add_markdown_cell(self, index=-1, content="", render=True): From 98c09f8fd0b76b88d72484d3b18e2705ce5d2257 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:02:43 -0700 Subject: [PATCH 26/40] use index method to get cell index in execute_cell --- notebook/tests/selenium/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 68e4e1d2bf..b4be1533cc 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -135,11 +135,11 @@ def edit_cell(self, cell=None, index=0, content="", render=False): if render: self.execute_cell(self.current_index) - def execute_cell(self, cell_or_index = None): + 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[cell_or_index] + index = self.index(cell_or_index) else: raise TypeError("execute_cell only accepts a WebElement or an int") self.focus_cell(index) From 5e43458d7dfdefcb54b7425868126d8a0d8cced3 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:03:03 -0700 Subject: [PATCH 27/40] add append, extend & coerce_to_cell methods --- notebook/tests/selenium/utils.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index b4be1533cc..3af9bec3b7 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -156,7 +156,32 @@ def add_cell(self, index=-1, cell_type="code", content=""): 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"): + start = len(self) + for i, value in enumerate(values): + if isinstance(value, dict): + _value = coerce_to_cell(value) + self.add_cell(index=start+i, + cell_type=_value["cell_type"], + content=_value["content"]) + elif isinstance(value, str): + self.add_cell(index=start+i, + cell_type=cell_type, + content=value) + + def extend(self, value): + self.append(*copy(value)) + + def coerce_to_cell(self, value): + """This verifies that you have specified a valid cell object + + """ + new_cell = {"cell_type": value.get("cell_type", "code"), + "metadata": value.get("metadata", {}), + "content": value.get("content", "")} + return new_cell @classmethod def new_notebook(cls, browser, kernel_name='kernel-python3'): From d9dd5d8f1c5306232196494540929e7cbc326bb9 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:04:02 -0700 Subject: [PATCH 28/40] add run_all method --- notebook/tests/selenium/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 3af9bec3b7..9587d32622 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -173,6 +173,10 @@ def append(self, *values, cell_type="code"): def extend(self, value): self.append(*copy(value)) + def run_all(self): + for cell in self: + self.execute_cell(cell) + def coerce_to_cell(self, value): """This verifies that you have specified a valid cell object From 33ca649b0b4e8957936e513e269f63dba46c481a Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:04:25 -0700 Subject: [PATCH 29/40] add get_rendered_contents helper function --- notebook/tests/selenium/test_markdown.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/notebook/tests/selenium/test_markdown.py b/notebook/tests/selenium/test_markdown.py index 053942e772..c79da273a1 100644 --- a/notebook/tests/selenium/test_markdown.py +++ b/notebook/tests/selenium/test_markdown.py @@ -13,6 +13,15 @@ def notebook(authenticated_browser): return Notebook.new_notebook(authenticated_browser) +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(notebook): nb = notebook nb[:] = ["# Foo"] From 0999798949cb1faec9184b4e6c90d794d504fc31 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:05:15 -0700 Subject: [PATCH 30/40] use new utilities, enable more markdown cells to be added easily --- notebook/tests/selenium/test_markdown.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/notebook/tests/selenium/test_markdown.py b/notebook/tests/selenium/test_markdown.py index c79da273a1..ff6c5c7ad5 100644 --- a/notebook/tests/selenium/test_markdown.py +++ b/notebook/tests/selenium/test_markdown.py @@ -24,11 +24,13 @@ def get_rendered_contents(nb): def test_markdown_cell(notebook): nb = notebook - nb[:] = ["# Foo"] - nb.convert_cell_type(0, cell_type="markdown") - for cell in nb: - nb.execute_cell(cell) - rendered_cells = nb.browser.find_elements_by_class_name('text_cell_render') - outputs = [x.get_attribute('innerHTML') for x in rendered_cells] - expected = '

Foo

' - assert outputs[0].strip() == expected + cell_text = ["# Foo"] + expected_contents = ['

Foo

'] + for i, cell in enumerate(nb): + if i==0: + nb.convert_cell_type(0, cell_type="markdown") + nb[0] = cell_text[0] + nb.append(cell_text[1:], cell_type="markdown") + nb.run_all() + rendered_contents = get_rendered_contents(nb) + assert rendered_contents == expected_contents From 7808a89fd36bd0460c0b639b398808d666a1fe50 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:23:32 -0700 Subject: [PATCH 31/40] make append actually append to the end of cell list --- notebook/tests/selenium/utils.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 9587d32622..85717e7649 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -158,16 +158,13 @@ def add_markdown_cell(self, index=-1, content="", render=True): self.edit_cell(index=index, content=content, render=render) def append(self, *values, cell_type="code"): - start = len(self) for i, value in enumerate(values): if isinstance(value, dict): _value = coerce_to_cell(value) - self.add_cell(index=start+i, - cell_type=_value["cell_type"], + self.add_cell(cell_type=_value["cell_type"], content=_value["content"]) elif isinstance(value, str): - self.add_cell(index=start+i, - cell_type=cell_type, + self.add_cell(cell_type=cell_type, content=value) def extend(self, value): From c081af569064c3e3f5661040224c516584071788 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:30:03 -0700 Subject: [PATCH 32/40] add other markdown string conversion examples for test --- notebook/tests/selenium/test_markdown.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/notebook/tests/selenium/test_markdown.py b/notebook/tests/selenium/test_markdown.py index ff6c5c7ad5..df95d57b29 100644 --- a/notebook/tests/selenium/test_markdown.py +++ b/notebook/tests/selenium/test_markdown.py @@ -8,7 +8,7 @@ pjoin = os.path.join -@pytest.fixture(scope='module') +@pytest.fixture def notebook(authenticated_browser): return Notebook.new_notebook(authenticated_browser) @@ -22,15 +22,23 @@ def get_rendered_contents(nb): for x in rendered_cells if x is not None] + def test_markdown_cell(notebook): nb = notebook - cell_text = ["# Foo"] - expected_contents = ['

Foo

'] + cell_text = ["# Foo", + '**Bar**', + '*Baz*', + '```\nx = 1\n```', + '```aaaa\nx = 1\n```', + ] + expected_contents = ['

Foo

', + '

Bar

', + '

Baz

', + '
x = 1\n
', + '
x = 1\n
' + ] for i, cell in enumerate(nb): - if i==0: - nb.convert_cell_type(0, cell_type="markdown") - nb[0] = cell_text[0] - nb.append(cell_text[1:], cell_type="markdown") + nb.append(*cell_text, cell_type="markdown") nb.run_all() rendered_contents = get_rendered_contents(nb) assert rendered_contents == expected_contents From d598ef517f6841f88486607505580092b4160f5b Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:50:22 -0700 Subject: [PATCH 33/40] switch fstring to format string --- notebook/tests/selenium/quick_selenium.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notebook/tests/selenium/quick_selenium.py b/notebook/tests/selenium/quick_selenium.py index faf8cd8740..1fceb55aae 100644 --- a/notebook/tests/selenium/quick_selenium.py +++ b/notebook/tests/selenium/quick_selenium.py @@ -17,6 +17,6 @@ def quick_driver(): except IndexError as e: e.message = 'You need a server running before you can run this command' driver = Firefox() - auth_url = f'{server["url"]}?token={server["token"]}' + auth_url = '{url}?token={token}'.format(**server) driver.get(auth_url) - return driver + return driver \ No newline at end of file From 3c4596bfdf4785e4edc74d83923257ea152618f1 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:51:56 -0700 Subject: [PATCH 34/40] Take into account lab as a potential endpoint for the driver --- notebook/tests/selenium/quick_selenium.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/notebook/tests/selenium/quick_selenium.py b/notebook/tests/selenium/quick_selenium.py index 1fceb55aae..239a40fbf4 100644 --- a/notebook/tests/selenium/quick_selenium.py +++ b/notebook/tests/selenium/quick_selenium.py @@ -2,15 +2,14 @@ from notebook.notebookapp import list_running_servers -def quick_driver(): +def quick_driver(lab=False): """Quickly create a selenium driver pointing at an active noteboook server. - Usage example + Usage example: - from inside the selenium test directory: - - import quick_selenium, test_markdown, utils - nb = utils.Notebook(test_markdown.notebook(quick_selenium.quick_driver())) + from notebook.tests.selenium.quick_selenium import quick_driver + driver = quick_driver + """ try: server = list(list_running_servers())[0] @@ -19,4 +18,10 @@ def quick_driver(): driver = Firefox() auth_url = '{url}?token={token}'.format(**server) driver.get(auth_url) - return driver \ No newline at end of file + + # 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 + From 79603b496fc40e7cf219fb29f85e0cc07acff896 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 11:53:40 -0700 Subject: [PATCH 35/40] add quick_notebook utility and give docstring reminder to quit browsers --- notebook/tests/selenium/quick_selenium.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/notebook/tests/selenium/quick_selenium.py b/notebook/tests/selenium/quick_selenium.py index 239a40fbf4..e0cef58f1b 100644 --- a/notebook/tests/selenium/quick_selenium.py +++ b/notebook/tests/selenium/quick_selenium.py @@ -1,4 +1,6 @@ from selenium.webdriver import Firefox + +from notebook.tests.selenium.utils import Notebook from notebook.notebookapp import list_running_servers @@ -10,6 +12,7 @@ def quick_driver(lab=False): 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] @@ -25,3 +28,16 @@ def quick_driver(lab=False): 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()) From 74af79c89d34df5138ccf5ff6e75c3a494b02d76 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Fri, 23 Mar 2018 12:09:48 -0700 Subject: [PATCH 36/40] Improve docstrings & comments; Remove unused code; Relocate script --- notebook/tests/selenium/utils.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 85717e7649..31d75b7d45 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -47,7 +47,9 @@ def __getitem__(self, key): def __setitem__(self, key, item): if isinstance(key, int): self.edit_cell(index=key, content=item, render=False) - # TODO: readd slicing support, handle general python slicing behaviour + # 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): @@ -75,11 +77,12 @@ def index(self, cell): return self.cells.index(cell) def remove_safety_check(self): - """Disable request to save before closing window. + """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 @@ -186,10 +189,8 @@ def coerce_to_cell(self, value): @classmethod def new_notebook(cls, browser, kernel_name='kernel-python3'): - # initial_window_handles = browser.window_handles with new_window(browser, selector=".cell"): select_kernel(browser, kernel_name=kernel_name) - browser.execute_script("Jupyter.notebook.set_autosave_interval(0)") return cls(browser) @@ -204,8 +205,25 @@ def select_kernel(browser, kernel_name='kernel-python3'): @contextmanager def new_window(browser, selector=None): - """Creates new window, switches you to that window, waits for selector if set. + """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.abs + + If you know a CSS selector that can be expected to appear on the window, + then this utility can wait on that selector appearing on the page before + releasing the context. + + Usage example: + + from notebook.tests.selenium.utils import new_window, Notebook + + ⋮ # something that creates a browser object + + with new_window(browser, selector=".cell"): + select_kernel(browser, kernel_name=kernel_name) + nb = Notebook(browser) + """ initial_window_handles = browser.window_handles yield From 515f8e22dc1d26a5d0bd2f50aa0a57b87e82f690 Mon Sep 17 00:00:00 2001 From: M Pacer Date: Tue, 27 Mar 2018 09:21:15 -0700 Subject: [PATCH 37/40] nicer error and use append directly do not iterate over cells --- notebook/tests/selenium/quick_selenium.py | 7 ++++++- notebook/tests/selenium/test_markdown.py | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/notebook/tests/selenium/quick_selenium.py b/notebook/tests/selenium/quick_selenium.py index e0cef58f1b..489002597c 100644 --- a/notebook/tests/selenium/quick_selenium.py +++ b/notebook/tests/selenium/quick_selenium.py @@ -3,6 +3,10 @@ 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. @@ -17,7 +21,8 @@ def quick_driver(lab=False): try: server = list(list_running_servers())[0] except IndexError as e: - e.message = 'You need a server running before you can run this command' + raise NoServerError('You need a server running before you can run ' + 'this command') driver = Firefox() auth_url = '{url}?token={token}'.format(**server) driver.get(auth_url) diff --git a/notebook/tests/selenium/test_markdown.py b/notebook/tests/selenium/test_markdown.py index df95d57b29..c71ef8a17f 100644 --- a/notebook/tests/selenium/test_markdown.py +++ b/notebook/tests/selenium/test_markdown.py @@ -37,8 +37,7 @@ def test_markdown_cell(notebook): '
x = 1\n
', '
x = 1\n
' ] - for i, cell in enumerate(nb): - nb.append(*cell_text, cell_type="markdown") + nb.append(*cell_text, cell_type="markdown") nb.run_all() rendered_contents = get_rendered_contents(nb) assert rendered_contents == expected_contents From c23ba2a87fb2d4f80286fca664842e1feaf61984 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 28 Mar 2018 11:07:30 +0200 Subject: [PATCH 38/40] Docstrings and naming clarity --- notebook/tests/selenium/quick_selenium.py | 5 +++++ notebook/tests/selenium/utils.py | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/notebook/tests/selenium/quick_selenium.py b/notebook/tests/selenium/quick_selenium.py index 489002597c..10e46e1f32 100644 --- a/notebook/tests/selenium/quick_selenium.py +++ b/notebook/tests/selenium/quick_selenium.py @@ -1,3 +1,8 @@ +"""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 diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 31d75b7d45..b2e331d3bd 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -36,7 +36,7 @@ class Notebook: def __init__(self, browser): self.browser = browser - self.remove_safety_check() + self.disable_autosave_and_onbeforeunload() def __len__(self): return len(self.cells) @@ -76,7 +76,7 @@ def current_index(self): def index(self, cell): return self.cells.index(cell) - def remove_safety_check(self): + def disable_autosave_and_onbeforeunload(self): """Disable request to save before closing window and autosave. This is most easily done by using js directly. @@ -208,7 +208,7 @@ def new_window(browser, selector=None): """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.abs + the created context and it will switch you to that new window. If you know a CSS selector that can be expected to appear on the window, then this utility can wait on that selector appearing on the page before From a6f604a54056965f24ab5deb4f86bebfe0826506 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 28 Mar 2018 11:08:20 +0200 Subject: [PATCH 39/40] No need for copy --- notebook/tests/selenium/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index b2e331d3bd..18156feee0 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -170,8 +170,8 @@ def append(self, *values, cell_type="code"): self.add_cell(cell_type=cell_type, content=value) - def extend(self, value): - self.append(*copy(value)) + def extend(self, values): + self.append(*values) def run_all(self): for cell in self: From 7266fd53b0aacbd5f2e19e92949efa093ab124df Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 28 Mar 2018 11:13:13 +0200 Subject: [PATCH 40/40] Remove unused variant of append for now --- notebook/tests/selenium/utils.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/notebook/tests/selenium/utils.py b/notebook/tests/selenium/utils.py index 18156feee0..3aed1d5540 100644 --- a/notebook/tests/selenium/utils.py +++ b/notebook/tests/selenium/utils.py @@ -162,13 +162,11 @@ def add_markdown_cell(self, index=-1, content="", render=True): def append(self, *values, cell_type="code"): for i, value in enumerate(values): - if isinstance(value, dict): - _value = coerce_to_cell(value) - self.add_cell(cell_type=_value["cell_type"], - content=_value["content"]) - elif isinstance(value, str): + 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) @@ -176,16 +174,6 @@ def extend(self, values): def run_all(self): for cell in self: self.execute_cell(cell) - - - def coerce_to_cell(self, value): - """This verifies that you have specified a valid cell object - - """ - new_cell = {"cell_type": value.get("cell_type", "code"), - "metadata": value.get("metadata", {}), - "content": value.get("content", "")} - return new_cell @classmethod def new_notebook(cls, browser, kernel_name='kernel-python3'):