diff --git a/e2e/support/test_tables.js b/e2e/support/test_tables.js
index f57de3ef5c8b8e6c14ccabfae03d10393564cdbd..9b4b9c81927976193f24cb7cdb6b7ce2eac3a412 100644
--- a/e2e/support/test_tables.js
+++ b/e2e/support/test_tables.js
@@ -202,3 +202,36 @@ export const multi_schema = async dbClient => {
 
   return schemas;
 };
+
+export const many_schemas = async dbClient => {
+  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
+  const schemas = Object.fromEntries(
+    alphabet.map(letter => {
+      const key = `Schema ${letter}`;
+      const value = [
+        "Animals",
+        [
+          { name: "Duck", score: 10 },
+          { name: "Horse", score: 20 },
+          { name: "Cow", score: 30 },
+        ],
+      ];
+      return [key, value];
+    }),
+  );
+
+  Object.entries(schemas).forEach(async ([schemaName, details]) => {
+    const [table, rows] = details;
+    await dbClient.schema.createSchemaIfNotExists(schemaName);
+    await dbClient.schema.withSchema(schemaName).dropTableIfExists(table);
+
+    await dbClient.schema.withSchema(schemaName).createTable(table, t => {
+      t.string("name");
+      t.integer("score");
+    });
+
+    await dbClient(`${schemaName}.${table}`).insert(rows);
+  });
+
+  return schemas;
+};
diff --git a/e2e/test/scenarios/question/notebook-data-source.cy.spec.ts b/e2e/test/scenarios/question/notebook-data-source.cy.spec.ts
index 9cbd9953b30b606f31573f593b4936709663ef55..c93829191845ba5241da59891efadc5c78c6b46c 100644
--- a/e2e/test/scenarios/question/notebook-data-source.cy.spec.ts
+++ b/e2e/test/scenarios/question/notebook-data-source.cy.spec.ts
@@ -393,6 +393,57 @@ describe("scenarios > notebook > data source", { tags: "@OSS" }, () => {
   });
 });
 
+describe("issue 28106", () => {
+  beforeEach(() => {
+    const dialect = "postgres";
+
+    resetTestTable({ type: dialect, table: "many_schemas" });
+    restore(`${dialect}-writable`);
+    cy.signInAsAdmin();
+
+    resyncDatabase({ dbId: WRITABLE_DB_ID });
+  });
+
+  it(
+    "should not jump to the top of schema list when scrolling (metabase#28106)",
+    { tags: "@external" },
+    () => {
+      startNewQuestion();
+      entityPickerModal().within(() => {
+        entityPickerModalTab("Tables").click();
+        cy.findByText("Writable Postgres12").click();
+
+        entityPickerModalLevel(1)
+          .findByTestId("scroll-container")
+          .as("schemasList");
+
+        // the list is virtualized and the scrollbar height changes during scrolling (metabase#44966)
+        // that's why we need to scroll twice and wait
+        cy.get("@schemasList").scrollTo("bottom");
+        cy.wait(100);
+        cy.get("@schemasList").scrollTo("bottom");
+
+        // assert scrolling worked and the last item is visible
+        entityPickerModalItem(1, "Public").should("be.visible");
+
+        // simulate scrolling up using mouse wheel 3 times
+        cy.get("@schemasList").realMouseWheel({ deltaY: -100 });
+        cy.wait(100);
+        cy.get("@schemasList").realMouseWheel({ deltaY: -100 });
+        cy.wait(100);
+        cy.get("@schemasList").realMouseWheel({ deltaY: -100 });
+        cy.wait(100);
+
+        // assert first item does not exist - this means the list has not been scrolled to the top
+        cy.findByText("Domestic").should("not.exist");
+        cy.get("@schemasList").should(([$element]) => {
+          expect($element.scrollTop).to.be.greaterThan(0);
+        });
+      });
+    },
+  );
+});
+
 function moveToCollection(collection: string) {
   cy.intercept("GET", "/api/collection/tree**").as("updateCollectionTree");