diff --git a/.editorconfig b/.editorconfig
index fc64505..e3b84d0 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -11,7 +11,7 @@ insert_final_newline = true
 indent_style = space
 indent_size = 4
 
-[{azure-pipelines.yml,package.json}]
+[{azure-pipelines.yml,package.json, ci/*.yml}]
 # The indent size used in the `package.json` file cannot be changed
 # https://github.com/npm/npm/pull/3180#issuecomment-16336516
 indent_size = 2
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 22bf5db..6471a1d 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -73,358 +73,68 @@ jobs:
       inputs:
         PathtoPublish: 'www/public'
         artifactName: docs
-  - job: Browser_Tests_Linux_Firefox_Stable
-    displayName: Linux Firefox Stable
-    pool:
-      vmImage: 'Ubuntu-16.04'
-    variables:
-      TARGET_BROWSER: Firefox_Stable
-    dependsOn: Build
-    condition: succeeded()
-    steps:
-    - task: NodeTool@0
-      inputs:
-        versionSpec: '10.x'
-      displayName: 'Install Node.js'
-    - task: Npm@0
-      inputs:
-        command: install
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download library'
-      inputs:
-        artifactName: dist
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download testrunner'
-      inputs:
-        artifactName: build
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - script: Xvfb :99 &
-      displayName: 'Start Xvfb'
-    - script: DISPLAY=:99 npm run karma
-      displayName: 'Run Firefox tests - Firefox Stable'
-    - task: PublishTestResults@2
-      condition: succeededOrFailed()
-      inputs:
-        testRunner: JUnit
-        testResultsFiles: 'tmp/junit/*.xml'
-    - task: PublishBuildArtifacts@1
-      displayName: Upload Screenshots
-      condition: succeededOrFailed()
-      inputs:
-        PathtoPublish: 'tmp/reftests'
-        artifactName: ReftestResults
-  - job: Browser_Tests_Linux_Chrome_Stable
-    displayName: Linux Chrome Stable
-    pool:
-      vmImage: 'Ubuntu-16.04'
-    variables:
-      TARGET_BROWSER: Chrome_Stable
-    dependsOn: Build
-    condition: succeeded()
-    steps:
-    - task: NodeTool@0
-      inputs:
-        versionSpec: '10.x'
-      displayName: 'Install Node.js'
-    - task: Npm@0
-      inputs:
-        command: install
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download library'
-      inputs:
-        artifactName: dist
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download testrunner'
-      inputs:
-        artifactName: build
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - script: Xvfb :99 &
-      displayName: 'Start Xvfb'
-    - script: DISPLAY=:99 npm run karma
-      displayName: 'Run Chrome tests - Chrome Stable'
-    - task: PublishTestResults@2
-      condition: succeededOrFailed()
-      inputs:
-        testRunner: JUnit
-        testResultsFiles: 'tmp/junit/*.xml'
-    - task: PublishBuildArtifacts@1
-      displayName: Upload Screenshots
-      condition: succeededOrFailed()
-      inputs:
-        PathtoPublish: 'tmp/reftests'
-        artifactName: ReftestResults
-  - job: Browser_Tests_OSX_Safari_IOS_9
-    displayName: iOS Simulator Safari 9
-    pool:
+
+  - template: ci/browser-tests.yml
+    parameters:
+      name: Browser_Tests_Linux_Firefox_Stable
+      displayName: Linux Firefox Stable
+      vmImage: 'ubuntu-16.04'
+      targetBrowser: Firefox_Stable
+      xvfb: true
+
+  - template: ci/browser-tests.yml
+    parameters:
+      name: Browser_Tests_Linux_Chrome_Stable
+      displayName: Linux Chrome Stable
+      vmImage: 'ubuntu-16.04'
+      targetBrowser: Chrome_Stable
+      xvfb: true
+
+  - template: ci/browser-tests.yml
+    parameters:
+      name: Browser_Tests_OSX_Safari_IOS_9
+      displayName: iOS Simulator Safari 9
       vmImage: 'macOS-10.13'
-    variables:
-      TARGET_BROWSER: Safari_IOS_9
-    dependsOn: Build
-    condition: succeeded()
-    steps:
-    - task: NodeTool@0
-      inputs:
-        versionSpec: '10.x'
-      displayName: 'Install Node.js'
-    - task: Npm@0
-      inputs:
-        command: install
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download library'
-      inputs:
-        artifactName: dist
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download testrunner'
-      inputs:
-        artifactName: build
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - script: npm run karma
-      displayName: 'Run Safari tests - Safari IOS'
-    - task: PublishTestResults@2
-      condition: succeededOrFailed()
-      inputs:
-        testRunner: JUnit
-        testResultsFiles: 'tmp/junit/*.xml'
-    - task: PublishBuildArtifacts@1
-      displayName: Upload Screenshots
-      condition: succeededOrFailed()
-      inputs:
-        PathtoPublish: 'tmp/reftests'
-        artifactName: ReftestResults
-  - job: Browser_Tests_OSX_Safari_IOS_10
-    displayName: iOS Simulator Safari 10
-    pool:
+      targetBrowser: Safari_IOS_9
+
+  - template: ci/browser-tests.yml
+    parameters:
+      name: Browser_Tests_OSX_Safari_IOS_10
+      displayName: iOS Simulator Safari 10
       vmImage: 'macOS-10.13'
-    variables:
-      TARGET_BROWSER: Safari_IOS_10
-    dependsOn: Build
-    condition: succeeded()
-    steps:
-    - task: NodeTool@0
-      inputs:
-        versionSpec: '10.x'
-      displayName: 'Install Node.js'
-    - task: Npm@0
-      inputs:
-        command: install
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download library'
-      inputs:
-        artifactName: dist
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download testrunner'
-      inputs:
-        artifactName: build
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - script: npm run karma
-      displayName: 'Run Safari tests - Safari IOS'
-    - task: PublishTestResults@2
-      condition: succeededOrFailed()
-      inputs:
-        testRunner: JUnit
-        testResultsFiles: 'tmp/junit/*.xml'
-    - task: PublishBuildArtifacts@1
-      displayName: Upload Screenshots
-      condition: succeededOrFailed()
-      inputs:
-        PathtoPublish: 'tmp/reftests'
-        artifactName: ReftestResults
-  - job: Browser_Tests_OSX_Safari_IOS_11
-    displayName: iOS Simulator Safari 11
-    pool:
+      targetBrowser: Safari_IOS_10
+
+  - template: ci/browser-tests.yml
+    parameters:
+      name: Browser_Tests_OSX_Safari_IOS_11
+      displayName: iOS Simulator Safari 11
       vmImage: 'macOS-10.13'
-    variables:
-      TARGET_BROWSER: Safari_IOS_11
-    dependsOn: Build
-    condition: succeeded()
-    steps:
-    - task: NodeTool@0
-      inputs:
-        versionSpec: '10.x'
-      displayName: 'Install Node.js'
-    - task: Npm@0
-      inputs:
-        command: install
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download library'
-      inputs:
-        artifactName: dist
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download testrunner'
-      inputs:
-        artifactName: build
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - script: npm run karma
-      displayName: 'Run Safari tests - Safari IOS'
-    - task: PublishTestResults@2
-      condition: succeededOrFailed()
-      inputs:
-        testRunner: JUnit
-        testResultsFiles: 'tmp/junit/*.xml'
-    - task: PublishBuildArtifacts@1
-      displayName: Upload Screenshots
-      condition: succeededOrFailed()
-      inputs:
-        PathtoPublish: 'tmp/reftests'
-        artifactName: ReftestResults
-  - job: Browser_Tests_OSX_Safari_Stable
-    displayName: OSX Safari Stable
-    pool:
+      targetBrowser: Safari_IOS_11
+
+  - template: ci/browser-tests.yml
+    parameters:
+      name: Browser_Tests_OSX_Safari_Stable
+      displayName: OSX Safari Stable
       vmImage: 'macOS-10.13'
-    variables:
-      TARGET_BROWSER: Safari_Stable
-    dependsOn: Build
-    condition: succeeded()
-    steps:
-    - task: NodeTool@0
-      inputs:
-        versionSpec: '10.x'
-      displayName: 'Install Node.js'
-    - task: Npm@0
-      inputs:
-        command: install
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download library'
-      inputs:
-        artifactName: dist
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download testrunner'
-      inputs:
-        artifactName: build
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - script: npm run karma
-      displayName: 'Run Safari tests - Safari Stable'
-    - task: PublishTestResults@2
-      condition: succeededOrFailed()
-      inputs:
-        testRunner: JUnit
-        testResultsFiles: 'tmp/junit/*.xml'
-    - task: PublishBuildArtifacts@1
-      displayName: Upload Screenshots
-      condition: succeededOrFailed()
-      inputs:
-        PathtoPublish: 'tmp/reftests'
-        artifactName: ReftestResults
-  - job: Browser_Tests_Windows_IE9
-    displayName: Windows Internet Explorer 9 (Emulated)
-    pool:
+      targetBrowser: Safari_Stable
+
+  - template: ci/browser-tests.yml
+    parameters:
+      name: Browser_Tests_Windows_IE9
+      displayName: Windows Internet Explorer 9 (Emulated)
       vmImage: 'vs2017-win2016'
-    variables:
-      TARGET_BROWSER: IE_9
-    dependsOn: Build
-    condition: succeeded()
-    steps:
-    - task: NodeTool@0
-      inputs:
-        versionSpec: '10.x'
-      displayName: 'Install Node.js'
-    - task: Npm@0
-      inputs:
-        command: install
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download library'
-      inputs:
-        artifactName: dist
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download testrunner'
-      inputs:
-        artifactName: build
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - script: npm run karma
-      displayName: 'Run Internet Explorer tests - IE 9'
-    - task: PublishTestResults@2
-      condition: succeededOrFailed()
-      inputs:
-        testRunner: JUnit
-        testResultsFiles: 'tmp/junit/*.xml'
-    - task: PublishBuildArtifacts@1
-      displayName: Upload Screenshots
-      condition: succeededOrFailed()
-      inputs:
-        PathtoPublish: 'tmp/reftests'
-        artifactName: ReftestResults
-  - job: Browser_Tests_Windows_IE10
-    displayName: Windows Internet Explorer 10 (Emulated)
-    pool:
+      targetBrowser: IE_9
+
+  - template: ci/browser-tests.yml
+    parameters:
+      name: Browser_Tests_Windows_IE10
+      displayName: Windows Internet Explorer 10 (Emulated)
       vmImage: 'vs2017-win2016'
-    variables:
-      TARGET_BROWSER: IE_10
-    dependsOn: Build
-    condition: succeeded()
-    steps:
-    - task: NodeTool@0
-      inputs:
-        versionSpec: '10.x'
-      displayName: 'Install Node.js'
-    - task: Npm@0
-      inputs:
-        command: install
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download library'
-      inputs:
-        artifactName: dist
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download testrunner'
-      inputs:
-        artifactName: build
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - script: npm run karma
-      displayName: 'Run Internet Explorer tests - IE 10'
-    - task: PublishTestResults@2
-      condition: succeededOrFailed()
-      inputs:
-        testRunner: JUnit
-        testResultsFiles: 'tmp/junit/*.xml'
-    - task: PublishBuildArtifacts@1
-      displayName: Upload Screenshots
-      condition: succeededOrFailed()
-      inputs:
-        PathtoPublish: 'tmp/reftests'
-        artifactName: ReftestResults
-  - job: Browser_Tests_Windows_IE11
-    displayName: Windows Internet Explorer 11
-    pool:
+      targetBrowser: IE_10
+
+  - template: ci/browser-tests.yml
+    parameters:
+      name: Browser_Tests_Windows_IE11
+      displayName: Windows Internet Explorer 11
       vmImage: 'vs2017-win2016'
-    variables:
-      TARGET_BROWSER: IE_11
-    dependsOn: Build
-    condition: succeeded()
-    steps:
-    - task: NodeTool@0
-      inputs:
-        versionSpec: '10.x'
-      displayName: 'Install Node.js'
-    - task: Npm@0
-      inputs:
-        command: install
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download library'
-      inputs:
-        artifactName: dist
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - task: DownloadBuildArtifacts@0
-      displayName: 'Download testrunner'
-      inputs:
-        artifactName: build
-        downloadPath: $(System.DefaultWorkingDirectory)
-    - script: npm run karma
-      displayName: 'Run Internet Explorer tests - IE 11'
-    - task: PublishTestResults@2
-      condition: succeededOrFailed()
-      inputs:
-        testRunner: JUnit
-        testResultsFiles: 'tmp/junit/*.xml'
-    - task: PublishBuildArtifacts@1
-      displayName: Upload Screenshots
-      condition: succeededOrFailed()
-      inputs:
-        PathtoPublish: 'tmp/reftests'
-        artifactName: ReftestResults
+      targetBrowser: IE_11
diff --git a/ci/browser-tests.yml b/ci/browser-tests.yml
new file mode 100644
index 0000000..5b8e832
--- /dev/null
+++ b/ci/browser-tests.yml
@@ -0,0 +1,52 @@
+parameters:
+  name: ''
+  vmImage: ''
+  targetBrowser: ''
+  xvfb: false
+
+jobs:
+  - job: ${{ parameters.name }}
+    displayName: ${{ parameters.displayName }}
+    pool:
+      vmImage: ${{ parameters.vmImage }}
+    variables:
+      TARGET_BROWSER: ${{ parameters.targetBrowser }}
+    dependsOn: Build
+    condition: succeeded()
+    steps:
+    - task: NodeTool@0
+      inputs:
+        versionSpec: '10.x'
+      displayName: 'Install Node.js'
+    - task: Npm@0
+      inputs:
+        command: install
+    - task: DownloadBuildArtifacts@0
+      displayName: 'Download library'
+      inputs:
+        artifactName: dist
+        downloadPath: $(System.DefaultWorkingDirectory)
+    - task: DownloadBuildArtifacts@0
+      displayName: 'Download testrunner'
+      inputs:
+        artifactName: build
+        downloadPath: $(System.DefaultWorkingDirectory)
+    - ${{ if not(eq(parameters.xvfb, 'true')) }}:
+      - script: npm run karma
+        displayName: 'Run browser tests'
+    - ${{ if eq(parameters.xvfb, 'true') }}:
+      - script: Xvfb :99 &
+        displayName: 'Start Xvfb'
+      - script: DISPLAY=:99 npm run karma
+        displayName: 'Run browser tests'
+    - task: PublishTestResults@2
+      condition: succeededOrFailed()
+      inputs:
+        testRunner: JUnit
+        testResultsFiles: 'tmp/junit/*.xml'
+    - task: PublishBuildArtifacts@1
+      displayName: Upload Screenshots
+      condition: succeededOrFailed()
+      inputs:
+        PathtoPublish: 'tmp/reftests'
+        artifactName: ReftestResults