diff --git a/.gitignore b/.gitignore
index 46e5469eeaa0cdc2749a696de34e248d8efb6aa5..179e5e16ed9770732dc73934dddc8571a25f7bd9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,9 @@
+# ignore html directory that contains output of pdoc
+html
+
+# gitlab pages directory
+public
+
 # virtual environment folder
 venv
 
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cd37ccfd0697a141ad8a9674e7cd361f82a763ce..a2ebe19ff16ae52b8d1ed92ae766b15b9695c13e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,41 +5,36 @@ stages:
 
 job_lint_flake8:
   stage: lint
-  image: morkolai/paa-bittet-ci
+  image: morkolai/soitool-ci
   script:
     - flake8 --version
-    - flake8 soitool
+    - flake8 --config scripts/.flake8 soitool test
     
 job_lint_pylint:
   stage: lint
-  image: morkolai/paa-bittet-ci
+  image: morkolai/soitool-ci
   script:
     - pylint --version
-    - pip install pyside2
-    - pylint --rcfile=scripts/.pylintrc soitool
+    - pylint --rcfile=scripts/.pylintrc soitool test
 
 job_lint_bandit:
   stage: lint
-  image: morkolai/paa-bittet-ci
+  image: morkolai/soitool-ci
   script:
     - bandit --version
-    - bandit -r soitool
+    - bandit -r soitool test
 
 job_lint_pydocstyle:
   stage: lint
-  image: morkolai/paa-bittet-ci
+  image: morkolai/soitool-ci
   script:
-    - pip install pydocstyle
     - pydocstyle --version
-    - pydocstyle --convention=numpy soitool
+    - pydocstyle --match '.*.py' --convention=numpy soitool test
       
 job_test_gui_ubuntu_vnc:
   stage: test
-  image: morkolai/paa-bittet-ci
-  before_script:
-    - apt-get install -y libgl1-mesa-glx
+  image: morkolai/soitool-ci
   script:
-    - pip install pyside2
     # -platform because running with a screen is not supported
     # https://stackoverflow.com/questions/17106315/failed-to-load-platform-plugin-xcb-while-launching-qt5-app-on-linux-without
     - QT_QPA_PLATFORM=vnc python3 -m unittest test.test_main
@@ -52,21 +47,20 @@ job_test_gui_windows:
     - python --version
     - python -m unittest test.test_main
 
-#job_test_gui_ubuntu:
-#  stage: test
-#  tags:
-#    - ci-ubuntu
-#  script:
-#    - python3 --version
-#    - DISPLAY=':10.0' python3 -m unittest test.test_main
+job_test_gui_ubuntu:
+  stage: test
+  tags:
+    - ci-ubuntu
+  script:
+    - python3 --version
+    - DISPLAY=':10.0' python3 -m unittest test.test_main
 
 # name **må** være pages
 pages:
   stage: deploy
-  image: morkolai/paa-bittet-ci
+  image: morkolai/soitool-ci
   script:
   - mkdir public
-  - pip install pdoc3 pyside2
   - pdoc --version
   - pdoc soitool --html
   - mv ./html/soitool/* ./public
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..e1cd2b012ede6a78fc06c7a1fc7e2889368f5ce6
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,27 @@
+FROM ubuntu:16.04
+
+# This Dockerfile describes the container used in .gitlab-ci.yml
+
+# Need docker build arg, such as --build-arg FLAKE8_VERSION=3.0.4
+ARG FLAKE8_VERSION=3.7
+
+# Installing python3.7
+RUN apt-get update
+RUN apt-get install software-properties-common -y
+RUN add-apt-repository ppa:deadsnakes/ppa -y
+RUN apt-get update
+RUN apt-get install python3.7 -y
+RUN python3.7 -V
+RUN apt-get install python3-pip -y
+RUN pip3 install --upgrade pip
+
+# To make -platform offscreen,minimal,vnc work
+RUN apt-get install -y libegl1-mesa-dev \
+                       libfontconfig1-dev \
+                       libsdl1.2-dev \
+                       libwayland-dev \
+                       libxkbcommon-dev
+
+WORKDIR /code
+COPY requirements.txt requirements.txt
+RUN pip3 install -r requirements.txt
diff --git a/README.md b/README.md
index 874486f50bd3b384696d0140027f4c9f74ce67fa..b7538b1afdab2a83a1990e9b1a4abfe14de44e56 100644
--- a/README.md
+++ b/README.md
@@ -51,4 +51,44 @@ Gjøres med pdoc3:
 
 * `pdoc3 --html --output-dir .\docs .\soitool\main.py`
 
-* Uten kildekode: `pdoc3 --html --config show_source_code=False --output-dir .\docs\ .\soitool\main.py`
\ No newline at end of file
+* Uten kildekode: `pdoc3 --html --config show_source_code=False --output-dir .\docs\ .\soitool\main.py`
+
+## Om `Dockerfile`
+
+Docker image som brukes i `.gitlab-ci.yml` er bygget med filen `Dockerfile` og er lastet opp som `morkolai/soitool-ci`. Docker image inneholder alle avhengigheter til prosjektet. Følgende prosedyre brukes for å oppdatere image. Dette må gjøres når `requirements.txt` endrer seg.
+
+```bash
+docker build -t morkolai/soitool-ci .
+docker login
+docker push morkolai/soitool-ci
+```
+
+## Arbeidsmetode - Bruk av `git`
+
+Arbeid skal ikke skje direkte på `master` branch. For hver oppgave en vil utføre skal en ny branch lages, og denne må senere merges inn ved hjelp av en "Merge Request". Gjennomgang av dette er lagt fram under:
+
+```bash
+# ny branch
+git branch <branch navn>
+# hoppe til eksisterende branch
+git checkout <branch navn>
+# ..jobb med koden..
+git add <...>
+git commit -m "..."
+# push til gitlab
+git push origin <branch navn>
+```
+
+Merging til master skal skje via Merge Requests i GitLab.
+
+### Om arbeid utføres på feil branch
+
+`git stash` kan brukes for å lagre endringer i et "stash". Deretter kan en hoppe til riktig branch med `git checkout <branch navn>`, og kjøre `git stash pop`.
+
+## Arbeidsmetode - Hvordan skrive tester
+
+Hver modul burde testes. I praksis vil dette si at hver fil under `soitool/` med navn `X.py` burde ha en tilsvarende fil under `test/` med navn `test_X.py`.
+
+Ved GUI-testing av modulære dialoger (dialoger som stopper eksekvering av hovedvindu) foretrekkes fremgangsmåten som demonstreres i `test\test_main.py`, hvor testfunksjoner kjøres med `singleShot`. Antall millisekunder testen skal vente før testfunksjoner kjøres kan stilles inn for tregere maskiner.
+
+*TODO* hvordan skrive GUI tester.
diff --git a/requirements.txt b/requirements.txt
index e7003db1ed313fd7ea05e86259946a579bafc7f9..c3395b573911f97cc9e70e8baee6e4eb101d7899 100644
Binary files a/requirements.txt and b/requirements.txt differ
diff --git a/scripts/.flake8 b/scripts/.flake8
new file mode 100644
index 0000000000000000000000000000000000000000..b67c07910db8b368186b279e7ae62434899ff202
--- /dev/null
+++ b/scripts/.flake8
@@ -0,0 +1,5 @@
+[flake8]
+# ignorerer sjekk for lange linjer, ettersom dette gjøres i pylint
+# vi vil tillate lange linjer som avsluttes med link, og dette er mulig i
+# pylint, men såvidt vi vet ikke mulig i flake8
+ignore = E501
diff --git a/scripts/.pylintrc b/scripts/.pylintrc
index 7e9ef2c59e6d2485745e4bb9538672918024a7cb..43b2b9bbb126b33d46a4ce3faab76ffb6c1b2b72 100644
--- a/scripts/.pylintrc
+++ b/scripts/.pylintrc
@@ -275,7 +275,7 @@ redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
 expected-line-ending-format=
 
 # Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+ignore-long-lines=^.*<?https?://\S+>?$
 
 # Number of spaces of indent required inside a hanging or continued line.
 indent-after-paren=4
diff --git a/test/__init__.py b/test/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5f1ae119d6f959cc9e9b1a127e305d5145dc620e 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -0,0 +1 @@
+"""Tester koden vår."""
diff --git a/test/test_main.py b/test/test_main.py
index 8f422d611bdb75722b1adefa7388d81cef5e74b4..beadccfe5991936ceba67b155d69db315ddf1414 100644
--- a/test/test_main.py
+++ b/test/test_main.py
@@ -1,7 +1,10 @@
+"""Test CoolWidget."""
+
 import unittest
 import sys
+from datetime import datetime as datetime_, timedelta
+from PySide2 import QtWidgets, QtTest, QtCore
 from soitool import main
-from PySide2 import QtGui, QtWidgets, QtTest, QtCore
 
 # references:
 # * findChild: https://srinikom.github.io/pyside-docs/PySide/QtCore/QObject.html#PySide.QtCore.PySide.QtCore.QObject.findChild
@@ -18,26 +21,42 @@ from PySide2 import QtGui, QtWidgets, QtTest, QtCore
 # moved here from setUp to avoid annoying startup messages
 app = QtWidgets.QApplication(sys.argv)
 
+
 def wait(msec):
-    QtTest.QTest.qWait(msec)
+    """Venter msec millisekunder.
+
+    Bruker https://stackoverflow.com/a/34745326/3545896
+
+    Parameter
+    -----
+    msec : number
+        msec to wait
+    """
+    end = datetime_.now() + timedelta(milliseconds=msec)
+    while datetime_.now() < end:
+        app.processEvents()
+
 
 class TestMain(unittest.TestCase):
+    """TestCase for main."""
 
     def setUp(self):
+        """Forbereder widget for testing."""
         self.test_text1 = "A bad boy"
         self.test_text2 = "Hei Anders!"
         self.widget = main.CoolWidget(self.test_text1)
         self.widget.show()
 
     def test_starts_up(self):
+        """Test at widget kan starte opp."""
         self.assertEqual(
-           self.widget.qlabel.text(),
-           self.test_text1,
+            self.widget.qlabel.text(),
+            self.test_text1,
         )
         self.assertTrue(self.widget.isVisible())
-    
-    # This test shows how waiting for a referenced dialog can create more reliable tests. The only downside is having to ensure that the mouseClick after the singleShot is allowed to run. If we put a singleShot delay too short the line is never allowed to execute, and the while-loop of the inner function takes over the whole world
+
     def test_change_text_ok(self):
+        """Test at endring av tekst funker."""
 
         def change_text_and_ok():
             while self.widget.dlg_input is None:
@@ -46,7 +65,6 @@ class TestMain(unittest.TestCase):
             QtTest.QTest.keyClicks(child_line_edit, self.test_text2)
             QtTest.QTest.keyClick(child_line_edit, QtCore.Qt.Key_Enter)
 
-        # delay of 100 to allow the next line to execute
         QtCore.QTimer.singleShot(100, change_text_and_ok)
         QtTest.QTest.mouseClick(self.widget.button, QtCore.Qt.LeftButton)
 
@@ -54,15 +72,17 @@ class TestMain(unittest.TestCase):
             self.widget.qlabel.text(),
             self.test_text2,
         )
-    
-    # This test shows how we can use singleShot to trick code to be ran after .exec() of a dialog. The downside is that for slow computers the singleShot could be ran before .exec(). See the above test for an example approach to avoid this. A boon of this method is that we don't need to test against stored references to dialogs, we can instead rely on app.activeModalWidget()
+
     def test_change_text_not_ok(self):
+        """Test at avbrytelse av endring av tekster ikke endrer tekst."""
 
         def accept_popup():
             QtTest.QTest.keyClick(app.activeModalWidget(), QtCore.Qt.Key_Enter)
 
         def change_text_and_not_ok():
-            # in PySide2 we need to store a reference to this. If we don't the widget is garbage collected somehow before we get to use child_line_edit (a child of the active widget)
+            # in PySide2 we need to store a reference to this. If we don't the
+            # widget is garbage collected somehow before we get to use
+            # child_line_edit (a child of the active widget)
             active_widget = app.activeModalWidget()
 
             child_line_edit = active_widget.findChild(QtWidgets.QLineEdit)
@@ -73,11 +93,12 @@ class TestMain(unittest.TestCase):
         QtCore.QTimer.singleShot(0, change_text_and_not_ok)
 
         QtTest.QTest.mouseClick(self.widget.button, QtCore.Qt.LeftButton)
-    
+
         self.assertNotEqual(
             self.widget.qlabel.text(),
             self.test_text2,
         )
 
+
 if __name__ == '__main__':
     unittest.main()