diff --git a/tests/print_random_number.py b/tests/print_random_number.py
new file mode 100644
index 0000000000000000000000000000000000000000..a105a4fb95f717678d2091beb3ac1512fdc87912
--- /dev/null
+++ b/tests/print_random_number.py
@@ -0,0 +1,3 @@
+print(203)
+with open("output/data.txt", "w") as handle:
+    handle.write(203)
diff --git a/tests/test_jupyter.ipynb b/tests/test_jupyter.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..e3faca2f32a017f780d0874e37f8c087a762775b
--- /dev/null
+++ b/tests/test_jupyter.ipynb
@@ -0,0 +1,331 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "id": "a5c6949c-da6a-40ab-aec7-de4534582ab2",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import nbformat as nbf\n",
+    "import sys\n",
+    "import os\n",
+    "from pathlib import Path\n",
+    "\n",
+    "import nbformat\n",
+    "import time\n",
+    "from nbconvert.preprocessors import ExecutePreprocessor\n",
+    "\n",
+    "from nbconvert.nbconvertapp import NbConvertApp\n",
+    "from ipylab import JupyterFrontEnd"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "id": "e4dd4791-5e5b-4fa7-89f1-8a9e574c300e",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "1\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(1)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "id": "e983e571-58f5-4310-8584-064fe4ac3570",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "2\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(2)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "id": "b28fb6d3-faa9-4788-86a1-4ec0d1026aa8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def check_execution_order(notebook_path,\n",
+    "                          check_all_executed=False,\n",
+    "                          check_all_except_last_executed=True,\n",
+    "                          check_top_to_bottom=False,\n",
+    "                          check_in_order=True,\n",
+    "                          exclude_last_cell=False):\n",
+    "    ntbk = nbf.read(notebook_path, nbf.NO_CONVERT)\n",
+    "\n",
+    "    # extract all code cells (disregard markdown, raw and others), then extract the execution order\n",
+    "    output_cells = [cell for cell in ntbk.cells if cell[\"cell_type\"] == \"code\"]\n",
+    "    # remove empty cells\n",
+    "    non_empty_cells = [cell for cell in output_cells if cell[\"source\"] != \"\"]\n",
+    "    execution_counts = [cell[\"execution_count\"] for cell in non_empty_cells]\n",
+    "\n",
+    "    def _all_none(item_list):\n",
+    "        return all([i is None for i in item_list])\n",
+    "\n",
+    "    # return early if no cells were executed\n",
+    "    if _all_none(execution_counts):\n",
+    "        return True\n",
+    "\n",
+    "    pass_check = [True]\n",
+    "\n",
+    "    def _check_all_executed(execution_counts: list) -> bool:\n",
+    "        \"\"\"Check all cells were executed.\n",
+    "\n",
+    "        Parameters\n",
+    "        ----------\n",
+    "        execution_counts : list\n",
+    "            execution_counts\n",
+    "\n",
+    "        Returns\n",
+    "        -------\n",
+    "        bool\n",
+    "        \"\"\"\n",
+    "        return not None in execution_counts\n",
+    "\n",
+    "    def _check_in_order(execution_counts: list) -> bool:\n",
+    "        \"\"\"Check that execution counts that aren't None go from 1 to N.\n",
+    "\n",
+    "        Parameters\n",
+    "        ----------\n",
+    "        execution_counts : list\n",
+    "            execution counts\n",
+    "\n",
+    "        Returns\n",
+    "        -------\n",
+    "        bool\n",
+    "        \"\"\"\n",
+    "        print(execution_counts)\n",
+    "        execution_counts = [x for x in execution_counts if x is not None]\n",
+    "        count_range = len(execution_counts)\n",
+    "        if exclude_last_cell:\n",
+    "            count_range = count_range - 1\n",
+    "        is_in_order = all([execution_counts[i] < execution_counts[i + 1] for i in range(count_range - 1)])\n",
+    "        return is_in_order\n",
+    "\n",
+    "    if check_in_order:\n",
+    "        pass_check.append(_check_in_order(execution_counts))\n",
+    "\n",
+    "    if check_all_executed:\n",
+    "        pass_check.append(_check_all_executed(execution_counts))\n",
+    "    \n",
+    "    if check_all_except_last_executed:\n",
+    "        pass_check.append(_check_all_executed(execution_counts[:-1]))\n",
+    "\n",
+    "    if check_top_to_bottom:\n",
+    "        pass_check.append(\n",
+    "            _check_all_executed(execution_counts) and _check_in_order(execution_counts)\n",
+    "        )\n",
+    "    return all(pass_check)\n",
+    "\n",
+    "\n",
+    "def save_ipynb():\n",
+    "    app = JupyterFrontEnd()\n",
+    "    print(\"Saving\", end=\"\")\n",
+    "    # note: docmanager:save doesn't lock the python thread until saving is completed.\n",
+    "    # Sometimes, new changes aren't completely saved before checks are performed.\n",
+    "    # Waiting for 0.1 seconds seems to prevent that.\n",
+    "    app.commands.execute('docmanager:save')\n",
+    "    time.sleep(0.1)\n",
+    "    print(\"\")\n",
+    "\n",
+    "\n",
+    "def reload_notebook():\n",
+    "    app = JupyterFrontEnd()\n",
+    "    app.commands.execute('docmanager:reload')\n",
+    "\n",
+    "\n",
+    "def wait_for_user(message):\n",
+    "    proceed = input(message + \" Y/n\")\n",
+    "    if proceed.lower() == \"y\" or proceed == \"\":\n",
+    "        return True\n",
+    "    else:\n",
+    "        return False\n",
+    "\n",
+    "\n",
+    "def clear_and_rerun_notebook(notebook_filename, force_rerun=False, timeout=600):\n",
+    "    if \"nbconvert_call\" in sys.argv:\n",
+    "        print(\"Finished rerun\")\n",
+    "        return\n",
+    "\n",
+    "    save_ipynb()\n",
+    "    time.sleep(1)\n",
+    "\n",
+    "    is_in_order = check_execution_order(notebook_filename)\n",
+    "\n",
+    "    if is_in_order and not force_rerun:\n",
+    "        print(\"Notebook was already executed in order.\")\n",
+    "        return\n",
+    "    else:\n",
+    "        rerun_confirmed_bool = wait_for_user(\"Notebook was not in order, rerun notebook now?\")\n",
+    "        if not rerun_confirmed_bool and not force_rerun:\n",
+    "            print(\"Aborting.\")\n",
+    "            return\n",
+    "\n",
+    "    print(\"Rerunning.\")\n",
+    "    with open(notebook_filename) as f:\n",
+    "        nb = nbformat.read(f, as_version=4)\n",
+    "\n",
+    "    ep = ExecutePreprocessor(timeout=timeout, kernel_name='python3', extra_arguments=[\"nbconvert_call\"])\n",
+    "    ep.preprocess(nb, )\n",
+    "\n",
+    "    with open(notebook_filename, 'w', encoding='utf-8') as f:\n",
+    "        nbformat.write(nb, f)\n",
+    "\n",
+    "    reload_notebook()\n",
+    "\n",
+    "\n",
+    "def convert_ipynb(filepath, formats=None):\n",
+    "    if formats is None:\n",
+    "        formats = [\"html\", \"python\"]\n",
+    "    app = NbConvertApp()\n",
+    "    app.initialize()\n",
+    "    output_root_directory = os.path.join(r\"C:\\Users\\ronal\\PycharmProjects\\git_lfs_test_2\", \"results\",\n",
+    "                                         os.path.basename(filepath.replace('.', '_')))\n",
+    "    for format in formats:\n",
+    "        app.export_format = format\n",
+    "        app.notebooks = [filepath]\n",
+    "        app.output_base = os.path.join(output_root_directory, os.path.basename(filepath.replace('.ipynb', '')))\n",
+    "        if not os.path.exists(output_root_directory):\n",
+    "            os.makedirs(output_root_directory)\n",
+    "        app.start()\n",
+    "\n",
+    "\n",
+    "\n",
+    "def export_all_figures():\n",
+    "    import junix\n",
+    "\n",
+    "    for file in os.listdir(git_repo.working_dir):\n",
+    "        if file.endswith(\"ipynb\"):\n",
+    "            file_without_extension = Path(file).stem\n",
+    "            images = junix.export_images(filepath=os.path.join(git_repo.working_dir, file),\n",
+    "                                         output_dir=os.path.join(git_repo.working_dir, \"results\",\n",
+    "                                                                 file.replace(\".\", \"_\")),\n",
+    "                                         prefix=file_without_extension)\n",
+    "            convert_ipynb(filepath=os.path.join(git_repo.working_dir, file), formats=[\"html\"])\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "id": "99404052-11f1-499b-a23f-4a90ed917300",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Saving\n",
+      "[13, 14, 15, 24, None, 25, 26]\n"
+     ]
+    },
+    {
+     "name": "stdin",
+     "output_type": "stream",
+     "text": [
+      "Notebook was not in order, rerun notebook now? Y/n n\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Aborting.\n"
+     ]
+    }
+   ],
+   "source": [
+    "clear_and_rerun_notebook(\"test_jupyter.ipynb\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "id": "fec9df11-5622-49d8-8e08-c6e8bbe73dfd",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Saving\n",
+      "[13, 14, 15, 24, 27, None, 26]\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "False"
+      ]
+     },
+     "execution_count": 28,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "save_ipynb()\n",
+    "time.sleep(1)\n",
+    "check_execution_order(\"test_jupyter.ipynb\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "id": "81841f17-c8d7-4a1e-b13d-d3f5a19adfd4",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Saving\n",
+      "[13, 14, 15, 24, 27, 28, None]\n",
+      "Notebook was already executed in order.\n"
+     ]
+    }
+   ],
+   "source": [
+    "clear_and_rerun_notebook(\"test_jupyter.ipynb\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "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.12.0"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/token instructions.png b/token instructions.png
new file mode 100644
index 0000000000000000000000000000000000000000..b1959f92dde40566c8aac588cf7ba90a26d6441f
Binary files /dev/null and b/token instructions.png differ