diff --git a/.github/actions/build-e2e-matrix/action.yml b/.github/actions/build-e2e-matrix/action.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cf43ed9d5b14778861ff24d91fc722f541f95f97
--- /dev/null
+++ b/.github/actions/build-e2e-matrix/action.yml
@@ -0,0 +1,74 @@
+name: Build E2E Matrix
+description: Build and output a custom matrix for E2E tests.
+
+outputs:
+  matrix:
+    description: Parameterized matrix in JSON format
+    value: ${{ steps.matrix.outputs.result }}
+
+runs:
+  using: "composite"
+  steps:
+    - uses: actions/github-script@v6
+      id: matrix
+      with:
+        script: |
+          const java = 11;
+
+          const defaultRunner = "ubuntu-22.04";
+          const beefierRunner = "buildjet-2vcpu-ubuntu-2204";
+
+          const folderContext = [
+            "actions",
+            "admin",
+            "admin-2",
+            "binning",
+            "collections",
+            "custom-column",
+            "dashboard",
+            "dashboard-cards",
+            "dashboard-filters",
+            "embedding",
+            "filters",
+            "joins",
+            "models",
+            "native",
+            "native-filters",
+            "onboarding",
+            "organization",
+            "permissions",
+            "question",
+            "sharing",
+            "visualizations",
+          ];
+
+          const specialContext = ["oss-subset", "slow"];
+
+          const defaultConfig = folderContext.map(folder => {
+            return {
+              "java-version": java,
+              folder,
+              runner: defaultRunner,
+              edition: "ee",
+            };
+          });
+
+          const extraConfig = specialContext.map(s => {
+            return {
+              "java-version": java,
+              context: s,
+              runner: getRunner(s),
+              edition: getEdition(s),
+            };
+          });
+
+          function getRunner(context) {
+            return context === "slow" ? beefierRunner : defaultRunner;
+          }
+
+          function getEdition(context) {
+            return context === "oss-subset" ? "oss" : "ee";
+          }
+
+          const matrix = { include: [...defaultConfig, ...extraConfig] };
+          return matrix;
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index 4a294e9fc39efc1181db526e2cbed43574538294..37eeb6039a20527b5d0c6c939860c7564f3ad5c6 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -39,13 +39,25 @@ jobs:
           token: ${{ github.token }}
           filters: .github/file-paths.yaml
 
+  e2e-matrix-builder:
+    runs-on: ubuntu-22.04
+    timeout-minutes: 5
+    outputs:
+      matrix: ${{ steps.e2e-matrix.outputs.matrix }}
+    steps:
+      - uses: actions/checkout@v3
+      - name: Generate matrix for E2E tests
+        id: e2e-matrix
+        uses: ./.github/actions/build-e2e-matrix
+
   download_uberjar:
     runs-on: ubuntu-22.04
     timeout-minutes: 10
-    needs: files-changed
+    needs: [files-changed, e2e-matrix-builder]
     if: |
       !cancelled() &&
       github.event.pull_request.draft == false &&
+      needs.e2e-matrix-builder.result == 'success' &&
       needs.files-changed.outputs.e2e_specs == 'true' &&
       needs.files-changed.outputs.e2e_all != 'true'
     strategy:
@@ -107,10 +119,11 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
   build:
-    needs: [download_uberjar, files-changed]
+    needs: [download_uberjar, files-changed, e2e-matrix-builder]
     if: |
       !cancelled() &&
       github.event.pull_request.draft == false &&
+      needs.e2e-matrix-builder.result == 'success' &&
       needs.download_uberjar.result == 'skipped' &&
       needs.files-changed.outputs.e2e_all == 'true'
     runs-on: ubuntu-22.04
@@ -137,7 +150,7 @@ jobs:
         uses: ./.github/actions/prepare-uberjar-artifact
 
   e2e-tests:
-    needs: [build, files-changed, test-run-id, download_uberjar]
+    needs: [build, files-changed, test-run-id, download_uberjar, e2e-matrix-builder]
     if: |
       !cancelled() &&
       (needs.download_uberjar.result == 'success' || needs.build.result == 'success')
@@ -158,41 +171,7 @@ jobs:
       TZ: US/Pacific # to make node match the instance tz
     strategy:
       fail-fast: false
-      matrix:
-        runner: [ubuntu-22.04]
-        java-version: [11]
-        edition: [ee]
-        folder:
-          - "actions"
-          - "admin"
-          - "admin-2"
-          - "binning"
-          - "collections"
-          - "custom-column"
-          - "dashboard"
-          - "dashboard-cards"
-          - "dashboard-filters"
-          - "embedding"
-          - "filters"
-          - "joins"
-          - "models"
-          - "native"
-          - "native-filters"
-          - "onboarding"
-          - "organization"
-          - "permissions"
-          - "question"
-          - "sharing"
-          - "visualizations"
-        include:
-          - edition: oss
-            runner: ubuntu-22.04
-            context: oss-subset
-            java-version: 11
-          - edition: ee
-            java-version: 11
-            context: slow
-            runner: buildjet-2vcpu-ubuntu-2204
+      matrix: ${{ fromJSON(needs.e2e-matrix-builder.outputs.matrix) }}
     services:
       maildev:
         image: maildev/maildev:2.0.5
@@ -375,7 +354,7 @@ jobs:
           if-no-files-found: ignore
 
   e2e-tests-skipped-stub:
-    needs: [build, files-changed, download_uberjar]
+    needs: [build, files-changed, download_uberjar, e2e-matrix-builder]
     if: |
       !cancelled() &&
       needs.files-changed.outputs.e2e_all == 'false' &&
@@ -386,42 +365,7 @@ jobs:
     name: e2e-tests-${{ matrix.folder }}${{ matrix.context }}-${{ matrix.edition }}
     strategy:
       fail-fast: false
-      matrix:
-        runner: [ubuntu-22.04]
-        java-version: [11]
-        edition: [ee]
-        folder:
-          - "actions"
-          - "admin"
-          - "admin-2"
-          - "binning"
-          - "collections"
-          - "custom-column"
-          - "dashboard"
-          - "dashboard-cards"
-          - "dashboard-filters"
-          - "embedding"
-          - "filters"
-          - "joins"
-          - "models"
-          - "native"
-          - "native-filters"
-          - "onboarding"
-          - "organization"
-          - "permissions"
-          - "question"
-          - "sharing"
-          - "visualizations"
-        include:
-          - edition: oss
-            runner: ubuntu-22.04
-            java-version: 11
-            context: oss-subset
-          - edition: ee
-            runner: ubuntu-latest
-            java-version: 11
-            context: slow
-
+      matrix: ${{ fromJSON(needs.e2e-matrix-builder.outputs.matrix) }}
     steps:
       - run: |
           echo "Didn't run due to conditional filtering"