diff --git a/anaconda-project.yml b/anaconda-project.yml index 0a66452b..f3102021 100644 --- a/anaconda-project.yml +++ b/anaconda-project.yml @@ -75,7 +75,7 @@ commands: env_spec: rfjl37 install:win: - windows: python -m pip install _artifacts\\sdist\\robotframework-jupyterlibrary-0.1.0.tar.gz + windows: python -m pip install _artifacts\\sdist\\robotframework-jupyterlibrary-0.2.0.tar.gz --no-deps --ignore-installed -vv env_spec: win_rfjl37 @@ -83,6 +83,10 @@ commands: unix: sphinx-build -M html docs _artifacts/docs env_spec: rfjl37 + docs:watch: + unix: sphinx-autobuild docs _artifacts/docs + env_spec: rfjl37 + publish:pypi: unix: twine upload --repository-url https://upload.pypi.org/legacy/ _artifacts/{sdist,wheel}/* env_spec: rfjl37 @@ -102,16 +106,16 @@ env_specs: - python-chromedriver-binary - robotframework >=3.1 - robotframework-lint - - robotframework-seleniumlibrary >=3.2 + - robotframework-seleniumlibrary >=3.3 - six - sphinx - sphinx_rtd_theme + - sphinx-autobuild - pip: - nteract_on_jupyter channels: - conda-forge - defaults - rfjl37: inherit_from: - robotframework-jupyterlibrary diff --git a/atest/acceptance/classic/10_notebook.robot b/atest/acceptance/classic/10_notebook.robot index 6934486d..de42bfe2 100644 --- a/atest/acceptance/classic/10_notebook.robot +++ b/atest/acceptance/classic/10_notebook.robot @@ -6,9 +6,17 @@ Library Process *** Test Cases *** IPython Notebook on Classic - Open Notebook Classic + Open Notebook Classic ${BROWSER} Launch a new Notebook Classic Notebook Add and Run Notebook Classic Code Cell print("hello world") Wait Until Notebook Classic Kernel Is Idle Current Notebook Classic Cell Output Should Contain hello world Capture Page Screenshot classic${/}ipython.png + +IPython Notebook Outputs on Classic + Open Notebook Classic ${BROWSER} + Launch a new Notebook Classic Notebook + : FOR ${i} IN RANGE ${10} + \ Add and Run Notebook Classic Code Cell print("${i} hello world " * ${i ** 2}) + Wait Until Notebook Classic Kernel Is Idle + Screenshot Each Output of Active Notebook Classic Document classic${/}ipython_outputs${/} diff --git a/atest/acceptance/lab/00_shell.robot b/atest/acceptance/lab/00_shell.robot index 0d1e79ca..4b11ca0a 100644 --- a/atest/acceptance/lab/00_shell.robot +++ b/atest/acceptance/lab/00_shell.robot @@ -6,10 +6,10 @@ Library Process *** Test Cases *** Open JupyterLab - Open JupyterLab + Open JupyterLab ${BROWSER} Get Help - Open JupyterLab + Open JupyterLab ${BROWSER} Open With JupyterLab Menu Help About JupyterLab Capture Element Screenshot css:.jp-Dialog-content lab${/}about.png Click Element css:${JLAB CSS ACCEPT} diff --git a/atest/acceptance/lab/10_notebook.robot b/atest/acceptance/lab/10_notebook.robot index e1646610..189a3ba8 100644 --- a/atest/acceptance/lab/10_notebook.robot +++ b/atest/acceptance/lab/10_notebook.robot @@ -7,17 +7,19 @@ Library Process *** Test Cases *** IPython Notebook on Lab - Open JupyterLab + Open JupyterLab ${BROWSER} Launch a new JupyterLab Document Add and Run JupyterLab Code Cell print("hello world") Wait Until JupyterLab Kernel Is Idle Current JupyterLab Cell Output Should Contain hello world Capture Page Screenshot lab${/}ipython.png + Save JupyterLab Notebook IPython Notebook Outputs on Lab - Open JupyterLab + Open JupyterLab ${BROWSER} Launch a new JupyterLab Document : FOR ${i} IN RANGE ${10} \ Add and Run JupyterLab Code Cell print("${i} hello world " * ${i ** 2}) Wait Until JupyterLab Kernel Is Idle Screenshot Each Output of Active JupyterLab Document lab${/}ipython_outputs${/} + Save JupyterLab Notebook diff --git a/atest/acceptance/nteract/10_notebook.robot b/atest/acceptance/nteract/10_notebook.robot index cd6848ac..6abced41 100644 --- a/atest/acceptance/nteract/10_notebook.robot +++ b/atest/acceptance/nteract/10_notebook.robot @@ -6,9 +6,19 @@ Library Process *** Test Cases *** IPython Notebook on nteract - Open nteract + Open nteract ${BROWSER} Launch a new nteract Notebook Add and Run nteract Code Cell print("hello world") Wait Until nteract Kernel Is Idle Current nteract Cell Output Should Contain hello world Capture Page Screenshot nteract${/}ipython.png + Save nteract Notebook + +IPython Notebook Outputs on nteract + Open nteract ${BROWSER} + Launch a new nteract Notebook + : FOR ${i} IN RANGE ${10} + \ Add and Run nteract Code Cell print("${i} hello world " * ${i ** 2}) + Wait Until nteract Kernel Is Idle + Screenshot Each Output of Active nteract Document nteract${/}ipython_outputs${/} + Save nteract Notebook diff --git a/ci/steps.common.yml b/ci/steps.common.yml index b1a3617d..f85b7127 100644 --- a/ci/steps.common.yml +++ b/ci/steps.common.yml @@ -9,13 +9,13 @@ steps: - ${{ if not(eq(parameters.name, 'Windows')) }}: - script: python setup.py sdist --dist-dir=_artifacts/sdist displayName: Build Source Distribution - - script: python -m pip install _artifacts/sdist/robotframework-jupyterlibrary-0.1.0.tar.gz --no-deps --ignore-installed + - script: python -m pip install _artifacts/sdist/robotframework-jupyterlibrary-0.2.0.tar.gz --no-deps --ignore-installed displayName: Install Source Distribution - ${{ if eq(parameters.name, 'Windows') }}: - script: python setup.py sdist --dist-dir=_artifacts\sdist displayName: Build Source Distribution - - script: python -m pip install _artifacts\sdist\robotframework-jupyterlibrary-0.1.0.tar.gz --no-deps --ignore-installed + - script: python -m pip install _artifacts\sdist\robotframework-jupyterlibrary-0.2.0.tar.gz --no-deps --ignore-installed displayName: Install Source Distribution - script: python -m scripts.atest @@ -25,6 +25,10 @@ steps: - script: BROWSER=headlesschrome python -m scripts.atest displayName: Test on Chrome + - ${{ if eq(parameters.name, 'Windows') }}: + - script: set "BROWSER=headlesschrome" && python -m scripts.atest + displayName: Test on Chrome + - task: PublishTestResults@2 displayName: Publish Test Results inputs: diff --git a/ci/steps.conda.full.yml b/ci/steps.conda.full.yml index 1819f5c2..5e5af495 100644 --- a/ci/steps.conda.full.yml +++ b/ci/steps.conda.full.yml @@ -18,7 +18,7 @@ steps: python-chromedriver-binary python>=3.6,<3.7 robotframework-lint - robotframework-seleniumlibrary>=3.2 + robotframework-seleniumlibrary>=3.3 robotframework>=3.1 sphinx_rtd_theme - script: pip install nteract_on_jupyter --no-deps --ignore-installed -vv diff --git a/ci/steps.conda.test.yml b/ci/steps.conda.test.yml index 0cb013d4..63f3800e 100644 --- a/ci/steps.conda.test.yml +++ b/ci/steps.conda.test.yml @@ -1,17 +1,33 @@ steps: - - task: CondaEnvironment@1 - inputs: - createOptions: -c conda-forge -c defaults - createCustomEnvironment: true - updateConda: false - environmentName: robotframework-jupyterlibrary-test - packageSpecs: > - geckodriver - jupyterlab>=0.35 - pillow - python-chromedriver-binary - python>=3.6,<3.7 - robotframework-seleniumlibrary>=3.2 - robotframework>=3.1 + - ${{ if not(eq(parameters.name, 'Windows')) }}: + - task: CondaEnvironment@1 + inputs: + createOptions: -c conda-forge -c defaults + createCustomEnvironment: true + updateConda: false + environmentName: robotframework-jupyterlibrary-test + packageSpecs: > + geckodriver + jupyterlab>=0.35 + pillow + python-chromedriver-binary + python>=3.6,<3.7 + robotframework-seleniumlibrary>=3.3 + robotframework>=3.1 + - ${{ if eq(parameters.name, 'Windows') }}: + - task: CondaEnvironment@1 + inputs: + createOptions: -c conda-forge -c defaults + createCustomEnvironment: true + updateConda: false + environmentName: robotframework-jupyterlibrary-test + packageSpecs: > + geckodriver + jupyterlab>=0.35 + pillow + python-chromedriver-binary==2.42 + python>=3.6,<3.7 + robotframework-seleniumlibrary>=3.3 + robotframework>=3.1 - script: pip install nteract_on_jupyter --no-deps --ignore-installed -vv displayName: Pip dependencies diff --git a/docs/CI.ipynb b/docs/CI.ipynb new file mode 100644 index 00000000..83578a86 --- /dev/null +++ b/docs/CI.ipynb @@ -0,0 +1,93 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# CI\n", + "At first, you'll want to write your tests locally, and test them against as many local browsers as possible. However, to really test out your features, you'll want to:\n", + "\n", + "- run them against as many real browsers on other operating systems as possible\n", + "- have easy access to human- and machine-readable test results and build assets\n", + "- integration with development tools like GitHub\n", + "\n", + "Enter Continuous Integration (CI). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cloud: Multi-Provider\n", + "Historically, Jupyter projects have used a mix of free-as-in-beer-for-open source hosted services:\n", + "- [Appveyor](https://www.appveyor.com) for Windows\n", + "- [Circle-CI](https://circleci.com) for Linux\n", + "- [TravisCI](https://travis-ci.org) for Linux and MacOS\n", + "\n", + "Each brings their own syntax, features, and constraints to building and maintaining robust CI workflows.\n", + "\n", + "> `JupyterLibrary` started on Travis-CI, but as soon as we wanted to support more platforms and browsers..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cloud: Azure Pipelines\n", + "At the risk of putting all your eggs in one (proprietary) basket, [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/) provides a single-file approach to automating all of your tests against reasonably modern versions of browsers. \n", + "\n", + "> `JupyterLibrary` is itself built on Azure, and looking at the [pipeline][] and various [jobs and steps][] used can provide the best patterns we have found.\n", + "\n", + "[pipeline]: https://github.com/bollwyvl/robotframework-jupyterlibrary/blob/master/azure-pipelines.yml\n", + "[jobs and steps]: https://github.com/bollwyvl/robotframework-jupyterlibrary/tree/master/ci" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## On-Premises: Jenkins\n", + "If you are working on in-house projects, and/or have the ability to support it, [Jenkins](https://jenkins.io) is the gold standard for self-hosted continuous integration. It has almost limitless configurability, and commercial support is available." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Approach: It's Just Scripts\n", + "No matter how shiny or magical your continuous integration tools appear the long-term well-being of your repo depends on techniques that are: \n", + "- simple\n", + "- cross-platform\n", + "- frequently run outside of your CI\n", + "\n", + "Since this is Jupyter, this boils down to putting as much as possible into platform-independent python (and, when neccessary, nodejs) code. \n", + "\n", + "> `JupyterLibrary` uses a small collection of [scripts][], not shipped as part of the distribution, which handle the pipeline. In addition, this library uses [anaconda-project][] to manage multiple environment versions, and to combine multiple script invocations with different parameters into small, easy-to-remember (and complete) commands. Unfortunately, some of these approaches don't _quite_ work in Azure Pipelines, so some duplication of commands and dependencies are present.\n", + "\n", + "[scripts]: https://github.com/bollwyvl/robotframework-jupyterlibrary/tree/master/scripts\n", + "[anaconda-project]: https://github.com/bollwyvl/robotframework-jupyterlibrary/blob/master/anaconda-project.yml" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/HISTORY.ipynb b/docs/HISTORY.ipynb new file mode 100644 index 00000000..91cac906 --- /dev/null +++ b/docs/HISTORY.ipynb @@ -0,0 +1,52 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# History" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 0.2.0\n", + "- Require SeleniumLibrary 3.3.0 and remove backport of `Press Keys`\n", + "- `Start New Jupyter Server` now has a default `command` of `jupyter-notebook` (instead of `jupyter`)\n", + "- `Build Jupyter Server Arguments` no longer returns `notebook` as the first argument\n", + "- Fix homepage URL for PyPI\n", + "- Test on Chrome/Windows" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 0.1.0\n", + "- Initial Release" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/LIMITS.ipynb b/docs/LIMITS.ipynb new file mode 100644 index 00000000..81cec7f8 --- /dev/null +++ b/docs/LIMITS.ipynb @@ -0,0 +1,51 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Limits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `Press Keys` on MacOS/Chrome\n", + "While SeleniumLibrary 3.3.0 added `Press Keys` which can target non-inputs, as of `chromedriver` version `2.45` the key cannot be used. As this is the favored key for shortcuts, this means almost all of the client keyboard shortcuts just won't work if you are trying to test on MacOS.\n", + "\n", + "> **Workaround**\n", + ">\n", + "> _If you are trying to `Press Keys` where the key would be used, try to find a combination of simpler key combinations and mouse clicks._" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/conf.py b/docs/conf.py index 07ae210c..715cf418 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,6 +34,7 @@ def setup(app): tdp = Path(td) agg = "" for sub in client.rglob("*.robot"): + print(f"collecting {sub.relative_to(client)}") agg += sub.read_text() out_file = Path(tdp / f"{client.name}.robot") out_file.write_text(agg) diff --git a/docs/index.ipynb b/docs/index.ipynb index 43b092b4..6c0da267 100644 --- a/docs/index.ipynb +++ b/docs/index.ipynb @@ -21,7 +21,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Write `.robot` files that use `JupyterLibrary` keywords.\n", + "[Install](./INSTALL.ipynb) the `robotframework-jupyterlibrary` package. Write plain-language `.robot` files that use `JupyterLibrary` [keywords](./KEYWORDS.ipynb).\n", "\n", "```robotframework\n", "*** Settings ***\n", @@ -37,7 +37,9 @@ " Add and Run JupyterLab Code Cell\n", " Wait Until JupyterLab Kernel Is Idle\n", " Capture Page Screenshot\n", - "```" + "```\n", + "\n", + "Run them with `python -m robot mytests`. Get pretty reports with embedded screenshots. Deploy your tests on [CI](./CI.ipynb). Write new Jupyter features **more confidently** and with **better documentation**!" ] }, { @@ -51,7 +53,10 @@ "# MORE\n", "- [WHY](WHY.ipynb)\n", "- [INSTALL](INSTALL.ipynb)\n", - "- [KEYWORDS](KEYWORDS.ipynb)" + "- [KEYWORDS](KEYWORDS.ipynb)\n", + "- [CI](CI.ipynb)\n", + "- [LIMITS](LIMITS.ipynb)\n", + "- [HISTORY](HISTORY.ipynb)" ] } ], diff --git a/environment.yml b/environment.yml index 11e26fde..db8a797b 100644 --- a/environment.yml +++ b/environment.yml @@ -17,9 +17,10 @@ dependencies: - python-chromedriver-binary - robotframework >=3.1 - robotframework-lint - - robotframework-seleniumlibrary >=3.2 + - robotframework-seleniumlibrary >=3.3 - six - sphinx - sphinx_rtd_theme + - sphinx-autobuild - pip: - nteract_on_jupyter diff --git a/scripts/__init__.py b/scripts/__init__.py index 758f1cf1..cf114a04 100644 --- a/scripts/__init__.py +++ b/scripts/__init__.py @@ -3,9 +3,10 @@ from os.path import abspath, dirname, join HERE = dirname(__file__) -TESTS = abspath(join(HERE, "..", "atest", "acceptance")) +ROOT = abspath(join(HERE, "..")) +TESTS = abspath(join(ROOT, "atest", "acceptance")) -TEST_OUT = abspath(join(HERE, "..", "_artifacts", "test_output")) +TEST_OUT = abspath(join(ROOT, "_artifacts", "test_output")) PLATFORM = platform.system().lower() BROWSER = os.environ.get("BROWSER", "headlessfirefox") diff --git a/scripts/atest.py b/scripts/atest.py index 9f429b54..a776aeae 100644 --- a/scripts/atest.py +++ b/scripts/atest.py @@ -5,7 +5,7 @@ # import for PATH side-effect. yuck. import chromedriver_binary # noqa -from . import BROWSER, HERE, PLATFORM, TEST_OUT, TESTS +from . import BROWSER, ROOT, PLATFORM, TESTS, TEST_OUT def run_tests(robot_args): @@ -17,13 +17,13 @@ def run_tests(robot_args): "-d", TEST_OUT, "--log", - join(TEST_OUT, join(".".join([PLATFORM, BROWSER, "log", "html"]))), + join(".".join([PLATFORM, BROWSER, "log", "html"])), "--name", "{} on {}".format(BROWSER, PLATFORM), "--output", - join(TEST_OUT, join(".".join([PLATFORM, BROWSER, "robot", "xml"]))), + join(".".join([PLATFORM, BROWSER, "robot", "xml"])), "--report", - join(TEST_OUT, join(".".join([PLATFORM, BROWSER, "report", "html"]))), + join(".".join([PLATFORM, BROWSER, "report", "html"])), "--variable", "BROWSER:" + BROWSER, "--variable", @@ -36,7 +36,7 @@ def run_tests(robot_args): ) print(" ".join(args)) - proc = subprocess.Popen(args, cwd=HERE) + proc = subprocess.Popen(args, cwd=ROOT) try: return proc.wait() diff --git a/setup.cfg b/setup.cfg index da9f85a6..8659378a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ name = robotframework-jupyterlibrary description = A Robot Framework library for automating (testing of) Jupyter end-user applications and extensions long_description = file: README.md long_description_content_type = text/markdown -url = https://github.com/robots-from-jupyter/robotframework-jupyterlibrary +url = https://github.com/bollwyvl/robotframework-jupyterlibrary author = Nicholas Bollweg author_email = nick.bollweg@gmail.com license = BSD-3-Clause @@ -29,7 +29,7 @@ classifiers = [options] install_requires = robotframework >=3.1 - robotframework-seleniumlibrary >=3.2 + robotframework-seleniumlibrary >=3.3 pillow six package_dir = diff --git a/src/JupyterLibrary/_version.py b/src/JupyterLibrary/_version.py index 3dc1f76b..d3ec452c 100644 --- a/src/JupyterLibrary/_version.py +++ b/src/JupyterLibrary/_version.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.2.0" diff --git a/src/JupyterLibrary/clients/jupyterlab/Commands.robot b/src/JupyterLibrary/clients/jupyterlab/Commands.robot index b97c9e0a..e3f89e59 100644 --- a/src/JupyterLibrary/clients/jupyterlab/Commands.robot +++ b/src/JupyterLibrary/clients/jupyterlab/Commands.robot @@ -1,7 +1,6 @@ *** Settings *** Resource JupyterLibrary/clients/jupyterlab/Selectors.robot - *** Keywords *** Execute JupyterLab Command [Arguments] ${command} ${accept}=${True} ${close}=${True} diff --git a/src/JupyterLibrary/clients/jupyterlab/Notebook.robot b/src/JupyterLibrary/clients/jupyterlab/Notebook.robot index 0cf8d6cc..0e29c236 100644 --- a/src/JupyterLibrary/clients/jupyterlab/Notebook.robot +++ b/src/JupyterLibrary/clients/jupyterlab/Notebook.robot @@ -2,7 +2,6 @@ Resource JupyterLibrary/clients/jupyterlab/Selectors.robot Resource JupyterLibrary/common/CodeMirror.robot - *** Keywords *** Add and Run JupyterLab Code Cell [Arguments] ${code}=print("hello world") @@ -12,10 +11,17 @@ Add and Run JupyterLab Code Cell ${cell} = Get WebElement css:${JLAB CSS ACTIVE INPUT} Click Element ${cell} Set CodeMirror Value ${JLAB CSS ACTIVE INPUT} ${code} - Click Element css:${JLAB CSS ICON RUN} + Run Current JupyterLab Code Cell Click Element ${cell} Wait Until JupyterLab Kernel Is Idle [Documentation] Wait for a kernel to be busy, and then stop being busy Wait Until Page Does Not Contain Element ${JLAB CSS BUSY KERNEL} Wait Until Page Does Not Contain ${JLAB TEXT BUSY PROMPT} + +Save JupyterLab Notebook + Execute JupyterLab Command Save Notebook + +Run Current JupyterLab Code Cell + Click Element css:${JLAB CSS ICON RUN} + Sleep 0.5s diff --git a/src/JupyterLibrary/clients/jupyterlab/Output.robot b/src/JupyterLibrary/clients/jupyterlab/Output.robot index d6d1e39e..455796dc 100644 --- a/src/JupyterLibrary/clients/jupyterlab/Output.robot +++ b/src/JupyterLibrary/clients/jupyterlab/Output.robot @@ -4,9 +4,9 @@ Resource JupyterLibrary/clients/jupyterlab/Selectors.robot *** Keywords *** Current JupyterLab Cell Output Should Contain [Arguments] ${expected} + Wait Until Page Contains Element css:${JLAB CSS ACTIVE OUTPUT CHILDREN} Element Should Contain css:${JLAB CSS ACTIVE OUTPUT CHILDREN} ${expected} - Screenshot Each Output of Active JupyterLab Cell [Arguments] ${prefix} [Documentation] Capture all of the outputs of the current Cell as screenshots diff --git a/src/JupyterLibrary/clients/jupyterlab/Shell.robot b/src/JupyterLibrary/clients/jupyterlab/Shell.robot index dbd3e214..a309a213 100644 --- a/src/JupyterLibrary/clients/jupyterlab/Shell.robot +++ b/src/JupyterLibrary/clients/jupyterlab/Shell.robot @@ -4,7 +4,6 @@ Documentation Keywords for working with the JupyterLab web application ... You should have already started a Jupyter Server, such as with ... *Wait For New Jupyter Server To Be Ready*. - *** Keywords *** Open JupyterLab [Arguments] ${browser}=headlessfirefox ${nbserver}=${None} ${url}=${EMPTY} &{configuration} diff --git a/src/JupyterLibrary/clients/jupyterlab/Sidebar.robot b/src/JupyterLibrary/clients/jupyterlab/Sidebar.robot index 204110d3..c7734df4 100644 --- a/src/JupyterLibrary/clients/jupyterlab/Sidebar.robot +++ b/src/JupyterLibrary/clients/jupyterlab/Sidebar.robot @@ -1,7 +1,6 @@ *** Settings *** Resource JupyterLibrary/clients/jupyterlab/Selectors.robot - *** Keywords *** Maybe Close JupyterLab Sidebar [Documentation] Attempt to close the JupyterLab sidebar diff --git a/src/JupyterLibrary/clients/notebook/Commands.robot b/src/JupyterLibrary/clients/notebook/Commands.robot index b10c7322..9ec0d953 100644 --- a/src/JupyterLibrary/clients/notebook/Commands.robot +++ b/src/JupyterLibrary/clients/notebook/Commands.robot @@ -1,13 +1,12 @@ *** Settings *** Resource JupyterLibrary/clients/notebook/Selectors.robot - *** Keywords *** Execute Notebook Classic Command [Arguments] ${command} ${accept}=${True} ${close}=${True} [Documentation] Use the Notebook Classic Command Pop-up ... to run a command and ``accept`` any resulting dialogs, then ``close`` ... the Command Palette. - Press Keys None CTRL+SHIFT+p + Click Element css:${JNC CSS CMD BUTTON} Input Text css:${JNC CSS CMD INPUT} ${command} Click Element css:${JNC CSS CMD ITEM} diff --git a/src/JupyterLibrary/clients/notebook/Notebook.robot b/src/JupyterLibrary/clients/notebook/Notebook.robot index ba9c6c2b..3a8f77da 100644 --- a/src/JupyterLibrary/clients/notebook/Notebook.robot +++ b/src/JupyterLibrary/clients/notebook/Notebook.robot @@ -11,10 +11,13 @@ Add and Run Notebook Classic Code Cell ${cell} = Get WebElement css:${JNC CSS ACTIVE INPUT} Click Element ${cell} Set CodeMirror Value ${JNC CSS ACTIVE INPUT} ${code} - Click Element css:${JNC CSS NB TOOLBAR} ${JNC CSS ICON RUN} + Run Current Notebook Classic Code Cell Click Element ${cell} Wait Until Notebook Classic Kernel Is Idle [Documentation] Wait for a kernel to be busy, and then stop being busy Wait Until Page Does Not Contain Element ${JNC CSS NB KERNEL BUSY} Wait Until Page Does Not Contain ${JNC TEXT BUSY PROMPT} + +Run Current Notebook Classic Code Cell + Click Element css:${JNC CSS NB TOOLBAR} ${JNC CSS ICON RUN} diff --git a/src/JupyterLibrary/clients/notebook/Output.robot b/src/JupyterLibrary/clients/notebook/Output.robot index 5cc77daa..77d179b5 100644 --- a/src/JupyterLibrary/clients/notebook/Output.robot +++ b/src/JupyterLibrary/clients/notebook/Output.robot @@ -3,5 +3,34 @@ Resource JupyterLibrary/clients/nteract_on_jupyter/Selectors.robot *** Keywords *** Current Notebook Classic Cell Output Should Contain - [Arguments] ${expected} - Element Should Contain css:${JNC CSS ACTIVE OUTPUT} ${expected} + [Arguments] ${expected} + Wait until Element Contains css:${JNC CSS ACTIVE OUTPUT} ${expected} + +Screenshot Each Output of Active Notebook Classic Cell + [Arguments] ${prefix} + [Documentation] Capture all of the outputs of the current Cell as screenshots + ... with a ``prefix``. + ${outputs} = Get WebElements css:${JNC CSS ACTIVE OUTPUT SUBAREAS} + :FOR ${i} IN RANGE ${outputs.__len__()} + \ Capture Element Screenshot ${outputs[${i}]} ${prefix}_output_${i}.png + +Screenshot Markdown of Active Notebook Classic Cell + [Arguments] ${prefix} + [Documentation] Capture all of the rendered Markdown of the current Document as screenshots + ... with a ``prefix``. + ${inputs} = Get WebElements css:${JNC CSS ACTIVE MARKDOWN} + :FOR ${i} IN RANGE ${inputs.__len__()} + \ Capture Element Screenshot ${inputs[${i}]} ${prefix}_markdown_${i}.png + +Screenshot Each Output of Active Notebook Classic Document + [Arguments] ${prefix} + [Documentation] Capture all of the outputs of the current **Notebook** as + ... screenshots with a ``prefix``. + ${cells} = Get WebElements css:${JNC CSS CELL} + :FOR ${i} IN RANGE ${cells.__len__()} + \ Click element ${cells[${i}]} + \ Run Keyword And Ignore Error Click element ${cells[${i + 1}]} + \ Click element ${cells[${i}]} + \ Sleep 0.1s + \ Screenshot Each Output of Active Notebook Classic Cell ${prefix}_cell_${i} + \ Screenshot Markdown of Active Notebook Classic Cell ${prefix}_cell_${i} diff --git a/src/JupyterLibrary/clients/notebook/Selectors.robot b/src/JupyterLibrary/clients/notebook/Selectors.robot index 4a89cc36..b449e121 100644 --- a/src/JupyterLibrary/clients/notebook/Selectors.robot +++ b/src/JupyterLibrary/clients/notebook/Selectors.robot @@ -8,6 +8,7 @@ ${JNC CSS NB KERNEL IDLE} .kernel_idle_icon ${JNC CSS NB KERNEL BUSY} .kernel_budy_icon ${JNC TEXT BUSY PROMPT} In [*]: +${JNC CSS CMD BUTTON} button[data-jupyter-action="jupyter-notebook:show-command-palette"] ${JNC CSS CMD PALETTE} .modal.cmd-palette.in ${JNC CSS CMD INPUT} ${JNC CSS CMD PALETTE} input[type="search"] ${JNC CSS CMD ITEM} ${JNC CSS CMD PALETTE} .typeahead-result li > a @@ -21,3 +22,5 @@ ${JNC CSS CELL} \#notebook-container .cell ${JNC CSS ACTIVE CELL} ${JNC CSS CELL}.selected ${JNC CSS ACTIVE INPUT} ${JNC CSS ACTIVE CELL} .CodeMirror ${JNC CSS ACTIVE OUTPUT} ${JNC CSS ACTIVE CELL} .output_wrapper +${JNC CSS ACTIVE OUTPUT SUBAREAS} ${JNC CSS ACTIVE OUTPUT} .output_subarea +${JNC CSS ACTIVE MARKDOWN} ${JNC CSS ACTIVE CELL} .text_cell_render diff --git a/src/JupyterLibrary/clients/notebook/Tree.robot b/src/JupyterLibrary/clients/notebook/Tree.robot index 70402d2d..c1b735e2 100644 --- a/src/JupyterLibrary/clients/notebook/Tree.robot +++ b/src/JupyterLibrary/clients/notebook/Tree.robot @@ -4,7 +4,6 @@ Documentation Keywords for working with the Jupyter Notebook Clasic web applic ... You should have already started a Jupyter Server, such as with ... *Wait For New Jupyter Server To Be Ready*. - *** Keywords *** Open Notebook Classic [Arguments] ${browser}=headlessfirefox ${nbserver}=${None} ${url}=${EMPTY} &{configuration} diff --git a/src/JupyterLibrary/clients/nteract_on_jupyter/Notebook.robot b/src/JupyterLibrary/clients/nteract_on_jupyter/Notebook.robot index ae48128b..82fe3916 100644 --- a/src/JupyterLibrary/clients/nteract_on_jupyter/Notebook.robot +++ b/src/JupyterLibrary/clients/nteract_on_jupyter/Notebook.robot @@ -7,10 +7,16 @@ Add and Run nteract Code Cell [Arguments] ${code}=print("hello world") [Documentation] Add a ``code`` cell to the currently active notebook and run it. ${creators} = Get WebElements css:${NOJ CSS CREATOR} - Mouse Over ${creators[-1]} + Hide nteract Status Bar + Scroll To End Of Page + Scroll Element Into View ${creators[-1]} + Click Element ${creators[-1]} Click Element css:${NOJ CSS CREATOR}:hover ${NOJ CSS ADD CODE CELL} ${cells} = Get WebElements css:${NOJ CSS CELL INPUT} + Scroll To End Of Page + Mouse Over ${cells[-1]} Click Element ${cells[-1]} + Show nteract Status Bar Set CodeMirror Value ${NOJ CSS ACTIVE CELL INPUT} ${code} Run Current nteract Code Cell @@ -21,3 +27,47 @@ Run Current nteract Code Cell Wait Until nteract Kernel Is Idle [Documentation] Wait for a kernel to be busy, and then stop being busy Wait Until Page Does Not Contain ${NOJ TEXT BUSY PROMPT} + +Hide nteract Status Bar + [Arguments] ${pointerevents}= + Execute JavaScript + ... document.querySelector("${NOJ CSS STATUS BAR}").style.display = "none" + +Show nteract Status Bar + [Arguments] ${pointerevents}= + Execute JavaScript + ... document.querySelector("${NOJ CSS STATUS BAR}").style.display = null + +Scroll To End Of Page + Execute JavaScript + ... document.querySelector("body").scrollTop = 999999 + +Scroll To Top Of Page + Execute JavaScript + ... document.querySelector("body").scrollTop = 0 + +Click nteract Menu + [Arguments] ${label} + [Documentation] Click a top-level nteract menu bar item with by ``label``, + ... e.g. File, Help, etc. + Scroll To Top Of Page + ${xpath} = Set Variable ${NOJ XP MENU}\[@title = '${label}'] + Wait Until Page Contains Element ${xpath} + Mouse Over ${xpath} + +Click nteract Menu Item + [Arguments] ${label} + [Documentation] Click a currently-visible nteract menu item by ``label``. + ${item} = Set Variable ${NOJ XP MENU ITEM}\[contains(text(), '${label}')] + Wait Until Page Contains Element ${item} + Click Element ${item} + +Open With nteract Menu + [Arguments] ${menu} @{submenus} + [Documentation] Click into a ``menu``, then a series of ``submenus`` + Click nteract Menu ${menu} + :FOR ${submenu} IN @{submenus} + \ Click nteract Menu Item ${submenu} + +Save nteract Notebook + Open With nteract Menu File Save diff --git a/src/JupyterLibrary/clients/nteract_on_jupyter/Output.robot b/src/JupyterLibrary/clients/nteract_on_jupyter/Output.robot index f7f3071c..7910cb59 100644 --- a/src/JupyterLibrary/clients/nteract_on_jupyter/Output.robot +++ b/src/JupyterLibrary/clients/nteract_on_jupyter/Output.robot @@ -5,3 +5,32 @@ Resource JupyterLibrary/clients/nteract_on_jupyter/Selectors.robot Current nteract Cell Output Should Contain [Arguments] ${expected} Element Should Contain css:${NOJ CSS ACTIVE CELL OUTPUTS} ${expected} + +Screenshot Each Output of Active nteract Cell + [Arguments] ${prefix} + [Documentation] Capture all of the outputs of the current Cell as screenshots + ... with a ``prefix``. + ${outputs} = Get WebElements css:${NOJ CSS ACTIVE CELL OUTPUTS} > * + :FOR ${i} IN RANGE ${outputs.__len__()} + \ Capture Element Screenshot ${outputs[${i}]} ${prefix}_output_${i}.png + +Screenshot Markdown of Active nteract Cell + [Arguments] ${prefix} + [Documentation] Capture all of the rendered Markdown of the current Document as screenshots + ... with a ``prefix``. + ${inputs} = Get WebElements css:${NOJ CSS ACTIVE CELL MARKDOWN} + :FOR ${i} IN RANGE ${inputs.__len__()} + \ Capture Element Screenshot ${inputs[${i}]} ${prefix}_markdown_${i}.png + +Screenshot Each Output of Active nteract Document + [Arguments] ${prefix} + [Documentation] Capture all of the outputs of the current **Document** as + ... screenshots with a ``prefix``. + ${cells} = Get WebElements css:${NOJ CSS CELL} + :FOR ${i} IN RANGE ${cells.__len__()} + \ Click element ${cells[${i}]} + \ Run Keyword And Ignore Error Click element ${cells[${i + 1}]} + \ Click element ${cells[${i}]} + \ Sleep 0.1s + \ Screenshot Each Output of Active nteract Cell ${prefix}_cell_${i} + \ Screenshot Markdown of Active nteract Cell ${prefix}_cell_${i} diff --git a/src/JupyterLibrary/clients/nteract_on_jupyter/Selectors.robot b/src/JupyterLibrary/clients/nteract_on_jupyter/Selectors.robot index ce22b964..e02259fa 100644 --- a/src/JupyterLibrary/clients/nteract_on_jupyter/Selectors.robot +++ b/src/JupyterLibrary/clients/nteract_on_jupyter/Selectors.robot @@ -2,8 +2,10 @@ ${NOJ CSS TREE LIST} .listing-root ${NOJ CSS CARD} .new-notebook ${NOJ CSS CARD KERNEL} ${NOJ CSS CARD} .display-name-long +${NOJ CSS CELLS} .cells ${NOJ CSS CELL} .cell ${NOJ CSS CELL INPUT} .cell .CodeMirror .CodeMirror +${NOJ CSS ACTIVE CELL} .focused.cell ${NOJ CSS ACTIVE CELL INPUT} .focused${NOJ CSS CELL INPUT} ${NOJ CSS ACTIVE CELL OUTPUTS} .cell.focused .outputs ${NOJ CSS CREATOR} .creator-hover-region @@ -11,3 +13,8 @@ ${NOJ CSS ADD CODE CELL} .add-code-cell ${NOJ CSS CELL TOOLBAR} .cell .cell-toolbar ${NOJ CSS EXECUTE} .focused${NOJ CSS CELL TOOLBAR} .executeButton ${NOJ TEXT BUSY PROMPT} [*] +${NOJ CSS ACTIVE CELL MARKDOWN} ${NOJ CSS ACTIVE CELL} > div > .outputs +${NOJ CSS STATUS BAR} \#root > .status-bar + +${NOJ XP MENU} //div[contains(@class, 'rc-menu-submenu-title')] +${NOJ XP MENU ITEM} //li[contains(@class, 'rc-menu-item')] diff --git a/src/JupyterLibrary/clients/nteract_on_jupyter/Tree.robot b/src/JupyterLibrary/clients/nteract_on_jupyter/Tree.robot index 2a1aa2c2..6ad4c12d 100644 --- a/src/JupyterLibrary/clients/nteract_on_jupyter/Tree.robot +++ b/src/JupyterLibrary/clients/nteract_on_jupyter/Tree.robot @@ -4,7 +4,6 @@ Documentation Keywords for working with nteract web application ... You should have already started a Jupyter Server, such as with ... *Wait For New Jupyter Server To Be Ready*. - *** Keywords *** Open nteract [Arguments] ${browser}=headlessfirefox ${nbserver}=${None} ${url}=${EMPTY} &{configuration} diff --git a/src/JupyterLibrary/core.py b/src/JupyterLibrary/core.py index 23d677be..94212c63 100644 --- a/src/JupyterLibrary/core.py +++ b/src/JupyterLibrary/core.py @@ -7,7 +7,6 @@ from robot.libraries.BuiltIn import BuiltIn from SeleniumLibrary import SeleniumLibrary -from SeleniumLibrary.keywords.element import ElementKeywords from SeleniumLibrary.utils.librarylistener import LibraryListener from .keywords import screenshots, server @@ -21,11 +20,6 @@ component_classes = [server.ServerKeywords, screenshots.ScreenshotKeywords] -if not hasattr(ElementKeywords, "press_keys"): - from .keywords import keys - - component_classes += [keys.KeysKeywords] - class JupyterLibrary(SeleniumLibrary): """JupyterLibrary is a Jupyter testing library for Robot Framework.""" diff --git a/src/JupyterLibrary/keywords/keys.py b/src/JupyterLibrary/keywords/keys.py deleted file mode 100644 index d805cf78..00000000 --- a/src/JupyterLibrary/keywords/keys.py +++ /dev/null @@ -1,180 +0,0 @@ -""" Backport of SeleniumLibrary >3.2.0's `Press Keys` -""" -from robot.utils import plural_or_not - -# flake8: noqa -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.keys import Keys -from SeleniumLibrary.base import LibraryComponent, keyword -from SeleniumLibrary.utils import is_truthy - - -class KeysKeywords(LibraryComponent): - @keyword - def press_keys(self, locator=None, *keys): - """Simulates user pressing key(s) to an element or on the active browser. - If ``locator`` evaluates as false, see `Boolean arguments` for more - details, then the ``keys`` are sent to the currently active browser. - Otherwise element is searched and ``keys`` are send to the element - identified by the ``locator``. In later case, keyword fails if element - is not found. See the `Locating elements` section for details about - the locator syntax. - ``keys`` arguments can contain one or many strings, but it can not - be empty. ``keys`` can also be a combination of - [https://seleniumhq.github.io/selenium/docs/api/py/webdriver/selenium.webdriver.common.keys.html|Selenium Keys] - and strings or a single Selenium Key. If Selenium Key is combined - with strings, Selenium key and strings must be separated by the - `+` character, like in `CONTROL+c`. Selenium Keys - are space and case sensitive and Selenium Keys are not parsed - inside of the string. Example AALTO, would send string `AALTO` - and `ALT` not parsed inside of the string. But `A+ALT+O` would - found Selenium ALT key from the ``keys`` argument. It also possible - to press many Selenium Keys down at the same time, example - 'ALT+ARROW_DOWN`. - If Selenium Keys are detected in the ``keys`` argument, keyword - will press the Selenium Key down, send the strings and - then release the Selenium Key. If keyword needs to send a Selenium - Key as a string, then each character must be separated with - `+` character, example `E+N+D`. - `CTRL` is alias for - [https://seleniumhq.github.io/selenium/docs/api/py/webdriver/selenium.webdriver.common.keys.html#selenium.webdriver.common.keys.Keys.CONTROL|Selenium CONTROL] - and ESC is alias for - [https://seleniumhq.github.io/selenium/docs/api/py/webdriver/selenium.webdriver.common.keys.html#selenium.webdriver.common.keys.Keys.ESCAPE|Selenium ESCAPE] - New in SeleniumLibrary 3.3 - Examples: - | `Press Keys` | text_field | AAAAA | | # Sends string "AAAAA" to element. | - | `Press Keys` | None | BBBBB | | # Sends string "BBBBB" to currently active browser. | - | `Press Keys` | text_field | E+N+D | | # Sends string "END" to element. | - | `Press Keys` | text_field | XXX | YY | # Sends strings "XXX" and "YY" to element. | - | `Press Keys` | text_field | XXX+YY | | # Same as above. | - | `Press Keys` | text_field | ALT+ARROW_DOWN | | # Pressing "ALT" key down, then pressing ARROW_DOWN and then releasing both keys. | - | `Press Keys` | text_field | ALT | ARROW_DOWN | # Pressing "ALT" key and then pressing ARROW_DOWN. | - | `Press Keys` | text_field | CTRL+c | | # Pressing CTRL key down, sends string "c" and then releases CTRL key. | - | `Press Keys` | button | RETURN | | # Pressing "ENTER" key to element. | - """ - parsed_keys = self._parse_keys(*keys) - if is_truthy(locator): - self.info("Sending key(s) %s to %s element." % (keys, locator)) - else: - self.info("Sending key(s) %s to page." % str(keys)) - self._press_keys(locator, parsed_keys) - - def _map_ascii_key_code_to_key(self, key_code): - map = { - 0: Keys.NULL, - 8: Keys.BACK_SPACE, - 9: Keys.TAB, - 10: Keys.RETURN, - 13: Keys.ENTER, - 24: Keys.CANCEL, - 27: Keys.ESCAPE, - 32: Keys.SPACE, - 42: Keys.MULTIPLY, - 43: Keys.ADD, - 44: Keys.SEPARATOR, - 45: Keys.SUBTRACT, - 56: Keys.DECIMAL, - 57: Keys.DIVIDE, - 59: Keys.SEMICOLON, - 61: Keys.EQUALS, - 127: Keys.DELETE, - } - key = map.get(key_code) - if key is None: - key = chr(key_code) - return key - - def _map_named_key_code_to_special_key(self, key_name): - try: - return getattr(Keys, key_name) - except AttributeError: - message = "Unknown key named '%s'." % (key_name) - self.debug(message) - raise ValueError(message) - - def _press_keys(self, locator, parsed_keys): - if is_truthy(locator): - element = self.find_element(locator) - else: - element = None - for parsed_key in parsed_keys: - actions = ActionChains(self.driver) - special_keys = [] - for key in parsed_key: - if self._selenium_keys_has_attr(key.original): - special_keys = self._press_keys_special_keys( - actions, element, parsed_key, key, special_keys - ) - else: - self._press_keys_normal_keys(actions, element, key) - for special_key in special_keys: - self.info("Releasing special key %s." % special_key.original) - actions.key_up(special_key.converted) - actions.perform() - - def _press_keys_normal_keys(self, actions, element, key): - self.info("Sending key%s %s" % (plural_or_not(key.converted), key.converted)) - if element: - actions.send_keys_to_element(element, key.converted) - else: - actions.send_keys(key.converted) - - def _press_keys_special_keys(self, actions, element, parsed_key, key, special_keys): - if len(parsed_key) == 1 and element: - self.info("Pressing special key %s to element." % key.original) - actions.send_keys_to_element(element, key.converted) - elif len(parsed_key) == 1 and not element: - self.info("Pressing special key %s to browser." % key.original) - actions.send_keys(key.converted) - else: - self.info("Pressing special key %s down." % key.original) - actions.key_down(key.converted) - special_keys.append(key) - return special_keys - - def _parse_keys(self, *keys): - if not keys: - raise AssertionError('"keys" argument can not be empty.') - list_keys = [] - for key in keys: - separate_keys = self._separate_key(key) - separate_keys = self._convert_special_keys(separate_keys) - list_keys.append(separate_keys) - return list_keys - - def _parse_aliases(self, key): - if key == "CTRL": - return "CONTROL" - if key == "ESC": - return "ESCAPE" - return key - - def _separate_key(self, key): - one_key = "" - list_keys = [] - for char in key: - if char == "+" and one_key != "": - list_keys.append(one_key) - one_key = "" - else: - one_key += char - if one_key: - list_keys.append(one_key) - return list_keys - - def _convert_special_keys(self, keys): - KeysRecord = namedtuple("KeysRecord", "converted, original") - converted_keys = [] - for key in keys: - key = self._parse_aliases(key) - if self._selenium_keys_has_attr(key): - converted_keys.append(KeysRecord(getattr(Keys, key), key)) - else: - converted_keys.append(KeysRecord(key, key)) - return converted_keys - - def _selenium_keys_has_attr(self, key): - try: - return hasattr(Keys, key) - except UnicodeError: # To support Python 2 and non ascii characters. - return False diff --git a/src/JupyterLibrary/keywords/screenshots.py b/src/JupyterLibrary/keywords/screenshots.py index a9f0546e..cf427870 100644 --- a/src/JupyterLibrary/keywords/screenshots.py +++ b/src/JupyterLibrary/keywords/screenshots.py @@ -6,10 +6,15 @@ class ScreenshotKeywords(LibraryComponent): @keyword def capture_element_screenshot(self, locator, filename): + """ This is an overload of SeleniumLibrary's implementation + which uses pillow on the backend to make this feature work across + multiple browsers. Sorta. + """ el = self.find_element(locator) bbox = self.normalize_bounding_box({**el.location, **el.size}) filename = BuiltIn().run_keyword("Capture Page Screenshot", filename) self.crop_image(filename, **bbox) + return filename def normalize_bounding_box(self, bbox): """ Normalize a bounding box to reasonable defaults diff --git a/src/JupyterLibrary/keywords/server.py b/src/JupyterLibrary/keywords/server.py index 4c014290..653e463c 100644 --- a/src/JupyterLibrary/keywords/server.py +++ b/src/JupyterLibrary/keywords/server.py @@ -38,7 +38,7 @@ def start_new_jupyter_server( directories will be cleaned up after the server process is terminated. """ - command = command or "jupyter" + command = command or "jupyter-notebook" port = port or self.get_unused_port() base_url = base_url or "/@rf/" token = str(uuid4()) if token is None else token @@ -82,7 +82,6 @@ def build_jupyter_server_arguments(self, port, base_url, token): """ Some default jupyter arguments """ return [ - "notebook", "--no-browser", "--debug", "--port={}".format(port),