From 8b2f617c613c35b1e63f2ba7e7e099df9f2bb871 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 17:40:39 +0000 Subject: [PATCH 01/10] ci: reduce iOS functional test flakiness - Set up Python, uv and dependencies before booting the simulator so they don't compete with the boot; pin the uv version. - Open Simulator.app after the boot instead of before it. - Cap the simulator boot step at 20 minutes. - Warm up the simulator (install WDA, launch Safari once) before tests. - Allow WDA startup retries in iOS and Flutter iOS capabilities. - Share one session across the Safari tests. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BEFRVrKrVcxwbWhhkzY9Wd --- .github/workflows/functional-test.yml | 85 +++++++++++++------ .../flutter_integration/helper/options.py | 2 + test/functional/ios/helper/options.py | 4 + test/functional/ios/safari_tests.py | 3 +- 4 files changed, 67 insertions(+), 27 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index fdc46e9b..fc7b198d 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -15,6 +15,7 @@ env: APPIUM_TEST_SERVER_HOST: '127.0.0.1' PYTHONUNBUFFERED: 1 UV_LOCKED: 'true' + UV_VERSION: '0.11.32' jobs: ios_test: @@ -49,6 +50,21 @@ jobs: with: xcode-version: ${{ env.XCODE_VERSION }} + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: 3.14 + + - name: Set up uv + uses: astral-sh/setup-uv@v10.3.1 + with: + enable-cache: true + python-version: '3.14' + version: ${{ env.UV_VERSION }} + + - name: Install Python dependencies + run: uv sync + - name: Install Appium and drivers run: | npm install -g appium @@ -62,11 +78,8 @@ jobs: host: ${{ env.APPIUM_TEST_SERVER_HOST }} server_args: '--relaxed-security' - - name: Start iOS Simulator UI - run: | - defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false - open -Fn "$(xcode-select --print-path)/Applications/Simulator.app" - name: Prepare iOS simulator + timeout-minutes: 20 uses: futureware-tech/simulator-action@v6 with: model: ${{ env.IPHONE_MODEL }} @@ -77,16 +90,19 @@ jobs: - name: Finalize iOS simulator boot run: xcrun --sdk iphonesimulator --show-sdk-version - - name: Set up Python - uses: actions/setup-python@v7 - with: - python-version: 3.14 + - name: Start iOS Simulator UI + run: | + defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false + open -Fn "$(xcode-select --print-path)/Applications/Simulator.app" - - name: Set up uv - uses: astral-sh/setup-uv@v10.3.1 - with: - enable-cache: true - python-version: '3.14' + - name: Warm up iOS simulator + # The first app launch on a fresh simulator is often slow enough to make + # the first Appium session time out, so do it once before the tests. + run: | + xcrun simctl install booted "$PREBUILT_WDA_PATH" + xcrun simctl launch booted com.apple.mobilesafari + sleep 15 + xcrun simctl terminate booted com.apple.mobilesafari || true - name: Run Tests run: | @@ -271,6 +287,16 @@ jobs: with: python-version: 3.14 + - name: Set up uv + uses: astral-sh/setup-uv@v10.3.1 + with: + enable-cache: true + python-version: '3.14' + version: ${{ env.UV_VERSION }} + + - name: Install Python dependencies + run: uv sync + - name: Install Node.js uses: actions/setup-node@v7 with: @@ -305,12 +331,6 @@ jobs: server_args: '--relaxed-security' log_file: appium-${{ matrix.e2e-tests }}.log - - name: Set up uv - uses: astral-sh/setup-uv@v10.3.1 - with: - enable-cache: true - python-version: '3.14' - - name: Run Android tests if: matrix.e2e-tests == 'flutter-android' uses: reactivecircus/android-emulator-runner@v2 @@ -322,14 +342,10 @@ jobs: disable-spellchecker: true disable-animations: true - - name: Start iOS Simulator UI - if: matrix.e2e-tests == 'flutter-ios' - run: | - defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false - open -Fn "$(xcode-select --print-path)/Applications/Simulator.app" - - - uses: futureware-tech/simulator-action@v6 + - name: Prepare iOS simulator + uses: futureware-tech/simulator-action@v6 if: matrix.e2e-tests == 'flutter-ios' + timeout-minutes: 20 with: # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md model: ${{ env.IPHONE_MODEL }} @@ -341,6 +357,23 @@ jobs: if: matrix.e2e-tests == 'flutter-ios' run: xcrun --sdk iphonesimulator --show-sdk-version + - name: Start iOS Simulator UI + if: matrix.e2e-tests == 'flutter-ios' + run: | + defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false + open -Fn "$(xcode-select --print-path)/Applications/Simulator.app" + + - name: Warm up iOS simulator + if: matrix.e2e-tests == 'flutter-ios' + # The first app launch on a fresh simulator is often slow enough to make + # the first Appium session time out, so do it once before the tests. + run: | + xcrun simctl install booted "$PREBUILT_WDA_PATH" + xcrun simctl launch booted com.apple.mobilesafari + sleep 15 + xcrun simctl terminate booted com.apple.mobilesafari || true + + - name: Run IOS tests if: matrix.e2e-tests == 'flutter-ios' run: | diff --git a/test/functional/flutter_integration/helper/options.py b/test/functional/flutter_integration/helper/options.py index 5cad7de3..0524a7db 100644 --- a/test/functional/flutter_integration/helper/options.py +++ b/test/functional/flutter_integration/helper/options.py @@ -60,6 +60,8 @@ def make_options() -> FlutterOptions: 'platformVersion': os.environ['IOS_VERSION'], 'allowTouchIdEnroll': True, 'wdaLaunchTimeout': 240000, + 'wdaStartupRetries': 3, + 'wdaStartupRetryInterval': 20000, 'wdaLocalPort': get_wda_port(), 'eventTimings': True, 'app': os.environ['FLUTTER_IOS_APP'], diff --git a/test/functional/ios/helper/options.py b/test/functional/ios/helper/options.py index c2d2fc3f..403aefa5 100644 --- a/test/functional/ios/helper/options.py +++ b/test/functional/ios/helper/options.py @@ -28,6 +28,10 @@ def make_options(app: str | None = None) -> XCUITestOptions: options.allow_touch_id_enroll = True options.wda_local_port = get_wda_port() options.simple_is_visible_check = True + # CI simulators are often slow to launch WDA right after boot, so allow retries. + options.wda_launch_timeout = 240000 + options.wda_startup_retries = 3 + options.wda_startup_retry_interval = 20000 if app is not None: options.app = app diff --git a/test/functional/ios/safari_tests.py b/test/functional/ios/safari_tests.py index 21199587..143a7144 100644 --- a/test/functional/ios/safari_tests.py +++ b/test/functional/ios/safari_tests.py @@ -28,7 +28,8 @@ from appium.webdriver.webdriver import WebDriver -@pytest.fixture +# One session for the whole module: creating a session on CI simulators is slow and flaky. +@pytest.fixture(scope='module') def driver() -> Generator['WebDriver', None, None]: """Create and configure Safari driver for testing.""" options = make_options() From efc258a9bfbc688981bf938397149b2514ec58ab Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 17:41:02 +0000 Subject: [PATCH 02/10] ci: remove stray blank line Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BEFRVrKrVcxwbWhhkzY9Wd --- .github/workflows/functional-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index fc7b198d..896a7612 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -373,7 +373,6 @@ jobs: sleep 15 xcrun simctl terminate booted com.apple.mobilesafari || true - - name: Run IOS tests if: matrix.e2e-tests == 'flutter-ios' run: | From d0ba8a9e79b784dd339c91d7fe19e6fd043715f9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 20:31:17 +0000 Subject: [PATCH 03/10] test: retry iOS session creation when WDA fails to start The XCUITest driver ignores wdaStartupRetries when usePreinstalledWDA is set (it starts WDA once), so those capabilities had no effect in CI. Remove them and retry session creation once in the iOS and Flutter fixtures when WDA startup fails. The driver deletes the failed session before raising, so the retry is safe. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BEFRVrKrVcxwbWhhkzY9Wd --- .../flutter_integration/conftest.py | 3 ++- .../flutter_integration/helper/options.py | 2 -- test/functional/ios/helper/options.py | 4 +--- test/functional/ios/safari_tests.py | 3 ++- test/functional/test_helper.py | 21 +++++++++++++++++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/test/functional/flutter_integration/conftest.py b/test/functional/flutter_integration/conftest.py index 011338bb..bfc4be24 100644 --- a/test/functional/flutter_integration/conftest.py +++ b/test/functional/flutter_integration/conftest.py @@ -20,6 +20,7 @@ from appium import webdriver from appium.webdriver.client_config import AppiumClientConfig from appium.webdriver.extensions.flutter_integration.flutter_commands import FlutterCommand +from test.functional.test_helper import create_session_with_wda_retry from test.helpers.constants import SERVER_URL_BASE from .helper.options import make_options @@ -36,7 +37,7 @@ def driver() -> Generator['WebDriver', None, None]: client_config = AppiumClientConfig(remote_server_addr=SERVER_URL_BASE) client_config.timeout = 600 - driver = webdriver.Remote(options=options, client_config=client_config) + driver = create_session_with_wda_retry(lambda: webdriver.Remote(options=options, client_config=client_config)) yield driver diff --git a/test/functional/flutter_integration/helper/options.py b/test/functional/flutter_integration/helper/options.py index 0524a7db..5cad7de3 100644 --- a/test/functional/flutter_integration/helper/options.py +++ b/test/functional/flutter_integration/helper/options.py @@ -60,8 +60,6 @@ def make_options() -> FlutterOptions: 'platformVersion': os.environ['IOS_VERSION'], 'allowTouchIdEnroll': True, 'wdaLaunchTimeout': 240000, - 'wdaStartupRetries': 3, - 'wdaStartupRetryInterval': 20000, 'wdaLocalPort': get_wda_port(), 'eventTimings': True, 'app': os.environ['FLUTTER_IOS_APP'], diff --git a/test/functional/ios/helper/options.py b/test/functional/ios/helper/options.py index 403aefa5..0dcd33fd 100644 --- a/test/functional/ios/helper/options.py +++ b/test/functional/ios/helper/options.py @@ -28,10 +28,8 @@ def make_options(app: str | None = None) -> XCUITestOptions: options.allow_touch_id_enroll = True options.wda_local_port = get_wda_port() options.simple_is_visible_check = True - # CI simulators are often slow to launch WDA right after boot, so allow retries. + # CI simulators are often slow to launch WDA right after boot. options.wda_launch_timeout = 240000 - options.wda_startup_retries = 3 - options.wda_startup_retry_interval = 20000 if app is not None: options.app = app diff --git a/test/functional/ios/safari_tests.py b/test/functional/ios/safari_tests.py index 143a7144..55a27ea1 100644 --- a/test/functional/ios/safari_tests.py +++ b/test/functional/ios/safari_tests.py @@ -20,6 +20,7 @@ from appium import webdriver from appium.webdriver.client_config import AppiumClientConfig +from test.functional.test_helper import create_session_with_wda_retry from test.helpers.constants import SERVER_URL_BASE from .helper.options import make_options @@ -40,7 +41,7 @@ def driver() -> Generator['WebDriver', None, None]: client_config = AppiumClientConfig(remote_server_addr=SERVER_URL_BASE) client_config.timeout = 600 - driver = webdriver.Remote(options=options, client_config=client_config) + driver = create_session_with_wda_retry(lambda: webdriver.Remote(options=options, client_config=client_config)) # Fresh iOS 17.4 simulator may not show up the webview context with "safari" # after a fresh simlator instance creation. diff --git a/test/functional/test_helper.py b/test/functional/test_helper.py index 550c26e4..737908f8 100644 --- a/test/functional/test_helper.py +++ b/test/functional/test_helper.py @@ -6,6 +6,7 @@ from time import sleep from typing import TYPE_CHECKING, Any +from selenium.common.exceptions import WebDriverException from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait @@ -141,3 +142,23 @@ def get_wda_port() -> int: """ worker_info = get_worker_info() return 8100 + (worker_info.worker_number or 0) + + +# Errors the XCUITest driver raises after it has already cleaned up a failed WDA startup, +# so creating a new session afterwards is safe. +_WDA_STARTUP_ERRORS = ('Unable to launch WebDriverAgent', 'Unable to start WebDriverAgent session') + + +def create_session_with_wda_retry(create: Callable[[], 'WebDriver'], interval_sec: float = 10) -> 'WebDriver': + """Create a session, retrying once when WebDriverAgent fails to start. + + The XCUITest driver does not retry WDA startup itself when `usePreinstalledWDA` is set, + and the first launch on a freshly booted CI simulator often fails. + """ + try: + return create() + except WebDriverException as e: + if not any(msg in str(e) for msg in _WDA_STARTUP_ERRORS): + raise + sleep(interval_sec) + return create() From 2a56e4c1bdf12cc01ed12d0e3a878cede0aa2165 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 22:14:12 +0000 Subject: [PATCH 04/10] ci: quote python-version values in functional tests workflow Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BEFRVrKrVcxwbWhhkzY9Wd --- .github/workflows/functional-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index 896a7612..bc26c5d7 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -53,7 +53,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v7 with: - python-version: 3.14 + python-version: '3.14' - name: Set up uv uses: astral-sh/setup-uv@v10.3.1 @@ -203,7 +203,7 @@ jobs: - name: Set up Python 3.14 uses: actions/setup-python@v7 with: - python-version: 3.14 + python-version: '3.14' - name: Set up uv uses: astral-sh/setup-uv@v10.3.1 @@ -285,7 +285,7 @@ jobs: - name: Set up Python 3.14 uses: actions/setup-python@v7 with: - python-version: 3.14 + python-version: '3.14' - name: Set up uv uses: astral-sh/setup-uv@v10.3.1 From 20557b4495cceeb0d24f5acb8b6c5e57bb4e11a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 05:00:14 +0000 Subject: [PATCH 05/10] ci: pin uv in Android job and use read-only token permissions Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BEFRVrKrVcxwbWhhkzY9Wd --- .github/workflows/functional-test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index bc26c5d7..37272bef 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ master ] +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -210,6 +213,7 @@ jobs: with: enable-cache: true python-version: '3.14' + version: ${{ env.UV_VERSION }} - name: Run tests uses: reactivecircus/android-emulator-runner@v2 From 0822e5b40a562bd827890dee3b16f6f61e182134 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 05:07:23 +0000 Subject: [PATCH 06/10] ci: warm up WebDriverAgent with a throwaway Appium session Launching Safari with simctl warms the simulator but not WDA, so the first test still paid for WDA's cold start. Start and quit one Appium session before the iOS and Flutter iOS tests. A failed warm-up is logged and does not fail the step. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BEFRVrKrVcxwbWhhkzY9Wd --- .github/workflows/functional-test.yml | 15 +++++++++ test/functional/ios/helper/warm_up.py | 44 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 test/functional/ios/helper/warm_up.py diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index 37272bef..4d0d835e 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -107,6 +107,13 @@ jobs: sleep 15 xcrun simctl terminate booted com.apple.mobilesafari || true + - name: Warm up WebDriverAgent + # Start and quit one Appium session so WDA's first launch happens here, not in a test. + timeout-minutes: 12 + run: uv run python -m test.functional.ios.helper.warm_up + env: + LOCAL_PREBUILT_WDA: ${{ env.PREBUILT_WDA_PATH }} + - name: Run Tests run: | uv run pytest -v ${{ matrix.test_targets.target}} \ @@ -377,6 +384,14 @@ jobs: sleep 15 xcrun simctl terminate booted com.apple.mobilesafari || true + - name: Warm up WebDriverAgent + if: matrix.e2e-tests == 'flutter-ios' + # Start and quit one Appium session so WDA's first launch happens here, not in a test. + timeout-minutes: 12 + run: uv run python -m test.functional.ios.helper.warm_up + env: + LOCAL_PREBUILT_WDA: ${{ env.PREBUILT_WDA_PATH }} + - name: Run IOS tests if: matrix.e2e-tests == 'flutter-ios' run: | diff --git a/test/functional/ios/helper/warm_up.py b/test/functional/ios/helper/warm_up.py new file mode 100644 index 00000000..bc160c93 --- /dev/null +++ b/test/functional/ios/helper/warm_up.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +# Licensed 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. + +"""Start and quit one Appium session so WebDriverAgent's first, slowest launch happens before the tests. + +Used by CI only. Failures are reported but don't fail the step: the tests retry on their own. +""" + +import sys + +from appium import webdriver +from appium.webdriver.client_config import AppiumClientConfig +from test.functional.test_helper import create_session_with_wda_retry +from test.helpers.constants import SERVER_URL_BASE + +from .options import make_options + + +def main() -> int: + client_config = AppiumClientConfig(remote_server_addr=SERVER_URL_BASE) + client_config.timeout = 300 + try: + driver = create_session_with_wda_retry(lambda: webdriver.Remote(options=make_options(), client_config=client_config)) + except Exception as e: # noqa: BLE001 + print(f'Warm-up session failed: {e}', file=sys.stderr) + return 0 + driver.quit() + print('Warm-up session started and quit successfully') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) From 5627629a3ed49b352302d74d2680a438d469a1f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 05:16:11 +0000 Subject: [PATCH 07/10] ci: don't let a failed WDA warm-up skip the tests Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BEFRVrKrVcxwbWhhkzY9Wd --- .github/workflows/functional-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index 4d0d835e..d3b0bae2 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -110,6 +110,7 @@ jobs: - name: Warm up WebDriverAgent # Start and quit one Appium session so WDA's first launch happens here, not in a test. timeout-minutes: 12 + continue-on-error: true run: uv run python -m test.functional.ios.helper.warm_up env: LOCAL_PREBUILT_WDA: ${{ env.PREBUILT_WDA_PATH }} @@ -388,6 +389,7 @@ jobs: if: matrix.e2e-tests == 'flutter-ios' # Start and quit one Appium session so WDA's first launch happens here, not in a test. timeout-minutes: 12 + continue-on-error: true run: uv run python -m test.functional.ios.helper.warm_up env: LOCAL_PREBUILT_WDA: ${{ env.PREBUILT_WDA_PATH }} From d188bdf3241b59e8a7665d4a59ca2594fa88275d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 21:22:48 +0000 Subject: [PATCH 08/10] test: keep per-test Safari sessions and give the WDA warm-up the full timeout A first session that hangs past the client's 600 s read timeout raises urllib3's ReadTimeoutError, which create_session_with_wda_retry does not retry. With a module-scoped fixture, pytest then re-raises that setup error for every Safari test; with one session per test, the next test gets a fresh attempt (as happened in upstream CI). Raise the warm-up session's timeout from 300 s to 600 s to match the tests. Giving up earlier leaves the server still creating the session, so the first test's request queues behind it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012HCUdSWNJMojXCjBoxhg41 --- test/functional/ios/helper/warm_up.py | 4 +++- test/functional/ios/safari_tests.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/functional/ios/helper/warm_up.py b/test/functional/ios/helper/warm_up.py index bc160c93..d5a4e2fc 100644 --- a/test/functional/ios/helper/warm_up.py +++ b/test/functional/ios/helper/warm_up.py @@ -29,7 +29,9 @@ def main() -> int: client_config = AppiumClientConfig(remote_server_addr=SERVER_URL_BASE) - client_config.timeout = 300 + # Match the tests' timeout: giving up earlier would leave the server still creating this + # session, so the first test's session request would queue behind it. + client_config.timeout = 600 try: driver = create_session_with_wda_retry(lambda: webdriver.Remote(options=make_options(), client_config=client_config)) except Exception as e: # noqa: BLE001 diff --git a/test/functional/ios/safari_tests.py b/test/functional/ios/safari_tests.py index 55a27ea1..db1ba721 100644 --- a/test/functional/ios/safari_tests.py +++ b/test/functional/ios/safari_tests.py @@ -29,8 +29,8 @@ from appium.webdriver.webdriver import WebDriver -# One session for the whole module: creating a session on CI simulators is slow and flaky. -@pytest.fixture(scope='module') +# One session per test: if a session fails to start, the next test still gets a fresh attempt. +@pytest.fixture def driver() -> Generator['WebDriver', None, None]: """Create and configure Safari driver for testing.""" options = make_options() From 3c9ef0ba73a3f418a41c53528ecde40f43723089 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 21:39:11 +0000 Subject: [PATCH 09/10] test: keep the WDA warm-up non-fatal when quitting the session fails The warm-up promises never to fail its step, but an error from driver.quit(), such as a session that is already gone, would exit non-zero. Catch and report it like a failed session start. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012HCUdSWNJMojXCjBoxhg41 --- test/functional/ios/helper/warm_up.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/functional/ios/helper/warm_up.py b/test/functional/ios/helper/warm_up.py index d5a4e2fc..edf981d8 100644 --- a/test/functional/ios/helper/warm_up.py +++ b/test/functional/ios/helper/warm_up.py @@ -37,7 +37,11 @@ def main() -> int: except Exception as e: # noqa: BLE001 print(f'Warm-up session failed: {e}', file=sys.stderr) return 0 - driver.quit() + try: + driver.quit() + except Exception as e: # noqa: BLE001 + print(f'Warm-up session started but failed to quit: {e}', file=sys.stderr) + return 0 print('Warm-up session started and quit successfully') return 0 From ab37714288e076bcba4b4063d843d2245877db98 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Nov 2026 21:59:08 +0000 Subject: [PATCH 10/10] test: make the WDA warm-up a single attempt so it fits its step timeout With the WDA retry, the warm-up could take 600 s + 10 s + 600 s, but its step is killed after 12 minutes. A kill mid-request leaves the server still creating the session, so the first test would queue behind it. One attempt always fits, and the test fixtures retry on their own. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012HCUdSWNJMojXCjBoxhg41 --- test/functional/ios/helper/warm_up.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/ios/helper/warm_up.py b/test/functional/ios/helper/warm_up.py index edf981d8..2c25e3d2 100644 --- a/test/functional/ios/helper/warm_up.py +++ b/test/functional/ios/helper/warm_up.py @@ -15,13 +15,13 @@ """Start and quit one Appium session so WebDriverAgent's first, slowest launch happens before the tests. Used by CI only. Failures are reported but don't fail the step: the tests retry on their own. +It makes a single attempt, so that it always finishes within the step's 12-minute timeout. """ import sys from appium import webdriver from appium.webdriver.client_config import AppiumClientConfig -from test.functional.test_helper import create_session_with_wda_retry from test.helpers.constants import SERVER_URL_BASE from .options import make_options @@ -33,7 +33,7 @@ def main() -> int: # session, so the first test's session request would queue behind it. client_config.timeout = 600 try: - driver = create_session_with_wda_retry(lambda: webdriver.Remote(options=make_options(), client_config=client_config)) + driver = webdriver.Remote(options=make_options(), client_config=client_config) except Exception as e: # noqa: BLE001 print(f'Warm-up session failed: {e}', file=sys.stderr) return 0