diff --git a/.github/workflows/tests.yml b/.github/workflows/tests-lab-3.yml similarity index 97% rename from .github/workflows/tests.yml rename to .github/workflows/tests-lab-3.yml index f20d22e..5da8b72 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests-lab-3.yml @@ -1,4 +1,4 @@ -name: Tests +name: Tests - lab 3 on: push: diff --git a/.github/workflows/tests-notebook-7.yml b/.github/workflows/tests-notebook-7.yml new file mode 100644 index 0000000..a1ebba9 --- /dev/null +++ b/.github/workflows/tests-notebook-7.yml @@ -0,0 +1,38 @@ +name: Tests - notebook-7 + +on: + push: + branches: [main] + pull_request: + # Check all PR + +jobs: + tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-22.04 + python-version: "3.11" + + steps: + - uses: actions/checkout@v3 + - name: Install Firefox + uses: browser-actions/setup-firefox@latest + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - run: pip install tox + + - name: run Python tests + run: tox -e tests-notebook-7 + + - name: run Python tests for coverage + run: tox -e coverage + - uses: codecov/codecov-action@v3 + with: + files: coverage.xml + verbose: true + diff --git a/tests/conftest.py b/tests/conftest.py index 6757979..82feb9b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,16 +9,48 @@ from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait +# Can be "notebook" for jupyter notebook or "lab" for jupyter lab +JUPYTER_TYPE = os.environ["JUPYTER_TYPE"] if "JUPYTER_TYPE" in os.environ else "lab" +# Because for the tests frontend elements are retrieved by class names which change +# between versions, these veriable are change depending on the version. The version is +# automatically determined on initialization of tests +JUPYTER_VERSION = None + + +def get_jupyter_version() -> str: + """ + Function so we can update the jupyter version during initialization + and use it in other files + """ + global JUPYTER_VERSION + if JUPYTER_VERSION is None: + raise ValueError("JUPYTER_VERSION was not correctly on initialization") + return JUPYTER_VERSION + @pytest.fixture(scope="session") def notebook_service(): + global JUPYTER_VERSION + + if JUPYTER_TYPE not in ["lab", "notebook"]: + raise ValueError( + f"Tests do not support jupyter type {JUPYTER_TYPE!r}. Please use" + " 'notebook' or 'lab'." + ) + # some hard coded port and token port = 8815 token = "fe47337ccb5b331e3d26a36b92112664af06462e511f66bb" + jupyter_version = subprocess.check_output( + ["jupyter", f"{JUPYTER_TYPE}", "--version"] + ) + # convert to string + JUPYTER_VERSION = jupyter_version.decode().replace("\n", "") + jupyter_process = subprocess.Popen( [ "jupyter", - "notebook", + f"{JUPYTER_TYPE}", f"--NotebookApp.token={token}", "--no-browser", f"--port={port}", @@ -33,7 +65,7 @@ def notebook_service(): yield url, token # teardown juypter notebook - os.system(f"jupyter notebook stop {port}") + os.system(f"jupyter {JUPYTER_TYPE} stop {port}") time.sleep(2) os.system(f"kill {jupyter_process.pid}") @@ -48,66 +80,133 @@ def _selenium_driver(nb_path): """ :param nb_path: jupyter notebook path """ + global JUPYTER_TYPE url, token = notebook_service - url_with_token = urljoin(url, f"tree/{nb_path}?token={token}") + + if JUPYTER_TYPE == "lab": + nb_path_prefix = "lab/tree" + elif JUPYTER_TYPE == "notebook": + nb_path_prefix = "tree" + else: + raise ValueError( + f"Tests do not support jupyter type {JUPYTER_TYPE!r}. Please use" + " 'notebook' or 'lab'." + ) + + # nb_path_prefix = + url_with_token = urljoin(url, f"{nb_path_prefix}/{nb_path}?token={token}") selenium.get(f"{url_with_token}") selenium.implicitly_wait(10) window_width = 1280 window_height = 1024 selenium.set_window_size(window_width, window_height) + # Click on restart kernel button + # ------------------------------ + + # jupyter lab < 4 + if JUPYTER_TYPE == "lab": + if get_jupyter_version() < "4.0.0": + restart_kernel_button_class_name = ( + "bp3-button.bp3-minimal.jp-ToolbarButtonComponent.minimal.jp-Button" + ) + restart_kernel_button_title_attribute = ( + "Restart Kernel and Run All Cells…" + ) + else: + raise ValueError("jupyter lab > 4.0.0 is not supported.") + elif JUPYTER_TYPE == "notebook": + if get_jupyter_version() < "7.0.0": + restart_kernel_button_class_name = "btn.btn-default" + restart_kernel_button_title_attribute = ( + "restart the kernel, then re-run the whole notebook (with dialog)" + ) + else: + restart_kernel_button_class_name = ( + "jp-ToolbarButtonComponent.jp-mod-minimal.jp-Button" + ) + restart_kernel_button_title_attribute = ( + "Restart the kernel and run all cells" + ) + # the code below imitates this code which cannot find the button # I think it is because the button is hidden by another element # WebDriverWait(driver, 5).until( # expected_conditions.text_to_be_present_in_element_attribute( - # (By.CLASS_NAME, "jp-ToolbarButtonComponent.jp-mod-minimal.jp-Button"), + # (By.CLASS_NAME, restart_kernel_button_class_name), # "title", - # "Restart the kernel and run all cells" + # restart_kernel_button_title_attribute # ) # ) restart_kernel_button = None waiting_time = 10 start = time.time() + while restart_kernel_button is None and time.time() - start < waiting_time: - # does not work for older notebook versions buttons = selenium.find_elements( - By.CLASS_NAME, "jp-ToolbarButtonComponent.jp-mod-minimal.jp-Button" + By.CLASS_NAME, restart_kernel_button_class_name ) for button in buttons: try: title = button.get_attribute("title") - if title == "Restart the kernel and run all cells": + if ( + button.is_displayed() + and title == restart_kernel_button_title_attribute + ): restart_kernel_button = button except StaleElementReferenceException: # element is not ready, go sleep continue + time.sleep(0.1) if restart_kernel_button is None: - raise ValueError('"Restart the kernel and run all cells" button not found.') + raise ValueError( + f"{restart_kernel_button_title_attribute!r} button not found." + ) restart_kernel_button.click() + # Click on confirm restart dialog + # ------------------------------- + + if JUPYTER_TYPE == "lab": + if get_jupyter_version() < "4.0.0": + restart_button_class_name = ( + "jp-Dialog-button.jp-mod-accept.jp-mod-warn.jp-mod-styled" + ) + restart_button_text = "Restart" + else: + raise ValueError("jupyter lab > 4.0.0 is not supported.") + elif JUPYTER_TYPE == "notebook": + if get_jupyter_version() < "7.0.0": + restart_button_class_name = "btn.btn-default.btn-sm.btn-danger" + restart_button_text = "Restart and Run All Cells" + else: + restart_button_class_name = "jp-Dialog-buttonLabel" + restart_button_text = "Restart" + # the code below imitates this code which cannot find the button # I think it is because the button is hidden by another element # WebDriverWait(driver, 5).until( # expected_conditions.text_to_be_present_in_element( - # (By.CLASS_NAME, "jp-Dialog-buttonLabel"), - # "Restart" + # (By.CLASS_NAME, restart_button_class_name), + # restart_button_text # ) # ) + restart_button = None waiting_time = 10 start = time.time() while restart_button is None and time.time() - start < waiting_time: - buttons = selenium.find_elements(By.CLASS_NAME, "jp-Dialog-buttonLabel") + buttons = selenium.find_elements(By.CLASS_NAME, restart_button_class_name) for button in buttons: - if button.text == "Restart": + if button.text == restart_button_text: restart_button = button time.sleep(0.1) + if restart_button is None: - raise ValueError('"Restart" button not found.') + raise ValueError(f"{restart_button_text!r} button not found.") restart_button.click() - # wait until everything has been run WebDriverWait(selenium, 10).until( expected_conditions.text_to_be_present_in_element_attribute( (By.CLASS_NAME, "jp-Notebook-ExecutionIndicator"), "data-status", "idle" diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 47b9793..78d887a 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -1,3 +1,4 @@ +import base64 import glob import os import time @@ -7,6 +8,7 @@ import pytest import requests from imageio.v3 import imread +from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.remote.webelement import WebElement @@ -15,8 +17,13 @@ from skimage.metrics import structural_similarity from skimage.transform import resize +from .conftest import JUPYTER_TYPE + def crop_const_color_borders(image: np.ndarray, const_color: int = 255): + """ + Removes all constant color borders of the image + """ if np.all(image == const_color): return image[:0, :0, :0] @@ -35,25 +42,42 @@ def crop_const_color_borders(image: np.ndarray, const_color: int = 255): return image[i1:i2, j1:j2, :] -def test_notebook_running(notebook_service): - """Tests if juypter notebook is running +if JUPYTER_TYPE == "notebook": + BUTTON_CLASS_NAME = "lm-Widget.jupyter-widgets.jupyter-button.widget-button" + OUTPUT_CLASS_NAME = "lm-Widget.jp-RenderedText.jp-mod-trusted.jp-OutputArea-output" + TEXT_INPUT_CLASS_NAME = "widget-input" + CODE_MIRROR_CLASS_NAME = "CodeMirror-code" + MATPLOTLIB_CANVAS_CLASS_NAME = "jupyter-widgets.jupyter-matplotlib-canvas-container" + CUE_BOX_CLASS_NAME = ( + "lm-Widget.lm-Panel.jupyter-widgets.widget-container" + ".widget-box.widget-vbox.scwidget-cue-box" + ) +elif JUPYTER_TYPE == "lab": + BUTTON_CLASS_NAME = ( + "lm-Widget.p-Widget.jupyter-widgets.jupyter-button.widget-button" + ) + OUTPUT_CLASS_NAME = ( + "lm-Widget.p-Widget.jp-RenderedText.jp-mod-trusted.jp-OutputArea-output" + ) + TEXT_INPUT_CLASS_NAME = "widget-input" + CODE_MIRROR_CLASS_NAME = "CodeMirror-code" - :param notebook_service: see conftest.py - """ - url, token = notebook_service - nb_path = "tree" - response = requests.get(f"{url}/{nb_path}?token={token}") - # status code 200 means it was successful - assert response.status_code == 200 + MATPLOTLIB_CANVAS_CLASS_NAME = "jupyter-widgets.jupyter-matplotlib-canvas-container" + CUE_BOX_CLASS_NAME = ( + "lm-Widget.p-Widget.lm-Panel.p-Panel.jupyter-widgets." + "widget-container.widget-box.widget-vbox.scwidget-cue-box" + ) +else: + raise ValueError( + f"Tests do not support jupyter type {JUPYTER_TYPE!r}. Please use 'notebook' or" + " 'lab'." + ) +CUED_CUE_BOX_CLASS_NAME = f"{CUE_BOX_CLASS_NAME}.scwidget-cue-box--cue" -CUE_BOX_CLASS_NAME = ( - "lm-Widget.lm-Panel.jupyter-widgets.widget-container" - ".widget-box.widget-vbox.scwidget-cue-box" -) -CUED_CUE_BOX_CLASS_NAME = ( - "lm-Widget.lm-Panel.jupyter-widgets.widget-container" - ".widget-box.widget-vbox.scwidget-cue-box.scwidget-cue-box--cue" +RESET_CUE_BUTTON_CLASS_NAME = f"{BUTTON_CLASS_NAME}.scwidget-reset-cue-button" +CUED_RESET_CUE_BUTTON_CLASS_NAME = ( + f"{RESET_CUE_BUTTON_CLASS_NAME}.scwidget-reset-cue-button--cue" ) @@ -74,16 +98,6 @@ def scwidget_cue_box_class_name(cue_type: str, cued: bool): return class_name.replace("cue-box", f"{cue_type}-cue-box") -RESET_CUE_BUTTON_CLASS_NAME = ( - "lm-Widget.jupyter-widgets.jupyter-button.widget-button" - ".scwidget-reset-cue-button" -) -CUED_RESET_CUE_BUTTON_CLASS_NAME = ( - "lm-Widget.jupyter-widgets.jupyter-button.widget-button" - ".scwidget-reset-cue-button.scwidget-reset-cue-button--cue" -) - - def reset_cue_button_class_name(cue_type: str, cued: bool): class_name = ( CUED_RESET_CUE_BUTTON_CLASS_NAME if cued else RESET_CUE_BUTTON_CLASS_NAME @@ -103,11 +117,69 @@ def scwidget_reset_cue_button_class_name(cue_type: str, cued: bool): return class_name.replace("reset-cue-button", f"{cue_type}-reset-cue-button") -BUTTON_CLASS_NAME = "lm-Widget.jupyter-widgets.jupyter-button.widget-button" -OUTPUT_CLASS_NAME = "lm-Widget.jp-RenderedText.jp-mod-trusted.jp-OutputArea-output" -TEXT_INPUT_CLASS_NAME = "widget-input" -CODE_MIRROR_CLASS_NAME = "CodeMirror-code" -MATPLOTLIB_CANVAS_CLASS_NAME = "jupyter-widgets.jupyter-matplotlib-canvas-container" +def get_nb_cells(driver) -> List[WebElement]: + """ + Filters out empty cells + + :param driver: see conftest.py selenium_driver function + """ + # Each cell of the notebook, the cell number can be retrieved from the + # attribute "data-windowed-list-index" + nb_cells = driver.find_elements( + By.CLASS_NAME, "lm-Widget.jp-Cell.jp-CodeCell.jp-Notebook-cell" + ) + return [nb_cell for nb_cell in nb_cells if nb_cell.text != ""] + + +######### +# Tests # +######### + + +def test_notebook_running(notebook_service): + """Tests if juypter notebook is running + + :param notebook_service: see conftest.py + """ + url, token = notebook_service + # jupyter lab + if JUPYTER_TYPE == "lab": + nb_path = "" + elif JUPYTER_TYPE == "notebook": + nb_path = "tree" + else: + raise ValueError( + f"Tests do not support jupyter type {JUPYTER_TYPE!r}. Please use 'notebook'" + " or 'lab'." + ) + response = requests.get(f"{url}/{nb_path}?token={token}") + # status code 200 means it was successful + assert response.status_code == 200 + + +def test_privacy_policy(selenium_driver): + """ + The first time jupyter lab is started on a fresh installation a privacy popup + appears that blocks any other button events. This test opens an arbitrary notebook + to trigger the popup and click it away. This test needs to be run before the widget + tests so the work correctly. + """ + if JUPYTER_TYPE == "lab": + driver = selenium_driver("tests/notebooks/widget_answers.ipynb") + # we search for the button to appear so we can be sure that the privacy window + # appeared + privacy_buttons = driver.find_elements( + By.CLASS_NAME, "bp3-button.bp3-small.jp-toast-button.jp-Button" + ) + yes_button = None + for button in privacy_buttons: + if button.text == "Yes": + yes_button = button + + if yes_button is not None: + WebDriverWait(driver, 5).until( + expected_conditions.element_to_be_clickable(yes_button) + ).click() class TestAnswerWidgets: @@ -138,16 +210,13 @@ def test_widget_answer(self, selenium_driver): driver = selenium_driver("tests/notebooks/widget_answers.ipynb") - # Each cell of the notebook, the cell number can be retrieved from the - # attribute "data-windowed-list-index" - nb_cells = driver.find_elements( - By.CLASS_NAME, "lm-Widget.jp-Cell.jp-CodeCell.jp-Notebook-cell" - ) + nb_cells = get_nb_cells(driver) # Test 1: # ------- nb_cell = nb_cells[2] + nb_cell.find_elements(By.CLASS_NAME, BUTTON_CLASS_NAME) answer_registry_buttons = nb_cell.find_elements( By.CLASS_NAME, BUTTON_CLASS_NAME ) @@ -267,6 +336,7 @@ def test_widget_answer(self, selenium_driver): answer_buttons = nb_cell.find_elements( By.CLASS_NAME, reset_cue_button_class_name("save", False) ) + assert len(answer_buttons) == 2 assert answer_buttons[0].text == "Save answer" save_button = answer_buttons[0] @@ -289,7 +359,12 @@ def test_widget_answer(self, selenium_driver): # WebDriverWait(driver, 1).until( expected_conditions.element_to_be_clickable(save_button) - ).click() + ) + WebDriverWait(driver, 1).until( + expected_conditions.element_to_be_clickable(save_button) + ) + # save button is hidden so we use actions to click + ActionChains(driver).move_to_element(save_button).click(save_button).perform() # wait for uncued box nb_cell.find_element(By.CLASS_NAME, cue_box_class_name("save", False)) # check if there are two buttons are uncued @@ -399,44 +474,44 @@ def test_widget_figure(selenium_driver, nb_filename, mpl_backend): # TODO for inline i need to get the image directly from the panel driver = selenium_driver(nb_filename) - # Each cell of the notebook, the cell number can be retrieved from the - # attribute "data-windowed-list-index" - nb_cells = driver.find_elements( - By.CLASS_NAME, "lm-Widget.jp-Cell.jp-CodeCell.jp-Notebook-cell" - ) + nb_cells = get_nb_cells(driver) if "inline" == mpl_backend: - WebDriverWait(nb_cells[20], 5).until( - expected_conditions.visibility_of_all_elements_located( - (By.TAG_NAME, "img"), - ) - ) - matplotlib_canvases = driver.find_elements(By.TAG_NAME, "img") + by_type = By.TAG_NAME + matplotlib_element_name = "img" + elif "ipympl" == mpl_backend: - WebDriverWait(nb_cells[20], 5).until( - expected_conditions.visibility_of_all_elements_located( - (By.CLASS_NAME, MATPLOTLIB_CANVAS_CLASS_NAME), - ) - ) - driver.find_elements(By.CLASS_NAME, MATPLOTLIB_CANVAS_CLASS_NAME) - matplotlib_canvases = driver.find_elements( - By.CLASS_NAME, MATPLOTLIB_CANVAS_CLASS_NAME - ) + by_type = By.CLASS_NAME + matplotlib_element_name = MATPLOTLIB_CANVAS_CLASS_NAME else: raise ValueError(f"matplotlib backend {mpl_backend!r} is not known.") - assert len(matplotlib_canvases) >= 5, ( - "Not all matplotlib canvases have been correctly " "loaded." + + WebDriverWait(nb_cells[20], 5).until( + expected_conditions.visibility_of_all_elements_located( + (by_type, matplotlib_element_name), + ) ) - assert len(matplotlib_canvases) == 5, ( - "Test that plt.show() does not show any plot " - "failed. For each test there should be only " - "one plot." + matplotlib_canvases = driver.find_elements(by_type, matplotlib_element_name) + + # sometimes the canvaeses are not ordered + matplotlib_canvases = sorted( + matplotlib_canvases, key=lambda canvas: canvas.location["y"] ) - def test_cue_figure(web_element: WebElement, ref_png_filename: str, rtol=5e-2): + def test_cue_figure( + web_element: WebElement, ref_png_filename: str, mpl_backend: str, rtol=5e-2 + ): # images can have different white border and slightly # different shape so we cut the border of and resize them - image = imread(web_element.screenshot_as_png) + if mpl_backend == "inline": + image = imread( + base64.decodebytes( + web_element.get_property("src").split(",")[1].encode() + ) + ) + elif mpl_backend == "ipympl": + image = imread(web_element.screenshot_as_png) + image = crop_const_color_borders(image) ref_image = imread(ref_png_filename) ref_image = crop_const_color_borders(ref_image) @@ -452,32 +527,61 @@ def test_cue_figure(web_element: WebElement, ref_png_filename: str, rtol=5e-2): time.sleep(0.2) # Test 1.1 - # inline does not show a plot if mpl_backend == "inline": - # in inline mode no image is shown by the figure but somehow - # an image of the python logo is show, we ignore this case + # in inline mode no image is shown by the first figure + nb_expected_canvases = 4 + if JUPYTER_TYPE == "notebook": + # in the notebook an image of the python logo is shown as first canvas, so + # we remove it + matplotlib_canvases = matplotlib_canvases[1:] + + assert ( + len(matplotlib_canvases) >= nb_expected_canvases + ), "Not all matplotlib canvases have been correctly loaded." + assert len(matplotlib_canvases) == nb_expected_canvases, ( + "Test that plt.show() does not show any plot " + "failed. For each test there should be only " + "one plot." + ) + # no test for inline pass elif mpl_backend == "ipympl": + nb_expected_canvases = 5 + assert ( + len(matplotlib_canvases) >= nb_expected_canvases + ), "Not all matplotlib canvases have been correctly loaded." + assert len(matplotlib_canvases) == nb_expected_canvases, ( + "Test that plt.show() does not show any plot " + "failed. For each test there should be only " + "one plot." + ) image = imread(matplotlib_canvases[0].screenshot_as_png) assert crop_const_color_borders(image).shape == (0, 0, 0), "Image is not white" + matplotlib_canvases = matplotlib_canvases[1:] + # Test 1.2 test_cue_figure( - matplotlib_canvases[1], "tests/screenshots/widget_cue_figure/empty_axis.png" + matplotlib_canvases[0], + "tests/screenshots/widget_cue_figure/empty_axis.png", + mpl_backend, ) # Test 1.3 test_cue_figure( - matplotlib_canvases[2], + matplotlib_canvases[1], "tests/screenshots/widget_cue_figure/update_figure_plot.png", + mpl_backend, ) # Test 1.4 test_cue_figure( - matplotlib_canvases[3], + matplotlib_canvases[2], "tests/screenshots/widget_cue_figure/update_figure_set.png", + mpl_backend, ) # Test 1.5 test_cue_figure( - matplotlib_canvases[4], + matplotlib_canvases[3], "tests/screenshots/widget_cue_figure/update_figure_plot.png", + mpl_backend, ) @@ -489,11 +593,7 @@ def test_widgets_cue(selenium_driver): """ driver = selenium_driver("tests/notebooks/widgets_cue.ipynb") - # Each cell of the notebook, the cell number can be retrieved from the - # attribute "data-windowed-list-index" - nb_cells = driver.find_elements( - By.CLASS_NAME, "lm-Widget.jp-Cell.jp-CodeCell.jp-Notebook-cell" - ) + nb_cells = get_nb_cells(driver) # Test 1: # ------- # Check if CueBox shows cue when changed @@ -710,11 +810,7 @@ def test_widget_check_registry(selenium_driver): """ driver = selenium_driver("tests/notebooks/widget_check_registry.ipynb") - # Each cell of the notebook, the cell number can be retrieved from the - # attribute "data-windowed-list-index" - nb_cells = driver.find_elements( - By.CLASS_NAME, "lm-Widget.jp-Cell.jp-CodeCell.jp-Notebook-cell" - ) + nb_cells = get_nb_cells(driver) # Test 1: # ------- @@ -842,11 +938,7 @@ def test_widgets_code(selenium_driver): """ driver = selenium_driver("tests/notebooks/widget_code_demo.ipynb") - # Each cell of the notebook, the cell number can be retrieved from the - # attribute "data-windowed-list-index" - nb_cells = driver.find_elements( - By.CLASS_NAME, "lm-Widget.jp-Cell.jp-CodeCell.jp-Notebook-cell" - ) + nb_cells = get_nb_cells(driver) # Test 1: # ------- WebDriverWait(driver, 5).until( @@ -971,10 +1063,8 @@ def test_code_demo( # asserts on reaction on text input # ##################################### # expected_conditions.text_to_be_present_in_element does not work for code input - code_input = nb_cell.find_elements(By.CLASS_NAME, "CodeMirror-lines") - - code_input = nb_cell.find_elements(By.CLASS_NAME, CODE_MIRROR_CLASS_NAME)[2] - assert "return" in code_input.text + code_input_lines = nb_cell.find_elements(By.CLASS_NAME, CODE_MIRROR_CLASS_NAME) + assert any(["return" in line.text for line in code_input_lines]) # Issue #22 # sending keys to code widget does not work at the moment # once this works please add this code diff --git a/tox.ini b/tox.ini index b130e26..bedf882 100644 --- a/tox.ini +++ b/tox.ini @@ -6,14 +6,18 @@ lint_folders = envlist = lint - tests + tests-lab-3 + tests-notebook-7 [testenv] -[testenv:tests] +[testenv:tests-notebook-7] +description = + Tests with jupyter notebook version > 7 setenv = # this is needed to run selenium on a machine without display to do CI SELENIUM_FIREFOX_DRIVER_ARGS = {env:SELENIUM_FIREFOX_DRIVER_ARGS:--headless} + JUPYTER_TYPE = notebook # use the jupyter config in the tox environment # otherwise the users config is used JUPYTER_CONFIG_DIR={envdir}/etc/jupyter @@ -39,6 +43,38 @@ commands = jupytext tests/notebooks/*.py --to ipynb pytest {posargs:-v} --driver Firefox +[testenv:tests-lab-3] +description = + Tests with jupyter lab version < 4 +setenv = + # this is needed to run selenium on a machine without display to do CI + SELENIUM_FIREFOX_DRIVER_ARGS = {env:SELENIUM_FIREFOX_DRIVER_ARGS:--headless} + JUPYTER_TYPE = lab + # use the jupyter config in the tox environment + # otherwise the users config is used + JUPYTER_CONFIG_DIR={envdir}/etc/jupyter + JUPYTER_DATA_DIR={envdir}/share/jupyter +deps = + pytest + pytest-html<4.0.0, + # selenium juypter notebook tests + jupyterlab==3.6.5 + # fixing selenium version to have backwards-compatibility with pytest-selenium + # see https://github.com/robotframework/SeleniumLibrary/issues/1835#issuecomment-1581426365 + selenium==4.9.0 + pytest-selenium + jupytext==1.15.0 + imageio + # we fix matplotlib for consistent image tests + matplotlib==3.7.2 + numpy + scikit-image + ipympl +commands = + # converts the python files to ipython notebooks + jupytext tests/notebooks/*.py --to ipynb + pytest {posargs:-v} --driver Firefox + [testenv:coverage] # We do coverage in a separate environment that skips the selenium tests but # includes the jupytext notebook files, because coverage is incompatible with