-
Notifications
You must be signed in to change notification settings - Fork 2
Image Update - March 2026 #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 21 commits
2340926
565bc1a
4a1bb81
5be5022
20b4ba4
eb52112
9ea491e
7377969
3fbc508
985b001
2d8033a
29196bc
b16946c
ece26fd
669fd9b
3d7cc98
2d80777
d14e1a0
9b2708a
f63d414
18b4ebe
3a49639
0eb3c9d
acb1bb9
5b9ec72
7068909
db20d18
0bd1dba
84c3d03
4047e0f
4d62b0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| FROM pavics/workflow-tests:py311-250423-update250730 | ||
| FROM pavics/workflow-tests:py312-260320 | ||
|
|
||
| USER root | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,89 +1,67 @@ | ||
| FROM continuumio/miniconda3 | ||
|
|
||
| # Use mamba for much improved performance over conda. | ||
| # The 'channel_priority strict' did help conda but it was not enough. | ||
| RUN conda config --prepend channels nodefaults || true && \ | ||
| conda config --remove channels defaults || true && \ | ||
| conda update conda -n base && \ | ||
| conda install mamba conda-pack -n base -c conda-forge && \ | ||
| conda clean --all --yes && \ | ||
| conda config --set channel_priority strict && \ | ||
| wget -qO- https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -C /usr/local -xvj bin/micromamba | ||
|
|
||
| # Default for micromamba is $HOME/micromamba which is wrong. | ||
| ENV MAMBA_ROOT_PREFIX="/opt/conda" | ||
|
Zeitsperre marked this conversation as resolved.
|
||
|
|
||
| # to checkout other notebooks and to run pip install | ||
| # Use micromamba for improved speed and reduced image size | ||
| FROM mambaorg/micromamba | ||
| # No interactive apt calls | ||
| ENV DEBIAN_FRONTEND=noninteractive | ||
|
|
||
| # Install all jupyter and docker build dependencies in base | ||
| COPY environment-base.yml /tmp/environment-base.yml | ||
| RUN micromamba install --yes --name base --file /tmp/environment-base.yml && \ | ||
| micromamba clean --all --yes | ||
|
|
||
| # Rename mambauser to jenkins for our Jenkins e2e notebooks test suite. | ||
| ARG NEW_MAMBA_USER=jenkins | ||
| ARG NEW_MAMBA_USER_ID=1000 | ||
| ARG NEW_MAMBA_USER_GID=1000 | ||
|
|
||
| USER root | ||
| # Rename mambauser to jenkins; copied from mambaorg/micromamba documentation | ||
| RUN if grep -q '^ID=alpine$' /etc/os-release; then \ | ||
| # alpine does not have usermod/groupmod | ||
| apk add --no-cache --virtual temp-packages shadow; \ | ||
| fi && \ | ||
| usermod "--login=${NEW_MAMBA_USER}" "--home=/home/${NEW_MAMBA_USER}" \ | ||
| --move-home "-u ${NEW_MAMBA_USER_ID}" "${MAMBA_USER}" && \ | ||
| groupmod "--new-name=${NEW_MAMBA_USER}" \ | ||
| "-g ${NEW_MAMBA_USER_GID}" "${MAMBA_USER}" && \ | ||
| if grep -q '^ID=alpine$' /etc/os-release; then \ | ||
| # remove the packages that were only needed for usermod/groupmod | ||
| apk del temp-packages; \ | ||
| fi && \ | ||
| # Update the expected value of MAMBA_USER for the | ||
| # _entrypoint.sh consistency check. | ||
| echo "${NEW_MAMBA_USER}" > "/etc/arg_mamba_user" && \ | ||
| : | ||
| ENV MAMBA_USER=$NEW_MAMBA_USER | ||
|
|
||
| # Alias UID and GID for use with docker-stacks | ||
| ENV NB_USER=${MAMBA_USER} | ||
| ARG NB_GID="${NEW_MAMBA_USER_GID}" | ||
|
|
||
| # Copy environment file | ||
| COPY --chown=$MAMBA_USER:$MAMBA_USER environment.yml /tmp/environment.yml | ||
|
|
||
| # Add additional fonts and x11-utils for matplotlib and firefox support | ||
| RUN apt-get update && \ | ||
| DEBIAN_FRONTEND=noninteractive apt-get install -y \ | ||
| git mercurial gcc unzip patch fonts-humor-sans firefox-esr x11-utils && \ | ||
|
Zeitsperre marked this conversation as resolved.
|
||
| apt-get install --yes fonts-humor-sans x11-utils && \ | ||
| apt-get clean | ||
|
|
||
| # Create user jenkins for our Jenkins e2e notebooks test suite. | ||
| # Change /opt/conda folder permissions for jupyter-conda extension. | ||
| RUN groupadd --gid 1000 jenkins && \ | ||
| useradd --uid 1000 --gid jenkins --shell /bin/bash --create-home jenkins && \ | ||
| chmod -R a+rwX /opt/conda | ||
|
Zeitsperre marked this conversation as resolved.
|
||
|
|
||
| COPY environment.yml /environment.yml | ||
|
Zeitsperre marked this conversation as resolved.
|
||
|
|
||
| # create env "birdy" | ||
| # Create "birdy" environment | ||
| # use umask 0000 so that the files for the new environment are usable by user 'jenkins' for the jupyter-conda-extension | ||
| # | ||
| # Perform 2 stages install because one single 'conda env create -f | ||
| # /environment.yml' was taking forever to complete, same with mamba. | ||
| # Had to do this 2 stages install. 2 stages install was also taking forever | ||
| # with conda so had to switch to mamba. | ||
| # | ||
| # One single 'conda env create -f /environment.yml' takes forever because we | ||
| # removed all direct dependencies of xclim and ravenpy in /environment.yml for | ||
| # dependencies pinning by xclim and ravenpy to take effect. This results in | ||
| # conda having a lot more packages to "solve" and it seems the solver | ||
| # performance dropped exponentially with the number of packages to solve. | ||
| # | ||
| # Conda was stuck at this step: | ||
| # DEBUG conda.common._logic:_run_sat(607): Invoking SAT with clause count: 2500273 | ||
| # | ||
| RUN umask 0000 && \ | ||
| mamba create --name birdy --channel conda-forge python=3.11 clisops figanos ravenpy xclim xscen xsdba --yes && \ | ||
| (conda env export --name birdy 2>&1 | tee /conda-env-export-phase1.yml) && \ | ||
| (du -sh /opt/conda 2>&1 | tee /conda-envs-size-phase1.txt) && \ | ||
| mamba env update --name birdy --file /environment.yml && \ | ||
| mamba clean --all --yes | ||
|
|
||
| # alternate way to 'source activate birdy' | ||
| ENV PATH="/opt/conda/envs/birdy/bin:$PATH" | ||
|
|
||
| # our notebooks are hardcoded to lookup for kernel named 'birdy' | ||
| # this python is from the birdy env above | ||
| RUN python -m ipykernel install --name birdy && \ | ||
| (conda env export --name birdy 2>&1 | tee /conda-env-export-final.yml) && \ | ||
| (diff --unified /conda-env-export-phase1.yml /conda-env-export-final.yml 2>&1 | tee /conda-env-export.diff) && \ | ||
| (du -sh /opt/conda 2>&1 | tee /conda-envs-size-final.txt) && \ | ||
| diff /conda-envs-size-phase1.txt /conda-envs-size-final.txt || true | ||
|
|
||
| # install using same channel preferences as birdy original env to not downgrade anything accidentally | ||
| # this is for debug only, all dependencies should be specified in environment.yml above | ||
| # RUN mamba install -c conda-forge -c cdat -c bokeh -c fortiers -n birdy nbdime | ||
|
|
||
| # build jupyterlab extensions installed by conda, see `jupyter labextension list` | ||
| # Supposedly not needed with jupyterlab v3 anymore but see | ||
| # https://github.com/jupyterlab/jupyterlab/issues/11726#issuecomment-998901247 | ||
| # TODO: remove 'jupyter lab build' step once all extensions move to prebuilt extensions, | ||
| # see comment https://github.com/jupyterlab/jupyterlab/issues/11726#issuecomment-998917305 | ||
| # Currently jupyter-dash is holding back this step, see | ||
| # https://github.com/plotly/jupyter-dash/issues/49 | ||
|
Zeitsperre marked this conversation as resolved.
|
||
| RUN jupyter lab build | ||
|
|
||
| RUN jupyter serverextension enable voila --sys-prefix && \ | ||
| jupyter serverextension enable panel.io.jupyter_server_extension --sys-prefix && \ | ||
|
Zeitsperre marked this conversation as resolved.
|
||
| jupyter serverextension list | ||
| # jupyter labextension install jupyterlab-clipboard | ||
|
|
||
| # This should be "master" but commit | ||
| # https://github.com/jupyter/docker-stacks/commit/c772e98ac794173d6ed83a08ec249038b27ca3be | ||
| # is breaking with us since we do not have user jovyan. | ||
| ENV DOCKER_STACKS_COMMIT=709206ac8788475728cc9c992c25fb5f1501bc29 | ||
| micromamba create --name birdy --file /tmp/environment.yml "python>=3.12,<3.13" --yes && \ | ||
| micromamba clean --all --yes | ||
|
|
||
| # Notebooks are hardcoded to lookup for kernel named 'birdy' created in the previous code block | ||
| RUN umask 0000 && \ | ||
| micromamba run --name birdy python -m ipykernel install --name birdy && \ | ||
| micromamba env export --name birdy | tee /conda-env-export-final.yml && \ | ||
| du -sh "${MAMBA_ROOT_PREFIX}" | tee /conda-envs-size-final.txt | ||
|
Zeitsperre marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Docker-stacks commit from last time base-notebook was modified (2025/05/24) | ||
| ENV DOCKER_STACKS_COMMIT=e73d308ba06fd5bcc75a4ee8417711743860c964 | ||
|
|
||
| # Activate base environment | ||
| ARG MAMBA_DOCKERFILE_ACTIVATE=1 | ||
|
|
||
| # /notebook_dir for Pavics-landing notebooks to re-create Jupyter env layout: | ||
| # /notebook_dir/writable-workspace, /notebook_dir/pavics-homepage. | ||
|
|
@@ -92,48 +70,67 @@ ENV DOCKER_STACKS_COMMIT=709206ac8788475728cc9c992c25fb5f1501bc29 | |
| # hardcoded so users can copy the nb to writable-workspace/ dir and still be able | ||
| # to run them seemlessly from the Jupyter env (without having to also copy | ||
| # those *.geojson files with the notebooks). | ||
| RUN wget https://raw.githubusercontent.com/jupyter/docker-stacks/$DOCKER_STACKS_COMMIT/base-notebook/start.sh --output-document /usr/local/bin/start.sh && \ | ||
| wget https://raw.githubusercontent.com/jupyter/docker-stacks/$DOCKER_STACKS_COMMIT/base-notebook/start-singleuser.sh --output-document /usr/local/bin/start-singleuser.sh && \ | ||
| wget https://raw.githubusercontent.com/jupyter/docker-stacks/$DOCKER_STACKS_COMMIT/base-notebook/start-notebook.sh --output-document /usr/local/bin/start-notebook.sh && \ | ||
| wget https://raw.githubusercontent.com/jupyter/docker-stacks/$DOCKER_STACKS_COMMIT/base-notebook/fix-permissions --output-document /usr/local/bin/fix-permissions && \ | ||
| RUN wget https://raw.githubusercontent.com/jupyter/docker-stacks/$DOCKER_STACKS_COMMIT/images/base-notebook/start-singleuser.py --output-document /usr/local/bin/start-singleuser.py && \ | ||
| wget https://raw.githubusercontent.com/jupyter/docker-stacks/$DOCKER_STACKS_COMMIT/images/base-notebook/start-notebook.py --output-document /usr/local/bin/start-notebook.py && \ | ||
| mkdir /etc/jupyter && \ | ||
| wget https://raw.githubusercontent.com/jupyter/docker-stacks/$DOCKER_STACKS_COMMIT/base-notebook/jupyter_notebook_config.py --output-document /etc/jupyter/jupyter_notebook_config.py && \ | ||
| chmod a+rx /usr/local/bin/start.sh /usr/local/bin/start-singleuser.sh /usr/local/bin/start-notebook.sh /usr/local/bin/fix-permissions && \ | ||
| chmod a+r /etc/jupyter/jupyter_notebook_config.py && \ | ||
| wget https://raw.githubusercontent.com/jupyter/docker-stacks/$DOCKER_STACKS_COMMIT/images/base-notebook/jupyter_server_config.py --output-document /etc/jupyter/jupyter_server_config.py && \ | ||
| wget https://raw.githubusercontent.com/jupyter/docker-stacks/$DOCKER_STACKS_COMMIT/images/base-notebook/docker_healthcheck.py --output-document /etc/jupyter/docker_healthcheck.py && \ | ||
| chmod a+rx /usr/local/bin/start-singleuser.py /usr/local/bin/start-notebook.py /etc/jupyter/docker_healthcheck.py && \ | ||
| chmod a+r /etc/jupyter/jupyter_server_config.py && \ | ||
| mkdir -p /notebook_dir/writable-workspace && \ | ||
| chown jenkins /notebook_dir/writable-workspace && \ | ||
| chown "${MAMBA_USER}" /notebook_dir/writable-workspace && \ | ||
| mkdir -p /notebook_dir/pavics-homepage && \ | ||
| chown jenkins /notebook_dir/pavics-homepage && \ | ||
| chown "${MAMBA_USER}" /notebook_dir/pavics-homepage && \ | ||
| chown root:root /notebook_dir && \ | ||
| chmod a-w /notebook_dir && \ | ||
| chmod a+rwX -R /opt/conda/envs/birdy/fonts && \ | ||
| mkdir /opt/conda/pkgs/cache && \ | ||
| chown jenkins:jenkins -R /opt/conda/pkgs/cache && \ | ||
| mkdir -p "${MAMBA_ROOT_PREFIX}/envs/birdy/fonts" && \ | ||
| chmod a+rwX -R "${MAMBA_ROOT_PREFIX}/envs/birdy/fonts" && \ | ||
| mkdir -p "${MAMBA_ROOT_PREFIX}/pkgs/cache" && \ | ||
| chown "${MAMBA_USER}":"${MAMBA_USER}" -R "${MAMBA_ROOT_PREFIX}/pkgs/cache" && \ | ||
| touch "${MAMBA_ROOT_PREFIX}/pkgs/pkgs.lock" && \ | ||
| chown "${MAMBA_USER}":"${MAMBA_USER}" "${MAMBA_ROOT_PREFIX}/pkgs/pkgs.lock" && \ | ||
| mkdir -p /usr/local/bin && \ | ||
| wget https://downloads.globus.org/globus-connect-personal/linux/stable/globusconnectpersonal-latest.tgz -O /usr/local/bin/globusconnectpersonal-latest.tgz && \ | ||
| tar xzf /usr/local/bin/globusconnectpersonal-latest.tgz -C /usr/local/bin/ && \ | ||
| ln -vs /usr/local/bin/globusconnectpersonal*/globusconnectpersonal /usr/local/bin/globusconnectpersonal && \ | ||
| rm -v /usr/local/bin/globusconnectpersonal-latest.tgz | ||
|
|
||
| # Set birdy as the default kernel | ||
| RUN echo "c.MultiKernelManager.default_kernel_name = 'birdy'" >> /etc/jupyter/jupyter_server_config.py | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This do not work, remove or comment out to not give false idea.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure? I am seeing a difference when it comes to the kernel order.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I though, via our discussions, that this kernel setting is to activate the kernel by default. So this is only to change the display ordering of the kernel? Can you update the comment to clarify it is for default kernel in the selection list display ordering, not for automated activation?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sets the default |
||
|
|
||
| # For jupyter-panel-proxy launcher. | ||
| ENV BOKEH_ALLOW_WS_ORIGIN="*" | ||
|
|
||
| # To supply the location of the RavenHydroFramework binary | ||
| # FIXME: This location should be provided via the raven-hydro package which would help with the discovery logic in RavenPy | ||
| ENV RAVENPY_RAVEN_BINARY_PATH="${MAMBA_ROOT_PREFIX}/envs/birdy/bin/raven" | ||
|
|
||
| # For import xesmf since esmf-8.4.0, see: https://github.com/conda-forge/esmf-feedstock/issues/91 | ||
| ENV ESMFMKFILE="/opt/conda/envs/birdy/lib/esmf.mk" | ||
| ENV ESMFMKFILE="${MAMBA_ROOT_PREFIX}/envs/birdy/lib/esmf.mk" | ||
|
|
||
| # To avoid error "PROJ: proj_create_from_database: Open of /opt/conda/envs/birdy/share/proj failed" | ||
| # To avoid error "PROJ: proj_create_from_database: Open of ${MAMBA_ROOT_PREFIX}/envs/birdy/share/proj failed" | ||
| # This simulates a real `conda activate birdy`. | ||
| ENV PROJ_DATA="/opt/conda/envs/birdy/share/proj" | ||
| ENV PROJ_DATA="${MAMBA_ROOT_PREFIX}/envs/birdy/share/proj" | ||
|
|
||
| # To expose conda-installed compilers so that users can make use of them | ||
| ENV gcc=x86_64-conda-linux-gnu-gcc | ||
| ENV g++=x86_64-conda-linux-gnu-g++ | ||
| ENV gfortran=x86_64-conda-linux-gnu-gfortran | ||
|
|
||
| # problem running start-notebook.sh when being root | ||
| # JupyterLab cannot write to hidden folders by design | ||
| ENV XDG_CACHE_HOME="/home/${MAMBA_USER}/cache" | ||
|
Zeitsperre marked this conversation as resolved.
|
||
|
|
||
| # Healthcheck - Requires NB_USER variable (copied from Docker-stacks) | ||
| HEALTHCHECK --interval=3s --timeout=1s --start-period=3s --retries=3 CMD /etc/jupyter/docker_healthcheck.py || exit 1 | ||
|
|
||
| # Problem running start-notebook.sh when being root | ||
| # the jupyter/base-notebook image also do not default to root user so we do the same here | ||
| USER jenkins | ||
| USER $MAMBA_USER | ||
| # Activate birdy environment | ||
| ENV ENV_NAME=birdy | ||
| # Set entrypoint for jenkins to root directory | ||
| WORKDIR / | ||
|
Zeitsperre marked this conversation as resolved.
Outdated
|
||
|
|
||
| # follow jupyter/base-notebook image so config in jupyterhub is simpler | ||
| # start notebook in conda environment to have working jupyter extensions | ||
| CMD ["conda", "run", "-n", "birdy", "/usr/local/bin/start-notebook.sh"] | ||
| CMD ["/usr/local/bin/_entrypoint.sh", "run", "-n", "base", "/usr/local/bin/start-notebook.py"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| # conda env create -f environment-base.yml | ||
| name: base | ||
| channels: | ||
| - conda-forge | ||
| dependencies: | ||
| # UTILITIES | ||
|
|
||
| # conda: a cross-platform, language-agnostic binary package manager | ||
| # NOTE: Included as a requirement for gator | ||
| # https://github.com/conda/conda | ||
| - conda >= 4.7.12 | ||
| # git: fast, scalable, distributed revision control system | ||
| # NOTES: Included as a requirement for jupyterlab-git | ||
| - git | ||
| # mamba: a reimplementation of the conda package manager in C++ | ||
| # NOTE: Included as a requirement for gator | ||
| # https://github.com/mamba-org/mamba | ||
| - mamba >= 2.0 | ||
| # wget: command-line utility for downloading files from the web | ||
| - wget | ||
|
|
||
| # JUPYTER LIBRARIES | ||
|
|
||
| # Jupyter conda metapackage: Install all the Jupyter components in one go. | ||
| # https://anaconda.org/conda-forge/jupyter | ||
| - jupyter | ||
| # notebook: A web-based notebook environment for interactive computing. | ||
| # to be launched by image jupyterhub/jupyterhub | ||
| # https://github.com/jupyter/notebook | ||
| - notebook | ||
| # jupyterlab: An extensible environment for interactive and reproducible computing | ||
| # https://github.com/jupyterlab/jupyterlab | ||
| - jupyterlab >= 4.0 | ||
| # JupyterHub: A multi-user server for Jupyter notebooks | ||
| # https://github.com/jupyterhub/jupyterhub | ||
| - jupyterhub | ||
| # ipywidgets: Interactive HTML Widgets | ||
| # https://github.com/jupyter-widgets/ipywidgets | ||
| # https://ipywidgets.readthedocs.io/en/latest/user_install.html | ||
| - ipywidgets | ||
| # ipyleaflet: A Jupyter / Leaflet bridge enabling interactive maps in the Jupyter notebook. | ||
| # https://github.com/jupyter-widgets/ipyleaflet | ||
| - ipyleaflet | ||
| # gator: The Mamba Navigator, a Web UI for managing conda environments | ||
| # https://github.com/mamba-org/gator | ||
| - mamba_gator | ||
| # nbdime: Jupyter Notebook Diff and Merge tools | ||
| # https://github.com/jupyter/nbdime | ||
| - nbdime | ||
| # jupyter_bokeh: An extension for rendering Bokeh content in JupyterLab notebooks | ||
| # https://github.com/bokeh/jupyter_bokeh | ||
| - jupyter_bokeh | ||
| # jupytext: extension to produce .py files from notebook .ipynb files | ||
| # https://github.com/mwouts/jupytext | ||
| - jupytext | ||
| # jupyterlab-git: A JupyterLab extension for version control using Git | ||
| # https://github.com/jupyterlab/jupyterlab-git | ||
| - jupyterlab-git >= 0.44.0 | ||
| # voila: Voilà turns Jupyter notebooks into standalone web applications | ||
| # https://github.com/voila-dashboards/voila | ||
| - voila | ||
| # jupyterlab-archive: A Jupyter extension to make, download and extract archive files. | ||
| # https://github.com/jupyterlab-contrib/jupyter-archive | ||
| - jupyter-archive | ||
| # jupyter-resource-usage: Jupyter Extension to show resource usage | ||
| # https://github.com/jupyter-server/jupyter-resource-usage | ||
| # migrated from https://github.com/jtpio/jupyterlab-system-monitor and originally from jupyterlab-topbar | ||
| - jupyter-resource-usage | ||
| # xeus-python: back-end kernel implementing the Jupyter Debug Protocol | ||
| # https://github.com/jupyter-xeus/xeus-python | ||
| - xeus-python | ||
| # jupyter-server-proxy: run arbitrary external processes alongside your notebook server | ||
| # https://github.com/jupyterhub/jupyter-server-proxy | ||
| - jupyter-server-proxy | ||
| # dask-labextension: A JupyterLab extension for Dask. | ||
| # https://github.com/dask/dask-labextension | ||
| - dask-labextension | ||
| # jupyterlab-geojson: JupyterLab extension for rendering GeoJSON | ||
| # https://github.com/jupyterlab/jupyter-renderers/tree/main/packages/geojson-extension | ||
| - jupyterlab-geojson | ||
| # jupyterlab-topbar: JupyterLab Top Bar Extensions | ||
| # https://github.com/jtpio/jupyterlab-topbar | ||
| - pip: | ||
| - jupyterlab-logout >= 1.1.0 | ||
| - jupyterlab-theme-toggler >= 1.1.0 | ||
|
|
||
| # BROWSER SUPPORT | ||
|
|
||
| # firefox: The one and only | ||
| # https://github.com/mozilla-firefox/firefox | ||
| - firefox | ||
| # selenium: Python library for automating web browser interaction | ||
| # https://github.com/SeleniumHQ/Selenium | ||
| - selenium |


Uh oh!
There was an error while loading. Please reload this page.