diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index 1bdd0df..0000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-build: false
-
-environment:
-  PYTHON: "C:\\myminiconda3"
-  matrix:
-    - PY: 3.7
-    - PY: 3.8
-
-init:
-  - "ECHO %PYTHON_VERSION% %MINICONDA%"
-
-install:
-  # Add path, activate `conda` and update conda.
-  - set URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe"
-  - curl -fsS -o miniconda3.exe %URL%
-  - start /wait "" miniconda3.exe /InstallationType=JustMe /RegisterPython=0 /S /D=%PYTHON%
-
-  - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
-  - call %PYTHON%\Scripts\activate
-
-  - conda config --set always_yes yes --set changeps1 no --set show_channel_urls true
-  - conda update conda --quiet
-  - conda config --add channels conda-forge --force
-  - conda config --set channel_priority strict
-  - conda config --set safety_checks disabled
-
-  # Create the test env.
-  - conda create --name TEST python=%PY% --file requirements.txt --file requirements-dev.txt
-  - activate TEST
-
-  # Debug.
-  - conda info --all
-  - conda list
-
-test_script:
-  - pip install -e . --no-deps --force-reinstall
-  - pytest -n 2 -rxXs -m "not headless" tests
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..89b5e7f
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,47 @@
+name: Publish to PyPI
+
+on:
+  release:
+    types:
+      - published
+
+jobs:
+  packages:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Set up Python
+      uses: actions/setup-python@v1
+      with:
+        python-version: 3.8
+
+    - name: Get tags
+      run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
+      shell: bash
+
+    - name: Install build tools
+      run: |
+        python -m pip install --upgrade pip wheel setuptools setuptools_scm build twine pytest pandas pytz requests
+
+      shell: bash
+
+    - name: Build binary wheel
+      run: python -m build --sdist --wheel . --outdir dist
+
+    - name: CheckFiles
+      run: |
+        ls dist
+      shell: bash
+
+    - name: Test wheels
+      run: |
+        cd dist && python -m pip install erddapy*.whl
+        python -m twine check *
+      shell: bash
+
+    - name: Publish a Python distribution to PyPI
+      uses: pypa/gh-action-pypi-publish@master
+      with:
+        user: __token__
+        password: ${{ secrets.PYPI_PASSWORD }}
diff --git a/.github/workflows/test_code.yml b/.github/workflows/test_code.yml
index 6f1a30f..a76fd7f 100644
--- a/.github/workflows/test_code.yml
+++ b/.github/workflows/test_code.yml
@@ -1,20 +1,17 @@
 name: Code Tests
 
 on:
-  push:
-    branches:
-    - master
   pull_request:
-    branches:
-    - master
+  push:
+    branches: [master]
 
 jobs:
   run:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        os: [ubuntu-latest, windows-latest]
-        python-version: [3.6, 3.9]
+        os: [ubuntu-latest, windows-latest, macos-latest]
+        python-version: ["3.6", "3.7", "3.8", "3.9"]
 
     steps:
     - uses: actions/checkout@v2
@@ -22,26 +19,21 @@ jobs:
     - name: Setup Conda
       uses: s-weigand/setup-conda@v1
       with:
+        update-conda: true
         activate-conda: false
         conda-channels: conda-forge
 
     - name: Python ${{ matrix.python-version }}
       shell: bash -l {0}
       run: |
-        conda create --yes --name TEST python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-dev.txt --channel conda-forge --strict-channel-priority
+        conda create --name TEST python=${{ matrix.python-version }} pip --file requirements.txt --file requirements-dev.txt
         source activate TEST
         pip install -e . --no-deps --force-reinstall
-
-    - name: Tarball tests
-      shell: bash -l {0}
-      run: |
-        source activate TEST
-        pip wheel . -w dist --no-deps
-        check-manifest --verbose
-        twine check dist/*
+        conda info --all
+        conda list
 
     - name: Tests
       shell: bash -l {0}
       run: |
         source activate TEST
-        pytest -vv
+        pytest -vv -rxs tests -m "not headless"
diff --git a/.github/workflows/test_code_notebooks.yml b/.github/workflows/test_code_notebooks.yml
new file mode 100644
index 0000000..af53f7a
--- /dev/null
+++ b/.github/workflows/test_code_notebooks.yml
@@ -0,0 +1,35 @@
+name: Notebook Tests
+
+on:
+  pull_request:
+  push:
+    branches: [master]
+
+jobs:
+  run:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Setup Conda
+      uses: s-weigand/setup-conda@v1
+      with:
+        update-conda: true
+        activate-conda: false
+        conda-channels: conda-forge
+
+    - name: Python 3.9
+      shell: bash -l {0}
+      run: |
+        conda create --name TEST python=3.9 pip --file requirements.txt --file requirements-dev.txt
+        source activate TEST
+        pip install -e . --no-deps --force-reinstall
+        conda info --all
+        conda list
+
+    - name: Notebook tests
+      shell: bash -l {0}
+      run: |
+        source activate TEST
+        pytest --nbval-lax examples
diff --git a/.github/workflows/test_selenium.yml b/.github/workflows/test_selenium.yml
new file mode 100644
index 0000000..aabeac5
--- /dev/null
+++ b/.github/workflows/test_selenium.yml
@@ -0,0 +1,35 @@
+name: Headless Tests
+
+on:
+  pull_request:
+  push:
+    branches: [master]
+
+jobs:
+  run:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Setup Conda
+      uses: s-weigand/setup-conda@v1
+      with:
+        update-conda: true
+        activate-conda: false
+        conda-channels: conda-forge
+
+    - name: Python 3.9
+      shell: bash -l {0}
+      run: |
+        conda create --name TEST python=3.9 pip --file requirements.txt --file requirements-dev.txt
+        source activate TEST
+        pip install -e . --no-deps --force-reinstall
+        conda info --all
+        conda list
+
+    - name: Tests
+      shell: bash -l {0}
+      run: |
+        source activate TEST
+        pytest -vv -rxs tests -m "headless"
diff --git a/.github/workflows/test_tarball.yml b/.github/workflows/test_tarball.yml
new file mode 100644
index 0000000..2de459a
--- /dev/null
+++ b/.github/workflows/test_tarball.yml
@@ -0,0 +1,37 @@
+name: Tarball Tests
+
+on:
+  pull_request:
+  push:
+    branches: [master]
+
+jobs:
+  run:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Setup Conda
+      uses: s-weigand/setup-conda@v1
+      with:
+        update-conda: true
+        activate-conda: false
+        conda-channels: conda-forge
+
+    - name: Python 3
+      shell: bash -l {0}
+      run: |
+        conda create --name TEST python=3 pip --file requirements.txt --file requirements-dev.txt
+        source activate TEST
+        pip install -e . --no-deps --force-reinstall
+        conda info --all
+        conda list
+
+    - name: Tarball tests
+      shell: bash -l {0}
+      run: |
+        source activate TEST
+        pip wheel . -w dist --no-deps
+        check-manifest --verbose
+        twine check dist/*
diff --git a/.travis.yml b/.travis.yml
index f7d265c..ea48117 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,16 +2,6 @@ language: minimal
 
 sudo: false
 
-env:
-  - MOZ_HEADLESS=1
-
-addons:
-  apt:
-    packages:
-      # Without libgtk (as of 2019-11-6), firefox cannot run properly
-      - libgtk-3-0
-  firefox: latest
-
 env:
   global:
     - secure: "JrdAJJKe+Y564LIiIaGPmw9X9utvj2lVI2MkVyZX6CQ7cJwqPUB+ukZpa6MnGUEJSoI6S7/5ZqX/cvciVAKpcYadi/snlDGq6q+lLnA+6rtB6ppy99Ib64aqm+VqDyohvvvrNtSxqhBY48DLG9UuQEcvviabpx9CG4s439/osspsA80SRXgDm/dELjU8vuIJvL1EFPeOu4a01sAHsqhknQw4F4YSWWjUr1oNjZIhSKroxCJV0o/0ok600SwTrQrbwJNO8QmEBOTw1JO1kaxDRlMAsJ5JTBaeGWdhqWQ3IRH/RogkU3ukOyyybbeoWU9ou5xG08P0r4CM3dGh44M8qrTOOCUSSNrFIG5OhAW6ZdkSR/2w76fNnW6y25jKUQ2444kd8n8+TObsp8aNl1FkCwou6/SpPluKvtaFA+I1zbk8roG58tW9anf6o9zCdIyYhSFysEoKNNihSC6xc4zHlJUHWr61f4zC0mU1Xf/djgpvj2YUQLsQ4++K6FNb6I/CN7LDl67kkOiyVCKv60J1mHVXAPJpj2Y9SxR87ob+GH+na8oFy5IMPMDoym2hPImtl2cjGvpP6RvE+gVE+mfvc4BizWfsR1zO49DUD15U1Y6jqhbcPMCgk5FoQ3ZKiH6LBeFbmV0qMjwSC6pZONgz8s/2nTUMV3VU+9SBiah51ho="
@@ -19,18 +9,10 @@ env:
 matrix:
   fast_finish: true
   include:
-  - name: "python-3.6"
-    env: PY=3.6
-  - name: "python-3.7"
-    env: PY=3.7
-  - name: "python-3.8"
-    env: PY=3.8
   - name: coding_standards
-    env: PY=3.8
+    env: PY=3
   - name: docs
-    env: PY=3.8
-  - name: "tarball"
-    env: PY=3.8
+    env: PY=3
 
 before_install:
   # Install miniconda and create TEST env.
@@ -46,29 +28,11 @@ before_install:
     conda create --name TEST python=$PY --file requirements.txt --file requirements-dev.txt
     source activate TEST
     conda info --all
-  # Install firefox headless driver.
-  - |
-    wget https://github.com/mozilla/geckodriver/releases/download/v0.23.0/geckodriver-v0.23.0-linux64.tar.gz -O geckodriver.tar.gz
-    mkdir geckodriver
-    tar -xzf geckodriver.tar.gz -C geckodriver
-    export PATH=$PATH:$PWD/geckodriver
 
 install:
   - pip install -e . --no-deps --force-reinstall
 
 script:
-  - if [[ $TRAVIS_JOB_NAME == python-* ]]; then
-      cp -r tests/ examples/ /tmp ;
-      pushd /tmp && pytest -n 2 -rxs --cov=branca -vv tests && popd ;
-    fi
-
-  - if [[ $TRAVIS_JOB_NAME == 'tarball' ]]; then
-      python setup.py --version ;
-      pip wheel . -w dist --no-deps ;
-      check-manifest --verbose ;
-      twine check dist/* ;
-    fi
-
   - if [[ $TRAVIS_JOB_NAME == 'coding_standards' ]]; then
       pytest --flake8 -m flake8 ;
     fi
@@ -85,17 +49,3 @@ script:
 doctr:
   require-master: true
   sync: False
-
-deploy:
-  skip_cleanup: true
-  provider: pypi
-  user: "__token__"
-  password:
-    secure: "ncVXDcUpadOXESusbUv/F5mhXqzjp0fFM+llYdBOWBGpipP2B1cU/cWRUU5fkhL1KZG/0DfVboJsRnNQCebZklJ0fczW2Z2QUzL8zCAK8KLOUqr0Nk8i8Oi5eC0h8Ew+HkDNORsZUCPCjKvqcFHRuSZnVYvEqqPPguSj1fwDdyBm9kW7lfZdPeR9mr79WJs+Miv5UKXGZcQAuzCU7g3AmaizRoUfhXnSgPXd9Yp2DY5nsBUZhmqFTj1xjQwLvqp5w/BiFZGkdqMuWVP9b/HbVJEeRxWgCaDPw+XLkQa+HdfUsDGBvPWaaQ+o6OwuADdCjIHLnVmfA2/E2FSU64IBaIZ1xOkHZpm74P8Y0NF5aRogyipkqhrm0WErRWFD4OYj8ud4y9AHLCAmyyLsKCka76NSxotbTd4W4Xu9MX7fkhGOVzhYXg1GNS6SDTIMn+bujFNg5fZrZUptn6FcNa8THkh7yKrduBv2kgbBcgx0/EodH4q2Ds7yfbFvPkQD+l0mBiOPXOXflcQDOiu0CZ70X2XmLe3v3zIEf51oigEtb/KRkf7B3r/Ab+ayNdS33xbHsE8JYE/6KKjGf+fEr0f4XiidadtCbUmNGCw8fgHo7dY4g7blYMPvab7z4zcHmqekzrSTTwxw2uRtmGLWWJupmOerasRIuLsKiJxnk9oiS+k="
-  distributions: sdist bdist_wheel
-  upload_docs: no
-  on:
-    repo: python-visualization/branca
-    tags: true
-    all_branches: master
-    condition: '$TRAVIS_JOB_NAME == "tarball"'
diff --git a/requirements-dev.txt b/requirements-dev.txt
index dcd6a13..b0dc9ef 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -8,6 +8,7 @@ flake8-print
 isort
 jupyter
 nbsphinx
+nbval
 pylint
 pytest
 pytest-cov
diff --git a/tests/test_notebooks.py b/tests/test_notebooks.py
deleted file mode 100644
index f1327e9..0000000
--- a/tests/test_notebooks.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-Branca Notebooks Tests
-----------------------
-
-Here we try to execute all notebooks that are in `branca/examples`.
-"""
-
-import os
-
-import nbconvert
-
-import branca.utilities
-
-rootpath = os.path.abspath(os.path.dirname(__file__))
-
-
-class NotebookTester(object):
-    def __init__(self, filename):
-        self.filename = filename
-
-    def __call__(self, exporter=None, filename=None):
-        raw_nb = nbconvert.exporters.Exporter().from_filename(self.filename)
-        raw_nb[0].metadata.setdefault('kernelspec', {})['name'] = 'python'
-        exec_nb = nbconvert.preprocessors.ExecutePreprocessor().preprocess(*raw_nb)
-
-        if exporter is not None:
-            out_nb = nbconvert.exporters.MarkdownExporter().from_notebook_node(*exec_nb)
-            if filename is None:
-                assert self.filename.endswith('.ipynb')
-                filename = self.filename[:-6] + exporter.file_extension
-            open(filename, 'w').write(out_nb[0].encode('utf-8'))
-
-
-class TestNotebooks(object):
-    _filepath = rootpath.rstrip('/')+'/../examples/'
-    _nblist = [x for x in os.listdir(_filepath) if x.endswith('.ipynb')]
-
-
-for fn in TestNotebooks._nblist:
-    setattr(
-        TestNotebooks,
-        'test_'+branca.utilities._camelify(fn[:-6]),
-        NotebookTester(TestNotebooks._filepath+fn).__call__
-    )
