{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Robots from Jupyter" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "Workshop on authoring Robot Framework test and task suites with JupyterLab" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "| Asko Soukka
University of Jyväskylä | Nicholas Bollweg
Project Jupyter |\n", "|:-----:|:----:|\n", "| | |" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Agenda" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "* Setting up **Robot Framework on Jupyter**\n", "* Introduction to notebooks" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "* Exercise: **Python notebook**\n", "* Exercise: **Robot notebook**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Agenda" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "* Exercise: **Selenium autocompletions**\n", "* Exercise: **Prototyping keywords**\n", "* Exercise: **Protoping libraries**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "* Importing notebooks\n", "* Sharing notebooks\n", "* Executing notebooks\n", "* Jupyter ecosystem" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Setting up Robot Framework on Jupyter" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## RobotLab installer\n", "\n", "https://github.com/robots-from-jupyter/robotlab/releases\n", "\n", "\n", "* Windows, MacOS, Linux\n", "* Desktop shortcut\n", " \n", "* Offline installable \n", "* Substantial download size\n", "\n", "**Ingredients:** Conda, JupyterLab, Robot[Mode|Kernel], Jupytext, Selenium[Library|Screenshots|Testability], OpenCV, RESTinstance, Firefox, GeckoDriver, starter notebooks..." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "
\n", "Don't register RobotLab as the default Python 3.6 interpreter.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Manual installation\n", "\n", "Install Miniconda, launch Anaconda Prompt, then:\n", "\n", "```bash\n", "$ conda install -c conda-forge nodejs jupyterlab ipywidgets\n", " \n", "$ pip install robotkernel\n", "\n", "$ jupyter labextension install \\\n", " jupyterlab_robotmode --no-build\n", " \n", "$ jupyter labextension install \\\n", " @jupyter-widgets/jupyterlab-manager\n", "```\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Complementing manual installation\n", "\n", "Launch Anaconda Prompt, then:\n", "\n", "```bash\n", "$ conda install -c conda-forge firefox geckodriver\n", " \n", "$ pip install robotframework-seleniumscreenshots \\\n", " RESTinstance jupytext jupyter-starters nbimporter\n", "\n", "$ jupyter labextension install \\\n", " jupyterlab-jupytext --no-build\n", " \n", "$ jupyter labextension install \\\n", " @deathbeds/jupyterlab-starters\n", "```\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Run JupyterLab manually\n", "\n", "From RobotLab with Windows Command Prompt:\n", "```bash\n", "$ c:\\robotlab\\Scripts\\activate.bat\n", "$ robotlab\n", "```\n", "\n", "From RobotLab with MacOS / Linux terminal:\n", "```bash\n", "$ source ~/robotlab/bin/activate\n", "$ robotlab\n", "```\n", "\n", "From manually installed Anaconda Prompt:\n", "```bash\n", "$ jupyter lab\n", "```\n", " \n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Introduction to JupyterLab\n", "\n", "```\n", "00 JupyterLab User Interface\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "
\n", "\n", "![](jupyterlab-01.png \"\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "
\n", "\n", "![](jupyterlab-02.png \"\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "
\n", "\n", "![](jupyterlab-03.png \"\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Introduction to notebooks\n", "\n", "```\n", "00 What is the Jupyter Notebook\n", "00 Keyboard Shortcuts\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "
\n", "\n", "![](shortcuts.png \"\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Exercise: Python notebook\n", "\n", "```\n", "01 Running Code\n", "02 Python XKCD\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Exercise: Python\n", "\n", "The JSON API for XKCD is described at\n", "\n", "https://xkcd.com/json.html\n", "\n", "Create a new Python notebook and implement function, which accepts an integer and returns XKCD image of the given number.\n", "```python\n", " def get_xkcd_by_num(num):\n", " return None\n", "```\n", "\n", "Write narrative documentation for that function in Markdown and executable Python example lines." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Recap\n", "\n", "* JupyterLab interface and contextual help\n", "* Loading old and creating new notebooks\n", "* Navigating around notebooks\n", "* Copying, cutting, pasting and deleting cells\n", "* Editing and executing notebook cells\n", "* Autocompleting statements with `TAB`\n", "* Iterating cell with `CTRL + ENTER` until ready\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Homework: magics\n", "\n", "https://ipython.readthedocs.io/en/stable/interactive/magics.html\n", "\n", "Magics are “magical” syntax for modifying the underlying Python environment supported mainly by Jupyter Python kernels.\n", "\n", "For example\n", "```bash\n", "!pip install requests\n", "```\n", " \n", "would install requests Python package into Python environment.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Exercise: Robot notebook\n", "\n", "```\n", "04 Robot XKCD\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Robot Framework" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "*** Settings ***\n", "\n", "Library Collections" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [], "source": [ "*** Variables ***\n", "\n", "@{series} 1 2 3 4" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [ { "data": { "text/html": [ "

Log | Report

" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "*** Test Cases ***\n", "\n", "Can append to list\n", " Append to list ${series} 0\n", " ${length}= Get length ${series}\n", " Should be equal \"${length}\" \"5\"" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Exercise: Robot\n", "\n", "Create a new Robot notebook and implement keyword\n", "\n", "```robotframework\n", "*** Keywords ***\n", " \n", "Get XKCD by num\n", " [Arguments] ${num}\n", "```\n", " \n", "which accepts an integer and `[Return]` image of the given number. Write narrative documentation for that keyword in Markdown and executable Robot example `*** Tasks ***`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Recap\n", "\n", "* Every Robot cell starts with a `*** [Heading] ***`\n", "* Combining suite from multiple different cells\n", "* Autocompleting Robot Framework structural words\n", "* Autocompleting Robot Framework keywords\n", "* Using JupyterLab contextual help for keyword documentation\n", "* Viewing and downloading logs and reports\n", "* Restarting kernel to reset RobotKernel state" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## RobotKernel quirks\n", "\n", "* Some completions can be suggested only after at least one cell with `*** Test Cases ***` or `*** Tasks ***` has been executed.\n", "\n", "* Cells without any robot `*** [Heading] ***` or content outside headings may be silently ignored.\n", "\n", "* Failing library import is silently ignored (but logged).\n", "\n", "* Keyword documentation is not updated without restart.\n", "\n", "Thumb of rule for weird or erratic behavior: **Restart the kernel.**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Exercise: Selenium autocompletions\n", "\n", "```\n", "05 Interactive Selenium\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Recap\n", "\n", "* Leaving a singleton test browser open while iterating\n", "* SeleniumLibrary locator prefixes with `TAB`-suggestions:
\n", " `id:`, `name:`, `link:`\n", "* SeleniumLibrary locator prefixes with `TAB`-completions:
\n", " `id:`, `name:`, `link:`, `tag:`, `xpath:`, `partial link:`\n", "* Interactive SeleniumLibrary picker with `css: + TAB`\n", "* Closing the test browser manually / with suite teardown\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Exercise: Prototyping keywords\n", "\n", "```\n", "07 Prototyping keywords\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Exercise: Prototyping libraries\n", "\n", "```\n", "09 Prototyping Libraries\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Importing Notebooks\n", "\n", "```\n", "06 Importing Notebooks\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Exercise: Multiple notebooks\n", "\n", "Parameterize `06 Importing Notebooks` with\n", "\n", "```robotframework\n", "*** Variables ***\n", " \n", "${DEPARTURE_DATE} ${EMPTY}\n", "${DEPARTURE_TIME} 17.00\n", "```\n", "\n", "Modify notebook task to use `${DEPARTURE_TIME}` and to prefer `${DEPARTURE_DATE}` when it is not empty. Customize or write new Python keywords when necessary. \n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Sharing and exporting notebooks\n", "\n", "Using JupyterLab\n", "```\n", "File → Export Notebook As… →\n", "```\n", "\n", "Using Jypyter nbconvert\n", "\n", "```shell\n", "$ jupyter nbconvert --to html MyNotebook.ipynb\n", "```\n", "\n", "How to customize `nbconvert` HTML-templates:\n", "https://nbconvert.readthedocs.io/en/latest/customizing.html\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Executing notebooks\n", "\n", "Executing notebook with Jupyter\n", "```shell\n", "jupyter nbconvert --to notebook --execute MyNotebook.ipynb\n", "```\n", "\n", "Executing notebook with RobotKernel\n", "```shell\n", "nbrobot MyNotebook.ipynb\n", "```\n", "\n", "Executing exported script with Robot Framework\n", "```shell\n", "jupyter nbconvert --to script MyNotebook.ipynb\n", "robot MyNotebook.robot\n", "```\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Jupyter ecosystem picks" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Stable\n", "\n", "* [RISE](https://rise.readthedocs.io/) – interactive reveal.js slides for Notebook Classic\n", "* Vim-keybindings for [Notebook Classic](https://github.com/lambdalisue/jupyter-vim-binding) and [JupyterLab](https://github.com/jwkvam/jupyterlab-vim)\n", "* [Widgets](https://ipywidgets.readthedocs.io/) – interactive widgets for live notebooks\n", "* [Jupytext](https://jupytext.readthedocs.io/) – sync between `.ipynb` and `.robot`\n", "\n", "## Emerging\n", "\n", "* [jupyterlab-starters](https://jupyterstarters.readthedocs.io/), [jupyterlab-commenting](https://github.com/jupyterlab/jupyterlab-commenting),
[jupyterlab-lsp](https://github.com/krassowski/jupyterlab-lsp), [jupyterlab-debugger](https://github.com/jupyterlab/debugger)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "1522eab75705469eaffe3ef259fdb1e3", "version_major": 2, "version_minor": 0 }, "text/plain": [ "IntSlider(value=0)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%python module WidgetDemo\n", "\n", "import ipywidgets as widgets\n", "w = widgets.IntSlider()\n", "display(w)\n", "\n", "class WidgetDemo:\n", " def get_value(self):\n", " return w.value" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "

Log | Report

" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "0\n" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "*** Settings ***\n", "\n", "Library WidgetDemo\n", "\n", "*** Test Cases ***\n", "\n", "Return widget value\n", " Get value" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Services\n", "\n", "* [NBViewer](https://nbviewer.jupyter.org/) – static previews for saved notebooks\n", "* [MyBinder](https://mybinder.org/) – reproducible live notebooks\n", "* [Google Colab](https://colab.research.google.com)\n", "* [Azure Notebooks](https://notebooks.azure.com/)\n", "\n", "## Other\n", "\n", "* [JupyterHub](https://jupyterhub.readthedocs.io/) – DIY private Jupyter cloud" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# [robots-from-jupyter.github.io](https://robots-from-jupyter.github.io)" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Robot Framework", "language": "robotframework", "name": "robotkernel" }, "language_info": { "codemirror_mode": "robotframework", "file_extension": ".robot", "mimetype": "text/plain", "name": "Robot Framework", "pygments_lexer": "robotframework" }, "rise": { "theme": "white" } }, "nbformat": 4, "nbformat_minor": 4 }