diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a40a081b0c41e7696d3f1fe319db14c90c36f3be
--- /dev/null
+++ b/.github/workflows/python-package.yml
@@ -0,0 +1,39 @@
+# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Python package
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+  build:
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [3.7, 3.8, 3.9]
+        os: [ubuntu-latest, macos-latest]
+
+    runs-on: ${{ matrix.os }}
+
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python-version }}
+
+    - name: Install project and dependencies
+      run: |
+        pip install --upgrade pip
+        pip install -r requirements.txt mdtraj
+        pip install pytest coverage
+
+    - name: Test with pytest
+      run: |
+        coverage run --source=pycgtool -m pytest
+        coverage report --skip-covered
diff --git a/.gitignore b/.gitignore
index a721a33b1495dbbe1ad625893fef6f760e70c133..2ad0dd57887f3f3ff6dbb7ab92de00654dc35415 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,39 @@
+# Data
+/*.gro*
+/*.itp*
+/*.xtc*
+/fftest.ff/
+/gromacs/
+*.offsets
+
+# Dependencies and runtime
+/.venv/
+/env/
+/minenv/
+/venv/
 *.pyc
+poetry.toml
+
+# Development and Testing
+.cache
+.coverage
+.python-version
+.tox/
+/build/
+/cover/
+/dist/
+/docs/_build/
+/htmlcov/
+/pycgtool.egg-info/
+/tmp/
+
+# IDE files
 .idea/
+.vscode/
+settings.json
+
+# Misc
 .cache/
-/*.gro
-/*.itp
-/fftest.ff
-/env
-/minenv
-.coverage
-/cover
-/tmp
-/doc/build
-/nose2-junit.xml
+/notes/
 *.offsets
+*.pkl
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 38ca124d19ea279fdca350fd3ccc1a87ce976eb1..0000000000000000000000000000000000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-language: python
-python:
-  - "3.3"
-  - "3.4"
-  - "3.5"
-  - "3.6"
-cache:
-    directories:
-        - $HOME/.cache/pip
-install:
-    - pip install --upgrade pip setuptools wheel
-    - pip install -r requirements.txt --only-binary=numpy
-    - pip install -r .requirements-test.txt --only-binary=scipy
-script:
-    - py.test test/
diff --git a/pycgtool/frame.py b/pycgtool/frame.py
index c50fb96119840063173bf98f4063ece1af4506de..67905287f4e2673adf3202fcc59e494bd3f22abc 100644
--- a/pycgtool/frame.py
+++ b/pycgtool/frame.py
@@ -111,7 +111,7 @@ class Frame:
     """
     Hold Atom data separated into Residues
     """
-    def __init__(self, gro=None, xtc=None, itp=None, frame_start=0, xtc_reader="simpletraj"):
+    def __init__(self, gro=None, xtc=None, itp=None, frame_start=0, xtc_reader=None):
         """
         Return Frame instance having read Residues and Atoms from GRO if provided
 
@@ -132,7 +132,7 @@ class Frame:
 
         if gro is not None:
             from .framereader import get_frame_reader
-            self._trajreader = get_frame_reader(gro, traj=xtc, frame_start=frame_start)
+            self._trajreader = get_frame_reader(gro, traj=xtc, frame_start=frame_start, name=xtc_reader)
 
             self._trajreader.initialise_frame(self)
 
diff --git a/pycgtool/interface.py b/pycgtool/interface.py
index 8ca93b8de99e53a2d1cb58e0f6fe97a0caefef15..d61f5f9a097e033f2f39f5c26a8359df6e34fed5 100644
--- a/pycgtool/interface.py
+++ b/pycgtool/interface.py
@@ -227,7 +227,7 @@ class Progress:
         self._dowhile = dowhile
         self._quiet = quiet
         self._its = -1
-        self._start_time = time.clock()
+        self._start_time = time.perf_counter()
 
     def __len__(self):
         """
@@ -298,13 +298,13 @@ class Progress:
 
     def _stop(self):
         if not self._quiet:
-            time_taken = int(time.clock() - self._start_time)
+            time_taken = int(time.perf_counter() - self._start_time)
             print(self._bar + " took {0}s".format(time_taken))
         raise StopIteration
 
     def _display(self):
         try:
-            time_remain = int((time.clock() - self._start_time) * ((self._maxits - self._its) / self._its))
+            time_remain = int((time.perf_counter() - self._start_time) * ((self._maxits - self._its) / self._its))
         except ZeroDivisionError:
             time_remain = "-"
         print(self._bar + " {0}s left".format(time_remain), end="\r")
diff --git a/pycgtool/util.py b/pycgtool/util.py
index 6a08c9d41a28d20491e74d768a1f6459378aa1f5..ca3a201c4f45ee3fc2780ed31c232f26e2254f84 100644
--- a/pycgtool/util.py
+++ b/pycgtool/util.py
@@ -270,11 +270,18 @@ def sliding(vals):
     """
     it = iter(vals)
     prev = None
-    current = next(it)
+
+    try:
+        current = next(it)
+
+    except StopIteration:
+        raise ValueError('Iterable contains no items')
+    
     for nxt in it:
         yield (prev, current, nxt)
         prev = current
         current = nxt
+
     yield (prev, current, None)