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

gopidesu 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 66d5e72a711 Add e2e test for remote logging (#56191)
66d5e72a711 is described below

commit 66d5e72a7117d3f7fb8b32a61f8215016facee57
Author: GPK <[email protected]>
AuthorDate: Wed Oct 15 19:09:57 2025 +0100

    Add e2e test for remote logging (#56191)
    
    * Prefetch remote log connection id for api server in order to read remote 
logs
    
    * fix docker compose file path
    
    * Fixup tests
    
    * Add test with mock_aws
    
    * Fixup test
    
    * Extend quick start docker with localstack
    
    * remove comment
    
    * add test connection
    
    * fix static checks
    
    * Add only e2e tests for remote logging
---
 .github/workflows/additional-prod-image-tests.yml  | 12 +++-
 .github/workflows/airflow-e2e-tests.yml            | 19 ++---
 .../docs/howto/docker-compose/docker-compose.yaml  |  2 +
 airflow-core/tests/unit/utils/test_log_handlers.py | 76 ++++++++++++++++++++
 airflow-e2e-tests/docker/localstack.yml            | 42 +++++++++++
 airflow-e2e-tests/pyproject.toml                   |  1 +
 airflow-e2e-tests/scripts/init-aws.sh              | 20 ++++++
 .../tests/airflow_e2e_tests/conftest.py            | 42 ++++++++---
 .../tests/airflow_e2e_tests/constants.py           |  7 +-
 .../airflow_e2e_tests/e2e_test_utils/clients.py    |  7 ++
 .../airflow_e2e_tests/remote_log_tests/__init__.py | 16 +++++
 .../remote_log_tests/test_remote_logging.py        | 83 ++++++++++++++++++++++
 .../images/output_testing_airflow-e2e-tests.svg    | 32 +++++----
 .../images/output_testing_airflow-e2e-tests.txt    |  2 +-
 .../airflow_breeze/commands/testing_commands.py    | 13 ++++
 .../commands/testing_commands_config.py            |  1 +
 dev/breeze/src/airflow_breeze/utils/run_tests.py   |  4 +-
 17 files changed, 343 insertions(+), 36 deletions(-)

diff --git a/.github/workflows/additional-prod-image-tests.yml 
b/.github/workflows/additional-prod-image-tests.yml
index a6d0f890d11..ec98cbb2d26 100644
--- a/.github/workflows/additional-prod-image-tests.yml
+++ b/.github/workflows/additional-prod-image-tests.yml
@@ -192,7 +192,7 @@ jobs:
       - name: "Run Task SDK integration tests"
         run: breeze testing task-sdk-integration-tests
 
-  test-e2e-integration-tests:
+  test-e2e-integration-tests-basic:
     name: "Test e2e integration tests with PROD image"
     uses: ./.github/workflows/airflow-e2e-tests.yml
     with:
@@ -200,3 +200,13 @@ jobs:
       platform: ${{ inputs.platform }}
       default-python-version: "${{ inputs.default-python-version }}"
       use-uv: ${{ inputs.use-uv }}
+
+  test-e2e-integration-tests-remote-log:
+    name: "Remote logging tests with PROD image"
+    uses: ./.github/workflows/airflow-e2e-tests.yml
+    with:
+      runners: ${{ inputs.runners }}
+      platform: ${{ inputs.platform }}
+      default-python-version: "${{ inputs.default-python-version }}"
+      use-uv: ${{ inputs.use-uv }}
+      e2e_test_mode: "remote_log"
diff --git a/.github/workflows/airflow-e2e-tests.yml 
b/.github/workflows/airflow-e2e-tests.yml
index 31e6758fdaa..a540fba4d6f 100644
--- a/.github/workflows/airflow-e2e-tests.yml
+++ b/.github/workflows/airflow-e2e-tests.yml
@@ -44,6 +44,10 @@ on:  # yamllint disable-line rule:truthy
         description: "Tag of the Docker image to test"
         type: string
         required: true
+      e2e_test_mode:
+        description: "Test mode - basic or remote_log"
+        type: string
+        default: "basic"
 
   workflow_call:
     inputs:
@@ -67,6 +71,10 @@ on:  # yamllint disable-line rule:truthy
         description: "Tag of the Docker image to test"
         type: string
         default: ""
+      e2e_test_mode:
+        description: "Test mode - quick or full"
+        type: string
+        default: "basic"
 
 jobs:
   test-e2e-integration-tests:
@@ -101,14 +109,7 @@ jobs:
         run: breeze testing airflow-e2e-tests
         env:
           DOCKER_IMAGE: "${{ inputs.docker-image-tag }}"
-      - name: "Upload e2e test report"
-        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 
 # v4.6.2
-        with:
-          name: e2e-test-report
-          path: './airflow-e2e-tests/_e2e_test_report.json'
-          retention-days: '7'
-          if-no-files-found: 'error'
-        if: always()
+          E2E_TEST_MODE: "${{ inputs.e2e_test_mode }}"
       - name: Zip logs
         run: |
           cd ./airflow-e2e-tests && zip -r logs.zip logs
@@ -116,7 +117,7 @@ jobs:
       - name: "Upload logs"
         uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 
 # v4.6.2
         with:
-          name: e2e-test-logs
+          name: "e2e-test-logs-${{ inputs.e2e_test_mode }}"
           path: './airflow-e2e-tests/logs.zip'
           retention-days: '7'
           if-no-files-found: 'error'
diff --git a/airflow-core/docs/howto/docker-compose/docker-compose.yaml 
b/airflow-core/docs/howto/docker-compose/docker-compose.yaml
index 2c2a614c9ef..3892e044143 100644
--- a/airflow-core/docs/howto/docker-compose/docker-compose.yaml
+++ b/airflow-core/docs/howto/docker-compose/docker-compose.yaml
@@ -51,6 +51,8 @@ x-airflow-common:
   # and uncomment the "build" line below, Then run `docker-compose build` to 
build the images.
   image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:|version|}
   # build: .
+  env_file:
+    - ${ENV_FILE_PATH:-.env}
   environment:
     &airflow-common-env
     AIRFLOW__CORE__EXECUTOR: CeleryExecutor
diff --git a/airflow-core/tests/unit/utils/test_log_handlers.py 
b/airflow-core/tests/unit/utils/test_log_handlers.py
index 6d2caaa8a43..0b85a31a117 100644
--- a/airflow-core/tests/unit/utils/test_log_handlers.py
+++ b/airflow-core/tests/unit/utils/test_log_handlers.py
@@ -72,6 +72,7 @@ from airflow.utils.state import State, TaskInstanceState
 from airflow.utils.types import DagRunType
 
 from tests_common.test_utils.config import conf_vars
+from tests_common.test_utils.db import clear_db_connections, clear_db_runs
 from tests_common.test_utils.file_task_handler import (
     convert_list_to_stream,
     extract_events,
@@ -86,6 +87,15 @@ TASK_LOGGER = "airflow.task"
 FILE_TASK_HANDLER = "task"
 
 
[email protected](autouse=True)
+def cleanup_tables():
+    clear_db_runs()
+    clear_db_connections()
+    yield
+    clear_db_runs()
+    clear_db_connections()
+
+
 class TestFileTaskLogHandler:
     def clean_up(self):
         with create_session() as session:
@@ -603,6 +613,72 @@ class TestFileTaskLogHandler:
         actual = h.handler.baseFilename
         assert actual == os.fspath(tmp_path / expected)
 
+    @skip_if_force_lowest_dependencies_marker
+    def test_read_remote_logs_with_real_s3_remote_log_io(self, 
create_task_instance, session):
+        """Test _read_remote_logs method using real S3RemoteLogIO with mock 
AWS"""
+        import tempfile
+
+        import boto3
+        from moto import mock_aws
+
+        from airflow.models.connection import Connection
+        from airflow.providers.amazon.aws.log.s3_task_handler import 
S3RemoteLogIO
+
+        def setup_mock_aws():
+            """Set up mock AWS S3 bucket and connection."""
+            s3_client = boto3.client("s3", region_name="us-east-1")
+            s3_client.create_bucket(Bucket="test-airflow-logs")
+            return s3_client
+
+        with mock_aws():
+            aws_conn = Connection(
+                conn_id="aws_s3_conn",
+                conn_type="aws",
+                login="test_access_key",
+                password="test_secret_key",
+                extra='{"region_name": "us-east-1"}',
+            )
+            session.add(aws_conn)
+            session.commit()
+            s3_client = setup_mock_aws()
+
+            ti = create_task_instance(
+                dag_id="test_dag_s3_remote_logs",
+                task_id="test_task_s3_remote_logs",
+                run_type=DagRunType.SCHEDULED,
+                logical_date=DEFAULT_DATE,
+            )
+            ti.try_number = 1
+
+            with tempfile.TemporaryDirectory() as temp_dir:
+                s3_remote_log_io = S3RemoteLogIO(
+                    remote_base="s3://test-airflow-logs/logs",
+                    base_log_folder=temp_dir,
+                    delete_local_copy=False,
+                )
+
+                with conf_vars({("logging", "REMOTE_LOG_CONN_ID"): 
"aws_s3_conn"}):
+                    fth = FileTaskHandler("")
+                    log_relative_path = fth._render_filename(ti, 1)
+
+                    log_content = "Log line 1 from S3\nLog line 2 from S3\nLog 
line 3 from S3"
+                    s3_client.put_object(
+                        Bucket="test-airflow-logs",
+                        Key=f"logs/{log_relative_path}",
+                        Body=log_content.encode("utf-8"),
+                    )
+
+                    import airflow.logging_config
+
+                    airflow.logging_config.REMOTE_TASK_LOG = s3_remote_log_io
+
+                    sources, logs = fth._read_remote_logs(ti, try_number=1)
+
+                    assert len(sources) > 0, f"Expected sources but got: 
{sources}"
+                    assert len(logs) > 0, f"Expected logs but got: {logs}"
+                    assert logs[0] == log_content
+                    assert f"s3://test-airflow-logs/logs/{log_relative_path}" 
in sources[0]
+
 
 @pytest.mark.parametrize("logical_date", ((None), (DEFAULT_DATE)))
 class TestFilenameRendering:
diff --git a/airflow-e2e-tests/docker/localstack.yml 
b/airflow-e2e-tests/docker/localstack.yml
new file mode 100644
index 00000000000..4a3f87d37d7
--- /dev/null
+++ b/airflow-e2e-tests/docker/localstack.yml
@@ -0,0 +1,42 @@
+# 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.
+---
+services:
+  localstack:
+    container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}"
+    image: localstack/localstack:4.7
+    labels:
+      breeze.description: "Integration that emulates AWS services locally."
+    ports:
+      - "4566:4566"            # LocalStack Gateway
+      - "4510-4559:4510-4559"  # external services port range
+    environment:
+      # LocalStack configuration: 
https://docs.localstack.cloud/references/configuration/
+      - DEBUG=${DEBUG:-0}
+      - SERVICES=s3
+      - AWS_ACCESS_KEY_ID=test
+      - AWS_SECRET_ACCESS_KEY=test
+      - AWS_DEFAULT_REGION=us-east-1
+    volumes:
+      - "./init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh"
+      - "/var/run/docker.sock:/var/run/docker.sock"
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health";]
+      interval: 10s
+      timeout: 5s
+      retries: 10
+      start_period: 10s
diff --git a/airflow-e2e-tests/pyproject.toml b/airflow-e2e-tests/pyproject.toml
index 950b1545e9d..67bac50b858 100644
--- a/airflow-e2e-tests/pyproject.toml
+++ b/airflow-e2e-tests/pyproject.toml
@@ -39,6 +39,7 @@ dependencies = [
     "apache-airflow-devel-common",
     "python-on-whales>=0.70.0",
     "testcontainers>=4.12.0",
+    "boto3",
 ]
 
 [tool.pytest.ini_options]
diff --git a/airflow-e2e-tests/scripts/init-aws.sh 
b/airflow-e2e-tests/scripts/init-aws.sh
new file mode 100755
index 00000000000..ca5a1cfe078
--- /dev/null
+++ b/airflow-e2e-tests/scripts/init-aws.sh
@@ -0,0 +1,20 @@
+#!/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
+# 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.
+
+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/conftest.py 
b/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
index d63c17e87a9..c639bffa80f 100644
--- a/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
+++ b/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
@@ -26,10 +26,13 @@ from rich.console import Console
 from testcontainers.compose import DockerCompose
 
 from airflow_e2e_tests.constants import (
-    AIRFLOW_ROOT_PATH,
+    AWS_INIT_PATH,
     DOCKER_COMPOSE_HOST_PORT,
+    DOCKER_COMPOSE_PATH,
     DOCKER_IMAGE,
     E2E_DAGS_FOLDER,
+    E2E_TEST_MODE,
+    LOCALSTACK_PATH,
     LOGS_FOLDER,
     TEST_REPORT_FILE,
 )
@@ -39,16 +42,33 @@ compose_instance = None
 airflow_logs_path = None
 
 
+def _setup_s3_integration(dot_env_file, tmp_dir):
+    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)
+
+    dot_env_file.write_text(
+        f"AIRFLOW_UID={os.getuid()}\n"
+        "AWS_DEFAULT_REGION=us-east-1\n"
+        "AWS_ENDPOINT_URL_S3=http://localstack:4566\n";
+        "AIRFLOW__LOGGING__REMOTE_LOGGING=true\n"
+        "AIRFLOW_CONN_AWS_S3_LOGS=aws://test:test@\n"
+        "AIRFLOW__LOGGING__REMOTE_LOG_CONN_ID=aws_s3_logs\n"
+        "AIRFLOW__LOGGING__DELETE_LOCAL_LOGS=true\n"
+        "AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER=s3://test-airflow-logs\n"
+    )
+    os.environ["ENV_FILE_PATH"] = str(dot_env_file)
+
+
 def spin_up_airflow_environment(tmp_path_factory):
     global compose_instance
     global airflow_logs_path
     tmp_dir = tmp_path_factory.mktemp("airflow-e2e-tests")
 
-    compose_file_path = (
-        AIRFLOW_ROOT_PATH / "airflow-core" / "docs" / "howto" / 
"docker-compose" / "docker-compose.yaml"
-    )
-
-    copyfile(compose_file_path, tmp_dir / "docker-compose.yaml")
+    console.print(f"[yellow]Using docker compose file: {DOCKER_COMPOSE_PATH}")
+    copyfile(DOCKER_COMPOSE_PATH, tmp_dir / "docker-compose.yaml")
 
     subfolders = ("dags", "logs", "plugins", "config")
 
@@ -63,19 +83,23 @@ def spin_up_airflow_environment(tmp_path_factory):
     copytree(E2E_DAGS_FOLDER, tmp_dir / "dags", dirs_exist_ok=True)
 
     dot_env_file = tmp_dir / ".env"
+    dot_env_file.write_text(f"AIRFLOW_UID={os.getuid()}\n")
 
     console.print(f"[yellow]Creating .env file :[/ {dot_env_file}")
 
-    dot_env_file.write_text(f"AIRFLOW_UID={os.getuid()}\n")
     os.environ["AIRFLOW_IMAGE_NAME"] = DOCKER_IMAGE
+    compose_file_names = ["docker-compose.yaml"]
+
+    if E2E_TEST_MODE == "remote_log":
+        compose_file_names.append("localstack.yml")
+        _setup_s3_integration(dot_env_file, tmp_dir)
 
     # If we are using the image from ghcr.io/apache/airflow/main we do not pull
     # as it is already available and loaded using prepare_breeze_and_image 
step in workflow
     pull = False if DOCKER_IMAGE.startswith("ghcr.io/apache/airflow/main/") 
else True
 
     console.print(f"[blue]Spinning up airflow environment using 
{DOCKER_IMAGE}")
-
-    compose_instance = DockerCompose(tmp_dir, 
compose_file_name=["docker-compose.yaml"], pull=pull)
+    compose_instance = DockerCompose(tmp_dir, 
compose_file_name=compose_file_names, pull=pull)
 
     compose_instance.start()
 
diff --git a/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py 
b/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py
index 54225059e0f..a208487da8b 100644
--- a/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py
+++ b/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py
@@ -27,7 +27,9 @@ DEFAULT_DOCKER_IMAGE = 
f"ghcr.io/apache/airflow/main/prod/python{DEFAULT_PYTHON_
 DOCKER_IMAGE = os.environ.get("DOCKER_IMAGE") or DEFAULT_DOCKER_IMAGE
 os.environ["AIRFLOW_UID"] = str(os.getuid())
 
-DOCKER_COMPOSE_PATH = AIRFLOW_ROOT_PATH / "airflow-core" / "docs" / "howto" / 
"docker-compose"
+DOCKER_COMPOSE_PATH = (
+    AIRFLOW_ROOT_PATH / "airflow-core" / "docs" / "howto" / "docker-compose" / 
"docker-compose.yaml"
+)
 
 AIRFLOW_WWW_USER_USERNAME = os.environ.get("_AIRFLOW_WWW_USER_USERNAME", 
"airflow")
 AIRFLOW_WWW_USER_PASSWORD = os.environ.get("_AIRFLOW_WWW_USER_PASSWORD", 
"airflow")
@@ -37,3 +39,6 @@ E2E_DAGS_FOLDER = AIRFLOW_ROOT_PATH / "airflow-e2e-tests" / 
"tests" / "airflow_e
 # The logs folder where the Airflow logs will be copied to and uploaded to 
github artifacts
 LOGS_FOLDER = AIRFLOW_ROOT_PATH / "airflow-e2e-tests" / "logs"
 TEST_REPORT_FILE = AIRFLOW_ROOT_PATH / "airflow-e2e-tests" / 
"_e2e_test_report.json"
+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"
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 6d775726347..32abeb5331d 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
@@ -121,6 +121,13 @@ 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):
+        """Get task logs via API."""
+        return self._make_request(
+            method="GET",
+            
endpoint=f"dags/{dag_id}/dagRuns/{run_id}/taskInstances/{task_id}/logs/{try_number}",
+        )
+
 
 class TaskSDKClient:
     """Client for interacting with the Task SDK API."""
diff --git 
a/airflow-e2e-tests/tests/airflow_e2e_tests/remote_log_tests/__init__.py 
b/airflow-e2e-tests/tests/airflow_e2e_tests/remote_log_tests/__init__.py
new file mode 100644
index 00000000000..13a83393a91
--- /dev/null
+++ b/airflow-e2e-tests/tests/airflow_e2e_tests/remote_log_tests/__init__.py
@@ -0,0 +1,16 @@
+# 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.
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
new file mode 100644
index 00000000000..265bfca6bbe
--- /dev/null
+++ 
b/airflow-e2e-tests/tests/airflow_e2e_tests/remote_log_tests/test_remote_logging.py
@@ -0,0 +1,83 @@
+# 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
+
+from datetime import datetime, timezone
+
+import boto3
+import pytest
+
+from airflow_e2e_tests.e2e_test_utils.clients import AirflowClient
+
+
+class TestRemoteLogging:
+    airflow_client = AirflowClient()
+    dag_id = "example_xcom_test"
+
+    def test_dag_unpause(self):
+        self.airflow_client.un_pause_dag(
+            TestRemoteLogging.dag_id,
+        )
+
+    def test_remote_logging_s3(self):
+        """Test that a DAG using remote logging to S3 completes 
successfully."""
+
+        self.airflow_client.un_pause_dag(TestRemoteLogging.dag_id)
+
+        resp = self.airflow_client.trigger_dag(
+            TestRemoteLogging.dag_id, json={"logical_date": 
datetime.now(timezone.utc).isoformat()}
+        )
+        state = self.airflow_client.wait_for_dag_run(
+            dag_id=TestRemoteLogging.dag_id,
+            run_id=resp["dag_run_id"],
+        )
+
+        assert state == "success", (
+            f"DAG {TestRemoteLogging.dag_id} did not complete successfully. 
Final state: {state}"
+        )
+
+        task_logs = self.airflow_client.get_task_logs(
+            dag_id=TestRemoteLogging.dag_id,
+            task_id="bash_pull",
+            run_id=resp["dag_run_id"],
+        )
+
+        task_log_sources = [
+            source for content in task_logs.get("content", [{}]) for source in 
content.get("sources", [])
+        ]
+
+        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",
+        )
+
+        # This bucket will be created part of the docker-compose setup in
+        bucket_name = "test-airflow-logs"
+        response = s3_client.list_objects_v2(Bucket=bucket_name)
+
+        if "Contents" not in response:
+            pytest.fail("No objects found in S3 bucket %s", bucket_name)
+
+        # s3 key format: 
dag_id=example_xcom/run_id=manual__2025-09-29T23:32:09.457215+00:00/task_id=bash_pull/attempt=1.log
+
+        log_files = [f"s3://{bucket_name}/{obj['Key']}" for obj in 
response["Contents"]]
+        assert any(source in log_files for source in task_log_sources), (
+            f"None of the log sources {task_log_sources} were found in S3 
bucket logs {log_files}"
+        )
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 9abcda1d332..7fc70e474b9 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"/>
@@ -120,20 +123,21 @@
 </text><text class="breeze-testing-airflow-e2e-tests-r1" x="12.2" y="93.2" 
textLength="268.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-3)">Run&#160;Airflow&#160;E2E&#160;tests.</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="1464" y="93.2" 
textLength="12.2" clip-path="url(#breeze-testing-airflow-e2e-tests-line-3)">
 </text><text class="breeze-testing-airflow-e2e-tests-r1" x="1464" y="117.6" 
textLength="12.2" clip-path="url(#breeze-testing-airflow-e2e-tests-line-4)">
 </text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="142" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-5)">╭─</text><text 
class="breeze-testing-airflow-e2e-tests-r5" x="24.4" y="142" textLength="305" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-5)">&#160;Airflow&#160;E2E&#160;tests&#160;flags&#160;</text><text
 class="breeze-testing-airflow-e2e-tests-r5" x="329.4" y="142" 
textLength="1110.2" clip-path="url(#breeze-testing-airflow-e2e-tests- [...]
-</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="166.4" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-6)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="166.4" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-6)">-</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="36.6" y="166.4" 
textLength="73.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-6)">-image</text><text 
class="breeze-test [...]
-</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="190.8" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-7)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="190.8" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-7)">-</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="36.6" y="190.8" 
textLength="85.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-7)">-python</text><text 
class="breeze-tes [...]
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="166.4" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-6)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="166.4" 
textLength="146.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-6)">--image-name</text><text
 class="breeze-testing-airflow-e2e-tests-r6" x="414.8" y="166.4" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-6)">-n</text><text 
class="br [...]
+</text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="190.8" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-7)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="190.8" 
textLength="97.6" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-7)">--python</text><text 
class="breeze-testing-airflow-e2e-tests-r6" x="414.8" y="190.8" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-7)">-p</text><text 
class="breeze- [...]
 </text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="215.2" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-8)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r7" x="463.6" y="215.2" 
textLength="732" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-8)">(&gt;3.10&lt;&#160;|&#160;3.11&#160;|&#160;3.12&#160;|&#160;3.13)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160
 [...]
 </text><text class="breeze-testing-airflow-e2e-tests-r5" x="0" y="239.6" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-9)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r5" x="463.6" y="239.6" 
textLength="732" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-9)">[default:&#160;3.10]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#
 [...]
-</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="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-10)">-</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="36.6" y="264" textLength="61" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-10)">-skip</text><text 
class="breeze-testing-ai [...]
-</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="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-11)">-</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="36.6" y="288.4" 
textLength="97.6" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-11)">-include</text><text 
class="breeze [...]
-</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-r4" x="24.4" y="312.8" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-12)">-</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="36.6" y="312.8" 
textLength="85.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-12)">-github</text><text 
class="breeze- [...]
+</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)">--skip-docker-compose-deletion</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="463.6" y="264" textLength="671" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-10)">Skip&#160;de [...]
+</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="305" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-11)">--include-success-outputs</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="463.6" y="288.4" 
textLength="841.8" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-11)">Whether&# [...]
+</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-r4" x="24.4" y="312.8" 
textLength="231.8" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-12)">--github-repository</text><text
 class="breeze-testing-airflow-e2e-tests-r6" x="414.8" y="312.8" 
textLength="24.4" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-12)">-g</text><text [...]
 </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-r5" x="463.6" y="337.2" 
textLength="585.6" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-13)">[default:&#160;apache/airflow]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class [...]
-</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="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-16)">-</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="36.6" y="410.4" 
textLength="97.6" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-16)">-verbose</text><text 
class="breeze [...]
-</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="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-17)">-</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="36.6" y="434.8" 
textLength="48.8" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-17)">-dry</text><text 
class="breeze-tes [...]
-</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="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-18)">-</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="36.6" y="459.2" textLength="61" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-18)">-help</text><text 
class="breeze-test [...]
-</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="361.6" 
textLength="12.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-14)">│</text><text 
class="breeze-testing-airflow-e2e-tests-r4" x="24.4" y="361.6" textLength="183" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-14)">--e2e-test-mode</text><text
 class="breeze-testing-airflow-e2e-tests-r1" x="463.6" y="361.6" 
textLength="463.6" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-14)">Specify&#160;the&#1 
[...]
+</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="73.2" 
clip-path="url(#breeze-testing-airflow-e2e-tests-line-19)">--help</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 
class="breeze [...]
+</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 b770f1f4d2d..e7095bc0b2d 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 @@
-4816cb6514217c64134f80453bc8e9de
+34b0e3acf4185caf7b76b05037e91c06
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands.py 
b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
index a10c823705d..efcd2a70f97 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
@@ -1267,6 +1267,16 @@ def python_api_client_tests(
     sys.exit(returncode)
 
 
+option_e2e_test_mode = click.option(
+    "--e2e-test-mode",
+    help="Specify the mode to use for E2E tests.",
+    default="basic",
+    show_default=True,
+    envvar="E2E_TEST_MODE",
+    type=click.Choice(["basic", "remote_log"], case_sensitive=False),
+)
+
+
 @group_for_testing.command(
     name="airflow-e2e-tests",
     context_settings=dict(
@@ -1281,6 +1291,7 @@ def python_api_client_tests(
 @option_include_success_outputs
 @option_verbose
 @option_dry_run
+@option_e2e_test_mode
 @click.argument("extra_pytest_args", nargs=-1, type=click.Path(path_type=str))
 def airflow_e2e_tests(
     python: str,
@@ -1288,6 +1299,7 @@ def airflow_e2e_tests(
     skip_docker_compose_deletion: bool,
     github_repository: str,
     include_success_outputs: bool,
+    e2e_test_mode: str,
     extra_pytest_args: tuple,
 ):
     """Run Airflow E2E tests."""
@@ -1308,6 +1320,7 @@ def airflow_e2e_tests(
         skip_docker_compose_deletion=skip_docker_compose_deletion,
         test_type="airflow-e2e-tests",
         skip_image_check=skip_image_check,
+        test_mode=e2e_test_mode,
     )
     sys.exit(return_code)
 
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py 
b/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
index e671be482bb..e8116d47ecc 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
@@ -281,6 +281,7 @@ TESTING_PARAMETERS: dict[str, list[dict[str, str | 
list[str]]]] = {
                 "--skip-docker-compose-deletion",
                 "--include-success-outputs",
                 "--github-repository",
+                "--e2e-test-mode",
             ],
         }
     ],
diff --git a/dev/breeze/src/airflow_breeze/utils/run_tests.py 
b/dev/breeze/src/airflow_breeze/utils/run_tests.py
index 55a9c1ec6cf..0ac96c9c663 100644
--- a/dev/breeze/src/airflow_breeze/utils/run_tests.py
+++ b/dev/breeze/src/airflow_breeze/utils/run_tests.py
@@ -104,6 +104,7 @@ def run_docker_compose_tests(
     include_success_outputs: bool,
     test_type: str = "docker-compose",
     skip_image_check: bool = False,
+    test_mode: str = "basic",
 ) -> tuple[int, str]:
     if not skip_image_check:
         command_result = run_command(["docker", "inspect", image_name], 
check=False, stdout=DEVNULL)
@@ -116,7 +117,7 @@ def run_docker_compose_tests(
         test_path = Path("tests") / "task_sdk_tests" / 
"test_task_sdk_health.py"
         cwd = TASK_SDK_TESTS_ROOT_PATH.as_posix()
     elif test_type == "airflow-e2e-tests":
-        test_path = Path("tests") / "airflow_e2e_tests"
+        test_path = Path("tests") / "airflow_e2e_tests" / f"{test_mode}_tests"
         cwd = AIRFLOW_E2E_TESTS_ROOT_PATH.as_posix()
     else:
         test_path = Path("tests") / "docker_tests" / 
"test_docker_compose_quick_start.py"
@@ -124,6 +125,7 @@ def run_docker_compose_tests(
 
     env = os.environ.copy()
     env["DOCKER_IMAGE"] = image_name
+    env["E2E_TEST_MODE"] = test_mode
     if skip_docker_compose_deletion:
         env["SKIP_DOCKER_COMPOSE_DELETION"] = "true"
     if include_success_outputs:


Reply via email to