diff --git a/.github/prtester-requirements.txt b/.github/prtester-requirements.txt
new file mode 100644
index 00000000..4fb08b57
--- /dev/null
+++ b/.github/prtester-requirements.txt
@@ -0,0 +1,2 @@
+beautifulsoup4>=4.10.0
+requests>=2.26.0
\ No newline at end of file
diff --git a/.github/prtester.py b/.github/prtester.py
new file mode 100644
index 00000000..56204ce7
--- /dev/null
+++ b/.github/prtester.py
@@ -0,0 +1,74 @@
+import requests
+from bs4 import BeautifulSoup
+import random
+import json
+import os.path
+
+# This script is specifically written to be used in automation for https://github.com/RSS-Bridge/rss-bridge
+#
+# This will scrape the whitelisted bridges in the current state (port 3000) and the PR state (port 3001) of
+# RSS-Bridge, generate a feed for each of the bridges and save the output as html files.
+# It also replaces the default static CSS link with a hardcoded link to @em92's public instance, so viewing
+# the HTML file locally will actually work as designed.
+
+def testBridges(bridges,status):
+    for bridge in bridges:
+        if bridge.get('data-ref'): # Some div entries are empty, this ignores those
+            bridgeid = bridge.get('id')
+            bridgeid = bridgeid.split('-')[1] # this extracts a readable bridge name from the bridge metadata
+            bridgestring = '/?action=display&bridge=' + bridgeid + '&format=Html'
+            forms = bridge.find_all("form")
+            formid = 1
+            for form in forms:
+                # a bridge can have multiple contexts, named 'forms' in html
+                # this code will produce a fully working formstring that should create a working feed when called
+                # this will create an example feed for every single context, to test them all
+                formstring = ''
+                errormessages = []
+                parameters = form.find_all("input")
+                lists = form.find_all("select")
+                # this for/if mess cycles through all available input parameters, checks if it required, then pulls
+                # the default or examplevalue and then combines it all together into the formstring
+                # if an example or default value is missing for a required attribute, it will throw an error
+                # any non-required fields are not tested!!!
+                for parameter in parameters:
+                    if parameter.get('type') == 'number' or parameter.get('type') == 'text':
+                        if parameter.has_attr('required'):
+                            if parameter.get('placeholder') == '':
+                                if parameter.get('value') == '':
+                                    errormessages.append(parameter.get('name'))
+                                else:
+                                    formstring = formstring + '&' + parameter.get('name') + '=' + parameter.get('value')
+                            else:
+                                formstring = formstring + '&' + parameter.get('name') + '=' + parameter.get('placeholder')
+                    # same thing, just for checkboxes. If a checkbox is checked per default, it gets added to the formstring
+                    if parameter.get('type') == 'checkbox':
+                        if parameter.has_attr('checked'):
+                            formstring = formstring + '&' + parameter.get('name') + '=on'
+                for list in lists:
+                    formstring = formstring + '&' + list.get('name') + '=' + list.contents[0].get('value')
+                if not errormessages:
+                    # if all example/default values are present, form the full request string, run the request, replace the static css
+                    # file with the url of em's public instance and then write it all to file.
+                    r = requests.get(URL + bridgestring + formstring)
+                    pagetext = r.text.replace('static/HtmlFormat.css','https://feed.eugenemolotov.ru/static/HtmlFormat.css')
+                    with open(os.getcwd() + "/results/" + bridgeid + '-' + status + '-context' + str(formid) + '.html', 'w+') as file:
+                        file.write(pagetext)
+                else:
+                    # if there are errors (which means that a required value has no example or default value), log out which error appeared
+                    with open(os.getcwd() + "/results/" + bridgeid + '-' + status + '-context' + str(formid) + '.html', 'w+') as file:
+                        file.write(str(errormessages))
+                formid += 1
+
+gitstatus = ["current", "pr"]
+
+for status in gitstatus: # run this twice, once for the current version, once for the PR version
+    if status == "current":
+        port = "3000" # both ports are defined in the corresponding workflow .yml file
+    elif status == "pr":
+        port = "3001"
+    URL = "http://localhost:" + port
+    page = requests.get(URL) # Use python requests to grab the rss-bridge main page
+    soup = BeautifulSoup(page.content, "html.parser") # use bs4 to turn the page into soup
+    bridges = soup.find_all("section") # get a soup-formatted list of all bridges on the rss-bridge page
+    testBridges(bridges,status) # run the main scraping code with the list of bridges and the info if this is for the current version or the pr version
diff --git a/.github/workflows/prhtmlgenerator.yml b/.github/workflows/prhtmlgenerator.yml
new file mode 100644
index 00000000..979eea67
--- /dev/null
+++ b/.github/workflows/prhtmlgenerator.yml
@@ -0,0 +1,59 @@
+name: 'PR Testing'
+
+on:
+  pull_request_target:
+    branches: [ master ]
+
+jobs:
+  test-pr:
+    name: Generate HTML
+    runs-on: ubuntu-latest
+    # Needs additional permissions https://github.com/actions/first-interaction/issues/10#issuecomment-1041402989
+    steps:
+      - name: Check out self
+        uses: actions/checkout@v2.3.2
+        with:
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+      - name: Check out rss-bridge
+        run: |
+          PR=${{github.event.number}};
+          mv .github/prtester-requirements.txt ./requirements.txt;
+          mv .github/prtester.py .;
+          wget https://patch-diff.githubusercontent.com/raw/$GITHUB_REPOSITORY/pull/$PR.patch;
+          cat $PR.patch | grep " bridges/.*\.php" | sed "s= bridges/\(.*\)Bridge.php.*=\1=g" | sort | uniq > whitelist.txt
+      - name: Start Docker - Current
+        run: |
+          docker run -d -v $GITHUB_WORKSPACE/whitelist.txt:/app/whitelist.txt -p 3000:80 ghcr.io/rss-bridge/rss-bridge:latest
+      - name: Start Docker - PR
+        run: |
+          docker build -t prbuild .;
+          docker run -d -v $GITHUB_WORKSPACE/whitelist.txt:/app/whitelist.txt -p 3001:80 prbuild
+      - name: Setup python
+        uses: actions/setup-python@v2
+        with:
+          python-version: '3.7'
+          cache: 'pip'
+      - name: Install requirements
+        run: |
+          cd $GITHUB_WORKSPACE
+          pip install -r requirements.txt
+      - name: Run bridge tests
+        run: |
+          mkdir results;
+          python prtester.py
+      - name: Find result files
+        uses: tj-actions/glob@v7.10
+        id: artifacts
+        with:
+          files: |
+            ./results/*.html
+      - name: Upload results to PR
+        uses: yamanq/pull-request-artifacts@v1.3.1
+        with:
+          commit: ${{ github.event.pull_request.head.sha }}
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+          artifacts-branch: artifacts
+          artifacts-prefix-url: "https://htmlpreview.github.io/?"
+          artifacts: |
+            ${{ steps.artifacts.outputs.paths }}
\ No newline at end of file