This is an automated email from the ASF dual-hosted git repository.

jasonliu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new cb586ce385b Add e2e test for XComObjectStorageBackend (#62907)
cb586ce385b is described below

commit cb586ce385b4699e2c7376c55e7f825a0306dd75
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Mon Mar 9 10:44:34 2026 +0800

    Add e2e test for XComObjectStorageBackend (#62907)
    
    * Add e2e test for XComObjectStorageBackend
    
    * Update GitHub Action and Breeze cmd
    
    * Add aws s3fs deps
    
    * Add debug log
    
    * Add required AWS env to fix the e2e test failure
    
    * Harden the client in e2e test
    
    * Fix nits
    
    * Fix copilot suggestion
    
    Co-authored-by: Copilot <[email protected]>
    
    ---------
    
    Co-authored-by: Copilot <[email protected]>
---
 .github/workflows/additional-prod-image-tests.yml  | 11 +++
 .github/workflows/airflow-e2e-tests.yml            |  4 +-
 airflow-e2e-tests/scripts/init-aws.sh              |  1 +
 .../tests/airflow_e2e_tests/conftest.py            | 32 +++++++-
 .../tests/airflow_e2e_tests/constants.py           |  3 +
 .../airflow_e2e_tests/e2e_test_utils/clients.py    | 51 +++++++++++--
 .../remote_log_tests/test_remote_logging.py        | 12 +--
 .../xcom_object_storage_tests/__init__.py}         |  4 -
 .../test_xcom_object_storage_backend.py            | 89 ++++++++++++++++++++++
 .../images/output_testing_airflow-e2e-tests.svg    | 24 +++---
 .../images/output_testing_airflow-e2e-tests.txt    |  2 +-
 .../airflow_breeze/commands/testing_commands.py    |  2 +-
 12 files changed, 199 insertions(+), 36 deletions(-)

diff --git a/.github/workflows/additional-prod-image-tests.yml 
b/.github/workflows/additional-prod-image-tests.yml
index 35c93a3344f..ad3f60042c9 100644
--- a/.github/workflows/additional-prod-image-tests.yml
+++ b/.github/workflows/additional-prod-image-tests.yml
@@ -226,6 +226,17 @@ jobs:
       use-uv: ${{ inputs.use-uv }}
       e2e_test_mode: "remote_log"
 
+  test-e2e-integration-tests-xcom-object-storage:
+    name: "XCom object storage backend tests with PROD image"
+    uses: ./.github/workflows/airflow-e2e-tests.yml
+    with:
+      workflow-name: "XCom object storage backend e2e test"
+      runners: ${{ inputs.runners }}
+      platform: ${{ inputs.platform }}
+      default-python-version: "${{ inputs.default-python-version }}"
+      use-uv: ${{ inputs.use-uv }}
+      e2e_test_mode: "xcom_object_storage"
+
   test-ui-e2e-chromium:
     name: "Chromium UI e2e tests with PROD image"
     uses: ./.github/workflows/ui-e2e-tests.yml
diff --git a/.github/workflows/airflow-e2e-tests.yml 
b/.github/workflows/airflow-e2e-tests.yml
index 5009987bc67..e9d9811ae29 100644
--- a/.github/workflows/airflow-e2e-tests.yml
+++ b/.github/workflows/airflow-e2e-tests.yml
@@ -49,7 +49,7 @@ on:  # yamllint disable-line rule:truthy
         type: string
         required: true
       e2e_test_mode:
-        description: "Test mode - basic or remote_log"
+        description: "Test mode - basic, remote_log, or xcom_object_storage"
         type: string
         default: "basic"
 
@@ -80,7 +80,7 @@ on:  # yamllint disable-line rule:truthy
         type: string
         default: ""
       e2e_test_mode:
-        description: "Test mode - quick or full"
+        description: "Test mode - basic, remote_log, or xcom_object_storage"
         type: string
         default: "basic"
 
diff --git a/airflow-e2e-tests/scripts/init-aws.sh 
b/airflow-e2e-tests/scripts/init-aws.sh
index ca5a1cfe078..4c78d873570 100755
--- a/airflow-e2e-tests/scripts/init-aws.sh
+++ b/airflow-e2e-tests/scripts/init-aws.sh
@@ -17,4 +17,5 @@
 # under the License.
 
 aws --endpoint-url=http://localstack:4566 s3 mb s3://test-airflow-logs
+aws --endpoint-url=http://localstack:4566 s3 mb 
s3://test-xcom-objectstorage-backend
 aws --endpoint-url=http://localstack:4566 s3 ls
diff --git a/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py 
b/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
index 24caa32a213..ad071d4e02b 100644
--- a/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
+++ b/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
@@ -36,6 +36,7 @@ from airflow_e2e_tests.constants import (
     LOCALSTACK_PATH,
     LOGS_FOLDER,
     TEST_REPORT_FILE,
+    XCOM_BUCKET,
 )
 
 from tests_common.test_utils.fernet import generate_fernet_key_string
@@ -48,13 +49,18 @@ class _E2ETestState:
     airflow_logs_path: Path | None = None
 
 
-def _setup_s3_integration(dot_env_file, tmp_dir):
+def _copy_localstack_files(tmp_dir):
+    """Copy localstack compose file and init script into the temp directory."""
     copyfile(LOCALSTACK_PATH, tmp_dir / "localstack.yml")
 
     copyfile(AWS_INIT_PATH, tmp_dir / "init-aws.sh")
     current_permissions = os.stat(tmp_dir / "init-aws.sh").st_mode
     os.chmod(tmp_dir / "init-aws.sh", current_permissions | 0o111)
 
+
+def _setup_s3_integration(dot_env_file, tmp_dir):
+    _copy_localstack_files(tmp_dir)
+
     dot_env_file.write_text(
         f"AIRFLOW_UID={os.getuid()}\n"
         "AWS_DEFAULT_REGION=us-east-1\n"
@@ -68,6 +74,27 @@ def _setup_s3_integration(dot_env_file, tmp_dir):
     os.environ["ENV_FILE_PATH"] = str(dot_env_file)
 
 
+def _setup_xcom_object_storage_integration(dot_env_file, tmp_dir):
+    _copy_localstack_files(tmp_dir)
+
+    dot_env_file.write_text(
+        f"AIRFLOW_UID={os.getuid()}\n"
+        # XComObjectStorageBackend requires AWS_ACCESS_KEY_ID and 
AWS_SECRET_ACCESS_KEY as env vars
+        # because `universal-path` uses boto3's native S3 client, which relies 
on environment variables
+        # for authentication rather than parsing credentials from the 
connection URI
+        "AWS_ACCESS_KEY_ID=test\n"
+        "AWS_SECRET_ACCESS_KEY=test\n"
+        "AWS_DEFAULT_REGION=us-east-1\n"
+        "AWS_ENDPOINT_URL_S3=http://localstack:4566\n";
+        "AIRFLOW_CONN_AWS_DEFAULT=aws://test:test@\n"
+        
"AIRFLOW__CORE__XCOM_BACKEND=airflow.providers.common.io.xcom.backend.XComObjectStorageBackend\n"
+        
f"AIRFLOW__COMMON_IO__XCOM_OBJECTSTORAGE_PATH=s3://aws_default@{XCOM_BUCKET}/xcom\n"
+        "AIRFLOW__COMMON_IO__XCOM_OBJECTSTORAGE_THRESHOLD=0\n"
+        "_PIP_ADDITIONAL_REQUIREMENTS=apache-airflow-providers-amazon[s3fs]\n"
+    )
+    os.environ["ENV_FILE_PATH"] = str(dot_env_file)
+
+
 def spin_up_airflow_environment(tmp_path_factory: pytest.TempPathFactory):
     tmp_dir = tmp_path_factory.mktemp("airflow-e2e-tests")
 
@@ -97,6 +124,9 @@ def spin_up_airflow_environment(tmp_path_factory: 
pytest.TempPathFactory):
     if E2E_TEST_MODE == "remote_log":
         compose_file_names.append("localstack.yml")
         _setup_s3_integration(dot_env_file, tmp_dir)
+    elif E2E_TEST_MODE == "xcom_object_storage":
+        compose_file_names.append("localstack.yml")
+        _setup_xcom_object_storage_integration(dot_env_file, tmp_dir)
 
     #
     # Please Do not use this Fernet key in any deployments! Please generate 
your own key.
diff --git a/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py 
b/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py
index a208487da8b..764b4e56dc5 100644
--- a/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py
+++ b/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py
@@ -42,3 +42,6 @@ TEST_REPORT_FILE = AIRFLOW_ROOT_PATH / "airflow-e2e-tests" / 
"_e2e_test_report.j
 LOCALSTACK_PATH = AIRFLOW_ROOT_PATH / "airflow-e2e-tests" / "docker" / 
"localstack.yml"
 E2E_TEST_MODE = os.environ.get("E2E_TEST_MODE", "basic")
 AWS_INIT_PATH = AIRFLOW_ROOT_PATH / "airflow-e2e-tests" / "scripts" / 
"init-aws.sh"
+
+# s3 bucket name for XComObjectStorageBackend tests. This bucket will be 
created in the `init-aws.sh` script that is run as part of the LocalStack 
container initialization.
+XCOM_BUCKET = "test-xcom-objectstorage-backend"
diff --git 
a/airflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py 
b/airflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py
index 32abeb5331d..3a11101d4ac 100644
--- a/airflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py
+++ b/airflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py
@@ -20,6 +20,7 @@ import time
 from datetime import datetime, timezone
 from functools import cached_property
 
+import boto3
 import requests
 from requests.adapters import HTTPAdapter
 from urllib3.util.retry import Retry
@@ -31,19 +32,41 @@ from airflow_e2e_tests.constants import (
 )
 
 
+def get_s3_client():
+    """Return a boto3 S3 client configured to use the local LocalStack 
endpoint."""
+    return boto3.client(
+        "s3",
+        endpoint_url="http://localhost:4566";,
+        aws_access_key_id="test",
+        aws_secret_access_key="test",
+        region_name="us-east-1",
+    )
+
+
+def create_request_session_with_retries(status_forcelist: list[int]):
+    """Create a requests Session with retry logic for handling transient 
errors."""
+    Retry.DEFAULT_BACKOFF_MAX = 32
+    retry_strategy = Retry(
+        total=10,
+        backoff_factor=1,
+        status_forcelist=status_forcelist,
+    )
+    session = requests.Session()
+    adapter = HTTPAdapter(max_retries=retry_strategy)
+    session.mount("http://";, adapter)
+    session.mount("https://";, adapter)
+    return session
+
+
 class AirflowClient:
     """Client for interacting with the Airflow REST API."""
 
     def __init__(self):
-        self.session = requests.Session()
+        self.session = 
create_request_session_with_retries(status_forcelist=[429])
 
     @cached_property
     def token(self):
-        Retry.DEFAULT_BACKOFF_MAX = 32
-        retry = Retry(total=10, backoff_factor=1, status_forcelist=[429, 500, 
502, 503, 504])
-        session = requests.Session()
-        session.mount("http://";, HTTPAdapter(max_retries=retry))
-        session.mount("https://";, HTTPAdapter(max_retries=retry))
+        session = create_request_session_with_retries(status_forcelist=[429, 
500, 502, 503, 504])
 
         api_server_url = DOCKER_COMPOSE_HOST_PORT
         if not api_server_url.startswith(("http://";, "https://";)):
@@ -121,11 +144,23 @@ class AirflowClient:
             run_id=resp["dag_run_id"],
         )
 
-    def get_task_logs(self, dag_id: str, run_id: str, task_id: str, 
try_number: int = 1):
+    def get_task_instances(self, dag_id: str, run_id: str):
+        """Get task instances for a given DAG run."""
+        return self._make_request(
+            method="GET",
+            endpoint=f"dags/{dag_id}/dagRuns/{run_id}/taskInstances",
+        )
+
+    def get_task_logs(
+        self, dag_id: str, run_id: str, task_id: str, try_number: int = 1, 
map_index: int | None = None
+    ):
         """Get task logs via API."""
+        endpoint = 
f"dags/{dag_id}/dagRuns/{run_id}/taskInstances/{task_id}/logs/{try_number}"
+        if map_index is not None:
+            endpoint += f"?map_index={map_index}"
         return self._make_request(
             method="GET",
-            
endpoint=f"dags/{dag_id}/dagRuns/{run_id}/taskInstances/{task_id}/logs/{try_number}",
+            endpoint=endpoint,
         )
 
 
diff --git 
a/airflow-e2e-tests/tests/airflow_e2e_tests/remote_log_tests/test_remote_logging.py
 
b/airflow-e2e-tests/tests/airflow_e2e_tests/remote_log_tests/test_remote_logging.py
index 52f19c5a1f5..9260f0abe03 100644
--- 
a/airflow-e2e-tests/tests/airflow_e2e_tests/remote_log_tests/test_remote_logging.py
+++ 
b/airflow-e2e-tests/tests/airflow_e2e_tests/remote_log_tests/test_remote_logging.py
@@ -19,10 +19,9 @@ from __future__ import annotations
 import time
 from datetime import datetime, timezone
 
-import boto3
 import pytest
 
-from airflow_e2e_tests.e2e_test_utils.clients import AirflowClient
+from airflow_e2e_tests.e2e_test_utils.clients import AirflowClient, 
get_s3_client
 
 
 class TestRemoteLogging:
@@ -56,15 +55,10 @@ class TestRemoteLogging:
 
         # This bucket will be created part of the docker-compose setup in
         bucket_name = "test-airflow-logs"
-        s3_client = boto3.client(
-            "s3",
-            endpoint_url="http://localhost:4566";,
-            aws_access_key_id="test",
-            aws_secret_access_key="test",
-            region_name="us-east-1",
-        )
+        s3_client = get_s3_client()
 
         # Wait for logs to be available in S3 before we call `get_task_logs`
+        contents = []
         for _ in range(self.max_retries):
             response = s3_client.list_objects_v2(Bucket=bucket_name)
             contents = response.get("Contents", [])
diff --git a/airflow-e2e-tests/scripts/init-aws.sh 
b/airflow-e2e-tests/tests/airflow_e2e_tests/xcom_object_storage_tests/__init__.py
old mode 100755
new mode 100644
similarity index 85%
copy from airflow-e2e-tests/scripts/init-aws.sh
copy to 
airflow-e2e-tests/tests/airflow_e2e_tests/xcom_object_storage_tests/__init__.py
index ca5a1cfe078..13a83393a91
--- a/airflow-e2e-tests/scripts/init-aws.sh
+++ 
b/airflow-e2e-tests/tests/airflow_e2e_tests/xcom_object_storage_tests/__init__.py
@@ -1,4 +1,3 @@
-#!/bin/bash
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information
@@ -15,6 +14,3 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-aws --endpoint-url=http://localstack:4566 s3 mb s3://test-airflow-logs
-aws --endpoint-url=http://localstack:4566 s3 ls
diff --git 
a/airflow-e2e-tests/tests/airflow_e2e_tests/xcom_object_storage_tests/test_xcom_object_storage_backend.py
 
b/airflow-e2e-tests/tests/airflow_e2e_tests/xcom_object_storage_tests/test_xcom_object_storage_backend.py
new file mode 100644
index 00000000000..8ddb9738b6b
--- /dev/null
+++ 
b/airflow-e2e-tests/tests/airflow_e2e_tests/xcom_object_storage_tests/test_xcom_object_storage_backend.py
@@ -0,0 +1,89 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import time
+from datetime import datetime, timezone
+from pprint import pprint
+from uuid import uuid4
+
+import pytest
+
+from airflow_e2e_tests.constants import XCOM_BUCKET
+from airflow_e2e_tests.e2e_test_utils.clients import AirflowClient, 
get_s3_client
+
+
+class TestXComObjectStorageBackend:
+    airflow_client = AirflowClient()
+    dag_id = "example_xcom_test"
+    retry_interval_in_seconds = 5
+    max_retries = 12
+
+    def test_dag_succeeds_and_xcom_values_stored_in_s3(self):
+        """Test that a DAG using XComObjectStorageBackend completes 
successfully and persists XCom values to S3."""
+        self.airflow_client.un_pause_dag(self.dag_id)
+
+        trigger_resp = self.airflow_client.trigger_dag(
+            self.dag_id,
+            json={
+                "dag_run_id": f"test_xcom_object_storage_backend_{uuid4()}",
+                "logical_date": datetime.now(timezone.utc).isoformat(),
+            },
+        )
+        dag_run_id = trigger_resp["dag_run_id"]
+        state = self.airflow_client.wait_for_dag_run(
+            dag_id=self.dag_id,
+            run_id=dag_run_id,
+        )
+
+        # try to get all the logs to help debugging
+        if state != "success":
+            task_instances_resp = 
self.airflow_client.get_task_instances(self.dag_id, dag_run_id)
+            for task_instance in task_instances_resp["task_instances"]:
+                task_id = task_instance["task_id"]
+                try_number = task_instance["try_number"]
+                try:
+                    print(f"\nLogs for task {task_id} (try {try_number}):")
+                    task_logs_resp = self.airflow_client.get_task_logs(
+                        dag_id=self.dag_id, task_id=task_id, 
run_id=dag_run_id, try_number=try_number
+                    )
+                    pprint(task_logs_resp)
+                except Exception as e:
+                    print(f"Could not get logs for task {task_id} (try 
{try_number}): {e}")
+
+        assert state == "success", f"DAG {self.dag_id} did not complete 
successfully. Final state: {state}"
+
+        s3_client = get_s3_client()
+
+        contents = []
+        for _ in range(self.max_retries):
+            response = s3_client.list_objects_v2(Bucket=XCOM_BUCKET)
+            contents = response.get("Contents", [])
+            if contents:
+                break
+
+            print(f"No XCom objects found in S3 bucket {XCOM_BUCKET!r} yet. 
Retrying...")
+            time.sleep(self.retry_interval_in_seconds)
+
+        if not contents:
+            pytest.fail(
+                f"Expected XCom objects in S3 bucket {XCOM_BUCKET!r}, but 
bucket is empty.\n"
+                f"List Objects Response: {response}"
+            )
+
+        keys = [obj["Key"] for obj in contents]
+        print(f"Found {len(keys)} XCom object(s) in S3: {keys}")
diff --git a/dev/breeze/doc/images/output_testing_airflow-e2e-tests.svg 
b/dev/breeze/doc/images/output_testing_airflow-e2e-tests.svg
index 3c444d9bfb5..9c48a6c8cb4 100644
--- a/dev/breeze/doc/images/output_testing_airflow-e2e-tests.svg
+++ b/dev/breeze/doc/images/output_testing_airflow-e2e-tests.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 538.0" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 562.4" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -43,7 +43,7 @@
 
     <defs>
     <clipPath id="breeze-testing-airflow-e2e-tests-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="487.0" />
+      <rect x="0" y="0" width="1463.0" height="511.4" />
     </clipPath>
     <clipPath id="breeze-testing-airflow-e2e-tests-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -102,9 +102,12 @@
 <clipPath id="breeze-testing-airflow-e2e-tests-line-18">
     <rect x="0" y="440.7" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-testing-airflow-e2e-tests-line-19">
+    <rect x="0" y="465.1" width="1464" height="24.65"/>
+            </clipPath>
     </defs>
 
-    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="536" rx="8"/><text 
class="breeze-testing-airflow-e2e-tests-title" fill="#c5c8c6" 
text-anchor="middle" x="740" 
y="27">Command:&#160;testing&#160;airflow-e2e-tests</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="560.4" rx="8"/><text 
class="breeze-testing-airflow-e2e-tests-title" fill="#c5c8c6" 
text-anchor="middle" x="740" 
y="27">Command:&#160;testing&#160;airflow-e2e-tests</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -127,13 +130,14 @@
 </text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="264" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-10)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="264" textLength="366" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-10)">--include-success-outputs&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="463.6" y="264" 
textLength="841.8" clip-path="url(#breeze-testing-airflow-e2e-t [...]
 </text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="288.4" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-11)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="288.4" textLength="366" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-11)">--github-repository&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-testing-airflow-e2e-tests-r6" x="414.8" y="288.4" 
textLength="24.4" clip-path [...]
 </text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="312.8" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-12)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r7" x="463.6" y="312.8" 
textLength="73.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-12)">(TEXT)</text><text 
class="breeze-testing-airflow-e2e-tests-r5" x="1451.8" y="312.8" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-12)">│</text><text 
class="breez [...]
-</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="337.2" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-13)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="337.2" textLength="366" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-13)">--e2e-test-mode&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="463.6" y="337.2" textLen [...]
-</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="361.6" 
textLength="1464" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-14)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="1464" y="361.6" 
textLength="12.2" clip-path="url(#breeze-testing-airflow-e2e-tests-line-14)">
-</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="386" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-15)">╭─</text><text 
class="breeze-testing-airflow-e2e-tests-r5" x="24.4" y="386" textLength="195.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-15)">&#160;Common&#160;options&#160;</text><text
 class="breeze-testing-airflow-e2e-tests-r5" x="219.6" y="386" 
textLength="1220" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-15)">─────── [...]
-</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="410.4" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-16)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="410.4" 
textLength="109.8" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-16)">--verbose</text><text
 class="breeze-testing-airflow-e2e-tests-r6" x="158.6" y="410.4" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-16)">-v</text><text 
class="br [...]
-</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="434.8" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-17)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="434.8" 
textLength="109.8" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-17)">--dry-run</text><text
 class="breeze-testing-airflow-e2e-tests-r6" x="158.6" y="434.8" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-17)">-D</text><text 
class="br [...]
-</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="459.2" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-18)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="459.2" 
textLength="109.8" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-18)">--help&#160;&#160;&#160;</text><text
 class="breeze-testing-airflow-e2e-tests-r6" x="158.6" y="459.2" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-18)">-h</text> [...]
-</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="483.6" 
textLength="1464" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-19)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="1464" y="483.6" 
textLength="12.2" clip-path="url(#breeze-testing-airflow-e2e-tests-line-19)">
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="337.2" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-13)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="337.2" textLength="366" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-13)">--e2e-test-mode&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="463.6" y="337.2" textLen [...]
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="361.6" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-14)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r7" x="463.6" y="361.6" 
textLength="463.6" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-14)">(basic|remote_log|xcom_object_storage)</text><text
 class="breeze-testing-airflow-e2e-tests-r5" x="1451.8" y="361.6" 
textLength="12.2" clip-path="url(#breeze-testing-airflow-e2e-tests-lin [...]
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="386" 
textLength="1464" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-15)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="1464" y="386" textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-15)">
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="410.4" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-16)">╭─</text><text 
class="breeze-testing-airflow-e2e-tests-r5" x="24.4" y="410.4" 
textLength="195.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-16)">&#160;Common&#160;options&#160;</text><text
 class="breeze-testing-airflow-e2e-tests-r5" x="219.6" y="410.4" 
textLength="1220" clip-path="url(#breeze-testing-airflow-e2e-tests-line-16)">─ 
[...]
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="434.8" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-17)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="434.8" 
textLength="109.8" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-17)">--verbose</text><text
 class="breeze-testing-airflow-e2e-tests-r6" x="158.6" y="434.8" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-17)">-v</text><text 
class="br [...]
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="459.2" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-18)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="459.2" 
textLength="109.8" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-18)">--dry-run</text><text
 class="breeze-testing-airflow-e2e-tests-r6" x="158.6" y="459.2" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-18)">-D</text><text 
class="br [...]
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="483.6" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-19)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="483.6" 
textLength="109.8" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-19)">--help&#160;&#160;&#160;</text><text
 class="breeze-testing-airflow-e2e-tests-r6" x="158.6" y="483.6" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-19)">-h</text> [...]
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="508" 
textLength="1464" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-20)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="1464" y="508" textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-20)">
 </text>
     </g>
     </g>
diff --git a/dev/breeze/doc/images/output_testing_airflow-e2e-tests.txt 
b/dev/breeze/doc/images/output_testing_airflow-e2e-tests.txt
index 050f043694d..1329a6ca615 100644
--- a/dev/breeze/doc/images/output_testing_airflow-e2e-tests.txt
+++ b/dev/breeze/doc/images/output_testing_airflow-e2e-tests.txt
@@ -1 +1 @@
-c0321134cc7d9545774114a6b78096ef
+7ae6755f2c4ee578b3ebf11d051e8809
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands.py 
b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
index 2386b700abf..30f3c7efcd3 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
@@ -1387,7 +1387,7 @@ option_e2e_test_mode = click.option(
     default="basic",
     show_default=True,
     envvar="E2E_TEST_MODE",
-    type=click.Choice(["basic", "remote_log"], case_sensitive=False),
+    type=click.Choice(["basic", "remote_log", "xcom_object_storage"], 
case_sensitive=False),
 )
 
 

Reply via email to