Repository: jupyrdf/ipyforcegraph (language: python)

Issue:
Retrieve canvas from browser as image
## Elevator Pitch

It would be good to be able to request a snapshot of the graph back from the browser for the kernel to have access to it.


## Motivation

Many use cases would benefit from having access to the image, not least of which would be testing.

## Design Ideas

Add a `ImageSnapshot` behavior which can stuff the canvas as a `png` into a buffer. As this would be pretty expensive, this would have a `enabled` flag so it could be turned on and off.


Discussion / hints:

Fix patch:
diff --git a/.binder/environment.yml b/.binder/environment.yml
index f69dcf97..c4deaee6 100644
--- a/.binder/environment.yml
+++ b/.binder/environment.yml
@@ -18,9 +18,9 @@ dependencies:
   - pytest-html
   - pytest-xdist
   - requests-cache
+  - robotframework-jupyterlibrary
   - robotframework-pabot
   - robotframework-robocop
-  - robotframework-seleniumlibrary
   - robotframework-tidy
   - rope
   - stringcase
diff --git a/.github/locks/linux-64_3.11_environment.conda.lock b/.github/locks/linux-64_3.11_environment.conda.lock
index c7ac8137..99a0866c 100644
--- a/.github/locks/linux-64_3.11_environment.conda.lock
+++ b/.github/locks/linux-64_3.11_environment.conda.lock
@@ -18,9 +18,9 @@
 #   - pytest-html
 #   - pytest-xdist
 #   - requests-cache
+#   - robotframework-jupyterlibrary
 #   - robotframework-pabot
 #   - robotframework-robocop
-#   - robotframework-seleniumlibrary
 #   - robotframework-tidy
 #   - rope
 #   - stringcase
@@ -110,7 +110,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.
 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.51.0-hff17c54_0.conda#dd682f0b6d65e75b2bc868fc8e93d87e
 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f
 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906
-https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-hca2bb57_1.conda#e67867487d8f19c9ac2b4ee7871e69de
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8
 https://conda.anaconda.org/conda-forge/linux-64/pandoc-2.19.2-h32600fe_1.tar.bz2#35a82883468c85ac8bf41f083c1933cf
 https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b
 https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-2_h7f98852_perl5.tar.bz2#09ba115862623f00962e9809ea248f1a
@@ -126,8 +126,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openbl
 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe
 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad
 https://conda.anaconda.org/conda-forge/linux-64/nodejs-18.12.1-h8839609_0.tar.bz2#305f25b78340b0f7b391a6919d1246f2
-https://conda.anaconda.org/conda-forge/linux-64/python-3.11.0-ha86cf86_0_cpython.tar.bz2#531b2b97ce96cc95a587bdf7c74e31c0
-https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
+https://conda.anaconda.org/conda-forge/linux-64/python-3.11.0-he550d4f_1_cpython.conda#8d14fc2aa12db370a443753c8230be1e
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e
 https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
 https://conda.anaconda.org/conda-forge/noarch/async_generator-1.10-py_0.tar.bz2#d56c596e61b1c4952acf0a9920856c12
 https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9
@@ -150,12 +150,12 @@ https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.
 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
 https://conda.anaconda.org/conda-forge/noarch/executing-1.2.0-pyhd8ed1ab_0.tar.bz2#4c1bc140e2be5c8ba6e3acab99e25c50
 https://conda.anaconda.org/conda-forge/noarch/flit-core-3.8.0-pyhd8ed1ab_0.tar.bz2#6d5e56de2e65da7aa35fd10131226efa
-https://conda.anaconda.org/conda-forge/noarch/future-0.18.2-pyhd8ed1ab_6.tar.bz2#a6966947ba28bbe60f9904653da7fed5
+https://conda.anaconda.org/conda-forge/noarch/future-0.18.3-pyhd8ed1ab_0.conda#fec8329fc739090f26a7d7803db254f1
 https://conda.anaconda.org/conda-forge/linux-64/greenlet-2.0.1-py311ha362b79_0.tar.bz2#7adf8b182f4fd2cd313349bce924afdd
 https://conda.anaconda.org/conda-forge/noarch/hunspell-en-2022.08.01-h1a96a4e_0.tar.bz2#825d717b54c12fb441c2062651e5eb47
 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed
 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
-https://conda.anaconda.org/conda-forge/noarch/importnb-2022.10.24-pyhd8ed1ab_0.tar.bz2#65e8d2add04a93849c09ab1b06130c13
+https://conda.anaconda.org/conda-forge/noarch/importnb-2023.1.7-pyhd8ed1ab_0.conda#4ff1d5888b17056732615d7975c254dd
 https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5
 https://conda.anaconda.org/conda-forge/noarch/ipython_genutils-0.2.0-py_1.tar.bz2#5071c982548b3a20caf70462f04f5287
 https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2#3c3de74912f11d2b590184f03c7cd09b
@@ -174,8 +174,8 @@ https://conda.anaconda.org/conda-forge/noarch/natsort-8.2.0-pyhd8ed1ab_0.tar.bz2
 https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.5.6-pyhd8ed1ab_0.tar.bz2#7b868f21adde0d9b8b38f9c16836589b
 https://conda.anaconda.org/conda-forge/noarch/networkx-3.0-pyhd8ed1ab_0.conda#88e40007414ea9a13f8df20fcffa87e2
 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.1-py311hbde0eaa_0.conda#236dda53b49b70717d7280efb3494db7
-https://conda.anaconda.org/conda-forge/linux-64/orjson-3.8.3-py311h6a9de79_0.conda#3f8a2df7dcfafc582824837853d4e735
-https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db
+https://conda.anaconda.org/conda-forge/linux-64/orjson-3.8.5-py311h382353d_1.conda#fbf5a398eb2638221bc7a0afc2615a9a
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
 https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2#457c2c8c08e54905d6954e79cb5b5db9
 https://conda.anaconda.org/conda-forge/noarch/parso-0.8.3-pyhd8ed1ab_0.tar.bz2#17a565a0c3899244e938cdf417e7b094
 https://conda.anaconda.org/conda-forge/noarch/pathspec-0.9.0-pyhd8ed1ab_0.tar.bz2#f93dc0ccbc0a8472624165f6e256c7d1
@@ -194,13 +194,13 @@ https://conda.anaconda.org/conda-forge/linux-64/pyrsistent-0.19.3-py311h2582759_
 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025
 https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.16.2-pyhd8ed1ab_0.tar.bz2#5fe4b6002f505336734ce92961b3e6a0
 https://conda.anaconda.org/conda-forge/linux-64/python-libarchive-c-4.0-py311h38be061_2.tar.bz2#1c3e2d22ddfb6ff2a0edd60e91e018db
-https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2
 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py311hd4cff14_5.tar.bz2#da8769492e423103c59f469f4f17f8d9
-https://conda.anaconda.org/conda-forge/linux-64/pyzmq-24.0.1-py311ha4b6469_1.tar.bz2#7d57a4cdfa05e444ed955b0319b97eed
+https://conda.anaconda.org/conda-forge/linux-64/pyzmq-25.0.0-py311hd6ccaeb_0.conda#a83c437f61566cff9eb7332c023bec99
 https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_0.tar.bz2#d337886e38f965bf97aaec382ff6db00
-https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_0.conda#cd9c28c39e4a899a1df100c2d94e01cf
+https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_1.conda#b4dcead8c5a99a14733d9b1ba5b7db09
 https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.0-pyhd8ed1ab_0.tar.bz2#edab14119efe85c3bf131ad747e9005c
-https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a
+https://conda.anaconda.org/conda-forge/noarch/setuptools-66.0.0-pyhd8ed1ab_0.conda#88ec352fde1a5561ce5f93a302539f7f
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
 https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2#dd6cbc539e74cb1f430efbd4575b9303
 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e
@@ -242,23 +242,23 @@ https://conda.anaconda.org/conda-forge/linux-64/coverage-7.0.5-py311h2582759_0.c
 https://conda.anaconda.org/conda-forge/linux-64/curl-7.87.0-hdc1c0ab_0.conda#b14123ca479b9473d7f7395b0fd25c97
 https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2#b21ed0883505ba1910994f1df031a428
 https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2#b2355343d6315c892543200231d7154a
-https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.62.0-pyha770c72_0.conda#0ba38faf3644821aec9e857b1f4f7a3c
+https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.62.1-pyha770c72_0.conda#fdb3c6a98a3c54e4f34d11a946ce93d8
 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62
 https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3
-https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_0.conda#cc1909c46e375f8d4e3eb9902b21fa6a
+https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_1.conda#0dbbfda0efc7fde25d97aa0df18767c2
 https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2#31e4a1506968d017229bdb64695013a1
 https://conda.anaconda.org/conda-forge/noarch/jedi-0.18.2-pyhd8ed1ab_0.conda#b5e695ef9c3f0d27d6cd96bf5adc9e07
 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
 https://conda.anaconda.org/conda-forge/noarch/livereload-2.6.3-pyh9f0ad1d_0.tar.bz2#b7190e3ec3eff52839434bf4698e2d62
 https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-2.1.0-pyhd8ed1ab_0.tar.bz2#d821ebc1ebe21d4cac2555278c2cd970
 https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.6-pyhd8ed1ab_0.tar.bz2#b21613793fcc81d944c76c9f2864a7de
-https://conda.anaconda.org/conda-forge/linux-64/mypy-0.991-py311hd4cff14_0.tar.bz2#0255dd3a667b3500039a496ffd27d474
+https://conda.anaconda.org/conda-forge/linux-64/mypy-0.991-py311h2582759_0.conda#2c081ea2792b1f73b57d53b41457039d
 https://conda.anaconda.org/conda-forge/linux-64/numcodecs-0.11.0-py311hcafe171_1.conda#ecdaf0772e524ed51218f6d52ef74424
 https://conda.anaconda.org/conda-forge/noarch/outcome-1.2.0-pyhd8ed1ab_0.tar.bz2#3dbb9ece72652131f12d66e889da7c0a
 https://conda.anaconda.org/conda-forge/noarch/pexpect-4.8.0-pyh1a96a4e_2.tar.bz2#330448ce4403cc74990ac07c555942a1
 https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d
 https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1
-https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c
+https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.1-pyhd8ed1ab_0.conda#f0be05afc9c9ab45e273c088e00c258b
 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
 https://conda.anaconda.org/conda-forge/noarch/robotframework-pythonlibcore-4.0.0-pyhd8ed1ab_0.tar.bz2#e3dd7ba929a82a21c6e568c00d5b538c
 https://conda.anaconda.org/conda-forge/noarch/robotframework-robocop-1.12.0-pyhd8ed1ab_0.tar.bz2#37e6dad59a24e6d7ff64a533c4135f57
@@ -275,7 +275,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py311hd4cff14_100
 https://conda.anaconda.org/conda-forge/linux-64/cmarkgfm-0.8.0-py311hd4cff14_2.tar.bz2#ed1a2dba3b53fcd450f2fe867af0ba3f
 https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.0-py311h9b4c7bb_0.conda#74eed5e75574839f1ae7a7048f529a66
 https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_0.tar.bz2#fc5e53d070f1ee7bb38c2ece282dcb82
-https://conda.anaconda.org/conda-forge/linux-64/git-2.39.0-pl5321h693f4a3_0.conda#d52d166cf45275bdc6c108f951371d37
+https://conda.anaconda.org/conda-forge/linux-64/git-2.39.1-pl5321h693f4a3_0.conda#12f9ef434e479d7ec9dcd3ab9799a49d
 https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.0.0-hd8ed1ab_0.conda#a67d43e1527a37199dd8db913366f68e
 https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda#723268a468177cd44568eb8f794e0d80
 https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.2.2-pyhd8ed1ab_0.tar.bz2#243f63592c8e449f40cd42eb5cf32f40
@@ -287,14 +287,14 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-cov-4.0.0-pyhd8ed1ab_0.tar.
 https://conda.anaconda.org/conda-forge/noarch/pytest-metadata-2.0.4-pyhd8ed1ab_0.tar.bz2#7ac02a65917993d38ca1bfd7b87208e4
 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9
 https://conda.anaconda.org/conda-forge/noarch/pytoolconfig-1.2.4-pyhd8ed1ab_1.conda#830a5eeda95b64fa74c30cf3d1b4d1ae
-https://conda.anaconda.org/conda-forge/noarch/rich-13.0.1-pyhd8ed1ab_0.conda#f6ed7a402e1378ca13bc0f3d8ec2763a
-https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.11.0-pyhd8ed1ab_0.conda#9e92b5ab35965c2d48110eca42cc3f9e
+https://conda.anaconda.org/conda-forge/noarch/rich-13.1.0-pyhd8ed1ab_0.conda#45901233f863e3b734c0916172c4cbdd
+https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.12.0-pyhd8ed1ab_0.conda#71b3c6e9c9f8757f6828c2466cdfc9d4
 https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.2-pyhd8ed1ab_0.conda#e7df0fdd404616638df5ece6e69ba7af
 https://conda.anaconda.org/conda-forge/linux-64/trio-0.22.0-py311h38be061_1.tar.bz2#0564e63c41c0527f8085a572a931f1e6
-https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.5-pyh9f0ad1d_2.tar.bz2#5266fcd697043c59621fda522b3d78ee
+https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda#078979d33523cb477bd1916ce41aacc9
 https://conda.anaconda.org/conda-forge/noarch/wsproto-1.2.0-pyhd8ed1ab_0.tar.bz2#00ba804b54f451d102f6a7615f08470d
 https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-21.3.0-pyhd8ed1ab_0.tar.bz2#a0b402db58f73aaab8ee0ca1025a362e
-https://conda.anaconda.org/conda-forge/linux-64/black-22.10.0-py311h38be061_2.tar.bz2#c3c459c7ddb8ee0d658ad1d50d6511de
+https://conda.anaconda.org/conda-forge/linux-64/black-22.12.0-py311h38be061_0.conda#a70f095279df88b632cf5b396132fa30
 https://conda.anaconda.org/conda-forge/linux-64/jupyter_core-5.1.3-py311h38be061_0.conda#6817c1a1234819ed0fd1ad87bdce9e1a
 https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.36-pyha770c72_0.conda#4d79ec192e0bfd530a254006d123b9a6
 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878
@@ -305,35 +305,36 @@ https://conda.anaconda.org/conda-forge/noarch/rope-1.6.0-pyhd8ed1ab_0.conda#c10a
 https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.3.3-py311h38be061_1.tar.bz2#ec745aaae03cc47120c1f11ac7b7bcf5
 https://conda.anaconda.org/conda-forge/noarch/trio-websocket-0.9.2-pyhd8ed1ab_0.tar.bz2#7acf3fc982eef23b24aed10e80d5dc99
 https://conda.anaconda.org/conda-forge/noarch/ipython-8.8.0-pyh41d4057_0.conda#788c234d910ad9f37174d1845c1a52c7
-https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.8-pyhd8ed1ab_0.conda#bd4951aa5fbf3336a249e25c90f50333
+https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.9-pyhd8ed1ab_0.conda#5cbf9a31a19d4ef9103adb7d71fd45fd
 https://conda.anaconda.org/conda-forge/linux-64/keyring-23.13.1-py311h38be061_0.conda#0dc0127b1daefefa5e2caa49dde5c230
-https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.1-pyhd8ed1ab_0.conda#facb42913140f1a399a5ebb65b30915e
-https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.2-pyhd8ed1ab_0.tar.bz2#354a5c374999c2688332c073e2450951
+https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.3-pyhd8ed1ab_0.conda#9714111cb6c7dbbc9a9f34de205c2f29
+https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.3-pyhd8ed1ab_0.conda#bc4e21b26f0e5bb7517c5d6200912d35
 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
-https://conda.anaconda.org/conda-forge/noarch/black-jupyter-22.10.0-hd8ed1ab_2.tar.bz2#80112c21a14035fe1d786f082ce27978
-https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.20.1-pyh210e3f2_0.conda#cfc405049e1e3c5111fe588bacddb36f
+https://conda.anaconda.org/conda-forge/noarch/black-jupyter-22.12.0-hd8ed1ab_0.conda#421077052ae1530402ee4c78ba7939bb
+https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.20.2-pyh210e3f2_0.conda#41b586f0826282574ab35a0087019bb6
 https://conda.anaconda.org/conda-forge/noarch/nbclient-0.5.13-pyhd8ed1ab_0.tar.bz2#3edde88a191701cf052216c4ba353a83
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
 https://conda.anaconda.org/conda-forge/noarch/selenium-4.7.2-pyhd8ed1ab_0.conda#4f6238a5e80996ee0bbee28b487aada5
 https://conda.anaconda.org/conda-forge/noarch/flit-3.8.0-pyhd8ed1ab_0.tar.bz2#d37c34176396c5402cf80c32e67731b7
 https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.0.4-pyhd8ed1ab_0.conda#9dea5ab3cc33084f7a3680a98859731e
 https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-0.5.0-pyhd8ed1ab_0.tar.bz2#293ede7de6b604f3586efbfa615ee2e1
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.7-pyhd8ed1ab_0.conda#dce063b1f5d6193646fc2ac43f88b77f
-https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.7-pyhd8ed1ab_0.conda#399c98b39eb6bdc4b1d40b7611a6d384
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.8-pyhd8ed1ab_0.conda#252899fe1f90aeb0c3391ff32a5035f1
+https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.8-pyhd8ed1ab_0.conda#b0af82591adbf1d403fb4d061a8ca940
 https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-0.10.1-pyhd8ed1ab_0.tar.bz2#a4cd20af9711434f89d1ec0d2b3ae6ba
 https://conda.anaconda.org/conda-forge/noarch/robotframework-seleniumlibrary-6.0.0-pyhd8ed1ab_0.tar.bz2#2acaef9c04081dc3644501ac575953bf
 https://conda.anaconda.org/conda-forge/noarch/sphinx-5.3.0-pyhd8ed1ab_0.tar.bz2#f9e1fcfe235d655900bfeb6aee426472
 https://conda.anaconda.org/conda-forge/noarch/bqplot-0.12.36-pyhd8ed1ab_0.tar.bz2#bff49231f6b67c01a7a89d56ca0693ab
-https://conda.anaconda.org/conda-forge/noarch/jupyter_server-1.23.4-pyhd8ed1ab_0.conda#35b7e9267926e864cc70ce137e6588ca
+https://conda.anaconda.org/conda-forge/noarch/jupyter_server-1.23.5-pyhd8ed1ab_0.conda#e3ae1d46749f48a1875b560fca41cb0c
 https://conda.anaconda.org/conda-forge/noarch/myst-parser-0.18.1-pyhd8ed1ab_0.tar.bz2#bcfdf5c7d8bf5c6f6be7b4c66fff2eca
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.7-pyhd8ed1ab_0.conda#7d48776bb3fdfa6447267c413a480ccd
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.8-pyhd8ed1ab_0.conda#701b0db88fe192e1685de29b8ca0840e
 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2
+https://conda.anaconda.org/conda-forge/noarch/robotframework-jupyterlibrary-0.4.2-pyhd8ed1ab_0.conda#6687f0def03dc3db713149fb8e026068
 https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2021.3.14-pyhd8ed1ab_0.tar.bz2#1909f784dc37b4ab97afe2c95aeeabaa
-https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.20.1-pyhd8ed1ab_0.conda#1364c5199876bb381ba1ad004027e11c
+https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.23.4-pyhd8ed1ab_0.conda#f2f2d8c21ec1933c039cc321a00bc10b
 https://conda.anaconda.org/conda-forge/noarch/twine-4.0.2-pyhd8ed1ab_0.conda#e3a16168d6b9deefb8c1caa7943fb49e
-https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.18.0-pyhd8ed1ab_0.conda#be95d2cc7614462440e4e10b5b373067
+https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.19.0-pyhd8ed1ab_0.conda#9dc0706ec2f8e13b7a0db9d6a19051ff
 https://conda.anaconda.org/conda-forge/noarch/myst-nb-0.17.1-pyhd8ed1ab_0.tar.bz2#8a11a646076b72bcaa71328f07c1aaab
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.7-pyhd8ed1ab_0.conda#b019fe41e6f6b88e0557532514f39cff
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.8-pyhd8ed1ab_0.conda#8b5cb7073f66beef0d7161406fdb6e8b
 https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.2-pyhd8ed1ab_0.tar.bz2#40be846cd4e78672a40e43db9dae753c
 https://conda.anaconda.org/conda-forge/noarch/nbclassic-0.4.8-pyhd8ed1ab_0.tar.bz2#f4f150f83ed90a1b833e6081bbf38257
 https://conda.anaconda.org/conda-forge/noarch/pytest-check-links-0.8.0-pyhd8ed1ab_0.conda#639513b3fc51ee25e00a423fa430a2da
diff --git a/.github/locks/linux-64_3.7_environment.conda.lock b/.github/locks/linux-64_3.7_environment.conda.lock
index 9d33fdac..0d4e105d 100644
--- a/.github/locks/linux-64_3.7_environment.conda.lock
+++ b/.github/locks/linux-64_3.7_environment.conda.lock
@@ -18,9 +18,9 @@
 #   - pytest-html
 #   - pytest-xdist
 #   - requests-cache
+#   - robotframework-jupyterlibrary
 #   - robotframework-pabot
 #   - robotframework-robocop
-#   - robotframework-seleniumlibrary
 #   - robotframework-tidy
 #   - rope
 #   - stringcase
@@ -120,7 +120,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e
 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f
 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906
 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904
-https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-hca2bb57_1.conda#e67867487d8f19c9ac2b4ee7871e69de
+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8
 https://conda.anaconda.org/conda-forge/linux-64/pandoc-2.19.2-h32600fe_1.tar.bz2#35a82883468c85ac8bf41f083c1933cf
 https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b
 https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-2_h7f98852_perl5.tar.bz2#09ba115862623f00962e9809ea248f1a
@@ -147,7 +147,7 @@ https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dc
 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.87.0-hdc1c0ab_0.conda#bc302fa1cf8eda15c60f669b7524a320
 https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2
 https://conda.anaconda.org/conda-forge/linux-64/python-3.7.12-hf930737_100_cpython.tar.bz2#416558a6f46b7a1fa8db7d0e98aff56a
-https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e
 https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
 https://conda.anaconda.org/conda-forge/noarch/async_generator-1.10-py_0.tar.bz2#d56c596e61b1c4952acf0a9920856c12
 https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9
@@ -172,7 +172,7 @@ https://conda.anaconda.org/conda-forge/linux-64/future-0.18.2-py37h89c1867_5.tar
 https://conda.anaconda.org/conda-forge/linux-64/greenlet-1.1.3-py37hd23a5d3_0.tar.bz2#426c53c95df106039da865c0095e2a65
 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed
 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
-https://conda.anaconda.org/conda-forge/noarch/importnb-2022.10.24-pyhd8ed1ab_0.tar.bz2#65e8d2add04a93849c09ab1b06130c13
+https://conda.anaconda.org/conda-forge/noarch/importnb-2023.1.7-pyhd8ed1ab_0.conda#4ff1d5888b17056732615d7975c254dd
 https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5
 https://conda.anaconda.org/conda-forge/noarch/ipython_genutils-0.2.0-py_1.tar.bz2#5071c982548b3a20caf70462f04f5287
 https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2#3c3de74912f11d2b590184f03c7cd09b
@@ -191,7 +191,7 @@ https://conda.anaconda.org/conda-forge/noarch/natsort-8.2.0-pyhd8ed1ab_0.tar.bz2
 https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.5.6-pyhd8ed1ab_0.tar.bz2#7b868f21adde0d9b8b38f9c16836589b
 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.21.6-py37h976b520_0.tar.bz2#b0701d8dc4849b6e06caf1c84135c13c
 https://conda.anaconda.org/conda-forge/linux-64/orjson-3.8.1-py37h540881e_0.tar.bz2#4207234c6822359b587813c471a77c88
-https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
 https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2#457c2c8c08e54905d6954e79cb5b5db9
 https://conda.anaconda.org/conda-forge/noarch/parso-0.8.3-pyhd8ed1ab_0.tar.bz2#17a565a0c3899244e938cdf417e7b094
 https://conda.anaconda.org/conda-forge/noarch/pathspec-0.9.0-pyhd8ed1ab_0.tar.bz2#f93dc0ccbc0a8472624165f6e256c7d1
@@ -211,11 +211,11 @@ https://conda.anaconda.org/conda-forge/linux-64/pyrsistent-0.18.1-py37h540881e_1
 https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py37h89c1867_5.tar.bz2#8c4b0563f96363ee99f1ab864616ac63
 https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.16.2-pyhd8ed1ab_0.tar.bz2#5fe4b6002f505336734ce92961b3e6a0
 https://conda.anaconda.org/conda-forge/linux-64/python-libarchive-c-4.0-py37h89c1867_1.tar.bz2#b42fd5e3bf686f1321021b0419bdf026
-https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2
 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py37h540881e_4.tar.bz2#f231119f9790da6da6bd93b250b7e42e
 https://conda.anaconda.org/conda-forge/linux-64/pyzmq-24.0.1-py37h0c0c2a8_0.tar.bz2#732c98a38c84984262940c868793ebb4
 https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_0.tar.bz2#d337886e38f965bf97aaec382ff6db00
-https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_0.conda#cd9c28c39e4a899a1df100c2d94e01cf
+https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_1.conda#b4dcead8c5a99a14733d9b1ba5b7db09
 https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.0-pyhd8ed1ab_0.tar.bz2#edab14119efe85c3bf131ad747e9005c
 https://conda.anaconda.org/conda-forge/linux-64/setuptools-59.8.0-py37h89c1867_1.tar.bz2#136867136f5a231f42cb5ea0e0b5a18b
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
@@ -258,12 +258,12 @@ https://conda.anaconda.org/conda-forge/noarch/commonmark-0.9.1-py_0.tar.bz2#6aa0
 https://conda.anaconda.org/conda-forge/linux-64/coverage-6.5.0-py37h540881e_0.tar.bz2#4dcbc678c181f7a416589e95b8e34158
 https://conda.anaconda.org/conda-forge/linux-64/doit-0.34.2-py37h89c1867_0.tar.bz2#f403c4f9b6e55cce39d37c9e6cde4b80
 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py37h540881e_0.tar.bz2#b2a41d12059b0557f3b654ddc08f1b33
-https://conda.anaconda.org/conda-forge/linux-64/git-2.39.0-pl5321h693f4a3_0.conda#d52d166cf45275bdc6c108f951371d37
+https://conda.anaconda.org/conda-forge/linux-64/git-2.39.1-pl5321h693f4a3_0.conda#12f9ef434e479d7ec9dcd3ab9799a49d
 https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2#b21ed0883505ba1910994f1df031a428
 https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2#b2355343d6315c892543200231d7154a
 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py37h89c1867_0.tar.bz2#71107630527e4bd82932952351231efe
 https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3
-https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_0.conda#cc1909c46e375f8d4e3eb9902b21fa6a
+https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_1.conda#0dbbfda0efc7fde25d97aa0df18767c2
 https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2#31e4a1506968d017229bdb64695013a1
 https://conda.anaconda.org/conda-forge/noarch/jedi-0.18.2-pyhd8ed1ab_0.conda#b5e695ef9c3f0d27d6cd96bf5adc9e07
 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
@@ -294,7 +294,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cmarkgfm-0.8.0-py37h540881e_1.ta
 https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.2-py37h5994e8b_1.tar.bz2#5a8bb0074be9d20d456957f92b72beed
 https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-4.11.4-hd8ed1ab_0.tar.bz2#9a1925fdb91c81437b8012e48ede6851
 https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda#723268a468177cd44568eb8f794e0d80
-https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.8-pyhd8ed1ab_0.conda#bd4951aa5fbf3336a249e25c90f50333
+https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.9-pyhd8ed1ab_0.conda#5cbf9a31a19d4ef9103adb7d71fd45fd
 https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.2.2-pyhd8ed1ab_0.tar.bz2#243f63592c8e449f40cd42eb5cf32f40
 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py37h7cecad7_0.tar.bz2#997912aa3ebab21640e3ba5ec168353d
 https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.3.3-pyhd8ed1ab_0.conda#7a69c057183706bc7ba6ac7c9130566a
@@ -302,17 +302,17 @@ https://conda.anaconda.org/conda-forge/linux-64/numcodecs-0.10.2-py37hd23a5d3_0.
 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.3.5-py37he8f5f7f_0.tar.bz2#6ebf1968b199a141a5cce6adaedb3651
 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54
 https://conda.anaconda.org/conda-forge/noarch/pytoolconfig-1.2.4-pyhd8ed1ab_1.conda#830a5eeda95b64fa74c30cf3d1b4d1ae
-https://conda.anaconda.org/conda-forge/noarch/rich-13.0.1-pyhd8ed1ab_0.conda#f6ed7a402e1378ca13bc0f3d8ec2763a
-https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.11.0-pyhd8ed1ab_0.conda#9e92b5ab35965c2d48110eca42cc3f9e
+https://conda.anaconda.org/conda-forge/noarch/rich-13.1.0-pyhd8ed1ab_0.conda#45901233f863e3b734c0916172c4cbdd
+https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.12.0-pyhd8ed1ab_0.conda#71b3c6e9c9f8757f6828c2466cdfc9d4
 https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-1.4.42-py37h540881e_0.tar.bz2#921bb310665fba1ed0f64f3287fda6d4
 https://conda.anaconda.org/conda-forge/linux-64/trio-0.21.0-py37h89c1867_0.tar.bz2#6ed8b58c9866953a5139eb501e6756ae
-https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.5-pyh9f0ad1d_2.tar.bz2#5266fcd697043c59621fda522b3d78ee
+https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda#078979d33523cb477bd1916ce41aacc9
 https://conda.anaconda.org/conda-forge/noarch/wsproto-1.2.0-pyhd8ed1ab_0.tar.bz2#00ba804b54f451d102f6a7615f08470d
 https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-21.3.0-pyhd8ed1ab_0.tar.bz2#a0b402db58f73aaab8ee0ca1025a362e
 https://conda.anaconda.org/conda-forge/linux-64/black-22.10.0-py37h89c1867_1.tar.bz2#848a8ee5f1905175749f2dda4f00b089
 https://conda.anaconda.org/conda-forge/linux-64/hypothesis-6.56.3-py37h89c1867_1.tar.bz2#a76d30b406597ad93e3d0fe483efadbb
 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py37hf395dca_2.tar.bz2#dcbe09584d1271ddd3d8636776d7b09d
-https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.1-pyhd8ed1ab_0.conda#facb42913140f1a399a5ebb65b30915e
+https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.3-pyhd8ed1ab_0.conda#9714111cb6c7dbbc9a9f34de205c2f29
 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py37h89c1867_3.tar.bz2#6364b39df084274961ed1d21a8cd060b
 https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.36-pyha770c72_0.conda#4d79ec192e0bfd530a254006d123b9a6
 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878
@@ -326,35 +326,36 @@ https://conda.anaconda.org/conda-forge/linux-64/keyring-23.9.3-py37h89c1867_0.ta
 https://conda.anaconda.org/conda-forge/noarch/nbclient-0.5.13-pyhd8ed1ab_0.tar.bz2#3edde88a191701cf052216c4ba353a83
 https://conda.anaconda.org/conda-forge/noarch/networkx-2.7-pyhd8ed1ab_0.tar.bz2#9d430f94458637bbb2f5e78526324412
 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.2.0-py37h89c1867_0.tar.bz2#c623307fdde70b00731a06dc62341a6c
-https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.2-pyhd8ed1ab_0.tar.bz2#354a5c374999c2688332c073e2450951
+https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.3-pyhd8ed1ab_0.conda#bc4e21b26f0e5bb7517c5d6200912d35
 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
 https://conda.anaconda.org/conda-forge/noarch/black-jupyter-22.10.0-hd8ed1ab_2.tar.bz2#80112c21a14035fe1d786f082ce27978
 https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.16.2-pyh210e3f2_0.tar.bz2#6b0f40821b784cac8a33d0c5eb7602c0
 https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-0.5.0-pyhd8ed1ab_0.tar.bz2#293ede7de6b604f3586efbfa615ee2e1
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.7-pyhd8ed1ab_0.conda#dce063b1f5d6193646fc2ac43f88b77f
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.8-pyhd8ed1ab_0.conda#252899fe1f90aeb0c3391ff32a5035f1
 https://conda.anaconda.org/conda-forge/noarch/pytest-asyncio-0.20.3-pyhd8ed1ab_0.conda#aa462bf71d7b327dcc00ae523f8923ef
 https://conda.anaconda.org/conda-forge/noarch/pytest-cov-4.0.0-pyhd8ed1ab_0.tar.bz2#c9e3f8bfdb9bfc34aa1836a6ed4b25d7
 https://conda.anaconda.org/conda-forge/noarch/pytest-metadata-2.0.4-pyhd8ed1ab_0.tar.bz2#7ac02a65917993d38ca1bfd7b87208e4
 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
 https://conda.anaconda.org/conda-forge/noarch/selenium-4.7.2-pyhd8ed1ab_0.conda#4f6238a5e80996ee0bbee28b487aada5
 https://conda.anaconda.org/conda-forge/noarch/flit-3.8.0-pyhd8ed1ab_0.tar.bz2#d37c34176396c5402cf80c32e67731b7
 https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.0.4-pyhd8ed1ab_0.conda#9dea5ab3cc33084f7a3680a98859731e
 https://conda.anaconda.org/conda-forge/noarch/jupyter_server-1.23.4-pyhd8ed1ab_0.conda#35b7e9267926e864cc70ce137e6588ca
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.7-pyhd8ed1ab_0.conda#7d48776bb3fdfa6447267c413a480ccd
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.8-pyhd8ed1ab_0.conda#701b0db88fe192e1685de29b8ca0840e
 https://conda.anaconda.org/conda-forge/noarch/pytest-html-3.2.0-pyhd8ed1ab_1.tar.bz2#d5c7a941dfbceaab4b172a56d7918eb0
-https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.7-pyhd8ed1ab_0.conda#399c98b39eb6bdc4b1d40b7611a6d384
+https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.8-pyhd8ed1ab_0.conda#b0af82591adbf1d403fb4d061a8ca940
 https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-0.10.1-pyhd8ed1ab_0.tar.bz2#a4cd20af9711434f89d1ec0d2b3ae6ba
 https://conda.anaconda.org/conda-forge/noarch/robotframework-seleniumlibrary-6.0.0-pyhd8ed1ab_0.tar.bz2#2acaef9c04081dc3644501ac575953bf
 https://conda.anaconda.org/conda-forge/noarch/sphinx-5.3.0-pyhd8ed1ab_0.tar.bz2#f9e1fcfe235d655900bfeb6aee426472
 https://conda.anaconda.org/conda-forge/noarch/bqplot-0.12.36-pyhd8ed1ab_0.tar.bz2#bff49231f6b67c01a7a89d56ca0693ab
-https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.18.0-pyhd8ed1ab_0.conda#be95d2cc7614462440e4e10b5b373067
+https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.19.0-pyhd8ed1ab_0.conda#9dc0706ec2f8e13b7a0db9d6a19051ff
 https://conda.anaconda.org/conda-forge/noarch/myst-parser-0.18.1-pyhd8ed1ab_0.tar.bz2#bcfdf5c7d8bf5c6f6be7b4c66fff2eca
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.7-pyhd8ed1ab_0.conda#b019fe41e6f6b88e0557532514f39cff
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.8-pyhd8ed1ab_0.conda#8b5cb7073f66beef0d7161406fdb6e8b
 https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.2-pyhd8ed1ab_0.tar.bz2#40be846cd4e78672a40e43db9dae753c
 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2
+https://conda.anaconda.org/conda-forge/noarch/robotframework-jupyterlibrary-0.4.2-pyhd8ed1ab_0.conda#6687f0def03dc3db713149fb8e026068
 https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2021.3.14-pyhd8ed1ab_0.tar.bz2#1909f784dc37b4ab97afe2c95aeeabaa
-https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.20.1-pyhd8ed1ab_0.conda#1364c5199876bb381ba1ad004027e11c
+https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.23.4-pyhd8ed1ab_0.conda#f2f2d8c21ec1933c039cc321a00bc10b
 https://conda.anaconda.org/conda-forge/noarch/twine-4.0.2-pyhd8ed1ab_0.conda#e3a16168d6b9deefb8c1caa7943fb49e
 https://conda.anaconda.org/conda-forge/noarch/myst-nb-0.17.1-pyhd8ed1ab_0.tar.bz2#8a11a646076b72bcaa71328f07c1aaab
 https://conda.anaconda.org/conda-forge/noarch/nbclassic-0.4.8-pyhd8ed1ab_0.tar.bz2#f4f150f83ed90a1b833e6081bbf38257
diff --git a/.github/locks/linux-64_lock-environment.conda.lock b/.github/locks/linux-64_lock-environment.conda.lock
index 28455ad5..23da825f 100644
--- a/.github/locks/linux-64_lock-environment.conda.lock
+++ b/.github/locks/linux-64_lock-environment.conda.lock
@@ -55,7 +55,7 @@ https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#ad
 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda#89a41adce7106749573d883b2f657d78
 https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.6.2-h3d51595_0.conda#9f915b4adeb9dcfd450b9ad238e2db4c
 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe
-https://conda.anaconda.org/conda-forge/linux-64/python-3.11.0-ha86cf86_0_cpython.tar.bz2#531b2b97ce96cc95a587bdf7c74e31c0
+https://conda.anaconda.org/conda-forge/linux-64/python-3.11.0-he550d4f_1_cpython.conda#8d14fc2aa12db370a443753c8230be1e
 https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
 https://conda.anaconda.org/conda-forge/noarch/cachy-0.3.0-pyhd8ed1ab_1.tar.bz2#5dfee17f24e2dfd18d7392b48c9351e2
 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6
@@ -71,7 +71,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.87.0-hdc1c0ab_0.conda#
 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py311hd4cff14_2.tar.bz2#6b0d96114b4a841630ef7d0dff10d4e8
 https://conda.anaconda.org/conda-forge/noarch/more-itertools-9.0.0-pyhd8ed1ab_0.tar.bz2#9b6ad26944f19f599800b068e0582227
 https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.0.4-py311h4dd048b_1.tar.bz2#bead0c840170e53af42618434617ea45
-https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
 https://conda.anaconda.org/conda-forge/noarch/pastel-0.2.1-pyhd8ed1ab_0.tar.bz2#a4eea5bff523f26442405bc5d1f52adb
 https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.9.6-pyhd8ed1ab_0.conda#be1e9f1c65a1ed0f2ae9352fec99db64
 https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.4-py311hd4cff14_1.tar.bz2#17399f8b68b4592f098ab18d4fdb0ac4
@@ -81,7 +81,7 @@ https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2
 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py311hd4cff14_5.tar.bz2#da8769492e423103c59f469f4f17f8d9
 https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.7-py311h2582759_1.conda#5e997292429a22ad50c11af0a2cb0f08
 https://conda.anaconda.org/conda-forge/linux-64/ruamel_yaml-0.15.80-py311hd4cff14_1008.tar.bz2#cfc7aa9d4e13c267fb6531d4788f2ede
-https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a
+https://conda.anaconda.org/conda-forge/noarch/setuptools-66.0.0-pyhd8ed1ab_0.conda#88ec352fde1a5561ce5f93a302539f7f
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
 https://conda.anaconda.org/conda-forge/noarch/smmap-3.0.5-pyh44b312d_0.tar.bz2#3a8dc70789709aa315325d5df06fb7e4
 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
@@ -108,7 +108,7 @@ https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.30-pyhd8ed1ab_0.cond
 https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.0.0-hd8ed1ab_0.conda#a67d43e1527a37199dd8db913366f68e
 https://conda.anaconda.org/conda-forge/linux-64/libmambapy-1.1.0-py311h1f88262_3.conda#70e46c3567528b98e088e8033f4eaad6
 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54
-https://conda.anaconda.org/conda-forge/linux-64/pydantic-1.10.4-py311h2582759_0.conda#c8f72e2bc6d9ef667a881c0922967c0f
+https://conda.anaconda.org/conda-forge/linux-64/pydantic-1.10.4-py311h2582759_1.conda#34efc4cef8386e6264a5df924336a9e0
 https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.19.0-py311hbe0fcd7_1.conda#1f48d127dd18240505cba632b67f7931
 https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.7.0-pyhd8ed1ab_1.conda#1a2fa9e53cfbc2e4d9ab21990805a436
 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878
@@ -116,8 +116,8 @@ https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.3.3-py311h38be06
 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.1-py311h38be061_0.conda#46ee18791b2f786c1e052664b3842580
 https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.0.2-pyh38be061_0.conda#44800e9bd13143292097c65e57323038
 https://conda.anaconda.org/conda-forge/linux-64/keyring-23.13.1-py311h38be061_0.conda#0dc0127b1daefefa5e2caa49dde5c230
-https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
 https://conda.anaconda.org/conda-forge/noarch/cachecontrol-0.12.11-pyhd8ed1ab_0.tar.bz2#6eefee9888f33f150b5d44d616b1a613
 https://conda.anaconda.org/conda-forge/linux-64/conda-22.9.0-py311h38be061_2.tar.bz2#d083486f8b405adad834ba22b9cd7340
 https://conda.anaconda.org/conda-forge/noarch/ensureconda-1.4.3-pyhd8ed1ab_0.tar.bz2#c99ae3abf501990769047b4b40a98f17
diff --git a/.github/locks/osx-64_3.11_environment.conda.lock b/.github/locks/osx-64_3.11_environment.conda.lock
index ee5b71c5..7a94a7ac 100644
--- a/.github/locks/osx-64_3.11_environment.conda.lock
+++ b/.github/locks/osx-64_3.11_environment.conda.lock
@@ -18,9 +18,9 @@
 #   - pytest-html
 #   - pytest-xdist
 #   - requests-cache
+#   - robotframework-jupyterlibrary
 #   - robotframework-pabot
 #   - robotframework-robocop
-#   - robotframework-seleniumlibrary
 #   - robotframework-tidy
 #   - rope
 #   - stringcase
@@ -73,7 +73,7 @@ https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.17-hac89ed1_0.tar.bz2#6
 https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.18-hbcb3906_1.tar.bz2#24632c09ed931af617fe6d5292919cab
 https://conda.anaconda.org/conda-forge/osx-64/libuv-1.44.2-hac89ed1_0.tar.bz2#958fa9add5701462a6c91e3774425ea1
 https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-hfd90126_4.tar.bz2#35eb3fce8d51ed3c1fd4122bad48250b
-https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-15.0.6-h61d9ccf_0.conda#3a900993deb973d86d98361ceef49ab3
+https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-15.0.7-h61d9ccf_0.conda#3faa9933dff6e96333b5ca5274674b63
 https://conda.anaconda.org/conda-forge/osx-64/lzo-2.10-haf1e3a3_1000.tar.bz2#0b6bca372a95d6c602c7a922e928ce79
 https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.3-h96cf925_1.tar.bz2#76217ebfbb163ff2770a261f955a5861
 https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2#9a66894dfd07c4510beb6b3f9672ccc0
@@ -103,10 +103,10 @@ https://conda.anaconda.org/conda-forge/osx-64/krb5-1.20.1-h049b76e_0.conda#db11f
 https://conda.anaconda.org/conda-forge/osx-64/libgfortran-5.0.0-11_3_0_h97931a8_27.conda#7d25335e67256924aa04de681e68e807
 https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.51.0-he2ab024_0.conda#910e7f012beecf5d845a993feb9259a9
 https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.10.0-h47af595_3.tar.bz2#5a28624eeb7812b585b9e2d75f846ba2
-https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.10.3-hb9e07b5_1.conda#eb3935321cd42650512d09af480fee22
+https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.10.3-hb9e07b5_0.tar.bz2#13ba8bf8f44cdac2e5401dac20a36040
 https://conda.anaconda.org/conda-forge/osx-64/nodejs-18.12.1-hd0c9b3c_0.tar.bz2#e5ab86f2155244c022f8db0165bce009
-https://conda.anaconda.org/conda-forge/osx-64/python-3.11.0-h559f36b_0_cpython.tar.bz2#9eac7bb07be3725945c23c4ae90f9faa
-https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
+https://conda.anaconda.org/conda-forge/osx-64/python-3.11.0-he7542f4_1_cpython.conda#9ecfa530b33aefd0d22e0272336f638a
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e
 https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
 https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.3-pyhd8ed1ab_0.tar.bz2#54ac328d703bff191256ffa1183126d1
 https://conda.anaconda.org/conda-forge/noarch/async_generator-1.10-py_0.tar.bz2#d56c596e61b1c4952acf0a9920856c12
@@ -129,11 +129,11 @@ https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.
 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
 https://conda.anaconda.org/conda-forge/noarch/executing-1.2.0-pyhd8ed1ab_0.tar.bz2#4c1bc140e2be5c8ba6e3acab99e25c50
 https://conda.anaconda.org/conda-forge/noarch/flit-core-3.8.0-pyhd8ed1ab_0.tar.bz2#6d5e56de2e65da7aa35fd10131226efa
-https://conda.anaconda.org/conda-forge/noarch/future-0.18.2-pyhd8ed1ab_6.tar.bz2#a6966947ba28bbe60f9904653da7fed5
+https://conda.anaconda.org/conda-forge/noarch/future-0.18.3-pyhd8ed1ab_0.conda#fec8329fc739090f26a7d7803db254f1
 https://conda.anaconda.org/conda-forge/osx-64/greenlet-2.0.1-py311h814d153_0.tar.bz2#2b9b69231dae02dbf18c9f3e3b270417
 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed
 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
-https://conda.anaconda.org/conda-forge/noarch/importnb-2022.10.24-pyhd8ed1ab_0.tar.bz2#65e8d2add04a93849c09ab1b06130c13
+https://conda.anaconda.org/conda-forge/noarch/importnb-2023.1.7-pyhd8ed1ab_0.conda#4ff1d5888b17056732615d7975c254dd
 https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5
 https://conda.anaconda.org/conda-forge/noarch/ipython_genutils-0.2.0-py_1.tar.bz2#5071c982548b3a20caf70462f04f5287
 https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2#3c3de74912f11d2b590184f03c7cd09b
@@ -152,8 +152,8 @@ https://conda.anaconda.org/conda-forge/osx-64/mypy_extensions-0.4.3-py311h6eed73
 https://conda.anaconda.org/conda-forge/noarch/natsort-8.2.0-pyhd8ed1ab_0.tar.bz2#a8b2456513ed910ef418021afc5150bb
 https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.5.6-pyhd8ed1ab_0.tar.bz2#7b868f21adde0d9b8b38f9c16836589b
 https://conda.anaconda.org/conda-forge/noarch/networkx-3.0-pyhd8ed1ab_0.conda#88e40007414ea9a13f8df20fcffa87e2
-https://conda.anaconda.org/conda-forge/osx-64/orjson-3.8.3-py311h8b8394e_0.conda#eb2a49e63887c86ef1a49ff70ab3c831
-https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db
+https://conda.anaconda.org/conda-forge/osx-64/orjson-3.8.5-py311h8b8394e_1.conda#c3c00a60a41942766e6522b1c619a873
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
 https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2#457c2c8c08e54905d6954e79cb5b5db9
 https://conda.anaconda.org/conda-forge/noarch/parso-0.8.3-pyhd8ed1ab_0.tar.bz2#17a565a0c3899244e938cdf417e7b094
 https://conda.anaconda.org/conda-forge/noarch/pathspec-0.9.0-pyhd8ed1ab_0.tar.bz2#f93dc0ccbc0a8472624165f6e256c7d1
@@ -171,13 +171,13 @@ https://conda.anaconda.org/conda-forge/noarch/pyflakes-3.0.1-pyhd8ed1ab_0.conda#
 https://conda.anaconda.org/conda-forge/osx-64/pyrsistent-0.19.3-py311h5547dcb_0.conda#f114c0dab9f9c894b260297371124634
 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025
 https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.16.2-pyhd8ed1ab_0.tar.bz2#5fe4b6002f505336734ce92961b3e6a0
-https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2
 https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0-py311h5547dcb_5.tar.bz2#8d1e456914ce961119b07f396187a564
-https://conda.anaconda.org/conda-forge/osx-64/pyzmq-24.0.1-py311habfacb3_1.tar.bz2#b31f67f24aaec921cac4562917201138
+https://conda.anaconda.org/conda-forge/osx-64/pyzmq-25.0.0-py311habfacb3_0.conda#cb8d8ef71e5b342af325a860faabd0f9
 https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_0.tar.bz2#d337886e38f965bf97aaec382ff6db00
-https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_0.conda#cd9c28c39e4a899a1df100c2d94e01cf
+https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_1.conda#b4dcead8c5a99a14733d9b1ba5b7db09
 https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.0-pyhd8ed1ab_0.tar.bz2#edab14119efe85c3bf131ad747e9005c
-https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a
+https://conda.anaconda.org/conda-forge/noarch/setuptools-66.0.0-pyhd8ed1ab_0.conda#88ec352fde1a5561ce5f93a302539f7f
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
 https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2#dd6cbc539e74cb1f430efbd4575b9303
 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e
@@ -219,10 +219,10 @@ https://conda.anaconda.org/conda-forge/osx-64/coverage-7.0.5-py311h5547dcb_0.con
 https://conda.anaconda.org/conda-forge/osx-64/curl-7.87.0-h6df9250_0.conda#af1db2937ff4a1af6440630102dd18bc
 https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2#b21ed0883505ba1910994f1df031a428
 https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2#b2355343d6315c892543200231d7154a
-https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.62.0-pyha770c72_0.conda#0ba38faf3644821aec9e857b1f4f7a3c
+https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.62.1-pyha770c72_0.conda#fdb3c6a98a3c54e4f34d11a946ce93d8
 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62
 https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3
-https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_0.conda#cc1909c46e375f8d4e3eb9902b21fa6a
+https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_1.conda#0dbbfda0efc7fde25d97aa0df18767c2
 https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2#31e4a1506968d017229bdb64695013a1
 https://conda.anaconda.org/conda-forge/noarch/jedi-0.18.2-pyhd8ed1ab_0.conda#b5e695ef9c3f0d27d6cd96bf5adc9e07
 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
@@ -235,7 +235,7 @@ https://conda.anaconda.org/conda-forge/noarch/outcome-1.2.0-pyhd8ed1ab_0.tar.bz2
 https://conda.anaconda.org/conda-forge/noarch/pexpect-4.8.0-pyh1a96a4e_2.tar.bz2#330448ce4403cc74990ac07c555942a1
 https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d
 https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1
-https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c
+https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.1-pyhd8ed1ab_0.conda#f0be05afc9c9ab45e273c088e00c258b
 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
 https://conda.anaconda.org/conda-forge/osx-64/python-libarchive-c-4.0-py311h6eed73b_2.tar.bz2#3f638415e24cec2635e49949205c3d1a
 https://conda.anaconda.org/conda-forge/noarch/robotframework-pythonlibcore-4.0.0-pyhd8ed1ab_0.tar.bz2#e3dd7ba929a82a21c6e568c00d5b538c
@@ -253,7 +253,7 @@ https://conda.anaconda.org/conda-forge/osx-64/brotlipy-0.7.0-py311h5547dcb_1005.
 https://conda.anaconda.org/conda-forge/osx-64/cmarkgfm-0.8.0-py311h5547dcb_2.tar.bz2#69cd422f24f98fb4f8a9a896798bff06
 https://conda.anaconda.org/conda-forge/osx-64/cryptography-39.0.0-py311h61927ef_0.conda#b2893b3bd6cb56dbac5278a976fc0502
 https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_0.tar.bz2#fc5e53d070f1ee7bb38c2ece282dcb82
-https://conda.anaconda.org/conda-forge/osx-64/git-2.39.0-pl5321h0195497_0.conda#29c45dfbe0ba95305ac12ad7d74a715d
+https://conda.anaconda.org/conda-forge/osx-64/git-2.39.1-pl5321h0195497_0.conda#a52d6e4a846f6381b815ad3442101559
 https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.0.0-hd8ed1ab_0.conda#a67d43e1527a37199dd8db913366f68e
 https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda#723268a468177cd44568eb8f794e0d80
 https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.2.2-pyhd8ed1ab_0.tar.bz2#243f63592c8e449f40cd42eb5cf32f40
@@ -266,14 +266,14 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-cov-4.0.0-pyhd8ed1ab_0.tar.
 https://conda.anaconda.org/conda-forge/noarch/pytest-metadata-2.0.4-pyhd8ed1ab_0.tar.bz2#7ac02a65917993d38ca1bfd7b87208e4
 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9
 https://conda.anaconda.org/conda-forge/noarch/pytoolconfig-1.2.4-pyhd8ed1ab_1.conda#830a5eeda95b64fa74c30cf3d1b4d1ae
-https://conda.anaconda.org/conda-forge/noarch/rich-13.0.1-pyhd8ed1ab_0.conda#f6ed7a402e1378ca13bc0f3d8ec2763a
-https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.11.0-pyhd8ed1ab_0.conda#9e92b5ab35965c2d48110eca42cc3f9e
+https://conda.anaconda.org/conda-forge/noarch/rich-13.1.0-pyhd8ed1ab_0.conda#45901233f863e3b734c0916172c4cbdd
+https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.12.0-pyhd8ed1ab_0.conda#71b3c6e9c9f8757f6828c2466cdfc9d4
 https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.2-pyhd8ed1ab_0.conda#e7df0fdd404616638df5ece6e69ba7af
 https://conda.anaconda.org/conda-forge/osx-64/trio-0.22.0-py311h6eed73b_1.tar.bz2#71a739c4b0b1e17cfd27cfdf828d52a8
-https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.5-pyh9f0ad1d_2.tar.bz2#5266fcd697043c59621fda522b3d78ee
+https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda#078979d33523cb477bd1916ce41aacc9
 https://conda.anaconda.org/conda-forge/noarch/wsproto-1.2.0-pyhd8ed1ab_0.tar.bz2#00ba804b54f451d102f6a7615f08470d
 https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-21.3.0-pyhd8ed1ab_0.tar.bz2#a0b402db58f73aaab8ee0ca1025a362e
-https://conda.anaconda.org/conda-forge/osx-64/black-22.10.0-py311h6eed73b_2.tar.bz2#ba682d521bf66779ec72ce63d3636197
+https://conda.anaconda.org/conda-forge/osx-64/black-22.12.0-py311h6eed73b_0.conda#347714cfb46a4fa7d59af28b557ea97f
 https://conda.anaconda.org/conda-forge/osx-64/jupyter_core-5.1.3-py311h6eed73b_0.conda#c674d24cf47b228c060a7b52e5f7d9a4
 https://conda.anaconda.org/conda-forge/osx-64/keyring-23.13.1-py311h6eed73b_0.conda#ae2618c59c8f3481aa7014de862c6d57
 https://conda.anaconda.org/conda-forge/osx-64/numpy-1.24.1-py311h62c7003_0.conda#b1994c9a248f5a0b45b641f185dbaede
@@ -285,36 +285,37 @@ https://conda.anaconda.org/conda-forge/noarch/rich-click-1.4-pyhd8ed1ab_0.tar.bz
 https://conda.anaconda.org/conda-forge/noarch/rope-1.6.0-pyhd8ed1ab_0.conda#c10a8588df896961f69e6887727fe167
 https://conda.anaconda.org/conda-forge/noarch/trio-websocket-0.9.2-pyhd8ed1ab_0.tar.bz2#7acf3fc982eef23b24aed10e80d5dc99
 https://conda.anaconda.org/conda-forge/noarch/ipython-8.8.0-pyhd1c38e8_0.conda#92012d3aee304230e25f564e7746e8dd
-https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.8-pyhd8ed1ab_0.conda#bd4951aa5fbf3336a249e25c90f50333
-https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.1-pyhd8ed1ab_0.conda#facb42913140f1a399a5ebb65b30915e
+https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.9-pyhd8ed1ab_0.conda#5cbf9a31a19d4ef9103adb7d71fd45fd
+https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.3-pyhd8ed1ab_0.conda#9714111cb6c7dbbc9a9f34de205c2f29
 https://conda.anaconda.org/conda-forge/osx-64/numcodecs-0.11.0-py311h814d153_1.conda#21f2ae35161c19b8c4ad0791d12ef2a3
 https://conda.anaconda.org/conda-forge/osx-64/pandas-1.5.2-py311hd84f3f5_2.conda#2952f7263a51d4286f1ff168887a5715
-https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.2-pyhd8ed1ab_0.tar.bz2#354a5c374999c2688332c073e2450951
+https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.3-pyhd8ed1ab_0.conda#bc4e21b26f0e5bb7517c5d6200912d35
 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
-https://conda.anaconda.org/conda-forge/noarch/black-jupyter-22.10.0-hd8ed1ab_2.tar.bz2#80112c21a14035fe1d786f082ce27978
-https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.20.1-pyh736e0ef_0.conda#4a1acae5e2868bb5ee67359ac69ad759
+https://conda.anaconda.org/conda-forge/noarch/black-jupyter-22.12.0-hd8ed1ab_0.conda#421077052ae1530402ee4c78ba7939bb
+https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.20.2-pyh736e0ef_0.conda#d6682923224428bb5c9910ff333c9261
 https://conda.anaconda.org/conda-forge/noarch/nbclient-0.5.13-pyhd8ed1ab_0.tar.bz2#3edde88a191701cf052216c4ba353a83
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
 https://conda.anaconda.org/conda-forge/noarch/selenium-4.7.2-pyhd8ed1ab_0.conda#4f6238a5e80996ee0bbee28b487aada5
 https://conda.anaconda.org/conda-forge/noarch/flit-3.8.0-pyhd8ed1ab_0.tar.bz2#d37c34176396c5402cf80c32e67731b7
 https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.0.4-pyhd8ed1ab_0.conda#9dea5ab3cc33084f7a3680a98859731e
 https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-0.5.0-pyhd8ed1ab_0.tar.bz2#293ede7de6b604f3586efbfa615ee2e1
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.7-pyhd8ed1ab_0.conda#dce063b1f5d6193646fc2ac43f88b77f
-https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.7-pyhd8ed1ab_0.conda#399c98b39eb6bdc4b1d40b7611a6d384
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.8-pyhd8ed1ab_0.conda#252899fe1f90aeb0c3391ff32a5035f1
+https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.8-pyhd8ed1ab_0.conda#b0af82591adbf1d403fb4d061a8ca940
 https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-0.10.1-pyhd8ed1ab_0.tar.bz2#a4cd20af9711434f89d1ec0d2b3ae6ba
 https://conda.anaconda.org/conda-forge/noarch/robotframework-seleniumlibrary-6.0.0-pyhd8ed1ab_0.tar.bz2#2acaef9c04081dc3644501ac575953bf
 https://conda.anaconda.org/conda-forge/noarch/sphinx-5.3.0-pyhd8ed1ab_0.tar.bz2#f9e1fcfe235d655900bfeb6aee426472
 https://conda.anaconda.org/conda-forge/noarch/bqplot-0.12.36-pyhd8ed1ab_0.tar.bz2#bff49231f6b67c01a7a89d56ca0693ab
-https://conda.anaconda.org/conda-forge/noarch/jupyter_server-1.23.4-pyhd8ed1ab_0.conda#35b7e9267926e864cc70ce137e6588ca
+https://conda.anaconda.org/conda-forge/noarch/jupyter_server-1.23.5-pyhd8ed1ab_0.conda#e3ae1d46749f48a1875b560fca41cb0c
 https://conda.anaconda.org/conda-forge/noarch/myst-parser-0.18.1-pyhd8ed1ab_0.tar.bz2#bcfdf5c7d8bf5c6f6be7b4c66fff2eca
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.7-pyhd8ed1ab_0.conda#7d48776bb3fdfa6447267c413a480ccd
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.8-pyhd8ed1ab_0.conda#701b0db88fe192e1685de29b8ca0840e
 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2
+https://conda.anaconda.org/conda-forge/noarch/robotframework-jupyterlibrary-0.4.2-pyhd8ed1ab_0.conda#6687f0def03dc3db713149fb8e026068
 https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2021.3.14-pyhd8ed1ab_0.tar.bz2#1909f784dc37b4ab97afe2c95aeeabaa
-https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.20.1-pyhd8ed1ab_0.conda#1364c5199876bb381ba1ad004027e11c
+https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.23.4-pyhd8ed1ab_0.conda#f2f2d8c21ec1933c039cc321a00bc10b
 https://conda.anaconda.org/conda-forge/noarch/twine-4.0.2-pyhd8ed1ab_0.conda#e3a16168d6b9deefb8c1caa7943fb49e
-https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.18.0-pyhd8ed1ab_0.conda#be95d2cc7614462440e4e10b5b373067
+https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.19.0-pyhd8ed1ab_0.conda#9dc0706ec2f8e13b7a0db9d6a19051ff
 https://conda.anaconda.org/conda-forge/noarch/myst-nb-0.17.1-pyhd8ed1ab_0.tar.bz2#8a11a646076b72bcaa71328f07c1aaab
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.7-pyhd8ed1ab_0.conda#b019fe41e6f6b88e0557532514f39cff
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.8-pyhd8ed1ab_0.conda#8b5cb7073f66beef0d7161406fdb6e8b
 https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.2-pyhd8ed1ab_0.tar.bz2#40be846cd4e78672a40e43db9dae753c
 https://conda.anaconda.org/conda-forge/noarch/nbclassic-0.4.8-pyhd8ed1ab_0.tar.bz2#f4f150f83ed90a1b833e6081bbf38257
 https://conda.anaconda.org/conda-forge/noarch/pytest-check-links-0.8.0-pyhd8ed1ab_0.conda#639513b3fc51ee25e00a423fa430a2da
diff --git a/.github/locks/osx-64_3.7_environment.conda.lock b/.github/locks/osx-64_3.7_environment.conda.lock
index 5556222c..7b59e78a 100644
--- a/.github/locks/osx-64_3.7_environment.conda.lock
+++ b/.github/locks/osx-64_3.7_environment.conda.lock
@@ -18,9 +18,9 @@
 #   - pytest-html
 #   - pytest-xdist
 #   - requests-cache
+#   - robotframework-jupyterlibrary
 #   - robotframework-pabot
 #   - robotframework-robocop
-#   - robotframework-seleniumlibrary
 #   - robotframework-tidy
 #   - rope
 #   - stringcase
@@ -76,7 +76,7 @@ https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.18-hbcb3906_1.tar.bz
 https://conda.anaconda.org/conda-forge/osx-64/libuv-1.44.2-hac89ed1_0.tar.bz2#958fa9add5701462a6c91e3774425ea1
 https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.2.4-h775f41a_0.tar.bz2#28807bef802a354f9c164e7ab242c5cb
 https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-hfd90126_4.tar.bz2#35eb3fce8d51ed3c1fd4122bad48250b
-https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-15.0.6-h61d9ccf_0.conda#3a900993deb973d86d98361ceef49ab3
+https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-15.0.7-h61d9ccf_0.conda#3faa9933dff6e96333b5ca5274674b63
 https://conda.anaconda.org/conda-forge/osx-64/lzo-2.10-haf1e3a3_1000.tar.bz2#0b6bca372a95d6c602c7a922e928ce79
 https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.3-h96cf925_1.tar.bz2#76217ebfbb163ff2770a261f955a5861
 https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2#9a66894dfd07c4510beb6b3f9672ccc0
@@ -117,7 +117,7 @@ https://conda.anaconda.org/conda-forge/osx-64/libgfortran-5.0.0-11_3_0_h97931a8_
 https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.51.0-he2ab024_0.conda#910e7f012beecf5d845a993feb9259a9
 https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.10.0-h47af595_3.tar.bz2#5a28624eeb7812b585b9e2d75f846ba2
 https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.4.0-h6268bbc_5.conda#551fc78ba147767ea5905d0746e2fbb5
-https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.10.3-hb9e07b5_1.conda#eb3935321cd42650512d09af480fee22
+https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.10.3-hb9e07b5_0.tar.bz2#13ba8bf8f44cdac2e5401dac20a36040
 https://conda.anaconda.org/conda-forge/osx-64/nodejs-18.12.1-hd0c9b3c_0.tar.bz2#e5ab86f2155244c022f8db0165bce009
 https://conda.anaconda.org/conda-forge/osx-64/sqlite-3.40.0-h9ae0607_0.tar.bz2#b66b0b11f1b901f3c2bce9406bedfd40
 https://conda.anaconda.org/conda-forge/osx-64/brotli-1.0.9-hb7f2c08_8.tar.bz2#55f612fe4a9b5f6ac76348b6de94aaeb
@@ -127,7 +127,7 @@ https://conda.anaconda.org/conda-forge/osx-64/libcurl-7.87.0-h6df9250_0.conda#10
 https://conda.anaconda.org/conda-forge/osx-64/libopenblas-0.3.21-openmp_h429af6e_3.tar.bz2#968c46aa7f4032c3f3873f3452ed4c34
 https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.0-h5d0d7b0_1.tar.bz2#be533cc782981a0ec5eed28aa618470a
 https://conda.anaconda.org/conda-forge/osx-64/python-3.7.12-hf3644f1_100_cpython.tar.bz2#535981b387b6ca0396b31ae9add5d995
-https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e
 https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
 https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.3-pyhd8ed1ab_0.tar.bz2#54ac328d703bff191256ffa1183126d1
 https://conda.anaconda.org/conda-forge/noarch/async_generator-1.10-py_0.tar.bz2#d56c596e61b1c4952acf0a9920856c12
@@ -153,7 +153,7 @@ https://conda.anaconda.org/conda-forge/osx-64/future-0.18.2-py37hf985489_5.tar.b
 https://conda.anaconda.org/conda-forge/osx-64/greenlet-1.1.3-py37hac51a3e_0.tar.bz2#4eff162bb0413f208bd8502edbfa4b25
 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed
 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
-https://conda.anaconda.org/conda-forge/noarch/importnb-2022.10.24-pyhd8ed1ab_0.tar.bz2#65e8d2add04a93849c09ab1b06130c13
+https://conda.anaconda.org/conda-forge/noarch/importnb-2023.1.7-pyhd8ed1ab_0.conda#4ff1d5888b17056732615d7975c254dd
 https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5
 https://conda.anaconda.org/conda-forge/noarch/ipython_genutils-0.2.0-py_1.tar.bz2#5071c982548b3a20caf70462f04f5287
 https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2#3c3de74912f11d2b590184f03c7cd09b
@@ -172,7 +172,7 @@ https://conda.anaconda.org/conda-forge/osx-64/mypy_extensions-0.4.3-py37hf985489
 https://conda.anaconda.org/conda-forge/noarch/natsort-8.2.0-pyhd8ed1ab_0.tar.bz2#a8b2456513ed910ef418021afc5150bb
 https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.5.6-pyhd8ed1ab_0.tar.bz2#7b868f21adde0d9b8b38f9c16836589b
 https://conda.anaconda.org/conda-forge/osx-64/orjson-3.8.1-py37h8052db5_0.tar.bz2#e6ac5703d3e21e9ab11df6c2ef182762
-https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
 https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2#457c2c8c08e54905d6954e79cb5b5db9
 https://conda.anaconda.org/conda-forge/noarch/parso-0.8.3-pyhd8ed1ab_0.tar.bz2#17a565a0c3899244e938cdf417e7b094
 https://conda.anaconda.org/conda-forge/noarch/pathspec-0.9.0-pyhd8ed1ab_0.tar.bz2#f93dc0ccbc0a8472624165f6e256c7d1
@@ -191,11 +191,11 @@ https://conda.anaconda.org/conda-forge/osx-64/pyrsistent-0.18.1-py37h69ee0a8_1.t
 https://conda.anaconda.org/conda-forge/osx-64/pysocks-1.7.1-py37hf985489_5.tar.bz2#e3a96bc60c8dd67efa0b18f81db028b8
 https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.16.2-pyhd8ed1ab_0.tar.bz2#5fe4b6002f505336734ce92961b3e6a0
 https://conda.anaconda.org/conda-forge/osx-64/python-libarchive-c-4.0-py37hf985489_1.tar.bz2#cf43b347ace29aaf94b89d922d5c47f0
-https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2
 https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0-py37h69ee0a8_4.tar.bz2#c22ba296e13338f77db4bdb683ddc010
 https://conda.anaconda.org/conda-forge/osx-64/pyzmq-24.0.1-py37haa7bc41_0.tar.bz2#6bdd853e3f83fb0cd62f16d2452801d1
 https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_0.tar.bz2#d337886e38f965bf97aaec382ff6db00
-https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_0.conda#cd9c28c39e4a899a1df100c2d94e01cf
+https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_1.conda#b4dcead8c5a99a14733d9b1ba5b7db09
 https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.0-pyhd8ed1ab_0.tar.bz2#edab14119efe85c3bf131ad747e9005c
 https://conda.anaconda.org/conda-forge/osx-64/setuptools-59.8.0-py37hf985489_1.tar.bz2#f805359f82917bc580a375583e367bb1
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
@@ -238,12 +238,12 @@ https://conda.anaconda.org/conda-forge/noarch/commonmark-0.9.1-py_0.tar.bz2#6aa0
 https://conda.anaconda.org/conda-forge/osx-64/coverage-6.5.0-py37h8052db5_0.tar.bz2#1373a8abff842b30928144bf2f5c0aae
 https://conda.anaconda.org/conda-forge/osx-64/doit-0.34.2-py37hf985489_0.tar.bz2#40c5b1874e3410068276419f0d722ba8
 https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.38.0-py37h8052db5_0.tar.bz2#6ae74c302be2ed90eae8f8f8f9b4f4eb
-https://conda.anaconda.org/conda-forge/osx-64/git-2.39.0-pl5321h0195497_0.conda#29c45dfbe0ba95305ac12ad7d74a715d
+https://conda.anaconda.org/conda-forge/osx-64/git-2.39.1-pl5321h0195497_0.conda#a52d6e4a846f6381b815ad3442101559
 https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2#b21ed0883505ba1910994f1df031a428
 https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2#b2355343d6315c892543200231d7154a
 https://conda.anaconda.org/conda-forge/osx-64/importlib-metadata-4.11.4-py37hf985489_0.tar.bz2#5433686b318d1725f4783d0c08523566
 https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3
-https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_0.conda#cc1909c46e375f8d4e3eb9902b21fa6a
+https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_1.conda#0dbbfda0efc7fde25d97aa0df18767c2
 https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2#31e4a1506968d017229bdb64695013a1
 https://conda.anaconda.org/conda-forge/noarch/jedi-0.18.2-pyhd8ed1ab_0.conda#b5e695ef9c3f0d27d6cd96bf5adc9e07
 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
@@ -275,25 +275,25 @@ https://conda.anaconda.org/conda-forge/osx-64/cmarkgfm-0.8.0-py37h69ee0a8_1.tar.
 https://conda.anaconda.org/conda-forge/osx-64/cryptography-38.0.2-py37hbf3704f_1.tar.bz2#34b7d946b916bfe87df2b141d2f7736f
 https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-4.11.4-hd8ed1ab_0.tar.bz2#9a1925fdb91c81437b8012e48ede6851
 https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda#723268a468177cd44568eb8f794e0d80
-https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.8-pyhd8ed1ab_0.conda#bd4951aa5fbf3336a249e25c90f50333
+https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.9-pyhd8ed1ab_0.conda#5cbf9a31a19d4ef9103adb7d71fd45fd
 https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.2.2-pyhd8ed1ab_0.tar.bz2#243f63592c8e449f40cd42eb5cf32f40
 https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.4.4-py37h229a17a_0.tar.bz2#542ec4f3e23b103f9fcafb9ab26e859c
 https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.3.3-pyhd8ed1ab_0.conda#7a69c057183706bc7ba6ac7c9130566a
 https://conda.anaconda.org/conda-forge/osx-64/numpy-1.21.6-py37h345d48f_0.tar.bz2#2aaa4d38d44a9a69f64bf757a5531165
 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54
 https://conda.anaconda.org/conda-forge/noarch/pytoolconfig-1.2.4-pyhd8ed1ab_1.conda#830a5eeda95b64fa74c30cf3d1b4d1ae
-https://conda.anaconda.org/conda-forge/noarch/rich-13.0.1-pyhd8ed1ab_0.conda#f6ed7a402e1378ca13bc0f3d8ec2763a
-https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.11.0-pyhd8ed1ab_0.conda#9e92b5ab35965c2d48110eca42cc3f9e
+https://conda.anaconda.org/conda-forge/noarch/rich-13.1.0-pyhd8ed1ab_0.conda#45901233f863e3b734c0916172c4cbdd
+https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.12.0-pyhd8ed1ab_0.conda#71b3c6e9c9f8757f6828c2466cdfc9d4
 https://conda.anaconda.org/conda-forge/osx-64/sqlalchemy-1.4.42-py37h8052db5_0.tar.bz2#46b721181843590249cbf47e587dfebe
 https://conda.anaconda.org/conda-forge/osx-64/trio-0.21.0-py37hf985489_0.tar.bz2#5c48a7515890f04d45a83015096f5200
-https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.5-pyh9f0ad1d_2.tar.bz2#5266fcd697043c59621fda522b3d78ee
+https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda#078979d33523cb477bd1916ce41aacc9
 https://conda.anaconda.org/conda-forge/noarch/wsproto-1.2.0-pyhd8ed1ab_0.tar.bz2#00ba804b54f451d102f6a7615f08470d
 https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-21.3.0-pyhd8ed1ab_0.tar.bz2#a0b402db58f73aaab8ee0ca1025a362e
 https://conda.anaconda.org/conda-forge/osx-64/black-22.10.0-py37hf985489_1.tar.bz2#14b72e6a62afd5cc960aa8d91e57b260
 https://conda.anaconda.org/conda-forge/osx-64/hypothesis-6.56.3-py37hf985489_1.tar.bz2#677c981a361a25a1be2a2890786b867d
 https://conda.anaconda.org/conda-forge/osx-64/keyring-23.9.3-py37hf985489_0.tar.bz2#adf5d27db8dbbde19e274de351295805
 https://conda.anaconda.org/conda-forge/osx-64/matplotlib-base-3.5.3-py37h3748cd6_2.tar.bz2#ad309097b34ab82b42bc8eac0668d226
-https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.1-pyhd8ed1ab_0.conda#facb42913140f1a399a5ebb65b30915e
+https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.3-pyhd8ed1ab_0.conda#9714111cb6c7dbbc9a9f34de205c2f29
 https://conda.anaconda.org/conda-forge/osx-64/numcodecs-0.10.2-py37hf6dfe07_0.tar.bz2#f836a75499b39404051c1c0efbfb5141
 https://conda.anaconda.org/conda-forge/osx-64/pandas-1.3.5-py37h5b83a90_0.tar.bz2#f5b03c55f0d4bd06495b726ed574412c
 https://conda.anaconda.org/conda-forge/osx-64/pluggy-1.0.0-py37hf985489_3.tar.bz2#c01a618055fe641f37b92e55b9104674
@@ -308,35 +308,36 @@ https://conda.anaconda.org/conda-forge/osx-64/ipython-7.33.0-py37hf985489_0.tar.
 https://conda.anaconda.org/conda-forge/noarch/nbclient-0.5.13-pyhd8ed1ab_0.tar.bz2#3edde88a191701cf052216c4ba353a83
 https://conda.anaconda.org/conda-forge/noarch/networkx-2.7-pyhd8ed1ab_0.tar.bz2#9d430f94458637bbb2f5e78526324412
 https://conda.anaconda.org/conda-forge/osx-64/pytest-7.2.0-py37hf985489_0.tar.bz2#e26d95efe84901a52eb7ccfd1bbf3712
-https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.2-pyhd8ed1ab_0.tar.bz2#354a5c374999c2688332c073e2450951
+https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.3-pyhd8ed1ab_0.conda#bc4e21b26f0e5bb7517c5d6200912d35
 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
 https://conda.anaconda.org/conda-forge/noarch/black-jupyter-22.10.0-hd8ed1ab_2.tar.bz2#80112c21a14035fe1d786f082ce27978
 https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.16.2-pyh736e0ef_0.tar.bz2#35bc6846dabf976973cf960cd5fe2148
 https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-0.5.0-pyhd8ed1ab_0.tar.bz2#293ede7de6b604f3586efbfa615ee2e1
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.7-pyhd8ed1ab_0.conda#dce063b1f5d6193646fc2ac43f88b77f
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.8-pyhd8ed1ab_0.conda#252899fe1f90aeb0c3391ff32a5035f1
 https://conda.anaconda.org/conda-forge/noarch/pytest-asyncio-0.20.3-pyhd8ed1ab_0.conda#aa462bf71d7b327dcc00ae523f8923ef
 https://conda.anaconda.org/conda-forge/noarch/pytest-cov-4.0.0-pyhd8ed1ab_0.tar.bz2#c9e3f8bfdb9bfc34aa1836a6ed4b25d7
 https://conda.anaconda.org/conda-forge/noarch/pytest-metadata-2.0.4-pyhd8ed1ab_0.tar.bz2#7ac02a65917993d38ca1bfd7b87208e4
 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
 https://conda.anaconda.org/conda-forge/noarch/selenium-4.7.2-pyhd8ed1ab_0.conda#4f6238a5e80996ee0bbee28b487aada5
 https://conda.anaconda.org/conda-forge/noarch/flit-3.8.0-pyhd8ed1ab_0.tar.bz2#d37c34176396c5402cf80c32e67731b7
 https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.0.4-pyhd8ed1ab_0.conda#9dea5ab3cc33084f7a3680a98859731e
 https://conda.anaconda.org/conda-forge/noarch/jupyter_server-1.23.4-pyhd8ed1ab_0.conda#35b7e9267926e864cc70ce137e6588ca
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.7-pyhd8ed1ab_0.conda#7d48776bb3fdfa6447267c413a480ccd
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.8-pyhd8ed1ab_0.conda#701b0db88fe192e1685de29b8ca0840e
 https://conda.anaconda.org/conda-forge/noarch/pytest-html-3.2.0-pyhd8ed1ab_1.tar.bz2#d5c7a941dfbceaab4b172a56d7918eb0
-https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.7-pyhd8ed1ab_0.conda#399c98b39eb6bdc4b1d40b7611a6d384
+https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.8-pyhd8ed1ab_0.conda#b0af82591adbf1d403fb4d061a8ca940
 https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-0.10.1-pyhd8ed1ab_0.tar.bz2#a4cd20af9711434f89d1ec0d2b3ae6ba
 https://conda.anaconda.org/conda-forge/noarch/robotframework-seleniumlibrary-6.0.0-pyhd8ed1ab_0.tar.bz2#2acaef9c04081dc3644501ac575953bf
 https://conda.anaconda.org/conda-forge/noarch/sphinx-5.3.0-pyhd8ed1ab_0.tar.bz2#f9e1fcfe235d655900bfeb6aee426472
 https://conda.anaconda.org/conda-forge/noarch/bqplot-0.12.36-pyhd8ed1ab_0.tar.bz2#bff49231f6b67c01a7a89d56ca0693ab
-https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.18.0-pyhd8ed1ab_0.conda#be95d2cc7614462440e4e10b5b373067
+https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.19.0-pyhd8ed1ab_0.conda#9dc0706ec2f8e13b7a0db9d6a19051ff
 https://conda.anaconda.org/conda-forge/noarch/myst-parser-0.18.1-pyhd8ed1ab_0.tar.bz2#bcfdf5c7d8bf5c6f6be7b4c66fff2eca
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.7-pyhd8ed1ab_0.conda#b019fe41e6f6b88e0557532514f39cff
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.8-pyhd8ed1ab_0.conda#8b5cb7073f66beef0d7161406fdb6e8b
 https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.2-pyhd8ed1ab_0.tar.bz2#40be846cd4e78672a40e43db9dae753c
 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2
+https://conda.anaconda.org/conda-forge/noarch/robotframework-jupyterlibrary-0.4.2-pyhd8ed1ab_0.conda#6687f0def03dc3db713149fb8e026068
 https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2021.3.14-pyhd8ed1ab_0.tar.bz2#1909f784dc37b4ab97afe2c95aeeabaa
-https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.20.1-pyhd8ed1ab_0.conda#1364c5199876bb381ba1ad004027e11c
+https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.23.4-pyhd8ed1ab_0.conda#f2f2d8c21ec1933c039cc321a00bc10b
 https://conda.anaconda.org/conda-forge/noarch/twine-4.0.2-pyhd8ed1ab_0.conda#e3a16168d6b9deefb8c1caa7943fb49e
 https://conda.anaconda.org/conda-forge/noarch/myst-nb-0.17.1-pyhd8ed1ab_0.tar.bz2#8a11a646076b72bcaa71328f07c1aaab
 https://conda.anaconda.org/conda-forge/noarch/nbclassic-0.4.8-pyhd8ed1ab_0.tar.bz2#f4f150f83ed90a1b833e6081bbf38257
diff --git a/.github/locks/osx-64_lock-environment.conda.lock b/.github/locks/osx-64_lock-environment.conda.lock
index 31e7949a..dfa93dc1 100644
--- a/.github/locks/osx-64_lock-environment.conda.lock
+++ b/.github/locks/osx-64_lock-environment.conda.lock
@@ -42,7 +42,7 @@ https://conda.anaconda.org/conda-forge/osx-64/krb5-1.20.1-h049b76e_0.conda#db11f
 https://conda.anaconda.org/conda-forge/osx-64/libnghttp2-1.51.0-he2ab024_0.conda#910e7f012beecf5d845a993feb9259a9
 https://conda.anaconda.org/conda-forge/osx-64/libssh2-1.10.0-h47af595_3.tar.bz2#5a28624eeb7812b585b9e2d75f846ba2
 https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.10.3-hb9e07b5_0.tar.bz2#13ba8bf8f44cdac2e5401dac20a36040
-https://conda.anaconda.org/conda-forge/osx-64/python-3.11.0-h559f36b_0_cpython.tar.bz2#9eac7bb07be3725945c23c4ae90f9faa
+https://conda.anaconda.org/conda-forge/osx-64/python-3.11.0-he7542f4_1_cpython.conda#9ecfa530b33aefd0d22e0272336f638a
 https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
 https://conda.anaconda.org/conda-forge/noarch/cachy-0.3.0-pyhd8ed1ab_1.tar.bz2#5dfee17f24e2dfd18d7392b48c9351e2
 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6
@@ -57,7 +57,7 @@ https://conda.anaconda.org/conda-forge/osx-64/libcurl-7.87.0-h6df9250_0.conda#10
 https://conda.anaconda.org/conda-forge/osx-64/markupsafe-2.1.1-py311h5547dcb_2.tar.bz2#cd912231195461aa21aa4e8f1eff1276
 https://conda.anaconda.org/conda-forge/noarch/more-itertools-9.0.0-pyhd8ed1ab_0.tar.bz2#9b6ad26944f19f599800b068e0582227
 https://conda.anaconda.org/conda-forge/osx-64/msgpack-python-1.0.4-py311hd2070f0_1.tar.bz2#e91973b43c03d74de80de087ff46e393
-https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
 https://conda.anaconda.org/conda-forge/noarch/pastel-0.2.1-pyhd8ed1ab_0.tar.bz2#a4eea5bff523f26442405bc5d1f52adb
 https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.9.6-pyhd8ed1ab_0.conda#be1e9f1c65a1ed0f2ae9352fec99db64
 https://conda.anaconda.org/conda-forge/osx-64/pycosat-0.6.4-py311h5547dcb_1.tar.bz2#229e642050372d50ebdc24e2265ce8b2
@@ -67,7 +67,7 @@ https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2
 https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0-py311h5547dcb_5.tar.bz2#8d1e456914ce961119b07f396187a564
 https://conda.anaconda.org/conda-forge/osx-64/ruamel.yaml.clib-0.2.7-py311h5547dcb_1.conda#fdae97fc41b9e4aa53d644cca8ba6c54
 https://conda.anaconda.org/conda-forge/osx-64/ruamel_yaml-0.15.80-py311h5547dcb_1008.tar.bz2#42c63993b1aaba9dc3c69a36e368d11c
-https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a
+https://conda.anaconda.org/conda-forge/noarch/setuptools-66.0.0-pyhd8ed1ab_0.conda#88ec352fde1a5561ce5f93a302539f7f
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
 https://conda.anaconda.org/conda-forge/noarch/smmap-3.0.5-pyh44b312d_0.tar.bz2#3a8dc70789709aa315325d5df06fb7e4
 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
@@ -94,15 +94,15 @@ https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.30-pyhd8ed1ab_0.cond
 https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.0.0-hd8ed1ab_0.conda#a67d43e1527a37199dd8db913366f68e
 https://conda.anaconda.org/conda-forge/osx-64/libmambapy-1.1.0-py311hcc19a12_3.conda#fc1b3c8a2451ea2c679e6332bfd5b52e
 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54
-https://conda.anaconda.org/conda-forge/osx-64/pydantic-1.10.4-py311h5547dcb_0.conda#0601ab2dab4e5c0542bbdc8d1cbb7b62
+https://conda.anaconda.org/conda-forge/osx-64/pydantic-1.10.4-py311h5547dcb_1.conda#8ea0ced2a3d36006ba337f41e628ad3a
 https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.19.0-py311hebd4beb_1.conda#b0d4f55c803038fd429494acf684e6ef
 https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.7.0-pyhd8ed1ab_1.conda#1a2fa9e53cfbc2e4d9ab21990805a436
 https://conda.anaconda.org/conda-forge/osx-64/keyring-23.13.1-py311h6eed73b_0.conda#ae2618c59c8f3481aa7014de862c6d57
 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878
 https://conda.anaconda.org/conda-forge/osx-64/virtualenv-20.17.1-py311h6eed73b_0.conda#e1d0ae9173b110079196d1ddbe2d3b24
 https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.0.2-pyh38be061_0.conda#44800e9bd13143292097c65e57323038
-https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
 https://conda.anaconda.org/conda-forge/noarch/cachecontrol-0.12.11-pyhd8ed1ab_0.tar.bz2#6eefee9888f33f150b5d44d616b1a613
 https://conda.anaconda.org/conda-forge/osx-64/conda-22.9.0-py311h6eed73b_2.tar.bz2#d18ef2c39925384887a22371e00b1160
 https://conda.anaconda.org/conda-forge/noarch/ensureconda-1.4.3-pyhd8ed1ab_0.tar.bz2#c99ae3abf501990769047b4b40a98f17
diff --git a/.github/locks/win-64_3.11_environment.conda.lock b/.github/locks/win-64_3.11_environment.conda.lock
index 591e5e04..6a93dfbe 100644
--- a/.github/locks/win-64_3.11_environment.conda.lock
+++ b/.github/locks/win-64_3.11_environment.conda.lock
@@ -18,9 +18,9 @@
 #   - pytest-html
 #   - pytest-xdist
 #   - requests-cache
+#   - robotframework-jupyterlibrary
 #   - robotframework-pabot
 #   - robotframework-robocop
-#   - robotframework-seleniumlibrary
 #   - robotframework-tidy
 #   - rope
 #   - stringcase
@@ -64,7 +64,7 @@
 
 @EXPLICIT
 https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2022.12.7-h5b45459_0.conda#31de4d9887dc8eaed9e6230a5dfbb9d6
-https://conda.anaconda.org/conda-forge/win-64/git-2.39.0-h57928b3_0.conda#40b9d7db4ad079a60271a7a36219f8ba
+https://conda.anaconda.org/conda-forge/win-64/git-2.39.1-h57928b3_0.conda#7e36f1af1e3c612f4506e93f0ce0b063
 https://conda.anaconda.org/conda-forge/noarch/llvm-meta-5.0.0-0.tar.bz2#213b5b5ad34008147a824460e50a691c
 https://conda.anaconda.org/conda-forge/win-64/nodejs-18.12.1-h57928b3_0.tar.bz2#89700013434ea5e61b78cbed8687c563
 https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2#9a66894dfd07c4510beb6b3f9672ccc0
@@ -73,8 +73,8 @@ https://conda.anaconda.org/conda-forge/win-64/python_abi-3.11-3_cp311.conda#fd16
 https://conda.anaconda.org/conda-forge/noarch/tzdata-2022g-h191b570_0.conda#51fc4fcfb19f5d95ffc8c339db5068e8
 https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2#72608f6cd3e5898229c3ea16deb1ac43
 https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2#1cee351bf20b830d991dbe0bc8cd7dfe
-https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.32.31332-h1d6e394_9.tar.bz2#c98b6e39006315599b793592bcc3c978
-https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h3d8a991_9.tar.bz2#ba28983ef4f6d430827d0e7c5cdd7b48
+https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.34.31931-h4c5c07a_10.conda#25640086ba777e79e5d233d079d7c5fc
+https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hb6edc58_10.conda#52d246d8d14b83c516229be5bb03a163
 https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h8ffe710_4.tar.bz2#7c03c66026944073040cb19a4f3ec3c9
 https://conda.anaconda.org/conda-forge/win-64/firefox-102.6.0esr-h63175ca_0.conda#60e0376658541caea85e1abecb107a83
 https://conda.anaconda.org/conda-forge/win-64/geckodriver-0.32.0-h611cf2b_0.tar.bz2#97b5d23b319be2f712581eda0c0abccd
@@ -91,11 +91,11 @@ https://conda.anaconda.org/conda-forge/win-64/tk-8.6.12-h8ffe710_0.tar.bz2#c69a5
 https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2#515d77642eaa3639413c6b1bc3f94219
 https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h8ffe710_2.tar.bz2#adbfb9f45d1004a26763652246a33764
 https://conda.anaconda.org/conda-forge/win-64/libflang-5.0.0-h6538335_20180525.tar.bz2#9f473a344e18668e99a93f7e21a54b69
-https://conda.anaconda.org/conda-forge/win-64/libxml2-2.10.3-hc3477c8_1.conda#ebeb5e4cb351f83724a50dda9190b609
+https://conda.anaconda.org/conda-forge/win-64/libxml2-2.10.3-hc3477c8_0.tar.bz2#6dcf17bf780618ddb559d992ea3b6961
 https://conda.anaconda.org/conda-forge/win-64/python-3.11.0-hcf16a7b_0_cpython.tar.bz2#13ee3577afc291dabd2d9edc59736688
 https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.4-h0e60522_1.tar.bz2#e1aff0583dda5fb917eb3d2c1025aa80
 https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.2-h7755175_4.tar.bz2#13acb3626fcc8c0577249f3a7b6129f4
-https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e
 https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
 https://conda.anaconda.org/conda-forge/noarch/async_generator-1.10-py_0.tar.bz2#d56c596e61b1c4952acf0a9920856c12
 https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9
@@ -116,11 +116,11 @@ https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.
 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2
 https://conda.anaconda.org/conda-forge/noarch/executing-1.2.0-pyhd8ed1ab_0.tar.bz2#4c1bc140e2be5c8ba6e3acab99e25c50
 https://conda.anaconda.org/conda-forge/noarch/flit-core-3.8.0-pyhd8ed1ab_0.tar.bz2#6d5e56de2e65da7aa35fd10131226efa
-https://conda.anaconda.org/conda-forge/noarch/future-0.18.2-pyhd8ed1ab_6.tar.bz2#a6966947ba28bbe60f9904653da7fed5
+https://conda.anaconda.org/conda-forge/noarch/future-0.18.3-pyhd8ed1ab_0.conda#fec8329fc739090f26a7d7803db254f1
 https://conda.anaconda.org/conda-forge/win-64/greenlet-2.0.1-py311h12c1d0e_0.tar.bz2#4ce3b0abbf2e20ea9de9c54fcba32a89
 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed
 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
-https://conda.anaconda.org/conda-forge/noarch/importnb-2022.10.24-pyhd8ed1ab_0.tar.bz2#65e8d2add04a93849c09ab1b06130c13
+https://conda.anaconda.org/conda-forge/noarch/importnb-2023.1.7-pyhd8ed1ab_0.conda#4ff1d5888b17056732615d7975c254dd
 https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5
 https://conda.anaconda.org/conda-forge/noarch/ipython_genutils-0.2.0-py_1.tar.bz2#5071c982548b3a20caf70462f04f5287
 https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2#3c3de74912f11d2b590184f03c7cd09b
@@ -138,8 +138,8 @@ https://conda.anaconda.org/conda-forge/win-64/mypy_extensions-0.4.3-py311h1ea47a
 https://conda.anaconda.org/conda-forge/noarch/natsort-8.2.0-pyhd8ed1ab_0.tar.bz2#a8b2456513ed910ef418021afc5150bb
 https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.5.6-pyhd8ed1ab_0.tar.bz2#7b868f21adde0d9b8b38f9c16836589b
 https://conda.anaconda.org/conda-forge/noarch/networkx-3.0-pyhd8ed1ab_0.conda#88e40007414ea9a13f8df20fcffa87e2
-https://conda.anaconda.org/conda-forge/win-64/orjson-3.8.3-py311h5443569_0.conda#6efb021a81cc38a38d8ae366139304a6
-https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db
+https://conda.anaconda.org/conda-forge/win-64/orjson-3.8.5-py311h5443569_1.conda#409e67882c03b6879d1ebbff04dff6c9
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
 https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2#457c2c8c08e54905d6954e79cb5b5db9
 https://conda.anaconda.org/conda-forge/noarch/parso-0.8.3-pyhd8ed1ab_0.tar.bz2#17a565a0c3899244e938cdf417e7b094
 https://conda.anaconda.org/conda-forge/noarch/pathspec-0.9.0-pyhd8ed1ab_0.tar.bz2#f93dc0ccbc0a8472624165f6e256c7d1
@@ -155,16 +155,16 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz
 https://conda.anaconda.org/conda-forge/noarch/pyflakes-3.0.1-pyhd8ed1ab_0.conda#44b7d77d96560c93e0e11437a3c35254
 https://conda.anaconda.org/conda-forge/win-64/pyrsistent-0.19.3-py311ha68e1ae_0.conda#3f2780a49f0b4bb2c622f7cf098e3d06
 https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.16.2-pyhd8ed1ab_0.tar.bz2#5fe4b6002f505336734ce92961b3e6a0
-https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2
 https://conda.anaconda.org/conda-forge/win-64/pywin32-304-py311h12c1d0e_2.tar.bz2#20a2d8e73b0be8e27ca4096d4f3a7053
 https://conda.anaconda.org/conda-forge/win-64/pywin32-ctypes-0.2.0-py311h1ea47a8_1006.tar.bz2#86ee973a16f5449d9ded34b512331dce
 https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.10-py311h12c1d0e_0.conda#4d7e034dc93f50757cf039a2e3183343
 https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0-py311ha68e1ae_5.tar.bz2#0c97d59d54eb52e170224b3de6ade906
-https://conda.anaconda.org/conda-forge/win-64/pyzmq-24.0.1-py311h7b3f143_1.tar.bz2#f19e3e02d0209ed21fb69a021296734b
+https://conda.anaconda.org/conda-forge/win-64/pyzmq-25.0.0-py311h7b3f143_0.conda#a5f8a22c2b3104a34dd32c829cf29097
 https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_0.tar.bz2#d337886e38f965bf97aaec382ff6db00
-https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_0.conda#cd9c28c39e4a899a1df100c2d94e01cf
+https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_1.conda#b4dcead8c5a99a14733d9b1ba5b7db09
 https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.0-pyhd8ed1ab_0.tar.bz2#edab14119efe85c3bf131ad747e9005c
-https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a
+https://conda.anaconda.org/conda-forge/noarch/setuptools-66.0.0-pyhd8ed1ab_0.conda#88ec352fde1a5561ce5f93a302539f7f
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
 https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.0-pyhd8ed1ab_0.tar.bz2#dd6cbc539e74cb1f430efbd4575b9303
 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e
@@ -209,7 +209,7 @@ https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2#b2
 https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2#b2355343d6315c892543200231d7154a
 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62
 https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3
-https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_0.conda#cc1909c46e375f8d4e3eb9902b21fa6a
+https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_1.conda#0dbbfda0efc7fde25d97aa0df18767c2
 https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2#31e4a1506968d017229bdb64695013a1
 https://conda.anaconda.org/conda-forge/noarch/jedi-0.18.2-pyhd8ed1ab_0.conda#b5e695ef9c3f0d27d6cd96bf5adc9e07
 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
@@ -222,7 +222,7 @@ https://conda.anaconda.org/conda-forge/noarch/outcome-1.2.0-pyhd8ed1ab_0.tar.bz2
 https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d
 https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1
 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh0701188_6.tar.bz2#56cd9fe388baac0e90c7149cfac95b60
-https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c
+https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.1-pyhd8ed1ab_0.conda#f0be05afc9c9ab45e273c088e00c258b
 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984
 https://conda.anaconda.org/conda-forge/win-64/python-libarchive-c-4.0-py311h1ea47a8_2.tar.bz2#587f3af8e6a1d545a1b3b5a2617435f7
 https://conda.anaconda.org/conda-forge/noarch/robotframework-pythonlibcore-4.0.0-pyhd8ed1ab_0.tar.bz2#e3dd7ba929a82a21c6e568c00d5b538c
@@ -240,7 +240,7 @@ https://conda.anaconda.org/conda-forge/win-64/brotlipy-0.7.0-py311ha68e1ae_1005.
 https://conda.anaconda.org/conda-forge/win-64/cmarkgfm-0.8.0-py311ha68e1ae_2.tar.bz2#206cb594ef199270b7e0d3d17aa87cbe
 https://conda.anaconda.org/conda-forge/win-64/cryptography-39.0.0-py311h28e9c30_0.conda#9e2a95fcaa6333eac22a51f2beb913ee
 https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_0.tar.bz2#fc5e53d070f1ee7bb38c2ece282dcb82
-https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.62.0-pyha770c72_0.conda#0ba38faf3644821aec9e857b1f4f7a3c
+https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.62.1-pyha770c72_0.conda#fdb3c6a98a3c54e4f34d11a946ce93d8
 https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.0.0-hd8ed1ab_0.conda#a67d43e1527a37199dd8db913366f68e
 https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda#723268a468177cd44568eb8f794e0d80
 https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.2.2-pyhd8ed1ab_0.tar.bz2#243f63592c8e449f40cd42eb5cf32f40
@@ -253,14 +253,14 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-cov-4.0.0-pyhd8ed1ab_0.tar.
 https://conda.anaconda.org/conda-forge/noarch/pytest-metadata-2.0.4-pyhd8ed1ab_0.tar.bz2#7ac02a65917993d38ca1bfd7b87208e4
 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9
 https://conda.anaconda.org/conda-forge/noarch/pytoolconfig-1.2.4-pyhd8ed1ab_1.conda#830a5eeda95b64fa74c30cf3d1b4d1ae
-https://conda.anaconda.org/conda-forge/noarch/rich-13.0.1-pyhd8ed1ab_0.conda#f6ed7a402e1378ca13bc0f3d8ec2763a
-https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.11.0-pyhd8ed1ab_0.conda#9e92b5ab35965c2d48110eca42cc3f9e
+https://conda.anaconda.org/conda-forge/noarch/rich-13.1.0-pyhd8ed1ab_0.conda#45901233f863e3b734c0916172c4cbdd
+https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.12.0-pyhd8ed1ab_0.conda#71b3c6e9c9f8757f6828c2466cdfc9d4
 https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.2-pyhd8ed1ab_0.conda#e7df0fdd404616638df5ece6e69ba7af
 https://conda.anaconda.org/conda-forge/win-64/trio-0.22.0-py311h1ea47a8_1.tar.bz2#1475d3775d4e2cc09c366720de842992
-https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.5-pyh9f0ad1d_2.tar.bz2#5266fcd697043c59621fda522b3d78ee
+https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda#078979d33523cb477bd1916ce41aacc9
 https://conda.anaconda.org/conda-forge/noarch/wsproto-1.2.0-pyhd8ed1ab_0.tar.bz2#00ba804b54f451d102f6a7615f08470d
 https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-21.3.0-pyhd8ed1ab_0.tar.bz2#a0b402db58f73aaab8ee0ca1025a362e
-https://conda.anaconda.org/conda-forge/win-64/black-22.10.0-py311h1ea47a8_2.tar.bz2#6c56efbb3ceb005c744f3c3bd13d88d2
+https://conda.anaconda.org/conda-forge/win-64/black-22.12.0-py311h1ea47a8_0.conda#5408e58b221409c1cfd538c6139bea2e
 https://conda.anaconda.org/conda-forge/win-64/jupyter_core-5.1.3-py311h1ea47a8_0.conda#8daab8569b66e2bcdac06c4344b3fe11
 https://conda.anaconda.org/conda-forge/win-64/keyring-23.13.1-py311h1ea47a8_0.conda#ff14a49b769ea59ee6709a890e17f317
 https://conda.anaconda.org/conda-forge/win-64/numpy-1.24.1-py311h95d790f_0.conda#9f63e4be1a15a4d4823e307a8328118c
@@ -272,36 +272,37 @@ https://conda.anaconda.org/conda-forge/noarch/rich-click-1.4-pyhd8ed1ab_0.tar.bz
 https://conda.anaconda.org/conda-forge/noarch/rope-1.6.0-pyhd8ed1ab_0.conda#c10a8588df896961f69e6887727fe167
 https://conda.anaconda.org/conda-forge/noarch/trio-websocket-0.9.2-pyhd8ed1ab_0.tar.bz2#7acf3fc982eef23b24aed10e80d5dc99
 https://conda.anaconda.org/conda-forge/noarch/ipython-8.8.0-pyh08f2357_0.conda#00944c2f3ff64758ec2cb62fef1e0021
-https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.8-pyhd8ed1ab_0.conda#bd4951aa5fbf3336a249e25c90f50333
-https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.1-pyhd8ed1ab_0.conda#facb42913140f1a399a5ebb65b30915e
+https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.9-pyhd8ed1ab_0.conda#5cbf9a31a19d4ef9103adb7d71fd45fd
+https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.3-pyhd8ed1ab_0.conda#9714111cb6c7dbbc9a9f34de205c2f29
 https://conda.anaconda.org/conda-forge/win-64/numcodecs-0.11.0-py311h12c1d0e_1.conda#9b66ec550742f950ba5fdc1fe650ce26
 https://conda.anaconda.org/conda-forge/win-64/pandas-1.5.2-py311hf63dbb6_2.conda#6657e2e9ccfc95b5f5cd6279c69c0e80
-https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.2-pyhd8ed1ab_0.tar.bz2#354a5c374999c2688332c073e2450951
+https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.3-pyhd8ed1ab_0.conda#bc4e21b26f0e5bb7517c5d6200912d35
 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
-https://conda.anaconda.org/conda-forge/noarch/black-jupyter-22.10.0-hd8ed1ab_2.tar.bz2#80112c21a14035fe1d786f082ce27978
-https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.20.1-pyh025b116_0.conda#513c1ede964e753f2573cbab165f0208
+https://conda.anaconda.org/conda-forge/noarch/black-jupyter-22.12.0-hd8ed1ab_0.conda#421077052ae1530402ee4c78ba7939bb
+https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.20.2-pyh025b116_0.conda#48865ae764b0843c660620960058fd61
 https://conda.anaconda.org/conda-forge/noarch/nbclient-0.5.13-pyhd8ed1ab_0.tar.bz2#3edde88a191701cf052216c4ba353a83
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
 https://conda.anaconda.org/conda-forge/noarch/selenium-4.7.2-pyhd8ed1ab_0.conda#4f6238a5e80996ee0bbee28b487aada5
 https://conda.anaconda.org/conda-forge/noarch/flit-3.8.0-pyhd8ed1ab_0.tar.bz2#d37c34176396c5402cf80c32e67731b7
 https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.0.4-pyhd8ed1ab_0.conda#9dea5ab3cc33084f7a3680a98859731e
 https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-0.5.0-pyhd8ed1ab_0.tar.bz2#293ede7de6b604f3586efbfa615ee2e1
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.7-pyhd8ed1ab_0.conda#dce063b1f5d6193646fc2ac43f88b77f
-https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.7-pyhd8ed1ab_0.conda#399c98b39eb6bdc4b1d40b7611a6d384
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.8-pyhd8ed1ab_0.conda#252899fe1f90aeb0c3391ff32a5035f1
+https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.8-pyhd8ed1ab_0.conda#b0af82591adbf1d403fb4d061a8ca940
 https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-0.10.1-pyhd8ed1ab_0.tar.bz2#a4cd20af9711434f89d1ec0d2b3ae6ba
 https://conda.anaconda.org/conda-forge/noarch/robotframework-seleniumlibrary-6.0.0-pyhd8ed1ab_0.tar.bz2#2acaef9c04081dc3644501ac575953bf
 https://conda.anaconda.org/conda-forge/noarch/sphinx-5.3.0-pyhd8ed1ab_0.tar.bz2#f9e1fcfe235d655900bfeb6aee426472
 https://conda.anaconda.org/conda-forge/noarch/bqplot-0.12.36-pyhd8ed1ab_0.tar.bz2#bff49231f6b67c01a7a89d56ca0693ab
-https://conda.anaconda.org/conda-forge/noarch/jupyter_server-1.23.4-pyhd8ed1ab_0.conda#35b7e9267926e864cc70ce137e6588ca
+https://conda.anaconda.org/conda-forge/noarch/jupyter_server-1.23.5-pyhd8ed1ab_0.conda#e3ae1d46749f48a1875b560fca41cb0c
 https://conda.anaconda.org/conda-forge/noarch/myst-parser-0.18.1-pyhd8ed1ab_0.tar.bz2#bcfdf5c7d8bf5c6f6be7b4c66fff2eca
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.7-pyhd8ed1ab_0.conda#7d48776bb3fdfa6447267c413a480ccd
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.8-pyhd8ed1ab_0.conda#701b0db88fe192e1685de29b8ca0840e
 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2
+https://conda.anaconda.org/conda-forge/noarch/robotframework-jupyterlibrary-0.4.2-pyhd8ed1ab_0.conda#6687f0def03dc3db713149fb8e026068
 https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2021.3.14-pyhd8ed1ab_0.tar.bz2#1909f784dc37b4ab97afe2c95aeeabaa
-https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.20.1-pyhd8ed1ab_0.conda#1364c5199876bb381ba1ad004027e11c
+https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.23.4-pyhd8ed1ab_0.conda#f2f2d8c21ec1933c039cc321a00bc10b
 https://conda.anaconda.org/conda-forge/noarch/twine-4.0.2-pyhd8ed1ab_0.conda#e3a16168d6b9deefb8c1caa7943fb49e
-https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.18.0-pyhd8ed1ab_0.conda#be95d2cc7614462440e4e10b5b373067
+https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.19.0-pyhd8ed1ab_0.conda#9dc0706ec2f8e13b7a0db9d6a19051ff
 https://conda.anaconda.org/conda-forge/noarch/myst-nb-0.17.1-pyhd8ed1ab_0.tar.bz2#8a11a646076b72bcaa71328f07c1aaab
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.7-pyhd8ed1ab_0.conda#b019fe41e6f6b88e0557532514f39cff
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.8-pyhd8ed1ab_0.conda#8b5cb7073f66beef0d7161406fdb6e8b
 https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.2-pyhd8ed1ab_0.tar.bz2#40be846cd4e78672a40e43db9dae753c
 https://conda.anaconda.org/conda-forge/noarch/nbclassic-0.4.8-pyhd8ed1ab_0.tar.bz2#f4f150f83ed90a1b833e6081bbf38257
 https://conda.anaconda.org/conda-forge/noarch/pytest-check-links-0.8.0-pyhd8ed1ab_0.conda#639513b3fc51ee25e00a423fa430a2da
diff --git a/.github/locks/win-64_3.7_environment.conda.lock b/.github/locks/win-64_3.7_environment.conda.lock
index a7dbf1d6..6568f37c 100644
--- a/.github/locks/win-64_3.7_environment.conda.lock
+++ b/.github/locks/win-64_3.7_environment.conda.lock
@@ -18,9 +18,9 @@
 #   - pytest-html
 #   - pytest-xdist
 #   - requests-cache
+#   - robotframework-jupyterlibrary
 #   - robotframework-pabot
 #   - robotframework-robocop
-#   - robotframework-seleniumlibrary
 #   - robotframework-tidy
 #   - rope
 #   - stringcase
@@ -63,7 +63,7 @@
 
 @EXPLICIT
 https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2022.12.7-h5b45459_0.conda#31de4d9887dc8eaed9e6230a5dfbb9d6
-https://conda.anaconda.org/conda-forge/win-64/git-2.39.0-h57928b3_0.conda#40b9d7db4ad079a60271a7a36219f8ba
+https://conda.anaconda.org/conda-forge/win-64/git-2.39.1-h57928b3_0.conda#7e36f1af1e3c612f4506e93f0ce0b063
 https://conda.anaconda.org/conda-forge/noarch/llvm-meta-5.0.0-0.tar.bz2#213b5b5ad34008147a824460e50a691c
 https://conda.anaconda.org/conda-forge/win-64/msys2-conda-epoch-20160418-1.tar.bz2#b0309b72560df66f71a9d5e34a5efdfa
 https://conda.anaconda.org/conda-forge/win-64/nodejs-18.12.1-h57928b3_0.tar.bz2#89700013434ea5e61b78cbed8687c563
@@ -75,9 +75,9 @@ https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.b
 https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2#1cee351bf20b830d991dbe0bc8cd7dfe
 https://conda.anaconda.org/conda-forge/win-64/m2w64-gmp-6.1.0-2.tar.bz2#53a1c73e1e3d185516d7e3af177596d9
 https://conda.anaconda.org/conda-forge/win-64/m2w64-libwinpthread-git-5.0.0.4634.697f757-2.tar.bz2#774130a326dee16f1ceb05cc687ee4f0
-https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.32.31332-h1d6e394_9.tar.bz2#c98b6e39006315599b793592bcc3c978
+https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.34.31931-h4c5c07a_10.conda#25640086ba777e79e5d233d079d7c5fc
 https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-core-5.3.0-7.tar.bz2#4289d80fb4d272f1f3b56cfe87ac90bd
-https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h3d8a991_9.tar.bz2#ba28983ef4f6d430827d0e7c5cdd7b48
+https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hb6edc58_10.conda#52d246d8d14b83c516229be5bb03a163
 https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h8ffe710_4.tar.bz2#7c03c66026944073040cb19a4f3ec3c9
 https://conda.anaconda.org/conda-forge/win-64/firefox-102.6.0esr-h63175ca_0.conda#60e0376658541caea85e1abecb107a83
 https://conda.anaconda.org/conda-forge/win-64/geckodriver-0.32.0-h611cf2b_0.tar.bz2#97b5d23b319be2f712581eda0c0abccd
@@ -102,7 +102,7 @@ https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.0.9-hcfcfb64_8.tar.
 https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.0.9-hcfcfb64_8.tar.bz2#88e62627120c20289bf8982b15e0a6a1
 https://conda.anaconda.org/conda-forge/win-64/libflang-5.0.0-h6538335_20180525.tar.bz2#9f473a344e18668e99a93f7e21a54b69
 https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.39-h19919ed_0.conda#ab6febdb2dbd9c00803609079db4de71
-https://conda.anaconda.org/conda-forge/win-64/libxml2-2.10.3-hc3477c8_1.conda#ebeb5e4cb351f83724a50dda9190b609
+https://conda.anaconda.org/conda-forge/win-64/libxml2-2.10.3-hc3477c8_0.tar.bz2#6dcf17bf780618ddb559d992ea3b6961
 https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2#fe759119b8b3bfa720b8762c6fdc35de
 https://conda.anaconda.org/conda-forge/win-64/sqlite-3.40.0-hcfcfb64_0.tar.bz2#c71d4be22d8402e7e17386aeb18b930e
 https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.4-h0e60522_1.tar.bz2#e1aff0583dda5fb917eb3d2c1025aa80
@@ -116,7 +116,7 @@ https://conda.anaconda.org/conda-forge/win-64/pthread-stubs-0.4-hcd874cb_1001.ta
 https://conda.anaconda.org/conda-forge/win-64/python-3.7.12-h900ac77_100_cpython.tar.bz2#3537e4c5900a4265dfb518bd480ab2f2
 https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.9-hcd874cb_0.tar.bz2#9cef622e75683c17d05ae62d66e69e6c
 https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.3-hcd874cb_0.tar.bz2#46878ebb6b9cbd8afcf8088d7ef00ece
-https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0
+https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e
 https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b
 https://conda.anaconda.org/conda-forge/noarch/async_generator-1.10-py_0.tar.bz2#d56c596e61b1c4952acf0a9920856c12
 https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9
@@ -141,7 +141,7 @@ https://conda.anaconda.org/conda-forge/win-64/future-0.18.2-py37h03978a9_5.tar.b
 https://conda.anaconda.org/conda-forge/win-64/greenlet-1.1.3-py37hf2a7229_0.tar.bz2#7b2ecb3e8f2dc79c2f81aeab7048ca6e
 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed
 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352
-https://conda.anaconda.org/conda-forge/noarch/importnb-2022.10.24-pyhd8ed1ab_0.tar.bz2#65e8d2add04a93849c09ab1b06130c13
+https://conda.anaconda.org/conda-forge/noarch/importnb-2023.1.7-pyhd8ed1ab_0.conda#4ff1d5888b17056732615d7975c254dd
 https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5
 https://conda.anaconda.org/conda-forge/noarch/ipython_genutils-0.2.0-py_1.tar.bz2#5071c982548b3a20caf70462f04f5287
 https://conda.anaconda.org/conda-forge/noarch/itsdangerous-2.1.2-pyhd8ed1ab_0.tar.bz2#3c3de74912f11d2b590184f03c7cd09b
@@ -162,7 +162,7 @@ https://conda.anaconda.org/conda-forge/noarch/natsort-8.2.0-pyhd8ed1ab_0.tar.bz2
 https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.5.6-pyhd8ed1ab_0.tar.bz2#7b868f21adde0d9b8b38f9c16836589b
 https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.0-hc9384bd_1.tar.bz2#a6834096f8d834339eca7ef4d23bcc44
 https://conda.anaconda.org/conda-forge/win-64/orjson-3.8.1-py37h03978a9_0.tar.bz2#b7623a90ee6b7917c224377501a1d8fb
-https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
 https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2#457c2c8c08e54905d6954e79cb5b5db9
 https://conda.anaconda.org/conda-forge/noarch/parso-0.8.3-pyhd8ed1ab_0.tar.bz2#17a565a0c3899244e938cdf417e7b094
 https://conda.anaconda.org/conda-forge/noarch/pathspec-0.9.0-pyhd8ed1ab_0.tar.bz2#f93dc0ccbc0a8472624165f6e256c7d1
@@ -178,14 +178,14 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b
 https://conda.anaconda.org/conda-forge/win-64/pyrsistent-0.18.1-py37hcc03f2d_1.tar.bz2#a0b645d94af985852f982eb861af3d26
 https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.16.2-pyhd8ed1ab_0.tar.bz2#5fe4b6002f505336734ce92961b3e6a0
 https://conda.anaconda.org/conda-forge/win-64/python-libarchive-c-4.0-py37h03978a9_1.tar.bz2#d3fd788000f15a530be89416077a86de
-https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689
+https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2
 https://conda.anaconda.org/conda-forge/win-64/pywin32-303-py37hcc03f2d_0.tar.bz2#e47cd4510d47e6e50994362cb641e6b1
 https://conda.anaconda.org/conda-forge/win-64/pywin32-ctypes-0.2.0-py37h03978a9_1005.tar.bz2#0fa224adb07d55b623ea86e03b1b1d68
 https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.8-py37h7f67f24_0.tar.bz2#d502f9c52ea0af461840dfa4eaf1c2f3
 https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0-py37hcc03f2d_4.tar.bz2#a689b5e99656ac68a905651566be8153
 https://conda.anaconda.org/conda-forge/win-64/pyzmq-24.0.1-py37h7347f05_0.tar.bz2#748a93bfb8fde178135b50fc915c79f2
 https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_0.tar.bz2#d337886e38f965bf97aaec382ff6db00
-https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_0.conda#cd9c28c39e4a899a1df100c2d94e01cf
+https://conda.anaconda.org/conda-forge/noarch/robotframework-6.0.2-pyhd8ed1ab_1.conda#b4dcead8c5a99a14733d9b1ba5b7db09
 https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.0-pyhd8ed1ab_0.tar.bz2#edab14119efe85c3bf131ad747e9005c
 https://conda.anaconda.org/conda-forge/win-64/setuptools-59.8.0-py37h03978a9_1.tar.bz2#22e9ef328bf7f5bda7dbdb91d78e4f69
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
@@ -233,7 +233,7 @@ https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2#b2
 https://conda.anaconda.org/conda-forge/noarch/html5lib-1.1-pyh9f0ad1d_0.tar.bz2#b2355343d6315c892543200231d7154a
 https://conda.anaconda.org/conda-forge/win-64/importlib-metadata-4.11.4-py37h03978a9_0.tar.bz2#d18db310efd87f4f943d547ab0f9943e
 https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3
-https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_0.conda#cc1909c46e375f8d4e3eb9902b21fa6a
+https://conda.anaconda.org/conda-forge/noarch/isort-5.11.4-pyhd8ed1ab_1.conda#0dbbfda0efc7fde25d97aa0df18767c2
 https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.2.3-pyhd8ed1ab_0.tar.bz2#31e4a1506968d017229bdb64695013a1
 https://conda.anaconda.org/conda-forge/noarch/jedi-0.18.2-pyhd8ed1ab_0.conda#b5e695ef9c3f0d27d6cd96bf5adc9e07
 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37
@@ -266,25 +266,25 @@ https://conda.anaconda.org/conda-forge/win-64/cmarkgfm-0.8.0-py37hcc03f2d_1.tar.
 https://conda.anaconda.org/conda-forge/win-64/cryptography-38.0.2-py37h953a470_1.tar.bz2#9b9e7cbe333626c115554205a1234b4a
 https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-4.11.4-hd8ed1ab_0.tar.bz2#9a1925fdb91c81437b8012e48ede6851
 https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.17.3-pyhd8ed1ab_0.conda#723268a468177cd44568eb8f794e0d80
-https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.8-pyhd8ed1ab_0.conda#bd4951aa5fbf3336a249e25c90f50333
+https://conda.anaconda.org/conda-forge/noarch/jupyter_client-7.4.9-pyhd8ed1ab_0.conda#5cbf9a31a19d4ef9103adb7d71fd45fd
 https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.2.2-pyhd8ed1ab_0.tar.bz2#243f63592c8e449f40cd42eb5cf32f40
 https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.4.4-py37h8c56517_0.tar.bz2#554b3933bdd6402d36fa88af21f5f07a
 https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.3.3-pyhd8ed1ab_0.conda#7a69c057183706bc7ba6ac7c9130566a
 https://conda.anaconda.org/conda-forge/win-64/numpy-1.21.6-py37h2830a78_0.tar.bz2#3386afc0c338f30d6f24ef046e9f31d6
 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54
 https://conda.anaconda.org/conda-forge/noarch/pytoolconfig-1.2.4-pyhd8ed1ab_1.conda#830a5eeda95b64fa74c30cf3d1b4d1ae
-https://conda.anaconda.org/conda-forge/noarch/rich-13.0.1-pyhd8ed1ab_0.conda#f6ed7a402e1378ca13bc0f3d8ec2763a
-https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.11.0-pyhd8ed1ab_0.conda#9e92b5ab35965c2d48110eca42cc3f9e
+https://conda.anaconda.org/conda-forge/noarch/rich-13.1.0-pyhd8ed1ab_0.conda#45901233f863e3b734c0916172c4cbdd
+https://conda.anaconda.org/conda-forge/noarch/robotframework-pabot-2.12.0-pyhd8ed1ab_0.conda#71b3c6e9c9f8757f6828c2466cdfc9d4
 https://conda.anaconda.org/conda-forge/win-64/sqlalchemy-1.4.42-py37h51bd9d9_0.tar.bz2#dd6adf90a1d783711f6e398888700779
 https://conda.anaconda.org/conda-forge/win-64/trio-0.21.0-py37h03978a9_0.tar.bz2#a2fe7ed3bdd86a25a695d6677f9fa8c1
-https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.5-pyh9f0ad1d_2.tar.bz2#5266fcd697043c59621fda522b3d78ee
+https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.6-pyhd8ed1ab_0.conda#078979d33523cb477bd1916ce41aacc9
 https://conda.anaconda.org/conda-forge/noarch/wsproto-1.2.0-pyhd8ed1ab_0.tar.bz2#00ba804b54f451d102f6a7615f08470d
 https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-21.3.0-pyhd8ed1ab_0.tar.bz2#a0b402db58f73aaab8ee0ca1025a362e
 https://conda.anaconda.org/conda-forge/win-64/black-22.10.0-py37h03978a9_1.tar.bz2#83f195560c1411eb324a0e32c2af053f
 https://conda.anaconda.org/conda-forge/win-64/hypothesis-6.56.3-py37h03978a9_1.tar.bz2#8e97c9471c6b208560788778b8a78166
 https://conda.anaconda.org/conda-forge/win-64/keyring-23.9.3-py37h03978a9_0.tar.bz2#588bd9ae41a6784d5dca08d780f66868
 https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.5.3-py37hbaab90a_2.tar.bz2#5707cb94dec1fe26c309ea579ef9d25e
-https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.1-pyhd8ed1ab_0.conda#facb42913140f1a399a5ebb65b30915e
+https://conda.anaconda.org/conda-forge/noarch/nbformat-5.7.3-pyhd8ed1ab_0.conda#9714111cb6c7dbbc9a9f34de205c2f29
 https://conda.anaconda.org/conda-forge/win-64/numcodecs-0.10.2-py37hf2a7229_0.tar.bz2#d6db5444bda5766a372378295481f953
 https://conda.anaconda.org/conda-forge/win-64/pandas-1.3.5-py37h9386db6_0.tar.bz2#9c7fb468cb205bc8ff6ef30c549198d7
 https://conda.anaconda.org/conda-forge/win-64/pluggy-1.0.0-py37h03978a9_3.tar.bz2#989525bff76b5a98de5da909f43eeb0d
@@ -299,35 +299,36 @@ https://conda.anaconda.org/conda-forge/win-64/ipython-7.33.0-py37h03978a9_0.tar.
 https://conda.anaconda.org/conda-forge/noarch/nbclient-0.5.13-pyhd8ed1ab_0.tar.bz2#3edde88a191701cf052216c4ba353a83
 https://conda.anaconda.org/conda-forge/noarch/networkx-2.7-pyhd8ed1ab_0.tar.bz2#9d430f94458637bbb2f5e78526324412
 https://conda.anaconda.org/conda-forge/win-64/pytest-7.2.0-py37h03978a9_0.tar.bz2#bc1cd0ec0102d6e13f7d82a70d5faf78
-https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.2-pyhd8ed1ab_0.tar.bz2#354a5c374999c2688332c073e2450951
+https://conda.anaconda.org/conda-forge/noarch/robotframework-tidy-3.3.3-pyhd8ed1ab_0.conda#bc4e21b26f0e5bb7517c5d6200912d35
 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
 https://conda.anaconda.org/conda-forge/noarch/black-jupyter-22.10.0-hd8ed1ab_2.tar.bz2#80112c21a14035fe1d786f082ce27978
 https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.16.2-pyh025b116_0.tar.bz2#5dc2bff03226e2ccc206ed20fc4a37cc
 https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-0.5.0-pyhd8ed1ab_0.tar.bz2#293ede7de6b604f3586efbfa615ee2e1
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.7-pyhd8ed1ab_0.conda#dce063b1f5d6193646fc2ac43f88b77f
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.2.8-pyhd8ed1ab_0.conda#252899fe1f90aeb0c3391ff32a5035f1
 https://conda.anaconda.org/conda-forge/noarch/pytest-asyncio-0.20.3-pyhd8ed1ab_0.conda#aa462bf71d7b327dcc00ae523f8923ef
 https://conda.anaconda.org/conda-forge/noarch/pytest-cov-4.0.0-pyhd8ed1ab_0.tar.bz2#c9e3f8bfdb9bfc34aa1836a6ed4b25d7
 https://conda.anaconda.org/conda-forge/noarch/pytest-metadata-2.0.4-pyhd8ed1ab_0.tar.bz2#7ac02a65917993d38ca1bfd7b87208e4
 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
 https://conda.anaconda.org/conda-forge/noarch/selenium-4.7.2-pyhd8ed1ab_0.conda#4f6238a5e80996ee0bbee28b487aada5
 https://conda.anaconda.org/conda-forge/noarch/flit-3.8.0-pyhd8ed1ab_0.tar.bz2#d37c34176396c5402cf80c32e67731b7
 https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.0.4-pyhd8ed1ab_0.conda#9dea5ab3cc33084f7a3680a98859731e
 https://conda.anaconda.org/conda-forge/noarch/jupyter_server-1.23.4-pyhd8ed1ab_0.conda#35b7e9267926e864cc70ce137e6588ca
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.7-pyhd8ed1ab_0.conda#7d48776bb3fdfa6447267c413a480ccd
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.2.8-pyhd8ed1ab_0.conda#701b0db88fe192e1685de29b8ca0840e
 https://conda.anaconda.org/conda-forge/noarch/pytest-html-3.2.0-pyhd8ed1ab_1.tar.bz2#d5c7a941dfbceaab4b172a56d7918eb0
-https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.7-pyhd8ed1ab_0.conda#399c98b39eb6bdc4b1d40b7611a6d384
+https://conda.anaconda.org/conda-forge/noarch/requests-cache-0.9.8-pyhd8ed1ab_0.conda#b0af82591adbf1d403fb4d061a8ca940
 https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-0.10.1-pyhd8ed1ab_0.tar.bz2#a4cd20af9711434f89d1ec0d2b3ae6ba
 https://conda.anaconda.org/conda-forge/noarch/robotframework-seleniumlibrary-6.0.0-pyhd8ed1ab_0.tar.bz2#2acaef9c04081dc3644501ac575953bf
 https://conda.anaconda.org/conda-forge/noarch/sphinx-5.3.0-pyhd8ed1ab_0.tar.bz2#f9e1fcfe235d655900bfeb6aee426472
 https://conda.anaconda.org/conda-forge/noarch/bqplot-0.12.36-pyhd8ed1ab_0.tar.bz2#bff49231f6b67c01a7a89d56ca0693ab
-https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.18.0-pyhd8ed1ab_0.conda#be95d2cc7614462440e4e10b5b373067
+https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.19.0-pyhd8ed1ab_0.conda#9dc0706ec2f8e13b7a0db9d6a19051ff
 https://conda.anaconda.org/conda-forge/noarch/myst-parser-0.18.1-pyhd8ed1ab_0.tar.bz2#bcfdf5c7d8bf5c6f6be7b4c66fff2eca
-https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.7-pyhd8ed1ab_0.conda#b019fe41e6f6b88e0557532514f39cff
+https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.2.8-pyhd8ed1ab_0.conda#8b5cb7073f66beef0d7161406fdb6e8b
 https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.2-pyhd8ed1ab_0.tar.bz2#40be846cd4e78672a40e43db9dae753c
 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2
+https://conda.anaconda.org/conda-forge/noarch/robotframework-jupyterlibrary-0.4.2-pyhd8ed1ab_0.conda#6687f0def03dc3db713149fb8e026068
 https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2021.3.14-pyhd8ed1ab_0.tar.bz2#1909f784dc37b4ab97afe2c95aeeabaa
-https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.20.1-pyhd8ed1ab_0.conda#1364c5199876bb381ba1ad004027e11c
+https://conda.anaconda.org/conda-forge/noarch/sphinx-autodoc-typehints-1.23.4-pyhd8ed1ab_0.conda#f2f2d8c21ec1933c039cc321a00bc10b
 https://conda.anaconda.org/conda-forge/noarch/twine-4.0.2-pyhd8ed1ab_0.conda#e3a16168d6b9deefb8c1caa7943fb49e
 https://conda.anaconda.org/conda-forge/noarch/myst-nb-0.17.1-pyhd8ed1ab_0.tar.bz2#8a11a646076b72bcaa71328f07c1aaab
 https://conda.anaconda.org/conda-forge/noarch/nbclassic-0.4.8-pyhd8ed1ab_0.tar.bz2#f4f150f83ed90a1b833e6081bbf38257
diff --git a/.github/locks/win-64_lock-environment.conda.lock b/.github/locks/win-64_lock-environment.conda.lock
index 6168484f..eeb1ae0f 100644
--- a/.github/locks/win-64_lock-environment.conda.lock
+++ b/.github/locks/win-64_lock-environment.conda.lock
@@ -15,8 +15,8 @@ https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2#
 https://conda.anaconda.org/conda-forge/win-64/python_abi-3.11-3_cp311.conda#fd1634ba85cfea9376e1fc02d6f592e9
 https://conda.anaconda.org/conda-forge/noarch/tzdata-2022g-h191b570_0.conda#51fc4fcfb19f5d95ffc8c339db5068e8
 https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_0.tar.bz2#72608f6cd3e5898229c3ea16deb1ac43
-https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.32.31332-h1d6e394_9.tar.bz2#c98b6e39006315599b793592bcc3c978
-https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h3d8a991_9.tar.bz2#ba28983ef4f6d430827d0e7c5cdd7b48
+https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.34.31931-h4c5c07a_10.conda#25640086ba777e79e5d233d079d7c5fc
+https://conda.anaconda.org/conda-forge/win-64/vc-14.3-hb6edc58_10.conda#52d246d8d14b83c516229be5bb03a163
 https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h8ffe710_4.tar.bz2#7c03c66026944073040cb19a4f3ec3c9
 https://conda.anaconda.org/conda-forge/win-64/fmt-9.1.0-h181d51b_0.tar.bz2#31a20cf261b2bd0a76d670db1b3e6fa1
 https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.2-h8ffe710_5.tar.bz2#2c96d1b6915b408893f9472569dee135
@@ -53,7 +53,7 @@ https://conda.anaconda.org/conda-forge/win-64/markupsafe-2.1.1-py311ha68e1ae_2.t
 https://conda.anaconda.org/conda-forge/win-64/menuinst-1.4.19-py311h1ea47a8_1.tar.bz2#8eb00075c8acb7dd264c4a8537b89549
 https://conda.anaconda.org/conda-forge/noarch/more-itertools-9.0.0-pyhd8ed1ab_0.tar.bz2#9b6ad26944f19f599800b068e0582227
 https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.0.4-py311h005e61a_1.tar.bz2#ef561ef7fcf37af359d9cb424a6346c1
-https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db
+https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b
 https://conda.anaconda.org/conda-forge/noarch/pastel-0.2.1-pyhd8ed1ab_0.tar.bz2#a4eea5bff523f26442405bc5d1f52adb
 https://conda.anaconda.org/conda-forge/noarch/pkginfo-1.9.6-pyhd8ed1ab_0.conda#be1e9f1c65a1ed0f2ae9352fec99db64
 https://conda.anaconda.org/conda-forge/win-64/pycosat-0.6.4-py311ha68e1ae_1.tar.bz2#c7af04cd8a66f6d782982751200239d0
@@ -63,7 +63,7 @@ https://conda.anaconda.org/conda-forge/win-64/pywin32-ctypes-0.2.0-py311h1ea47a8
 https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0-py311ha68e1ae_5.tar.bz2#0c97d59d54eb52e170224b3de6ade906
 https://conda.anaconda.org/conda-forge/win-64/ruamel.yaml.clib-0.2.7-py311ha68e1ae_1.conda#654fbe603c79490699cd7447e4627aee
 https://conda.anaconda.org/conda-forge/win-64/ruamel_yaml-0.15.80-py311ha68e1ae_1008.tar.bz2#c1c3cca1078977cfa12d36f32eb58fbe
-https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a
+https://conda.anaconda.org/conda-forge/noarch/setuptools-66.0.0-pyhd8ed1ab_0.conda#88ec352fde1a5561ce5f93a302539f7f
 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2
 https://conda.anaconda.org/conda-forge/noarch/smmap-3.0.5-pyh44b312d_0.tar.bz2#3a8dc70789709aa315325d5df06fb7e4
 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96
@@ -93,15 +93,15 @@ https://conda.anaconda.org/conda-forge/noarch/gitpython-3.1.30-pyhd8ed1ab_0.cond
 https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-6.0.0-hd8ed1ab_0.conda#a67d43e1527a37199dd8db913366f68e
 https://conda.anaconda.org/conda-forge/win-64/libmambapy-1.1.0-py311h29ee5fe_3.conda#578de8993499c1436875075c2b4bd6b6
 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54
-https://conda.anaconda.org/conda-forge/win-64/pydantic-1.10.4-py311ha68e1ae_0.conda#525c3930015c4284c2d389dec238f043
+https://conda.anaconda.org/conda-forge/win-64/pydantic-1.10.4-py311ha68e1ae_1.conda#d4ff40a570ad37109f392cf9740c5863
 https://conda.anaconda.org/conda-forge/win-64/zstandard-0.19.0-py311he5d195f_1.conda#af889af78c5389e344392e305b4d7bc7
 https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.7.0-pyhd8ed1ab_1.conda#1a2fa9e53cfbc2e4d9ab21990805a436
 https://conda.anaconda.org/conda-forge/win-64/keyring-23.13.1-py311h1ea47a8_0.conda#ff14a49b769ea59ee6709a890e17f317
 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878
 https://conda.anaconda.org/conda-forge/win-64/virtualenv-20.17.1-py311h1ea47a8_0.conda#b758084a6113e2266912b3058391b6c4
 https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.0.2-pyh38be061_0.conda#44800e9bd13143292097c65e57323038
-https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592
-https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0
+https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29
+https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983
 https://conda.anaconda.org/conda-forge/noarch/cachecontrol-0.12.11-pyhd8ed1ab_0.tar.bz2#6eefee9888f33f150b5d44d616b1a613
 https://conda.anaconda.org/conda-forge/win-64/conda-22.9.0-py311h1ea47a8_2.tar.bz2#b10f47d78cb51f295ee9e6f3a611fa17
 https://conda.anaconda.org/conda-forge/noarch/ensureconda-1.4.3-pyhd8ed1ab_0.tar.bz2#c99ae3abf501990769047b4b40a98f17
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2f6285cc..f2168914 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,12 +9,12 @@ on:
 env:
   PYTHONIOENCODING: utf-8
   PYTHONUNBUFFERED: '1'
-  PIP_DISABLE_PIP_VERSION_CHECK: '1'
+  PIP_DISABLE_PIP_VERSION_CHECK: 'True'
   # until have mamabforge...
   CONDA_EXE: mamba
   # our stuff
   SKIP_CONDA_PREFLIGHT: 1
-  CACHE_EPOCH: 0
+  CACHE_EPOCH: 1
 
 jobs:
   build:
@@ -181,13 +181,34 @@ jobs:
       ATEST_RETRIES: '2'
       IPYFORCEGRAPH_PY: ${{ matrix.py }}
     steps:
+      - name: install apt dependencies (linux)
+        if: ${{ contains(matrix.os, 'ubuntu') }}
+        run: |
+          set -eux
+          sudo apt-get update
+          sudo apt-get install -y --no-install-recommends \
+            libdbus-glib-1-2 \
+            libegl1 \
+            libgdk-pixbuf2.0-0 \
+            libgl1 \
+            libgles2 \
+            libgudev-1.0-0 \
+            libhyphen0 \
+            libnotify4 \
+            libopus0 \
+            libsecret-1-0 \
+            libwebpdemux2 \
+            libwoff1 \
+            libxslt1.1 \
+            libxt6 \
+            xvfb
+
       - name: configure line endings
         run: |
           git config --global core.autocrlf false
 
       - name: git checkout
         uses: actions/checkout@v3
-
       - name: env (conda)
         uses: conda-incubator/setup-miniconda@v2
         with:
@@ -222,7 +243,7 @@ jobs:
         if: ${{ contains(matrix.os, 'ubuntu') }}
         shell: bash -l {0}
         run: |
-          doit -n4 test || doit test || exit 1
+          xvfb-run --auto-servernum doit -n4 test || xvfb-run --auto-servernum doit test || exit 1
 
       - name: test (macos)
         if: ${{ contains(matrix.os, 'macos') }}
@@ -243,7 +264,7 @@ jobs:
           name: |-
             ipyforcegraph-${{ github.run_number }}-${{ job.status }}-reports-${{ matrix.os }}-${{ matrix.py }}
           path: |
-            ./atest/output
+            ./build/robot
             ./build/htmcov
             ./build/pytest.html
         if: always()
diff --git a/.gitignore b/.gitignore
index f8001c47..dda3ef3e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -164,6 +164,5 @@ _d/
 # task stuff
 *.doit.db*
 .pabotsuitenames
-atest/output
 *.tsbuildinfo*
 *.isorted
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 28a19d05..24d1a973 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@
 - Get [doit](https://pydoit.org)
 
 ```bash
-mamba install doit
+mamba install -c conda-forge doit
 ```
 
 ## Get Started
@@ -41,12 +41,15 @@ in the extension's source and automatically rebuild the extension and applicatio
 
 - Run:
 
-```bash
-doit watch
-```
+  ```bash
+  doit watch
+  ```
 
 - Open a tab with the provided URL in a standards-compliant browser of choice
+  - e.g., Firefox
 - After making changes, wait for `webpack` terminal output, then reload the browser
+  - output should read something like:
+    `webpack 5.75.0 compiled with 7 warnings in 1528 ms`, it's not necessary to
 - If you add a new file, probably will have to restart the whole thing
 
 ### Logging
@@ -71,9 +74,9 @@ _Log Console_, opened with the _Show Log Console_ command.
 
 - Run:
 
-```bash
-doit lint
-```
+  ```bash
+  doit lint
+  ```
 
 - Ensure the [examples](https://github.com/jupyrdf/ipyforcegraph/tree/main/examples)
   work. These will be tested in CI with:
@@ -130,26 +133,31 @@ doit watch_docs
 ## Releasing
 
 - After merging to `main`, download the ipyforcegraph dist artifacts
-- Inspect the files in `./dist`.
+- Make sure that the [`CHANGELOG`][changelog] contains the relevant changes for the
+  version to be released
+- Inspect the files in `./dist`
 - Check out `main`
-- Tag appropriately
+- Tag appropriately:
 
-```bash
-git push upstream --tags
-```
+  ```bash
+  git push upstream --tags
+  ```
 
 - Ensure you have credentials for `pypi` and `npmjs`
+
   - `npmjs` requires you have set up two-factor authentication (2FA)... this is
     _strongly recommended_ for `pypi`
   - do _not_ use `jlpm publish` or `yarn publish`, as this appears to drop files from
     the distribution
 
-```bash
-npm login
-npm publish
-npm logout
-twine upload where-you-expanded-the-archive/ipyforcegraph-*
-```
+    ```bash
+    npm login
+    npm publish
+    npm logout
+    twine upload where-you-expanded-the-archive/ipyforcegraph-*
+    ```
+
+[changelog]: https://github.com/jupyrdf/ipyforcegraph/tree/main/CHANGELOG.md
 
 ## Updating Dependencies
 
@@ -161,18 +169,18 @@ twine upload where-you-expanded-the-archive/ipyforcegraph-*
   [binder environment](https://github.com/jupyrdf/ipyforcegraph/tree/main/.binder/environment.yml).
 - Run:
 
-```bash
-doit lock
-```
+  ```bash
+  doit lock
+  ```
 
 - Commit the changes to the env specs and the
   [lock files](https://github.com/jupyrdf/ipyforcegraph/tree/main/.github/locks).
 
-> if you delete _all_ the lockfiles, you'll need to `conda-lock` on path with e.g.
->
-> ```bash
-> mamba install -c conda-forge conda-lock
-> ```
+  > if you delete _all_ the lockfiles, you'll need to `conda-lock` on path with e.g.
+  >
+  > ```bash
+  > mamba install -c conda-forge conda-lock
+  > ```
 
 ### Browser Dependencies
 
@@ -180,10 +188,10 @@ doit lock
   [package file](https://github.com/jupyrdf/ipyforcegraph/tree/main/package.json).
 - Run:
 
-```bash
-doit setup:js || doit setup:js || doit setup:js
-doit lint
-```
+  ```bash
+  doit setup:js || doit setup:js || doit setup:js
+  doit lint
+  ```
 
 - Commit the changes to the package file and the
   [yarn lock file](https://github.com/jupyrdf/ipyforcegraph/tree/main/yarn.lock).
diff --git a/README.md b/README.md
index 2714663e..b447bbca 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # `ipyforcegraph`
 
-[Jupyter Widgets][widgets] for interactive graphs powered by the
-[force-graph][forcegraph] library.
+[Jupyter Widgets][widgets] for interactive 2D and 3D graphs powered by the
+[force-graph][force-graph] and [3d-force-graph][3d-force-graph] libraries.
 
 |                                       Install                                       |           Demo            |       Build       |                                     Docs                                     |
 | :---------------------------------------------------------------------------------: | :-----------------------: | :---------------: | :--------------------------------------------------------------------------: |
@@ -68,7 +68,8 @@ This work is licensed under the [BSD-3-Clause License][license].
 [binder-badge]: https://mybinder.org/badge_logo.svg
 [binder]:
   https://mybinder.org/v2/gh/jupyrdf/ipyforcegraph/main?urlpath=lab%2Ftree%2Fexamples%2F_index.ipynb
-[forcegraph]: https://github.com/vasturiano/force-graph
+[force-graph]: https://github.com/vasturiano/force-graph
+[3d-force-graph]: https://github.com/vasturiano/3d-force-graph
 [jupyterlab]: https://github.com/jupyterlab/jupyterlab
 [networkx]: https://networkx.github.io
 [widgets]: https://jupyter.org/widgets
diff --git a/docs/_static/theme.css b/docs/_static/theme.css
new file mode 100644
index 00000000..bef5e736
--- /dev/null
+++ b/docs/_static/theme.css
@@ -0,0 +1,3 @@
+.skip-link {
+  display: none;
+}
diff --git a/docs/conf.py b/docs/conf.py
index ad5bbbe5..d45258e9 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -56,4 +56,15 @@
     "github_version": "main",
     "doc_path": "docs",
 }
-html_static_path = ["_static", "../build/lite"]
+html_static_path = [
+    "_static",
+    "../build/lite",
+]
+html_css_files = [
+    "theme.css",
+]
+
+intersphinx_mapping = {
+    "python": ("https://docs.python.org/3", (None, "python-inv.txt")),
+    "pandas": ("http://pandas.pydata.org/pandas-docs/dev", None),
+}
diff --git a/docs/dictionary.txt b/docs/dictionary.txt
index 608b9e05..545c785d 100644
--- a/docs/dictionary.txt
+++ b/docs/dictionary.txt
@@ -3,27 +3,50 @@
 ci
 composable
 conda
+dataframe
+DataFrame
+DataFrameSource
 doit
 env
 evented
+forcegraph
+ForceGraph
+ForceGraph3D
+GraphImage
 ipyforcegraph
+ipywidgets
 js
 jupyrdf
 jupyter
 Jupyter
 JupyterLab
+kwargs
 labextension
+LinkColors
+LinkDirectionalArrowColor
+LinkDirectionalArrowLength
+LinkDirectionalArrowRelPos
+LinkDirectionalParticleColor
+LinkDirectionalParticles
+LinkDirectionalParticleSpeed
+LinkDirectionalParticleWidth
+LinkLabels
 lockfiles
 maintenance
 Mambaforge
 networkx
 NetworkX
+NodeColors
+NodeLabels
+NodeSelection
 npm
+nunjucks
 PRs
 PyData
 pypi
 PyPI
 ReadTheDocs
+repr
 TBD
 TODO
 TypeScript
diff --git a/docs/reference/behaviors.md b/docs/reference/behaviors.md
new file mode 100644
index 00000000..18470330
--- /dev/null
+++ b/docs/reference/behaviors.md
@@ -0,0 +1,8 @@
+# Behaviors
+
+```{eval-rst}
+.. currentmodule:: ipyforcegraph
+.. automodule:: ipyforcegraph.behaviors
+    :members:
+    :special-members:
+```
diff --git a/docs/reference/index.md b/docs/reference/index.md
index 3936f1e8..ef4b03fc 100644
--- a/docs/reference/index.md
+++ b/docs/reference/index.md
@@ -1,7 +1,10 @@
 # API Reference
 
-This page describe the overall API for ipyforcegraph.
+This page describe the overall API for `ipyforcegraph`.
 
 ```{toctree}
 :maxdepth: 2
+widgets
+sources
+behaviors
 ```
diff --git a/docs/reference/sources.md b/docs/reference/sources.md
new file mode 100644
index 00000000..57b3f4aa
--- /dev/null
+++ b/docs/reference/sources.md
@@ -0,0 +1,10 @@
+# Sources
+
+## DataFrame
+
+```{eval-rst}
+.. currentmodule:: ipyforcegraph
+.. automodule:: ipyforcegraph.sources.dataframe
+    :members:
+    :special-members:
+```
diff --git a/docs/reference/widgets.md b/docs/reference/widgets.md
new file mode 100644
index 00000000..5a1a3a1c
--- /dev/null
+++ b/docs/reference/widgets.md
@@ -0,0 +1,8 @@
+# Widgets
+
+```{eval-rst}
+.. currentmodule:: ipyforcegraph
+.. automodule:: ipyforcegraph.forcegraph
+    :members:
+    :special-members:
+```
diff --git a/examples/3D.ipynb b/examples/3D.ipynb
index d0987396..d98ed1a0 100644
--- a/examples/3D.ipynb
+++ b/examples/3D.ipynb
@@ -43,6 +43,7 @@
     "from ipyforcegraph.forcegraph import ForceGraph3D\n",
     "\n",
     "behaviors = [\n",
+    "    B.add_graph_image,\n",
     "    B.add_node_selection,\n",
     "    B.add_node_colors,\n",
     "    B.add_node_labels,\n",
diff --git a/examples/Behaviors.ipynb b/examples/Behaviors.ipynb
index 2079354a..406072fd 100644
--- a/examples/Behaviors.ipynb
+++ b/examples/Behaviors.ipynb
@@ -46,6 +46,7 @@
    "outputs": [],
    "source": [
     "with __import__(\"importnb\").Notebook():\n",
+    "    import Utils as U\n",
     "    from _index import make_a_simple_example"
    ]
   },
@@ -75,6 +76,69 @@
     "    display(box)"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "id": "3367b571-0ee6-4a47-9486-6b30743dde02",
+   "metadata": {},
+   "source": [
+    "## Graph Behaviors"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ee45a1d4-1f69-4664-a50b-97206820914e",
+   "metadata": {},
+   "source": [
+    "### `ImageCapture`\n",
+    "\n",
+    "The current image can be streamed back to the kernel."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "fffa0595-34aa-4a5c-9991-d808906d992b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def add_graph_image(fg, box):\n",
+    "    behavior = B.GraphImage()\n",
+    "    capturing = W.ToggleButton(description=\"capturing\")\n",
+    "    frame_count = W.IntSlider()\n",
+    "    frames = W.HBox()\n",
+    "    ui = U.make_a_collapsible_picker(\n",
+    "        \"graph: image\",\n",
+    "        children={\n",
+    "            \"capture\": [\n",
+    "                W.VBox(\n",
+    "                    [\n",
+    "                        capturing,\n",
+    "                        frame_count,\n",
+    "                        frames,\n",
+    "                    ]\n",
+    "                )\n",
+    "            ]\n",
+    "        },\n",
+    "    )\n",
+    "    T.link((behavior, \"capturing\"), (capturing, \"value\"))\n",
+    "    T.link((behavior, \"frame_count\"), (frame_count, \"value\"))\n",
+    "    T.dlink((behavior, \"frames\"), (frames, \"children\"))\n",
+    "    box.behaviors = {**box.behaviors, \"graph_image\": behavior}\n",
+    "    box.graph_ui = {**box.graph_ui, \"graph_image\": ui}\n",
+    "    return fg, box"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "25de6e44-8266-425e-8c54-b473bc42972c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "if __name__ == \"__main__\":\n",
+    "    add_graph_image(fg, box)"
+   ]
+  },
   {
    "cell_type": "markdown",
    "id": "a44b8a8e-16ce-4ee4-8dcf-f2976f9b84dc",
@@ -101,9 +165,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "def add_node_selection(fg=None, box=None):\n",
-    "    if fg is None:\n",
-    "        fg, box = make_a_simple_example()\n",
+    "def add_node_selection(fg, box):\n",
     "    selection = B.NodeSelection()\n",
     "    ui_selection = W.TagsInput(\n",
     "        placeholder=\"select some nodes\",\n",
@@ -155,37 +217,6 @@
     "be used. Ensure `NodeColors` is in `behaviors` _after_ e.g. `NodeSelection`."
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "c7e5a586-9e26-4436-88ad-9012e1072e37",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "def make_a_collapsible_picker(title, children):\n",
-    "    select = W.Dropdown(options=list(children), layout=dict(flex=\"0\"))\n",
-    "    ui = W.HBox([select], layout=dict(flex_wrap=\"wrap\"))\n",
-    "    T.dlink((select, \"value\"), (ui, \"children\"), lambda x: [select, *children[x]])\n",
-    "\n",
-    "    return W.Accordion([ui], titles=[title])"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "ba984087-3008-420f-a2fd-5b1e6fcdf8ae",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "def make_random_color_series(fg, trait, column_name):\n",
-    "    df = getattr(fg.source, trait)\n",
-    "    digits = \"01234567abcdef\"\n",
-    "    df[column_name] = [\n",
-    "        \"#\" + \"\".join([random.choice(digits) for j in range(6)]) for i in range(len(df))\n",
-    "    ]\n",
-    "    fg.source.send_state(trait)"
-   ]
-  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -203,7 +234,7 @@
     "    colors = NodeColors(column_name=column_name)\n",
     "\n",
     "    if column_name not in fg.source.nodes:\n",
-    "        make_random_color_series(fg, \"nodes\", column_name)\n",
+    "        U.make_random_color_series(fg, \"nodes\", column_name)\n",
     "    ui_color_column = W.Dropdown(\n",
     "        options=list(fg.source.nodes.columns), value=column_name\n",
     "    )\n",
@@ -216,7 +247,7 @@
     "        lambda x: x if ui_color_template_enabled.value else \"\",\n",
     "    )\n",
     "\n",
-    "    ui_colors = make_a_collapsible_picker(\n",
+    "    ui_colors = U.make_a_collapsible_picker(\n",
     "        \"node: color\",\n",
     "        {\n",
     "            \"off\": [],\n",
@@ -224,8 +255,9 @@
     "            \"template\": [W.VBox([ui_color_template_enabled, ui_color_template])],\n",
     "        },\n",
     "    )\n",
-    "    box.behaviors = {**box.behaviors, \"node_color\": colors}\n",
     "    box.node_ui = {**box.node_ui, \"color\": ui_colors}\n",
+    "    U.make_link_dropdown_responsive(colors, \"node: color\", ui_colors, box)\n",
+    "\n",
     "    return fg, box"
    ]
   },
@@ -306,19 +338,19 @@
     "\n",
     "\n",
     "def add_node_labels(fg, box, column_name=\"id\"):\n",
-    "    labels = NodeLabels()\n",
+    "    behavior = NodeLabels()\n",
     "    ui_label_column = W.Dropdown(\n",
     "        options=list(fg.source.nodes.columns), value=column_name\n",
     "    )\n",
     "    ui_label_template = W.Textarea()\n",
     "    ui_label_template_enabled = W.Checkbox(description=\"enabled?\")\n",
-    "    T.link((ui_label_column, \"value\"), (labels, \"column_name\"))\n",
+    "    T.link((ui_label_column, \"value\"), (behavior, \"column_name\"))\n",
     "    T.dlink(\n",
     "        (ui_label_template, \"value\"),\n",
-    "        (labels, \"template\"),\n",
+    "        (behavior, \"template\"),\n",
     "        lambda x: x if ui_label_template_enabled.value else \"\",\n",
     "    )\n",
-    "    ui_labels = make_a_collapsible_picker(\n",
+    "    ui = U.make_a_collapsible_picker(\n",
     "        \"node: label\",\n",
     "        {\n",
     "            \"off\": [],\n",
@@ -326,8 +358,9 @@
     "            \"template\": [W.VBox([ui_label_template_enabled, ui_label_template])],\n",
     "        },\n",
     "    )\n",
-    "    box.behaviors = {**box.behaviors, \"node_label\": labels}\n",
-    "    box.node_ui = {**box.node_ui, \"label\": ui_labels}\n",
+    "    label = \"node: labels\"\n",
+    "    box.node_ui = {**box.node_ui, label: ui}\n",
+    "    U.make_link_dropdown_responsive(behavior, label, ui, box)\n",
     "    return fg, box"
    ]
   },
@@ -379,47 +412,6 @@
     "## Link Behaviors"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "a5aa1fe6-9829-4e9f-bee0-1f661936894d",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "def make_link_behavior_with_ui(WidgetClass, label, column_name, is_color=False):\n",
-    "    def add_behavior(fg, box, column_name=column_name):\n",
-    "        behavior = WidgetClass(column_name=column_name)\n",
-    "        if is_color:\n",
-    "            if column_name not in fg.source.links:\n",
-    "                make_random_color_series(fg, \"links\", column_name)\n",
-    "        ui_column_name = W.Dropdown(\n",
-    "            options=list(fg.source.links.columns), value=column_name\n",
-    "        )\n",
-    "        ui_template = W.Textarea()\n",
-    "        ui_template_enabled = W.Checkbox(description=\"enabled?\")\n",
-    "        T.link((ui_column_name, \"value\"), (behavior, \"column_name\"))\n",
-    "        T.dlink(\n",
-    "            (ui_template, \"value\"),\n",
-    "            (behavior, \"template\"),\n",
-    "            lambda x: x if ui_template_enabled.value else \"\",\n",
-    "        )\n",
-    "\n",
-    "        ui = make_a_collapsible_picker(\n",
-    "            label,\n",
-    "            {\n",
-    "                \"off\": [],\n",
-    "                \"column\": [ui_column_name],\n",
-    "                \"template\": [W.VBox([ui_template_enabled, ui_template])],\n",
-    "            },\n",
-    "        )\n",
-    "\n",
-    "        box.behaviors = {**box.behaviors, label: behavior}\n",
-    "        box.link_ui = {**box.link_ui, label: ui}\n",
-    "        return fg, box\n",
-    "\n",
-    "    return add_behavior"
-   ]
-  },
   {
    "cell_type": "markdown",
    "id": "5ecec943-13b8-44e8-b207-6c4e498f81e8",
@@ -437,7 +429,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "add_link_colors = make_link_behavior_with_ui(B.LinkColors, \"link: color\", \"color\", True)"
+    "add_link_colors = U.make_link_behavior_with_ui(\n",
+    "    B.LinkColors, \"link: color\", \"color\", True\n",
+    ")"
    ]
   },
   {
@@ -468,7 +462,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "add_link_labels = make_link_behavior_with_ui(B.LinkLabels, \"link: label\", \"value\")\n",
+    "add_link_labels = U.make_link_behavior_with_ui(B.LinkLabels, \"link: label\", \"value\")\n",
     "\n",
     "if __name__ == \"__main__\":\n",
     "    add_link_labels(fg, box)"
@@ -489,7 +483,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "add_link_directional_arrow_color = make_link_behavior_with_ui(\n",
+    "add_link_directional_arrow_color = U.make_link_behavior_with_ui(\n",
     "    B.LinkDirectionalArrowColor, \"link: directional arrow color\", \"color\", is_color=True\n",
     ")\n",
     "\n",
@@ -512,7 +506,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "add_link_directional_arrow_length = make_link_behavior_with_ui(\n",
+    "add_link_directional_arrow_length = U.make_link_behavior_with_ui(\n",
     "    B.LinkDirectionalArrowLength, \"link: directional arrow length\", \"value\"\n",
     ")\n",
     "\n",
@@ -535,7 +529,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "add_link_directional_arrow_rel_pos = make_link_behavior_with_ui(\n",
+    "add_link_directional_arrow_rel_pos = U.make_link_behavior_with_ui(\n",
     "    B.LinkDirectionalArrowRelPos, \"link: directional arrow relative position\", \"value\"\n",
     ")\n",
     "\n",
@@ -558,7 +552,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "add_link_directional_particle_color = make_link_behavior_with_ui(\n",
+    "add_link_directional_particle_color = U.make_link_behavior_with_ui(\n",
     "    B.LinkDirectionalParticleColor,\n",
     "    \"link: directional particle color\",\n",
     "    \"color\",\n",
@@ -584,7 +578,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "add_link_directional_particle_speed = make_link_behavior_with_ui(\n",
+    "add_link_directional_particle_speed = U.make_link_behavior_with_ui(\n",
     "    B.LinkDirectionalParticleSpeed, \"link: directional particle speed\", \"value\"\n",
     ")\n",
     "\n",
@@ -607,7 +601,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "add_link_directional_particle_width = make_link_behavior_with_ui(\n",
+    "add_link_directional_particle_width = U.make_link_behavior_with_ui(\n",
     "    B.LinkDirectionalParticleWidth, \"link: directional particle width\", \"value\"\n",
     ")\n",
     "\n",
@@ -630,7 +624,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "add_link_directional_particles = make_link_behavior_with_ui(\n",
+    "add_link_directional_particles = U.make_link_behavior_with_ui(\n",
     "    B.LinkDirectionalParticles, \"link: directional particles\", \"value\"\n",
     ")\n",
     "\n",
diff --git a/examples/Utils.ipynb b/examples/Utils.ipynb
new file mode 100644
index 00000000..d97c6400
--- /dev/null
+++ b/examples/Utils.ipynb
@@ -0,0 +1,166 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "62ec73d2-42cb-46ef-b938-587b8d209ee3",
+   "metadata": {},
+   "source": [
+    "# 🔧 Utilities\n",
+    "\n",
+    "Some utilities for demos, used in other notebooks."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a6283c28-99f4-48d0-aa66-6b0e6366f881",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "if __name__ == \"__main__\" and \"pyodide\" in __import__(\"sys\").modules:\n",
+    "    %pip install -q -r requirements.txt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9f3f85fe-a0a9-4086-992e-2ca1552f07b8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import random\n",
+    "\n",
+    "import ipywidgets as W\n",
+    "import traitlets as T"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "fdcacf2a-57bb-4242-a398-9e5e627ec4a1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def make_a_collapsible_picker(title, children):\n",
+    "    ui_children = []\n",
+    "    ui = W.HBox(layout=dict(flex_wrap=\"wrap\"))\n",
+    "    box = W.Accordion([ui], titles=[title])\n",
+    "    select = None\n",
+    "    if len(children) > 1:\n",
+    "        select = W.Dropdown(options=list(children), layout=dict(flex=\"0\"))\n",
+    "        ui.children = [select]\n",
+    "        T.dlink((select, \"value\"), (ui, \"children\"), lambda x: [select, *children[x]])\n",
+    "        T.dlink((select, \"value\"), (box, \"titles\"), lambda x: (f\"{title} ({x})\",))\n",
+    "        box.titles = (f\"{title} ({select.value})\",)\n",
+    "    elif len(children) == 1:\n",
+    "        ui.children = tuple(list(children.values())[0])\n",
+    "    else:\n",
+    "        raise ValueError(f\"unexpected number of children {children}\")\n",
+    "    box._select = select\n",
+    "    return box"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "79767776-100b-4679-b84f-ab146f08cb3b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def make_random_color_series(fg, trait, column_name):\n",
+    "    df = getattr(fg.source, trait)\n",
+    "    digits = \"01234567abcdef\"\n",
+    "    df[column_name] = [\n",
+    "        \"#\" + \"\".join([random.choice(digits) for j in range(6)]) for i in range(len(df))\n",
+    "    ]\n",
+    "    fg.source.send_state(trait)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4bfc07df-1e6f-40a3-b1ea-db9f6796c746",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def make_link_dropdown_responsive(behavior, label, ui, box):\n",
+    "    box.behaviors = {**box.behaviors, label: None}\n",
+    "\n",
+    "    def on_select(change: T.Bunch = None):\n",
+    "        new_behaviors = dict(box.behaviors.items())\n",
+    "        if ui._select.value == \"off\":\n",
+    "            new_behaviors[label] = None\n",
+    "        else:\n",
+    "            new_behaviors[label] = behavior\n",
+    "        box.behaviors = new_behaviors\n",
+    "\n",
+    "    ui._select.observe(on_select, T.All)\n",
+    "    on_select()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "8b50ea3e-2a7d-4269-89c3-274b57a8ed24",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def make_link_behavior_with_ui(WidgetClass, label, column_name, is_color=False):\n",
+    "    def add_behavior(fg, box, column_name=column_name):\n",
+    "        behavior = WidgetClass(column_name=column_name)\n",
+    "        if is_color:\n",
+    "            if column_name not in fg.source.links:\n",
+    "                make_random_color_series(fg, \"links\", column_name)\n",
+    "        ui_column_name = W.Dropdown(\n",
+    "            options=list(fg.source.links.columns), value=column_name\n",
+    "        )\n",
+    "        ui_template = W.Textarea()\n",
+    "        ui_template_enabled = W.Checkbox(description=\"enabled?\")\n",
+    "        T.link((ui_column_name, \"value\"), (behavior, \"column_name\"))\n",
+    "        T.dlink(\n",
+    "            (ui_template, \"value\"),\n",
+    "            (behavior, \"template\"),\n",
+    "            lambda x: x if ui_template_enabled.value else \"\",\n",
+    "        )\n",
+    "\n",
+    "        ui = make_a_collapsible_picker(\n",
+    "            label,\n",
+    "            {\n",
+    "                \"off\": [],\n",
+    "                \"column\": [ui_column_name],\n",
+    "                \"template\": [W.VBox([ui_template_enabled, ui_template])],\n",
+    "            },\n",
+    "        )\n",
+    "\n",
+    "        box.link_ui = {**box.link_ui, label: ui}\n",
+    "\n",
+    "        make_link_dropdown_responsive(behavior, label, ui, box)\n",
+    "\n",
+    "        return fg, box\n",
+    "\n",
+    "    return add_behavior"
+   ]
+  }
+ ],
+ "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.11.0"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/examples/_index.ipynb b/examples/_index.ipynb
index e4793405..88b028e5 100644
--- a/examples/_index.ipynb
+++ b/examples/_index.ipynb
@@ -67,15 +67,28 @@
     "        .widget-box:empty{display:none;}\n",
     "    </style>\"\"\"\n",
     "    )\n",
+    "    graph_ui = W.VBox()\n",
     "    node_ui = W.VBox()\n",
     "    link_ui = W.VBox()\n",
-    "    ui = W.VBox([node_ui, link_ui], layout=dict(width=\"500px\", overflow_y=\"scroll\"))\n",
+    "    ui = W.VBox(\n",
+    "        [graph_ui, node_ui, link_ui], layout=dict(width=\"500px\", overflow_y=\"scroll\")\n",
+    "    )\n",
     "    box = W.HBox([style, fg, ui], layout=dict(height=\"100%\", max_height=\"100vh\"))\n",
     "    box.add_class(\"jp-fg-demo\")\n",
     "    box.add_traits(\n",
-    "        behaviors=T.Dict(), node_behaviors=T.Dict(), node_ui=T.Dict(), link_ui=T.Dict()\n",
+    "        behaviors=T.Dict(),\n",
+    "        node_ui=T.Dict(),\n",
+    "        link_ui=T.Dict(),\n",
+    "        graph_ui=T.Dict(),\n",
     "    )\n",
-    "    T.dlink((box, \"behaviors\"), (fg, \"behaviors\"), lambda d: tuple(d.values()))\n",
+    "\n",
+    "    def on_box_behaviors(change=None):\n",
+    "        new_behaviors = [b for b in box.behaviors.values() if b is not None]\n",
+    "        fg.behaviors = tuple(new_behaviors)\n",
+    "\n",
+    "    box.observe(on_box_behaviors, [\"behaviors\"])\n",
+    "\n",
+    "    T.dlink((box, \"graph_ui\"), (graph_ui, \"children\"), lambda d: tuple(d.values()))\n",
     "    T.dlink((box, \"node_ui\"), (node_ui, \"children\"), lambda d: tuple(d.values()))\n",
     "    T.dlink((box, \"link_ui\"), (link_ui, \"children\"), lambda d: tuple(d.values()))\n",
     "    return fg, box"
@@ -116,11 +129,27 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "# Read more\n",
+    "# Read More"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Examples\n",
     "\n",
     "- [👟 Behaviors](./Behaviors.ipynb)\n",
     "- [🔮 3D](./3D.ipynb)"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Miscellaneous\n",
+    "\n",
+    "- [🔧 Demo Utilities](./Utils.ipynb)"
+   ]
   }
  ],
  "metadata": {
diff --git a/install.json b/install.json
deleted file mode 100644
index 1bbaf31e..00000000
--- a/install.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "packageManager": "python",
-  "packageName": "ipyforcegraph",
-  "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package ipyforcegraph"
-}
diff --git a/js/tokens.ts b/js/tokens.ts
index 9c821b63..2c311f9e 100644
--- a/js/tokens.ts
+++ b/js/tokens.ts
@@ -3,6 +3,7 @@
  * Distributed under the terms of the Modified BSD License.
  */
 import type { GraphData, LinkObject, NodeObject } from 'force-graph';
+import type { WebGLRenderer } from 'three';
 
 import type { ISignal } from '@lumino/signaling';
 
@@ -60,19 +61,27 @@ export interface IBehave {
   getNodeLabel?(options: INodeBehaveOptions): string | null;
   // evented
   onNodeClick?(options: INodeEventBehaveOptions): boolean;
+  onRender?(options: IRenderOptions): void;
 }
 
-export type TNodeBehaveMethod = 'getNodeLabel' | 'getNodeColor';
-export type TLinkBehaveMethod =
-  | 'getLinkLabel'
-  | 'getLinkColor'
-  | 'getLinkDirectionalArrowColor'
-  | 'getLinkDirectionalArrowLength'
-  | 'getLinkDirectionalArrowRelPos'
-  | 'getLinkDirectionalParticleColor'
-  | 'getLinkDirectionalParticleSpeed'
-  | 'getLinkDirectionalParticleWidth'
-  | 'getLinkDirectionalParticles';
+export const ALL_LINK_METHODS = [
+  'getLinkLabel',
+  'getLinkColor',
+  'getLinkDirectionalArrowColor',
+  'getLinkDirectionalArrowLength',
+  'getLinkDirectionalArrowRelPos',
+  'getLinkDirectionalParticleColor',
+  'getLinkDirectionalParticleSpeed',
+  'getLinkDirectionalParticleWidth',
+  'getLinkDirectionalParticles',
+];
+export type TLinkBehaveMethod = typeof ALL_LINK_METHODS[number];
+
+export const ALL_NODE_METHODS = ['getNodeLabel', 'getNodeColor'];
+export type TNodeBehaveMethod = typeof ALL_NODE_METHODS[number];
+
+export type TNodeMethodMap = Map<TNodeBehaveMethod, IBehave[]>;
+export type TLinkMethodMap = Map<TLinkBehaveMethod, IBehave[]>;
 
 export interface IBehaveOptions {
   view: IHasGraph;
@@ -91,6 +100,13 @@ export interface ILinkBehaveOptions extends IBehaveOptions {
   link: LinkObject;
 }
 
+export interface IRenderOptions extends IBehaveOptions {
+  context2d?: CanvasRenderingContext2D;
+  renderer3d?: WebGLRenderer;
+  globalScale?: number;
+  time?: number;
+}
+
 export interface IHasGraph<T = any> extends DOMWidgetView {
   graph: T;
   source: ISource;
@@ -100,4 +116,7 @@ export interface IHasGraph<T = any> extends DOMWidgetView {
 
 export interface ISource {
   graphData: GraphData;
+  dataUpdated: ISignal<ISource, void>;
 }
+
+export const emptyArray = Object.freeze([]);
diff --git a/js/widgets/behaviors/base.ts b/js/widgets/behaviors/base.ts
index e014d05b..7694972a 100644
--- a/js/widgets/behaviors/base.ts
+++ b/js/widgets/behaviors/base.ts
@@ -50,7 +50,7 @@ export class ColumnOrTemplateModel extends BehaviorModel implements IBehave {
     return this.get('template') || null;
   }
 
-  initialize(attributes: any, options: any) {
+  initialize(attributes: Backbone.ObjectHash, options: IBackboneModelOptions) {
     super.initialize(attributes, options);
     this.on('change:column_name', this.onColumnNameChange, this);
     this.on('change:template', this.onTemplateChange, this);
diff --git a/js/widgets/behaviors/graph-image.ts b/js/widgets/behaviors/graph-image.ts
new file mode 100644
index 00000000..1cb0e25b
--- /dev/null
+++ b/js/widgets/behaviors/graph-image.ts
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2023 ipyforcegraph contributors.
+ * Distributed under the terms of the Modified BSD License.
+ */
+import {
+  IBackboneModelOptions,
+  WidgetModel,
+  unpack_models as deserialize,
+} from '@jupyter-widgets/base';
+import { ImageModel } from '@jupyter-widgets/controls';
+
+import { EMOJI, IBehave, IRenderOptions, WIDGET_DEFAULTS } from '../../tokens';
+
+import { BehaviorModel } from './base';
+
+export class GraphImageModel extends BehaviorModel implements IBehave {
+  static model_name = 'GraphImageModel';
+  static serializers = {
+    ...WidgetModel.serializers,
+    frames: { deserialize },
+  };
+
+  protected _framesToCapture = 0;
+
+  defaults() {
+    return {
+      ...super.defaults(),
+      ...WIDGET_DEFAULTS,
+      _model_name: GraphImageModel.model_name,
+      enabled: false,
+      frame_count: 1,
+      frames: [],
+    };
+  }
+
+  get frameCount(): number {
+    return this.get('frame_count');
+  }
+
+  get capturing(): boolean {
+    return this.get('capturing');
+  }
+
+  get frames(): ImageModel[] {
+    return this.get('frames');
+  }
+
+  initialize(attributes: Backbone.ObjectHash, options: IBackboneModelOptions) {
+    super.initialize(attributes, options);
+    this.on('change:capturing', this.onCapturingChanged, this);
+    this.onCapturingChanged();
+  }
+
+  protected onCapturingChanged(e?: any): void {
+    this._framesToCapture = this.capturing ? this.frameCount : 0;
+  }
+
+  onRender(options: IRenderOptions): void {
+    const { _framesToCapture } = this;
+
+    if (!_framesToCapture) {
+      return;
+    }
+
+    const { frameCount } = this;
+
+    const index = frameCount - _framesToCapture;
+
+    this._framesToCapture -= 1;
+
+    const { context2d, renderer3d } = options;
+
+    let canvas: HTMLCanvasElement | null = null;
+
+    if (context2d) {
+      canvas = context2d.canvas;
+    } else if (renderer3d) {
+      canvas = renderer3d.domElement;
+    }
+
+    if (canvas == null) {
+      console.warn(`${EMOJI} couldn't handle post render of`, options);
+      return;
+    }
+
+    canvas.toBlob(this.onBlob.bind(this, index, this._framesToCapture === 0));
+  }
+
+  protected onBlob = async (
+    index: number,
+    save: boolean,
+    blob: Blob
+  ): Promise<void> => {
+    let frame = this.frames[index];
+    const value = new DataView(await blob.arrayBuffer());
+    frame.set({ value });
+    if (!save) {
+      return;
+    }
+    for (frame of this.frames) {
+      frame.save();
+    }
+    this.set({ capturing: false });
+    this.save();
+  };
+}
diff --git a/js/widgets/behaviors/index.ts b/js/widgets/behaviors/index.ts
index 7a474328..94b1a58e 100644
--- a/js/widgets/behaviors/index.ts
+++ b/js/widgets/behaviors/index.ts
@@ -13,6 +13,7 @@ export * from './link-directional-particle-speed';
 export * from './link-directional-particle-width';
 export * from './link-directional-particles';
 export * from './link-label';
+export * from './graph-image';
 export * from './node-color';
 export * from './node-label';
 export * from './node-selection';
diff --git a/js/widgets/behaviors/node-selection.ts b/js/widgets/behaviors/node-selection.ts
index 2e27c1c2..eb57a50c 100644
--- a/js/widgets/behaviors/node-selection.ts
+++ b/js/widgets/behaviors/node-selection.ts
@@ -2,7 +2,11 @@
  * Copyright (c) 2023 ipyforcegraph contributors.
  * Distributed under the terms of the Modified BSD License.
  */
-import { WidgetModel, unpack_models as deserialize } from '@jupyter-widgets/base';
+import {
+  IBackboneModelOptions,
+  WidgetModel,
+  unpack_models as deserialize,
+} from '@jupyter-widgets/base';
 
 import {
   DEFAULT_COLORS,
@@ -23,8 +27,6 @@ export class NodeSelectionModel extends BehaviorModel implements IBehave {
     value: { deserialize },
   };
 
-  protected _viewId: string | null = null;
-
   defaults() {
     return {
       ...super.defaults(),
@@ -36,7 +38,7 @@ export class NodeSelectionModel extends BehaviorModel implements IBehave {
     };
   }
 
-  initialize(attributes: any, options: any) {
+  initialize(attributes: Backbone.ObjectHash, options: IBackboneModelOptions) {
     super.initialize(attributes, options);
     this.on('change:selected', this.onValueChange, this);
     this.onValueChange();
diff --git a/js/widgets/display/2d.ts b/js/widgets/display/2d.ts
index ff429597..fec8e916 100644
--- a/js/widgets/display/2d.ts
+++ b/js/widgets/display/2d.ts
@@ -2,6 +2,7 @@
  * Copyright (c) 2023 ipyforcegraph contributors.
  * Distributed under the terms of the Modified BSD License.
  */
+import { ObjectHash } from 'backbone';
 import type {
   ForceGraphGenericInstance,
   ForceGraphInstance,
@@ -11,14 +12,19 @@ import type {
 } from 'force-graph';
 
 import { PromiseDelegate } from '@lumino/coreutils';
+import { ISignal, Signal } from '@lumino/signaling';
 
 import {
   DOMWidgetModel,
   DOMWidgetView,
+  IBackboneModelOptions,
+  WidgetView,
   unpack_models as deserialize,
 } from '@jupyter-widgets/base';
 
 import {
+  ALL_LINK_METHODS,
+  ALL_NODE_METHODS,
   CSS,
   DEBUG,
   DEFAULT_COLORS,
@@ -29,10 +35,14 @@ import {
   ILinkBehaveOptions,
   INodeBehaveOptions,
   INodeEventBehaveOptions,
+  IRenderOptions,
   ISource,
   TLinkBehaveMethod,
+  TLinkMethodMap,
   TNodeBehaveMethod,
+  TNodeMethodMap,
   WIDGET_DEFAULTS,
+  emptyArray,
 } from '../../tokens';
 
 export class ForceGraphModel extends DOMWidgetModel {
@@ -43,6 +53,10 @@ export class ForceGraphModel extends DOMWidgetModel {
     behaviors: { deserialize },
   };
 
+  protected _nodeBehaviorsByMethod: TNodeMethodMap;
+  protected _linkBehaviorsByMethod: TLinkMethodMap;
+  protected _behaviorsChanged: Signal<ForceGraphModel, void>;
+
   defaults() {
     return {
       ...super.defaults(),
@@ -52,10 +66,54 @@ export class ForceGraphModel extends DOMWidgetModel {
       source: null,
       behaviors: [],
       default_node_color: DEFAULT_COLORS.node,
-      default_edge_color: DEFAULT_COLORS.link,
+      default_link_color: DEFAULT_COLORS.link,
     };
   }
 
+  initialize(attributes: ObjectHash, options: IBackboneModelOptions): void {
+    super.initialize(attributes, options);
+    this.on('change:behaviors', this.onBehaviorsChange, this);
+    this.onBehaviorsChange();
+  }
+
+  onBehaviorsChange(): void {
+    if (!this._behaviorsChanged) {
+      this._behaviorsChanged = new Signal(this);
+      this._linkBehaviorsByMethod = new Map();
+      this._nodeBehaviorsByMethod = new Map();
+    }
+    const { behaviors } = this;
+
+    for (let linkMethod of ALL_LINK_METHODS) {
+      let methodBehaviors: IBehave[] = [];
+      for (const behavior of behaviors) {
+        if (behavior[linkMethod]) {
+          methodBehaviors.push(behavior);
+        }
+      }
+      this._linkBehaviorsByMethod.set(linkMethod, methodBehaviors);
+    }
+
+    for (let nodeMethod of ALL_NODE_METHODS) {
+      let methodBehaviors: IBehave[] = [];
+      for (const behavior of behaviors) {
+        if (behavior[nodeMethod]) {
+          methodBehaviors.push(behavior);
+        }
+      }
+      this._nodeBehaviorsByMethod.set(nodeMethod, methodBehaviors);
+    }
+    this._behaviorsChanged.emit(void 0);
+  }
+
+  linkBehaviorsForMethod(method: TLinkBehaveMethod): readonly IBehave[] {
+    return this._linkBehaviorsByMethod.get(method) || emptyArray;
+  }
+
+  nodeBehaviorsForMethod(method: TNodeBehaveMethod): readonly IBehave[] {
+    return this._nodeBehaviorsByMethod.get(method) || emptyArray;
+  }
+
   get graphData(): GraphData {
     const source = this.get('source');
     return source ? source.graphData : EMPTY_GRAPH_DATA;
@@ -65,6 +123,14 @@ export class ForceGraphModel extends DOMWidgetModel {
     return this.get('behaviors') || [];
   }
 
+  get behaviorsChanged(): ISignal<ForceGraphModel, void> {
+    return this._behaviorsChanged;
+  }
+
+  get previousBehaviors(): IBehave[] {
+    return (this.previous && this.previous('behaviors')) || [];
+  }
+
   get defaultNodeColor(): string {
     return this.get('default_node_color') || DEFAULT_COLORS.node;
   }
@@ -89,20 +155,26 @@ export class ForceGraphView<T = ForceGraphGenericInstance<ForceGraphInstance>>
     return this.model.get('source');
   }
 
+  get previousSource(): ISource | null {
+    return (this.model.previous && this.model.previous('source')) || null;
+  }
+
   get rendered(): Promise<void> {
     return this._rendered.promise;
   }
 
-  initialize(parameters: any) {
+  initialize(parameters: WidgetView.IInitializeParameters<ForceGraphModel>) {
     super.initialize(parameters);
     this.luminoWidget.addClass(CSS.widget);
     this.luminoWidget.addClass(`${CSS.widget}-${this.graphJsClass}`);
     this._rendered = new PromiseDelegate();
     this.model.on('change:source', this.onSourceChange, this);
-    this.model.on('change:behaviors', this.onBehaviorsChange, this);
+
+    this.model.behaviorsChanged.connect(this.onBehaviorsChange, this);
+    this.luminoWidget.disposed.connect(this.onDisposed, this);
+
     this.onSourceChange();
     this.onBehaviorsChange();
-    this.luminoWidget.disposed.connect(this.onDisposed, this);
   }
 
   onDisposed() {
@@ -179,10 +251,10 @@ export class ForceGraphView<T = ForceGraphGenericInstance<ForceGraphInstance>>
       </head>
       <body>
         <script>
-          window.init = () => {
+          window.init = (args) => {
             const div = document.createElement('div');
             document.body.appendChild(div);
-            return ${this.graphJsClass}()(div);
+            return ${this.graphJsClass}(args || {})(div);
           }
           window.wrapFunction = (fn) => {
             return (...args) => fn(...args);
@@ -206,8 +278,12 @@ export class ForceGraphView<T = ForceGraphGenericInstance<ForceGraphInstance>>
   }
 
   onSourceChange(change?: any) {
-    // TODO disconnect old model...
-    let source = this.model.get('source');
+    const { source, previousSource } = this;
+
+    if (previousSource) {
+      previousSource.dataUpdated.disconnect(this.update, this);
+    }
+
     if (source) {
       source.dataUpdated.connect(this.update, this);
       this.update();
@@ -250,18 +326,35 @@ export class ForceGraphView<T = ForceGraphGenericInstance<ForceGraphInstance>>
 
       // evented
       graph.onNodeClick(this.wrapFunction(this.onNodeClick));
+
+      // (3d-)force-graph-specific
+      this.getOnRenderPostUpdate();
     } else {
       console.warn(`${EMOJI} no graph to postUpdate`);
     }
   }
 
+  protected getOnRenderPostUpdate() {
+    const graph = this.graph as ForceGraphInstance;
+    graph.onRenderFramePost(this.wrapFunction(this.onRender));
+  }
+
   // composable behaviors
   async onBehaviorsChange(): Promise<void> {
-    // TODO: disconnect old model...
-    const behaviors: IBehave[] = this.model.behaviors;
+    const { behaviors, previousBehaviors } = this.model;
+
+    for (const previous of previousBehaviors) {
+      if (!behaviors.includes(previous)) {
+        previous.updateRequested.disconnect(this.postUpdate, this);
+      }
+    }
+
     for (const behavior of behaviors) {
-      behavior.updateRequested.connect(this.postUpdate, this);
+      if (!previousBehaviors.includes(behavior)) {
+        behavior.updateRequested.connect(this.postUpdate, this);
+      }
     }
+
     await this.update();
   }
 
@@ -326,7 +419,6 @@ export class ForceGraphView<T = ForceGraphGenericInstance<ForceGraphInstance>>
     methodName: TLinkBehaveMethod,
     defaultValue: string
   ) {
-    const { behaviors } = this.model;
     let value: string | null;
     const graphData = (this.graph as ForceGraphInstance).graphData();
     const options: ILinkBehaveOptions = {
@@ -335,11 +427,8 @@ export class ForceGraphView<T = ForceGraphGenericInstance<ForceGraphInstance>>
       link,
     };
 
-    for (const behavior of behaviors) {
+    for (const behavior of this.model.linkBehaviorsForMethod(methodName)) {
       let method = behavior[methodName];
-      if (!method) {
-        continue;
-      }
       value = method.call(behavior, options);
       if (value != null) {
         break;
@@ -363,7 +452,6 @@ export class ForceGraphView<T = ForceGraphGenericInstance<ForceGraphInstance>>
     methodName: TNodeBehaveMethod,
     defaultValue: string
   ) {
-    const { behaviors } = this.model;
     let value: string | null;
     const graphData = (this.graph as ForceGraphInstance).graphData();
     const options: INodeBehaveOptions = {
@@ -372,11 +460,8 @@ export class ForceGraphView<T = ForceGraphGenericInstance<ForceGraphInstance>>
       node,
     };
 
-    for (const behavior of behaviors) {
+    for (const behavior of this.model.nodeBehaviorsForMethod(methodName)) {
       let method = behavior[methodName];
-      if (!method) {
-        continue;
-      }
       value = method.call(behavior, options);
       if (value != null) {
         break;
@@ -406,4 +491,26 @@ export class ForceGraphView<T = ForceGraphGenericInstance<ForceGraphInstance>>
       }
     }
   };
+
+  protected updateRenderOptions(options: IRenderOptions): IRenderOptions {
+    return options;
+  }
+
+  protected onRender = (ctx: CanvasRenderingContext2D, globalScale: any) => {
+    const { behaviors } = this.model;
+    const graphData = (this.graph as ForceGraphInstance).graphData();
+    let options: IRenderOptions = {
+      view: this,
+      graphData,
+      context2d: ctx,
+      globalScale,
+    };
+    options = this.updateRenderOptions(options);
+    for (const behavior of behaviors) {
+      if (!behavior.onRender) {
+        continue;
+      }
+      behavior.onRender(options);
+    }
+  };
 }
diff --git a/js/widgets/display/3d.ts b/js/widgets/display/3d.ts
index 9b339612..ca9eb668 100644
--- a/js/widgets/display/3d.ts
+++ b/js/widgets/display/3d.ts
@@ -3,6 +3,9 @@
  * Distributed under the terms of the Modified BSD License.
  */
 import type { ForceGraph3DGenericInstance, ForceGraph3DInstance } from '3d-force-graph';
+import type { WebGLRenderer } from 'three';
+
+import { IRenderOptions } from '../../tokens';
 
 import { ForceGraphModel, ForceGraphView } from './2d';
 
@@ -36,4 +39,20 @@ export class ForceGraph3DView extends ForceGraphView<
     return (await import('!!file-loader!3d-force-graph/dist/3d-force-graph.js'))
       .default;
   }
+
+  protected get threeRenderer(): WebGLRenderer {
+    const graph = this.graph as ForceGraph3DInstance;
+    return graph.renderer() as WebGLRenderer;
+  }
+
+  protected getOnRenderPostUpdate() {
+    this.threeRenderer.setAnimationLoop(this.wrapFunction(this.onRender));
+  }
+
+  protected updateRenderOptions(options: IRenderOptions): IRenderOptions {
+    delete options.context2d;
+    delete options.globalScale;
+    options.renderer3d = this.threeRenderer;
+    return options;
+  }
 }
diff --git a/js/widgets/sources/dataframe.ts b/js/widgets/sources/dataframe.ts
index c56710a0..901614bb 100644
--- a/js/widgets/sources/dataframe.ts
+++ b/js/widgets/sources/dataframe.ts
@@ -6,13 +6,16 @@ import { GraphData } from 'force-graph';
 
 import { ISignal, Signal } from '@lumino/signaling';
 
-import { WidgetModel } from '@jupyter-widgets/base';
-
-import { DEFAULT_COLUMNS, EMPTY_GRAPH_DATA, WIDGET_DEFAULTS } from '../../tokens';
+import { IBackboneModelOptions, WidgetModel } from '@jupyter-widgets/base';
+
+import {
+  DEFAULT_COLUMNS,
+  EMPTY_GRAPH_DATA,
+  WIDGET_DEFAULTS,
+  emptyArray,
+} from '../../tokens';
 import { jsonToDataFrame } from '../serializers';
 
-const emptyArray = Object.freeze([]);
-
 export class DataFrameSourceModel extends WidgetModel {
   static model_name = 'DataFrameSourceModel';
   static serializers = {
@@ -38,7 +41,7 @@ export class DataFrameSourceModel extends WidgetModel {
     };
   }
 
-  initialize(attributes: any, options: any) {
+  initialize(attributes: Backbone.ObjectHash, options: IBackboneModelOptions) {
     super.initialize(attributes, options);
 
     this.on('change:nodes', this.graphUpdate, this);
diff --git a/package.json b/package.json
index 8caf5cc0..586374b4 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
   "version": "0.1.0",
   "description": "force-graph widget for Jupyter",
   "license": "BSD-3-Clause",
-  "author": "Dane Freeman",
+  "author": "ipyforcegraph contributors",
   "homepage": "https://github.com/jupyrdf/ipyforcegraph",
   "repository": {
     "type": "git",
@@ -59,6 +59,7 @@
     "@trivago/prettier-plugin-sort-imports": "^4.0.0",
     "@types/lodash": "^4.14.162",
     "@types/nunjucks": "^3.2.1",
+    "@types/three": "^0.148.0",
     "file-loader": "^6.2.0",
     "npm-run-all": "^4.1.5",
     "prettier": "^2.8.0",
diff --git a/pyproject.toml b/pyproject.toml
index aed2053f..b40e88c7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,9 +5,9 @@ build-backend = "flit_core.buildapi"
 [project]
 name = "ipyforcegraph"
 version = "0.1.0"
-description = "force-graph widget for Jupyter"
+description = "2D and 3D force-directed graph widgets for Jupyter"
 authors = [
-    {name = "Dane Freeman", email = "dane.freeman@gtri.gatech.edu"},
+    {name = "ipyforcegraph contributors", email = "jupyrdf@users.noreply.github.com"},
 ]
 readme = "README.md"
 classifiers = [
@@ -19,17 +19,17 @@ classifiers = [
     "Intended Audience :: Developers",
     "Intended Audience :: Science/Research",
     "License :: OSI Approved :: BSD License",
+    "Programming Language :: Python :: 3",
     "Programming Language :: Python :: 3 :: Only",
-    "Programming Language :: Python :: 3.10",
-    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.7",
     "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
-    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
 ]
-requires-python = ">=3.8"
+requires-python = ">=3.7"
 dependencies = [
     "ipywidgets >=8.0.4,<9",
-    "networkx",
     "numcodecs",
     "traittypes",
     "pandas",
diff --git a/scripts/project.py b/scripts/project.py
index 68204224..9f8bb1e1 100644
--- a/scripts/project.py
+++ b/scripts/project.py
@@ -229,7 +229,7 @@ def _get_boolish(name, default="false"):
 
 HTMLCOV = BUILD / "htmlcov"
 HTMLCOV_INDEX = HTMLCOV / "index.html"
-PYTEST_COV_THRESHOLD = 87
+PYTEST_COV_THRESHOLD = 85
 PYTEST_HTML = BUILD / "pytest.html"
 PYTEST_XUNIT = BUILD / "pytest.xunit.xml"
 
@@ -248,8 +248,9 @@ def _get_boolish(name, default="false"):
 
 # robot testing
 ATEST = ROOT / "atest"
-ALL_ROBOT = [*ATEST.rglob("*.robot")]
-ATEST_OUT = ATEST / "output"
+ATEST_FIXTURES = ATEST / "_resources/fixtures"
+ALL_ROBOT = [*ATEST.rglob("*.robot"), *ATEST.rglob("*.resource")]
+ATEST_OUT = BUILD / "robot"
 ATEST_CANARY = BUILD / f"robot.{PLATFORM.lower()}_success.ok"
 
 # docs
diff --git a/src/ipyforcegraph/behaviors.py b/src/ipyforcegraph/behaviors.py
index 49a67270..add62f43 100644
--- a/src/ipyforcegraph/behaviors.py
+++ b/src/ipyforcegraph/behaviors.py
@@ -1,10 +1,9 @@
-"""Behaviors for ipyforcegraph."""
+"""Behaviors for ``ipyforcegraph``."""
 
 # Copyright (c) 2023 ipyforcegraph contributors.
 # Distributed under the terms of the Modified BSD License.
 
-import typing
-from typing import Optional
+from typing import Optional, Tuple
 
 import ipywidgets as W
 import traitlets as T
@@ -12,13 +11,47 @@
 from ._base import Behavior
 
 
+@W.register
+class GraphImage(Behavior):
+    _model_name: str = T.Unicode("GraphImageModel").tag(sync=True)
+
+    capturing = T.Bool(False, help="Whether the frame capture is currently active").tag(
+        sync=True
+    )
+
+    frame_count = T.Int(1, help="The number of frames to capture").tag(sync=True)
+
+    frames: Tuple[W.Image] = W.TypedTuple(
+        T.Instance(W.Image),
+        help="A tuple of `ipywidgets.Image`s to be populated with frames of the graph.",
+    ).tag(sync=True, **W.widget_serialization)
+
+    def _get_frames(self):
+        return [W.Image(description=f"frame {i}") for i in range(self.frame_count)]
+
+    @T.default("frames")
+    def _default_frames(self):
+        return self._get_frames()
+
+    @T.observe("frame_count")
+    def _on_frame_count(self, change: T.Bunch):
+        frames = self.frames
+
+        self.frames = []
+
+        for frame in frames:
+            frame.close()
+
+        self.frames = self._get_frames()
+
+
 @W.register
 class NodeSelection(Behavior):
     """Enable node selection with synced ids of selected nodes."""
 
     _model_name: str = T.Unicode("NodeSelectionModel").tag(sync=True)
 
-    selected: typing.Tuple[int] = W.TypedTuple(
+    selected: Tuple[int] = W.TypedTuple(
         T.Union((T.Int(), T.Unicode())),
         allow_none=True,
         help="the ids of any selected nodes",
@@ -41,8 +74,8 @@ class NodeLabels(Behavior):
         None,
         allow_none=True,
         help=(
-            "name of the source column to use for node labels. If ``None``, use "
-            "the source's ``node_id_column``."
+            "name of the source column to use for node labels. If `None`, use "
+            "the source's `node_id_column`."
         ),
     ).tag(sync=True)
 
diff --git a/src/ipyforcegraph/forcegraph.py b/src/ipyforcegraph/forcegraph.py
index 45489b58..26a2c2ca 100644
--- a/src/ipyforcegraph/forcegraph.py
+++ b/src/ipyforcegraph/forcegraph.py
@@ -17,15 +17,21 @@ class ForceGraph(W.DOMWidget, ForceBase):
     _model_name: str = T.Unicode("ForceGraphModel").tag(sync=True)
     _view_name: str = T.Unicode("ForceGraphView").tag(sync=True)
 
-    source: DataFrameSource = T.Instance(DataFrameSource, kw={}).tag(
-        sync=True, **W.widget_serialization
-    )
-    behaviors: tuple[Behavior] = W.TypedTuple(T.Instance(Behavior), kw={}).tag(
-        sync=True, **W.widget_serialization
-    )
-
-    default_node_color: str = T.Unicode("#1f78b3").tag(sync=True)
-    default_edge_color: str = T.Unicode("#666666").tag(sync=True)
+    source: DataFrameSource = T.Instance(
+        DataFrameSource, kw={}, help="the source of `nodes` and `link` data"
+    ).tag(sync=True, **W.widget_serialization)
+    behaviors: tuple[Behavior] = W.TypedTuple(
+        T.Instance(Behavior),
+        kw={},
+        help="the behaviors that modify the appearance of nodes, links and the graph itself",
+    ).tag(sync=True, **W.widget_serialization)
+
+    default_node_color: str = T.Unicode(
+        "#1f78b3", help="a default node color, which can be overriden by `NodeColors`"
+    ).tag(sync=True)
+    default_link_color: str = T.Unicode(
+        "#666666", help="a default link color, which can be overriden by `LinkColors`"
+    ).tag(sync=True)
 
 
 @W.register
diff --git a/src/ipyforcegraph/schema/forcegraph_schema.json b/src/ipyforcegraph/schema/forcegraph_schema.json
deleted file mode 100644
index 66d93fb3..00000000
--- a/src/ipyforcegraph/schema/forcegraph_schema.json
+++ /dev/null
@@ -1,84 +0,0 @@
-{
-  "$ref": "#/definitions/GraphData",
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "definitions": {
-    "GraphData": {
-      "additionalProperties": false,
-      "properties": {
-        "links": {
-          "items": {
-            "$ref": "#/definitions/LinkObject"
-          },
-          "type": "array"
-        },
-        "nodes": {
-          "items": {
-            "$ref": "#/definitions/NodeObject"
-          },
-          "type": "array"
-        }
-      },
-      "required": ["nodes", "links"],
-      "type": "object"
-    },
-    "LinkObject": {
-      "additionalProperties": false,
-      "properties": {
-        "source": {
-          "anyOf": [
-            {
-              "type": "string"
-            },
-            {
-              "type": "number"
-            },
-            {
-              "$ref": "#/definitions/NodeObject"
-            }
-          ]
-        },
-        "target": {
-          "anyOf": [
-            {
-              "type": "string"
-            },
-            {
-              "type": "number"
-            },
-            {
-              "$ref": "#/definitions/NodeObject"
-            }
-          ]
-        }
-      },
-      "type": "object"
-    },
-    "NodeObject": {
-      "additionalProperties": false,
-      "properties": {
-        "fx": {
-          "type": "number"
-        },
-        "fy": {
-          "type": "number"
-        },
-        "id": {
-          "type": ["string", "number"]
-        },
-        "vx": {
-          "type": "number"
-        },
-        "vy": {
-          "type": "number"
-        },
-        "x": {
-          "type": "number"
-        },
-        "y": {
-          "type": "number"
-        }
-      },
-      "type": "object"
-    }
-  }
-}
diff --git a/src/ipyforcegraph/sources/dataframe.py b/src/ipyforcegraph/sources/dataframe.py
index 26d4e83f..2088c39b 100644
--- a/src/ipyforcegraph/sources/dataframe.py
+++ b/src/ipyforcegraph/sources/dataframe.py
@@ -22,7 +22,7 @@ class DataFrameSource(ForceBase):
 
     node_id_column: str = T.Unicode(
         "id",
-        help="the name of the column for a node's identifier, or 0-based position in the column if None",
+        help="the name of the column for a node's identifier, or 0-based position in the column if `None`",
     ).tag(sync=True)
 
     links: P.DataFrame = TT.PandasType(
@@ -31,26 +31,14 @@ class DataFrameSource(ForceBase):
 
     link_source_column: str = T.Unicode(
         "source",
-        help="the name of the column for a link's source, defaulting to ``source``",
+        help="the name of the column for a link's source, defaulting to `source`",
     ).tag(sync=True)
 
     link_target_column: str = T.Unicode(
         "target",
-        help="the name of the column for a link's target, defaulting to ``target``",
+        help="the name of the column for a link's target, defaulting to `target`",
     ).tag(sync=True)
 
-    # TODO: these go on a subclass of PandasType
-    # sync_node_columns: tuple[str] = W.TypedTuple(
-    #     klass=T.Unicode(),
-    #     allow_none=True,
-    #     help="The columns to provide to the frontend for nodes: send all if ``None``"
-    # ).tag(sync=True)
-    # sync_link_columns: tuple[str] = W.TypedTuple(
-    #     klass=T.Unicode(),
-    #     allow_none=True,
-    #     help="The columns to provide to the frontend for links: send all if ``None``"
-    # ).tag(sync=True)
-
     def __repr__(self):
         """A dumb repr to avoid string nasty pandas comparison stuff."""
         name = self.__class__.__name__
diff --git a/yarn.lock b/yarn.lock
index 831fc86d..99a74c1c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1264,9 +1264,9 @@
   integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
 
 "@types/node@*":
-  version "18.7.23"
-  resolved "https://registry.npmjs.org/@types/node/-/node-18.7.23.tgz#75c580983846181ebe5f4abc40fe9dfb2d65665f"
-  integrity sha512-DWNcCHolDq0ZKGizjx2DZjR/PqsYwAcYUJmfMWqtVU2MBMG5Mo+xFZrhGId5r/O5HOuMPyQEcM6KUBp5lBZZBg==
+  version "18.11.17"
+  resolved "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz#5c009e1d9c38f4a2a9d45c0b0c493fe6cdb4bcb5"
+  integrity sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==
 
 "@types/nunjucks@^3.2.1":
   version "3.2.1"
@@ -1317,6 +1317,13 @@
   resolved "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
   integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==
 
+"@types/three@^0.148.0":
+  version "0.148.0"
+  resolved "https://registry.npmjs.org/@types/three/-/three-0.148.0.tgz#bf7f0f6f12a9b318eebb7dc3813ae69824e13ae6"
+  integrity sha512-1GiErjt1pJMadO8EhaxNrtULzdn+f3BbM3YTukiiXBVeRvvx4CXc2rpYdEuEBU/ouCX7Rc95j1HoVG9kFQXQJw==
+  dependencies:
+    "@types/webxr" "*"
+
 "@types/underscore@*":
   version "1.11.4"
   resolved "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz#62e393f8bc4bd8a06154d110c7d042a93751def3"
@@ -1331,6 +1338,11 @@
     "@types/source-list-map" "*"
     source-map "^0.6.1"
 
+"@types/webxr@*":
+  version "0.5.0"
+  resolved "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz#aae1cef3210d88fd4204f8c33385a0bbc4da07c9"
+  integrity sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA==
+
 "@webassemblyjs/ast@1.11.1":
   version "1.11.1"
   resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"


Test patch:
diff --git a/atest/API/01_Behaviors.robot b/atest/API/01_Behaviors.robot
new file mode 100644
index 00000000..8fa2223c
--- /dev/null
+++ b/atest/API/01_Behaviors.robot
@@ -0,0 +1,62 @@
+*** Settings ***
+Resource            ../_resources/keywords/Server.robot
+Resource            ../_resources/keywords/Browser.robot
+Resource            ../_resources/keywords/Lab.robot
+Resource            ../_resources/keywords/IPyForceGraph.robot
+Library             Collections
+Library             JupyterLibrary
+Library             OperatingSystem
+
+Test Teardown       Clean Up Behavior Example
+
+Force Tags          suite:behaviors
+
+
+*** Variables ***
+${SCREENS}      ${SCREENS ROOT}${/}api
+
+
+*** Test Cases ***
+2D Node Select
+    [Setup]    Set Up Behavior Example    NodeSelection    ForceGraph
+    Click IPyForceGraph Canvas
+    Wait Until Tag Widget Exists    hello world
+    Remove Widget Tag
+    Wait Until No Tag Widgets Exist
+
+2D Node Labels
+    [Setup]    Set Up Behavior Example    NodeLabels    ForceGraph
+    Click IPyForceGraph Canvas    text=hello world
+
+3D Node Select
+    [Setup]    Set Up Behavior Example    NodeSelection    ForceGraph3D
+    Click IPyForceGraph Canvas
+    Wait Until Tag Widget Exists    hello world
+    Remove Widget Tag
+    Wait Until No Tag Widgets Exist
+
+3D Node Labels
+    [Setup]    Set Up Behavior Example    NodeLabels    ForceGraph3D
+    Click IPyForceGraph Canvas    text=hello world
+
+
+*** Keywords ***
+Set Up Behavior Example
+    [Arguments]    ${behavior}    ${widget_class}
+    Set Tags    behavior:${behavior.lower()}    widget:${widget_class.lower()}
+    Set Screenshot Directory    ${SCREENS}${/}${widget_class.lower()}_${behavior.lower()}
+    ${text} =    Get File    ${IPYFORCEGRAPH_FIXTURES}${/}api${/}${behavior}.py
+    ${text} =    Set Variable    ${text.replace("WIDGET_CLASS", "${widget_class}")}
+    Launch A New JupyterLab Document
+    Set CodeMirror Value    .jp-CodeCell .CodeMirror    ${text.strip()}
+    Execute JupyterLab Command    Show Log Console
+    Execute JupyterLab Command    Run All Cells
+    Wait Until Force Graph Is Visible
+    Capture Page Screenshot    00-start.png
+
+Clean Up Behavior Example
+    Capture Page Screenshot    99-fin.png
+    ${nb_dir} =    Get Jupyter Directory
+    Remove File    ${nb_dir}${/}Untitled.ipynb
+    Refresh File List
+    Try To Close All Tabs
diff --git a/atest/API/__init__.robot b/atest/API/__init__.robot
new file mode 100644
index 00000000..11a2b7e2
--- /dev/null
+++ b/atest/API/__init__.robot
@@ -0,0 +1,8 @@
+*** Settings ***
+Documentation       Tests APIs
+
+Resource            ../_resources/keywords/Browser.robot
+
+Suite Setup         Setup Suite For Screenshots    api
+
+Force Tags          ui:notebook
diff --git a/atest/Notebooks/01_Index.robot b/atest/Notebooks/01_Index.robot
index 32844f15..07131577 100644
--- a/atest/Notebooks/01_Index.robot
+++ b/atest/Notebooks/01_Index.robot
@@ -13,5 +13,5 @@ ${SCREENS}      ${SCREENS ROOT}${/}index
 
 *** Test Cases ***
 Index
-    [Tags]    data:miserables    lib:force-graph
+    [Tags]    data:miserables    widget:forcegraph
     Example Should Restart-and-Run-All    ${INDEX}
diff --git a/atest/Notebooks/02_Behaviors.robot b/atest/Notebooks/02_Behaviors.robot
index 007217ce..a40624a2 100644
--- a/atest/Notebooks/02_Behaviors.robot
+++ b/atest/Notebooks/02_Behaviors.robot
@@ -13,5 +13,5 @@ ${SCREENS}      ${SCREENS ROOT}${/}behaviors
 
 *** Test Cases ***
 Behaviors
-    [Tags]    data:miserables    lib:force-graph
+    [Tags]    data:miserables    widget:forcegraph
     Example Should Restart-and-Run-All    ${BEHAVIORS}
diff --git a/atest/Notebooks/03_3D.robot b/atest/Notebooks/03_3D.robot
index f1a3e576..9020be66 100644
--- a/atest/Notebooks/03_3D.robot
+++ b/atest/Notebooks/03_3D.robot
@@ -13,5 +13,5 @@ ${SCREENS}      ${SCREENS ROOT}${/}3d
 
 *** Test Cases ***
 3D
-    [Tags]    data:miserables    lib:3d-force-graph
+    [Tags]    data:miserables    widget:forcegraph3d
     Example Should Restart-and-Run-All    ${THREEDEE}
diff --git a/atest/_resources/fixtures/api/NodeLabels.py b/atest/_resources/fixtures/api/NodeLabels.py
new file mode 100644
index 00000000..d3f1d5e7
--- /dev/null
+++ b/atest/_resources/fixtures/api/NodeLabels.py
@@ -0,0 +1,6 @@
+from ipyforcegraph.forcegraph import WIDGET_CLASS
+from ipyforcegraph import behaviors as B
+import pandas as pd
+fg = WIDGET_CLASS(behaviors=[B.NodeLabels(column_name="id")])
+fg.source.nodes = pd.DataFrame([{"id": "hello world"}])
+fg
diff --git a/atest/_resources/fixtures/api/NodeSelection.py b/atest/_resources/fixtures/api/NodeSelection.py
new file mode 100644
index 00000000..78db045c
--- /dev/null
+++ b/atest/_resources/fixtures/api/NodeSelection.py
@@ -0,0 +1,10 @@
+import traitlets as T, ipywidgets as W, pandas as pd
+from ipyforcegraph.forcegraph import WIDGET_CLASS
+from ipyforcegraph import behaviors as B
+
+b = B.NodeSelection()
+fg = WIDGET_CLASS(behaviors=[b])
+fg.source.nodes = pd.DataFrame([{"id": "hello world"}])
+t = W.TagsInput(allowed_tags=sorted(fg.source.nodes.id))
+T.link((b, "selected"), (t, "value"))
+W.VBox([t, fg])
diff --git a/atest/_resources/keywords/Browser.robot b/atest/_resources/keywords/Browser.robot
index d29fe4d8..08fcea64 100644
--- a/atest/_resources/keywords/Browser.robot
+++ b/atest/_resources/keywords/Browser.robot
@@ -1,6 +1,6 @@
 *** Settings ***
 Resource    CLI.robot
-Library     SeleniumLibrary
+Library     JupyterLibrary
 Library     Collections
 Resource    ../variables/Browser.robot
 
diff --git a/atest/_resources/keywords/IPyForceGraph.robot b/atest/_resources/keywords/IPyForceGraph.robot
index e9c588ff..2bc83bef 100644
--- a/atest/_resources/keywords/IPyForceGraph.robot
+++ b/atest/_resources/keywords/IPyForceGraph.robot
@@ -47,26 +47,60 @@ Open IPyForceGraph Notebook
 
 Copy Support Files
     [Arguments]    ${paths}    ${target}=${/}
+    ${nb_dir} =    Get Jupyter Directory
     FOR    ${path}    IN    @{paths}
         ${parent}    ${name} =    Split Path    ${path}
-        Copy File    ${path}    ${OUTPUT DIR}${/}home${target}${name}
+        Copy File    ${path}    ${nb_dir}${target}${name}
     END
 
 Example Should Restart-and-Run-All
     [Arguments]    ${example}
     Set Screenshot Directory    ${SCREENS}${/}${example.lower()}
+    Execute JupyterLab Command    Show Log Console
     Open IPyForceGraph Notebook    ${example}
     # nothing should be on the page, yet
     Restart and Run All
-    Wait Until Page Contains Element    ${IPYFORCEGRAPH GRAPH}    timeout=60s
+    Wait Until Force Graph Is Visible
     Sleep    5s
     Capture All Code Cells
     Page Should Not Contain Standard Errors
     Capture Page Screenshot    10-ran-all-without-stderr.png
 
+Wait Until Force Graph Is Visible
+    [Arguments]    ${timeout}=60s
+    Wait Until Page Contains Element    css:${IPYFORCEGRAPH FRAME}    timeout=${timeout}
+    Sleep    1s
+
 Clean up after IPyForceGraph Example
     Capture Page Screenshot    99-fin.png
     ${files} =    Get All IPyForceGraph Example File Names
     ${data} =    Get All IPyForceGraph Example Data Names
     Clean up after Working With Files    @{files}
     Clean up after Working With Files    @{data}
+
+Click IPyForceGraph Canvas
+    [Arguments]    ${x}=0    ${y}=0    ${text}=${EMPTY}
+    Select Frame    css:${IPYFORCEGRAPH FRAME}
+    Wait Until Page Contains Element    css:canvas    timeout=10s
+    Click Element At Coordinates    css:canvas    ${x}    ${y}
+    IF    '''${text}'''
+        Wait Until Element Is Visible    css:${IPYFORCEGRAPH TOOLTIP}
+        Element Should Contain    css:${IPYFORCEGRAPH TOOLTIP}    ${text}
+    END
+    [Teardown]    Unselect Frame
+
+Wait Until Tag Widget Exists
+    [Arguments]    ${text}    ${screenshot}=01-tagged.png
+    Wait Until Element Is Visible    css:${CSS WIDGET TAG}
+    Wait Until Element Contains    css:${CSS WIDGET TAG}    ${text}
+    Capture Page Screenshot    ${screenshot}
+
+Remove Widget Tag
+    ${sel} =    Set Variable    css:${CSS WIDGET TAG} ${CSS WIDGET TAG CLOSE}
+    Wait Until Page Contains Element    ${sel}
+    Click Element    ${sel}
+
+Wait Until No Tag Widgets Exist
+    [Arguments]    ${screenshot}=02-not-tagged.png
+    Wait Until Element Is Not Visible    css:${CSS WIDGET TAG}
+    Capture Page Screenshot    ${screenshot}
diff --git a/atest/_resources/keywords/Lab.robot b/atest/_resources/keywords/Lab.robot
index 89a93ac1..da863f7a 100644
--- a/atest/_resources/keywords/Lab.robot
+++ b/atest/_resources/keywords/Lab.robot
@@ -1,32 +1,12 @@
 *** Settings ***
 Library     OperatingSystem
-Library     SeleniumLibrary
+Library     JupyterLibrary
 Resource    Browser.robot
 Resource    ../variables/Lab.robot
 Resource    ../variables/Browser.robot
 
 
 *** Keywords ***
-Open JupyterLab
-    Set Environment Variable    MOZ_HEADLESS    ${HEADLESS}
-    ${firefox} =    Which    firefox
-    ${geckodriver} =    Which    geckodriver
-    ${service args} =    Create List    --log    info
-    Set Global Variable    ${NEXT BROWSER}    ${NEXT BROWSER.__add__(1)}
-    Wait Until Keyword Succeeds    5x    5s    Create WebDriver    Firefox
-    ...    executable_path=${geckodriver}
-    ...    firefox_binary=${firefox}
-    ...    service_log_path=${OUTPUT DIR}${/}geckodriver-${NEXT BROWSER}.log
-    ...    service_args=${service args}
-    Wait Until Keyword Succeeds    3x    5s    Wait For Splash
-
-Wait For Splash
-    Go To    ${URL}lab?reset&token=${TOKEN}
-    Set Window Size    1920    1080
-    Wait Until Page Contains Element    ${SPLASH}    timeout=30s
-    Wait Until Page Does Not Contain Element    ${SPLASH}    timeout=10s
-    Execute Javascript    window.onbeforeunload \= function (){}
-
 Try to Close All Tabs
     Wait Until Keyword Succeeds    5x    50ms    Close All Tabs
 
@@ -77,10 +57,13 @@ Ensure Sidebar Is Closed
         Wait Until Keyword Succeeds    3x    0.5s    Click Element    css:.jp-mod-${side} .p-TabBar-tab.p-mod-current
     END
 
-Open Context Menu for File
-    [Arguments]    ${file}
+Refresh File List
     Ensure File Browser is Open
     Click Element    css:button[data-command="filebrowser:refresh"]
+
+Open Context Menu for File
+    [Arguments]    ${file}
+    Refresh File List
     ${selector} =    Set Variable    xpath://span[@class='jp-DirListing-itemText']//span\[text() = '${file}']
     Wait Until Page Contains Element    ${selector}
     Open Context Menu    ${selector}
@@ -110,9 +93,10 @@ Open ${file} in ${editor}
 
 Clean Up After Working With Files
     [Arguments]    @{files}
+    ${nb_dir} =    Get Jupyter Directory
     FOR    ${file}    IN    @{files}
         ${src}    ${name} =    Split Path    ${file}
-        Remove File    ${OUTPUT DIR}${/}home${/}${name}
+        Remove File    ${nb_dir}${/}${name}
     END
     Maybe Reset Application State
 
@@ -151,7 +135,8 @@ Open Context Menu Over
 Open File
     [Arguments]    ${file}    ${editor}=${MENU EDITOR}
     ${parent}    ${name} =    Split Path    ${file}
-    Copy File    ${file}    ${OUTPUT DIR}${/}home${/}${name}
+    ${nb_dir} =    Get Jupyter Directory
+    Copy File    ${file}    ${nb_dir}${/}${name}
     Open ${name} in ${editor}
     Capture Page Screenshot    00-opened.png
 
@@ -221,7 +206,7 @@ Reset Application State
     Accept Default Dialog Option
     Ensure All Kernels Are Shut Down
     Lab Command    Reset Application State
-    Wait Until Keyword Succeeds    3x    5s    Wait For Splash
+    Wait Until Keyword Succeeds    3x    5s    Wait For JupyterLab Splash Screen
 
 Accept Default Dialog Option
     [Documentation]    Accept a dialog, if it exists
diff --git a/atest/_resources/keywords/Server.robot b/atest/_resources/keywords/Server.robot
index 6b8eca1b..dc55e22a 100644
--- a/atest/_resources/keywords/Server.robot
+++ b/atest/_resources/keywords/Server.robot
@@ -6,78 +6,37 @@ Resource    Lab.robot
 Resource    Browser.robot
 Resource    Meta.robot
 Resource    ../variables/Server.robot
-Library     ../../_libraries/Ports.py
 
 
 *** Keywords ***
 Setup Server and Browser
-    ${port} =    Get Unused Port
-    Run Keyword and Ignore Error    Tag for Pabot
-    Set Global Variable    ${PORT}    ${port}
-    Set Global Variable    ${URL}    http://localhost:${PORT}${URL PREFIX}
-    ${accel} =    Evaluate    "COMMAND" if "${OS}" == "Darwin" else "CTRL"
-    Set Global Variable    ${ACCEL}    ${accel}
-    ${token} =    Generate Random String
-    Set Global Variable    ${TOKEN}    ${token}
-    ${home} =    Set Variable    ${OUTPUT DIR}${/}home
-    ${root} =    Normalize Path    ${OUTPUT DIR}${/}..${/}..${/}..
-    Create Directory    ${home}
-    Create Notebok Server Config    ${home}
-    Initialize User Settings
-    ${cmd} =    Create Lab Launch Command    ${root}
-    Set Screenshot Directory    ${SCREENS ROOT}
-    Set Global Variable    ${NEXT LAB}    ${NEXT LAB.__add__(1)}
-    Set Global Variable    ${LAB LOG}    ${OUTPUT DIR}${/}lab-${NEXT LAB}.log
-    Set Global Variable    ${PREVIOUS LAB LOG LENGTH}    0
-    ${server} =    Start Process    ${cmd}    shell=yes    env:HOME=${home}    cwd=${home}    stdout=${LAB LOG}
+    ${nb_dir} =    Initialize Jupyter Directory
+    Wait For New Jupyter Server To Be Ready    jupyter-lab    notebook_dir=${nb_dir}
+    ...    cwd=${nb_dir}
+    ...    stdout=${nb_dir}${/}jupyter-lab.log
     ...    stderr=STDOUT
-    Set Global Variable    ${SERVER}    ${server}
-    Wait For Jupyter Server To Be Ready
     Open JupyterLab
-    ${script} =    Get Element Attribute    id:jupyter-config-data    innerHTML
-    ${config} =    Evaluate    __import__("json").loads(r"""${script}""")
-    Set Global Variable    ${PAGE CONFIG}    ${config}
-    Set Global Variable    ${LAB VERSION}    ${config["appVersion"]}
-    Set Tags    lab:${LAB VERSION}
+    Set Window Size    1920    1080
 
-Create Lab Launch Command
-    [Documentation]    Create a JupyterLab CLI shell string, escaping for traitlets
-    [Arguments]    ${root}
-    ${WORKSPACES DIR} =    Set Variable    ${OUTPUT DIR}${/}workspaces
-    ${app args} =    Set Variable
-    ...    --no-browser --debug --LabApp.base_url\='${URL PREFIX}' --port\=${PORT} --LabApp.token\='${TOKEN}' --ExtensionApp.open_browser\=False --ServerApp.open_browser\=False
-    ${path args} =    Set Variable
-    ...    --LabApp.user_settings_dir\='${SETTINGS DIR.replace('\\', '\\\\')}' --LabApp.workspaces_dir\='${WORKSPACES DIR.replace('\\', '\\\\')}'
-    ${cmd} =    Set Variable
-    ...    ${JUPYTERLAB_EXE} ${app args} ${path args}
-    RETURN    ${cmd}
-
-Create Notebok Server Config
-    [Documentation]    Copies in notebook server config file to disables npm/build checks
-    [Arguments]    ${home}
-    Copy File    ${FIXTURES}${/}${NBSERVER CONF}    ${home}${/}${NBSERVER CONF}
-    Set Environment Variable    JUPYTER_CONFIG_DIR    ${home}
-
-Initialize User Settings
+Initialize Jupyter Directory
     [Documentation]    Configure the settings directory, and modify settings that make tests less reproducible
-    Set Suite Variable    ${SETTINGS DIR}    ${OUTPUT DIR}${/}user-settings    children=${True}
+    ${nb_dir} =    Set Variable    ${OUTPUT_DIR}${/}nb
+    ${etc} =    Set Variable    ${nb_dir}${/}.etc
+    Create Directory    ${etc}
+    Copy File    ${IPYFORCEGRAPH_FIXTURES}${/}${NBSERVER CONF}    ${etc}${/}.etc${/}${NBSERVER CONF}
+    Set Environment Variable    JUPYTER_CONFIG_DIR    ${etc}
+    ${settings} =    Set Variable    ${etc}${/}lab${/}user-settings
     Create File
-    ...    ${SETTINGS DIR}${/}@jupyterlab${/}codemirror-extension${/}commands.jupyterlab-settings
+    ...    ${settings}${/}@jupyterlab${/}codemirror-extension${/}commands.jupyterlab-settings
     ...    {"styleActiveLine": true}
     Create File
-    ...    ${SETTINGS DIR}${/}@jupyterlab${/}extensionmanager-extension${/}plugin.jupyterlab-settings
+    ...    ${settings}${/}@jupyterlab${/}extensionmanager-extension${/}plugin.jupyterlab-settings
     ...    {"enabled": false}
     Create File
-    ...    ${SETTINGS DIR}${/}@jupyterlab${/}apputils-extension${/}palette.jupyterlab-settings
+    ...    ${settings}${/}@jupyterlab${/}apputils-extension${/}palette.jupyterlab-settings
     ...    {"modal": false}
+    RETURN    ${nb_dir}
 
 Tear Down Everything
     Close All Browsers
-    Evaluate    __import__("urllib.request").request.urlopen("${URL}api/shutdown?token=${TOKEN}", data=[])
-    Wait For Process    ${SERVER}    timeout=30s
-    Terminate All Processes
-    Terminate All Processes    kill=${True}
-
-Wait For Jupyter Server To Be Ready
-    Wait Until Keyword Succeeds    5x    5s
-    ...    Evaluate    __import__("urllib.request").request.urlopen("${URL}")
+    Terminate All Jupyter Servers
diff --git a/atest/_resources/variables/Browser.robot b/atest/_resources/variables/Browser.robot
index ca7084fb..22773501 100644
--- a/atest/_resources/variables/Browser.robot
+++ b/atest/_resources/variables/Browser.robot
@@ -2,4 +2,3 @@
 # override with `python scripts/atest.py --variable HEADLESS:0`
 ${HEADLESS}         1
 ${SCREENS ROOT}     ${OUTPUT DIR}${/}screens
-${NEXT BROWSER}     ${0}
diff --git a/atest/_resources/variables/IPyForceGraph.robot b/atest/_resources/variables/IPyForceGraph.robot
index 85409688..0d45e14c 100644
--- a/atest/_resources/variables/IPyForceGraph.robot
+++ b/atest/_resources/variables/IPyForceGraph.robot
@@ -3,4 +3,6 @@ ${INDEX}                    _index
 ${BEHAVIORS}                Behaviors
 ${THREEDEE}                 3D
 
-${IPYFORCEGRAPH GRAPH}      css:.jp-ForceGraph
+${IPYFORCEGRAPH GRAPH}      .jp-ForceGraph
+${IPYFORCEGRAPH FRAME}      ${IPYFORCEGRAPH GRAPH} iframe
+${IPYFORCEGRAPH TOOLTIP}    .graph-tooltip,.scene-tooltip
diff --git a/atest/_resources/variables/Lab.robot b/atest/_resources/variables/Lab.robot
index a1d524b3..f3560dbf 100644
--- a/atest/_resources/variables/Lab.robot
+++ b/atest/_resources/variables/Lab.robot
@@ -39,3 +39,8 @@ ${CM CURSORS}                   css:.CodeMirror-cursors:not([style='visibility:
 # settings
 ${CSS USER SETTINGS}            .jp-SettingsRawEditor-user
 ${JLAB XP CLOSE SETTINGS}       ${JLAB XP DOCK TAB}\[contains(., 'Settings')]/*[@data-icon='ui-components:close']
+
+# widgets
+${CSS WIDGET TAGSINPUT}         .jupyter-widget-tagsinput
+${CSS WIDGET TAG}               div.jupyter-widget-tag
+${CSS WIDGET TAG CLOSE}         .jupyter-widget-tag-close
diff --git a/atest/_resources/variables/Server.robot b/atest/_resources/variables/Server.robot
index 1d496ad8..02844dac 100644
--- a/atest/_resources/variables/Server.robot
+++ b/atest/_resources/variables/Server.robot
@@ -1,6 +1,3 @@
 *** Variables ***
 # to help catch hard-coded paths
-${URL PREFIX}       /@est/
-${FIXTURES}         ${CURDIR}${/}..${/}fixtures
 ${NBSERVER CONF}    jupyter_config.json
-${NEXT LAB}         ${0}
diff --git a/scripts/atest.py b/scripts/atest.py
index e9aca992..15234323 100644
--- a/scripts/atest.py
+++ b/scripts/atest.py
@@ -17,6 +17,7 @@
 
 PROCESSES = int(os.environ.get("ATEST_PROCESSES", "4"))
 RETRIES = int(os.environ.get("ATEST_RETRIES", "0"))
+ATTEMPT = int(os.environ.get("ATEST_ATTEMPT", "0"))
 
 
 def get_stem(attempt, extra_args):
@@ -37,6 +38,8 @@ def atest(attempt, extra_args):
         previous = P.ATEST_OUT / f"{get_stem(attempt - 1, extra_args)}" / "output.xml"
         print(f"Attempt {attempt} will try to re-run tests from {previous}")
 
+        extra_args += ["--loglevel", "TRACE"]
+
         if previous.exists():
             extra_args += ["--rerunfailed", previous]
         else:
@@ -52,14 +55,13 @@ def atest(attempt, extra_args):
         *["--xunit", out_dir / "xunit.xml"],
         *["--variable", f"OS:{P.PLATFORM}"],
         *["--variable", f"IPYFORCEGRAPH_EXAMPLES:{P.EXAMPLES}"],
+        *["--variable", f"IPYFORCEGRAPH_FIXTURES:{P.ATEST_FIXTURES}"],
         *["--variable", f"""JUPYTERLAB_EXE:{" ".join(map(str, P.JUPYTERLAB_EXE))}"""],
         *["--randomize", "all"],
         *(extra_args or []),
         *(os.environ.get("ATEST_ARGS", "").split()),
     ]
 
-    os.chdir(P.ATEST)
-
     if out_dir.exists():
         print("trying to clean out {}".format(out_dir))
         try:
@@ -67,6 +69,10 @@ def atest(attempt, extra_args):
         except Exception as err:
             print("Error deleting {}, hopefully harmless: {}".format(out_dir, err))
 
+    out_dir.mkdir(parents=True)
+
+    os.chdir(out_dir)
+
     if "--dryrun" in extra_args or PROCESSES == 1:
         run_robot = robot.run_cli
         fake_cmd = "robot"
@@ -82,7 +88,7 @@ def atest(attempt, extra_args):
             *args,
         ]
 
-    args += ["."]
+    args += [P.ATEST]
 
     print(f"[{fake_cmd} test root]\n", P.ATEST)
     print(f"[{fake_cmd} arguments]\n", " ".join(list(map(str, args))))
@@ -97,7 +103,7 @@ def atest(attempt, extra_args):
 
 def attempt_atest_with_retries(*extra_args):
     """retry the robot tests a number of times"""
-    attempt = 0
+    attempt = ATTEMPT
     error_count = -1
 
     is_real = "--dryrun" not in extra_args