diff --git a/.circleci/config.yml b/.circleci/config.yml
index 47ca790..fb1bc8e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -18,10 +18,10 @@ workflows:
           name:  Python (<< matrix.python_version >>) - ArangoDB (<< matrix.arangodb_license >>, << matrix.arangodb_version >> << matrix.arangodb_config >>)
           matrix:
             parameters:
-              python_version: ["3.10"]
+              python_version: ["3.10", "3.11", "3.12"]
               arangodb_config: ["single", "cluster"]
               arangodb_license: ["community", "enterprise"]
-              arangodb_version: ["3.12"]
+              arangodb_version: ["3.11", "3.12"]
 
 jobs:
   lint:
diff --git a/.github/workflows/pypi.yaml b/.github/workflows/pypi.yaml
new file mode 100644
index 0000000..5bfab90
--- /dev/null
+++ b/.github/workflows/pypi.yaml
@@ -0,0 +1,36 @@
+name: Upload to PyPI
+
+on:
+  release:
+    types: [published]
+
+jobs:
+  upload:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - uses: actions/setup-python@v4
+        with:
+          python-version: "3.12"
+
+      - name: Install build dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install build twine
+
+      - name: Build package
+        run: python -m build
+
+      - name: Publish to PyPI Test
+        env:
+          TWINE_USERNAME: __token__
+          TWINE_PASSWORD: ${{ secrets.PYPI_TEST_TOKEN }}
+        run: twine upload --repository testpypi dist/*
+
+      - name: Publish to PyPI
+        env:
+          TWINE_USERNAME: __token__
+          TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
+        run: twine upload --repository pypi dist/*
diff --git a/tests/static/cluster-3.11.conf b/tests/static/cluster-3.11.conf
new file mode 100644
index 0000000..86f7855
--- /dev/null
+++ b/tests/static/cluster-3.11.conf
@@ -0,0 +1,14 @@
+[starter]
+mode = cluster
+local = true
+address = 0.0.0.0
+port = 8528
+
+[auth]
+jwt-secret = /tests/static/keyfile
+
+[args]
+all.database.password = passwd
+all.database.extended-names = true
+all.log.api-enabled = true
+all.javascript.allow-admin-execute = true
diff --git a/tests/static/single-3.11.conf b/tests/static/single-3.11.conf
new file mode 100644
index 0000000..df45cb7
--- /dev/null
+++ b/tests/static/single-3.11.conf
@@ -0,0 +1,12 @@
+[starter]
+mode = single
+address = 0.0.0.0
+port = 8528
+
+[auth]
+jwt-secret = /tests/static/keyfile
+
+[args]
+all.database.password = passwd
+all.database.extended-names = true
+all.javascript.allow-admin-execute = true
diff --git a/tests/test_document.py b/tests/test_document.py
index ef84101..fbfd2b3 100644
--- a/tests/test_document.py
+++ b/tests/test_document.py
@@ -1,6 +1,7 @@
 import asyncio
 
 import pytest
+from packaging import version
 
 from arangoasync.exceptions import (
     DocumentDeleteError,
@@ -306,7 +307,7 @@ async def test_document_find(doc_col, bad_col, docs):
 
 
 @pytest.mark.asyncio
-async def test_document_insert_many(doc_col, bad_col, docs):
+async def test_document_insert_many(cluster, db_version, doc_col, bad_col, docs):
     # Check errors
     with pytest.raises(DocumentInsertError):
         await bad_col.insert_many(docs)
@@ -328,6 +329,9 @@ async def test_document_insert_many(doc_col, bad_col, docs):
         assert "error" in res
 
     # Silent mode
+    if cluster and db_version < version.parse("3.12.0"):
+        pytest.skip("Skipping silent option")
+
     result = await doc_col.insert_many(docs, silent=True)
     assert len(result) == len(docs)
     for res in result:
@@ -338,7 +342,7 @@ async def test_document_insert_many(doc_col, bad_col, docs):
 
 
 @pytest.mark.asyncio
-async def test_document_replace_many(doc_col, bad_col, docs):
+async def test_document_replace_many(cluster, db_version, doc_col, bad_col, docs):
     # Check errors
     with pytest.raises(DocumentReplaceError):
         await bad_col.replace_many(docs)
@@ -365,6 +369,9 @@ async def test_document_replace_many(doc_col, bad_col, docs):
         assert "text" not in doc["new"]
 
     # Silent mode
+    if cluster and db_version < version.parse("3.12.0"):
+        pytest.skip("Skipping silent option")
+
     result = await doc_col.replace_many(docs, silent=True)
     assert len(result) == 0
     await doc_col.truncate()
@@ -375,7 +382,7 @@ async def test_document_replace_many(doc_col, bad_col, docs):
 
 
 @pytest.mark.asyncio
-async def test_document_update_many(doc_col, bad_col, docs):
+async def test_document_update_many(db_version, cluster, doc_col, bad_col, docs):
     # Check errors
     with pytest.raises(DocumentUpdateError):
         await bad_col.update_many(docs)
@@ -402,6 +409,9 @@ async def test_document_update_many(doc_col, bad_col, docs):
         assert "text" in doc["new"]
 
     # Silent mode
+    if cluster and db_version < version.parse("3.12.0"):
+        pytest.skip("Skipping silent option")
+
     result = await doc_col.update_many(docs, silent=True)
     assert len(result) == 0
     await doc_col.truncate()
@@ -412,7 +422,7 @@ async def test_document_update_many(doc_col, bad_col, docs):
 
 
 @pytest.mark.asyncio
-async def test_document_delete_many(doc_col, bad_col, docs):
+async def test_document_delete_many(db_version, cluster, doc_col, bad_col, docs):
     # Check errors
     with pytest.raises(DocumentDeleteError):
         await bad_col.delete_many(docs)
@@ -444,6 +454,9 @@ async def test_document_delete_many(doc_col, bad_col, docs):
     assert "error" in result[1]
 
     # Silent mode
+    if cluster and db_version < version.parse("3.12.0"):
+        pytest.skip("Skipping silent option")
+
     await doc_col.truncate()
     _ = await doc_col.insert_many(docs)
     result = await doc_col.delete_many(docs, silent=True)