From 6d6618a49989fdf22b53d9e6d73b213eedca711f Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Oct 2025 09:20:29 +0200 Subject: [PATCH 1/9] test: Update flutter tests to canonic pytest format --- .../flutter_integration/commands_test.py | 192 ++++++++++-------- .../flutter_integration/finder_test.py | 85 ++++---- .../helper/desired_capabilities.py | 47 ----- .../flutter_integration/helper/options.py | 97 +++++++++ .../flutter_integration/helper/test_helper.py | 42 ++-- test/functional/ios/helper/options.py | 52 +---- test/functional/test_helper.py | 47 ++++- 7 files changed, 320 insertions(+), 242 deletions(-) delete mode 100644 test/functional/flutter_integration/helper/desired_capabilities.py create mode 100644 test/functional/flutter_integration/helper/options.py diff --git a/test/functional/flutter_integration/commands_test.py b/test/functional/flutter_integration/commands_test.py index f256222e0..7afe4156e 100644 --- a/test/functional/flutter_integration/commands_test.py +++ b/test/functional/flutter_integration/commands_test.py @@ -13,122 +13,136 @@ # limitations under the License. import os +from typing import TYPE_CHECKING from appium.webdriver.common.appiumby import AppiumBy from appium.webdriver.extensions.flutter_integration.flutter_finder import FlutterFinder from appium.webdriver.extensions.flutter_integration.scroll_directions import ScrollDirection -from test.functional.flutter_integration.helper.test_helper import BaseTestCase +if TYPE_CHECKING: + from appium.webdriver.extensions.flutter_integration.flutter_commands import FlutterCommand + from appium.webdriver.webdriver import WebDriver -class TestFlutterCommands(BaseTestCase): - def test_wait_command(self) -> None: - self.__open_screen('Lazy Loading') - message_field_finder = FlutterFinder.by_key('message_field') - toggle_button_finder = FlutterFinder.by_key('toggle_button') +def _open_screen(driver: 'WebDriver', flutter_command: 'FlutterCommand', screen_name: str) -> None: + """Helper function to open a specific screen in the Flutter app.""" + driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Login').click() + element = flutter_command.scroll_till_visible(FlutterFinder.by_text(screen_name)) + element.click() - message_field = self.driver.find_element(*message_field_finder.as_args()) - toggle_button = self.driver.find_element(*toggle_button_finder.as_args()) - assert message_field.is_displayed() == True - assert message_field.text == 'Hello world' - toggle_button.click() - self.flutter_command.wait_for_invisible(message_field_finder) - assert len(self.driver.find_elements(*message_field_finder.as_args())) == 0 +def test_wait_command(driver: 'WebDriver', flutter_command: 'FlutterCommand') -> None: + """Test Flutter wait commands for element visibility.""" + _open_screen(driver, flutter_command, 'Lazy Loading') - toggle_button.click() - self.flutter_command.wait_for_visible(message_field) - assert len(self.driver.find_elements(*message_field_finder.as_args())) == 1 + message_field_finder = FlutterFinder.by_key('message_field') + toggle_button_finder = FlutterFinder.by_key('toggle_button') - def test_scroll_till_visible_command(self) -> None: - self.__open_screen('Vertical Swiping') + message_field = driver.find_element(*message_field_finder.as_args()) + toggle_button = driver.find_element(*toggle_button_finder.as_args()) + assert message_field.is_displayed() == True + assert message_field.text == 'Hello world' - java_text_finder = FlutterFinder.by_text('Java') - protractor_text_finder = FlutterFinder.by_text('Protractor') + toggle_button.click() + flutter_command.wait_for_invisible(message_field_finder) + assert len(driver.find_elements(*message_field_finder.as_args())) == 0 - first_element = self.flutter_command.scroll_till_visible(java_text_finder) - assert first_element.get_attribute('displayed') == 'true' + toggle_button.click() + flutter_command.wait_for_visible(message_field) + assert len(driver.find_elements(*message_field_finder.as_args())) == 1 - second_element = self.flutter_command.scroll_till_visible(protractor_text_finder) - assert second_element.get_attribute('displayed') == 'true' - assert first_element.get_attribute('displayed') == 'false' - first_element = self.flutter_command.scroll_till_visible(java_text_finder, ScrollDirection.UP) - assert second_element.get_attribute('displayed') == 'false' - assert first_element.get_attribute('displayed') == 'true' +def test_scroll_till_visible_command(driver: 'WebDriver', flutter_command: 'FlutterCommand') -> None: + """Test Flutter scroll till visible command.""" + _open_screen(driver, flutter_command, 'Vertical Swiping') - def test_scroll_till_visible_with_scroll_params_command(self) -> None: - self.__open_screen('Vertical Swiping') + java_text_finder = FlutterFinder.by_text('Java') + protractor_text_finder = FlutterFinder.by_text('Protractor') - scroll_params = { - 'scrollView': FlutterFinder.by_type('Scrollable').to_dict(), - 'delta': 30, - 'maxScrolls': 30, - 'settleBetweenScrollsTimeout': 5000, - 'dragDuration': 35, - } - first_element = self.flutter_command.scroll_till_visible( - FlutterFinder.by_text('Playwright'), scroll_direction=ScrollDirection.DOWN, **scroll_params - ) - assert first_element.get_attribute('displayed') == 'true' + first_element = flutter_command.scroll_till_visible(java_text_finder) + assert first_element.get_attribute('displayed') == 'true' - def test_double_click_command(self) -> None: - self.__open_screen('Double Tap') + second_element = flutter_command.scroll_till_visible(protractor_text_finder) + assert second_element.get_attribute('displayed') == 'true' + assert first_element.get_attribute('displayed') == 'false' - double_tap_button = self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'double_tap_button').find_element( - AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Double Tap' - ) - assert double_tap_button.text == 'Double Tap' + first_element = flutter_command.scroll_till_visible(java_text_finder, ScrollDirection.UP) + assert second_element.get_attribute('displayed') == 'false' + assert first_element.get_attribute('displayed') == 'true' - self.flutter_command.perform_double_click(double_tap_button) - assert ( - self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT_CONTAINING, 'Successful').text == 'Double Tap Successful' - ) - self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Ok').click() - self.flutter_command.perform_double_click(double_tap_button, (10, 2)) - assert ( - self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT_CONTAINING, 'Successful').text == 'Double Tap Successful' - ) +def test_scroll_till_visible_with_scroll_params_command(driver: 'WebDriver', flutter_command: 'FlutterCommand') -> None: + """Test Flutter scroll till visible command with custom scroll parameters.""" + _open_screen(driver, flutter_command, 'Vertical Swiping') - self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Ok').click() + scroll_params = { + 'scrollView': FlutterFinder.by_type('Scrollable').to_dict(), + 'delta': 30, + 'maxScrolls': 30, + 'settleBetweenScrollsTimeout': 5000, + 'dragDuration': 35, + } + first_element = flutter_command.scroll_till_visible( + FlutterFinder.by_text('Playwright'), scroll_direction=ScrollDirection.DOWN, **scroll_params + ) + assert first_element.get_attribute('displayed') == 'true' - def test_long_press_command(self) -> None: - self.__open_screen('Long Press') - long_press_button = self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'long_press_button') - self.flutter_command.perform_long_press(long_press_button) +def test_double_click_command(driver: 'WebDriver', flutter_command: 'FlutterCommand') -> None: + """Test Flutter double click command.""" + _open_screen(driver, flutter_command, 'Double Tap') - success_pop_up = self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'It was a long press') - assert success_pop_up.text == 'It was a long press' - assert success_pop_up.is_displayed() == True + double_tap_button = driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'double_tap_button').find_element( + AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Double Tap' + ) + assert double_tap_button.text == 'Double Tap' - def test_drag_and_drop_command(self) -> None: - self.__open_screen('Drag & Drop') + flutter_command.perform_double_click(double_tap_button) + assert driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT_CONTAINING, 'Successful').text == 'Double Tap Successful' - drag_element = self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'drag_me') - drop_element = self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'drop_zone') - self.flutter_command.perform_drag_and_drop(drag_element, drop_element) - assert self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'The box is dropped').is_displayed() == True + driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Ok').click() + flutter_command.perform_double_click(double_tap_button, (10, 2)) + assert driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT_CONTAINING, 'Successful').text == 'Double Tap Successful' - def test_camera_mocking(self) -> None: - self.__open_screen('Image Picker') + driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Ok').click() - success_qr_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'file', 'success_qr.png') - second_qr_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'file', 'second_qr.png') - image_id = self.flutter_command.inject_mock_image(success_qr_file_path) - self.flutter_command.inject_mock_image(second_qr_file_path) - self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'capture_image').click() - self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'PICK').click() - assert self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'SecondInjectedImage').is_displayed() == True - - self.flutter_command.activate_injected_image(image_id) - self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'capture_image').click() - self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'PICK').click() - assert self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Success!').is_displayed() == True - - def __open_screen(self, screen_name: str) -> None: - self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Login').click() - element = self.flutter_command.scroll_till_visible(FlutterFinder.by_text(screen_name)) - element.click() +def test_long_press_command(driver: 'WebDriver', flutter_command: 'FlutterCommand') -> None: + """Test Flutter long press command.""" + _open_screen(driver, flutter_command, 'Long Press') + + long_press_button = driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'long_press_button') + flutter_command.perform_long_press(long_press_button) + + success_pop_up = driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'It was a long press') + assert success_pop_up.text == 'It was a long press' + assert success_pop_up.is_displayed() == True + + +def test_drag_and_drop_command(driver: 'WebDriver', flutter_command: 'FlutterCommand') -> None: + """Test Flutter drag and drop command.""" + _open_screen(driver, flutter_command, 'Drag & Drop') + + drag_element = driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'drag_me') + drop_element = driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'drop_zone') + flutter_command.perform_drag_and_drop(drag_element, drop_element) + assert driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'The box is dropped').is_displayed() == True + + +def test_camera_mocking(driver: 'WebDriver', flutter_command: 'FlutterCommand') -> None: + """Test Flutter camera mocking functionality.""" + _open_screen(driver, flutter_command, 'Image Picker') + + success_qr_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'file', 'success_qr.png') + second_qr_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'file', 'second_qr.png') + + image_id = flutter_command.inject_mock_image(success_qr_file_path) + flutter_command.inject_mock_image(second_qr_file_path) + driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'capture_image').click() + driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'PICK').click() + assert driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'SecondInjectedImage').is_displayed() == True + + flutter_command.activate_injected_image(image_id) + driver.find_element(AppiumBy.FLUTTER_INTEGRATION_KEY, 'capture_image').click() + driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'PICK').click() + assert driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Success!').is_displayed() == True diff --git a/test/functional/flutter_integration/finder_test.py b/test/functional/flutter_integration/finder_test.py index d9bc3f56b..5357fc6f1 100644 --- a/test/functional/flutter_integration/finder_test.py +++ b/test/functional/flutter_integration/finder_test.py @@ -12,45 +12,58 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import TYPE_CHECKING + from appium.webdriver.common.appiumby import AppiumBy from appium.webdriver.extensions.flutter_integration.flutter_finder import FlutterFinder -from test.functional.flutter_integration.helper.test_helper import BaseTestCase + +if TYPE_CHECKING: + from appium.webdriver.extensions.flutter_integration.flutter_commands import FlutterCommand + from appium.webdriver.webdriver import WebDriver LOGIN_BUTTON_FINDER = FlutterFinder.by_text('Login') -class TestFlutterFinders(BaseTestCase): - def test_by_flutter_key(self) -> None: - user_name_field_finder = FlutterFinder.by_key('username_text_field') - user_name_field = self.driver.find_element(*user_name_field_finder.as_args()) - assert user_name_field.text == 'admin' - - user_name_field.clear() - user_name_field = self.driver.find_element(*user_name_field_finder.as_args()).send_keys('admin123') - assert user_name_field.text == 'admin123' - - def test_by_flutter_type(self) -> None: - login_button = self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TYPE, 'ElevatedButton') - assert login_button.find_element(AppiumBy.FLUTTER_INTEGRATION_TYPE, 'Text').text == 'Login' - - def test_by_flutter_text(self) -> None: - login_button = self.driver.find_element(*LOGIN_BUTTON_FINDER.as_args()) - assert login_button.text == 'Login' - - login_button.click() - slider = self.driver.find_elements(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Slider') - assert len(slider) == 1 - - def test_by_flutter_text_containing(self) -> None: - login_button = self.driver.find_element(*LOGIN_BUTTON_FINDER.as_args()) - login_button.click() - vertical_swipe_label = self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT_CONTAINING, 'Vertical') - assert vertical_swipe_label.text == 'Vertical Swiping' - - def test_by_flutter_semantics_label(self) -> None: - login_button = self.driver.find_element(*LOGIN_BUTTON_FINDER.as_args()) - login_button.click() - element = self.flutter_command.scroll_till_visible(FlutterFinder.by_text('Lazy Loading')) - element.click() - message_field = self.driver.find_element(AppiumBy.FLUTTER_INTEGRATION_SEMANTICS_LABEL, 'message_field') - assert message_field.text == 'Hello world' +def test_by_flutter_key(driver: 'WebDriver') -> None: + """Test finding elements by Flutter key.""" + user_name_field_finder = FlutterFinder.by_key('username_text_field') + user_name_field = driver.find_element(*user_name_field_finder.as_args()) + assert user_name_field.text == 'admin' + + user_name_field.clear() + user_name_field = driver.find_element(*user_name_field_finder.as_args()).send_keys('admin123') + assert user_name_field.text == 'admin123' + + +def test_by_flutter_type(driver: 'WebDriver') -> None: + """Test finding elements by Flutter type.""" + login_button = driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TYPE, 'ElevatedButton') + assert login_button.find_element(AppiumBy.FLUTTER_INTEGRATION_TYPE, 'Text').text == 'Login' + + +def test_by_flutter_text(driver: 'WebDriver') -> None: + """Test finding elements by Flutter text.""" + login_button = driver.find_element(*LOGIN_BUTTON_FINDER.as_args()) + assert login_button.text == 'Login' + + login_button.click() + slider = driver.find_elements(AppiumBy.FLUTTER_INTEGRATION_TEXT, 'Slider') + assert len(slider) == 1 + + +def test_by_flutter_text_containing(driver: 'WebDriver') -> None: + """Test finding elements by Flutter text containing.""" + login_button = driver.find_element(*LOGIN_BUTTON_FINDER.as_args()) + login_button.click() + vertical_swipe_label = driver.find_element(AppiumBy.FLUTTER_INTEGRATION_TEXT_CONTAINING, 'Vertical') + assert vertical_swipe_label.text == 'Vertical Swiping' + + +def test_by_flutter_semantics_label(driver: 'WebDriver', flutter_command: 'FlutterCommand') -> None: + """Test finding elements by Flutter semantics label.""" + login_button = driver.find_element(*LOGIN_BUTTON_FINDER.as_args()) + login_button.click() + element = flutter_command.scroll_till_visible(FlutterFinder.by_text('Lazy Loading')) + element.click() + message_field = driver.find_element(AppiumBy.FLUTTER_INTEGRATION_SEMANTICS_LABEL, 'message_field') + assert message_field.text == 'Hello world' diff --git a/test/functional/flutter_integration/helper/desired_capabilities.py b/test/functional/flutter_integration/helper/desired_capabilities.py deleted file mode 100644 index b03053418..000000000 --- a/test/functional/flutter_integration/helper/desired_capabilities.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/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. - -import os -from typing import Any, Dict - - -def get_desired_capabilities(platform_name: str) -> Dict[str, Any]: - desired_caps: Dict[str, Any] = {} - if platform_name == 'android': - desired_caps.update( - { - 'platformName': 'Android', - 'deviceName': 'Android Emulator', - 'newCommandTimeout': 120, - 'uiautomator2ServerInstallTimeout': 120000, - 'adbExecTimeout': 120000, - 'app': os.getenv('FLUTTER_ANDROID_APP'), - 'autoGrantPermissions': True, - } - ) - else: - desired_caps.update( - { - 'deviceName': os.getenv('IPHONE_MODEL'), - 'platformName': 'iOS', - 'platformVersion': os.getenv('IOS_VERSION'), - 'allowTouchIdEnroll': True, - 'wdaLaunchTimeout': 240000, - 'wdaLocalPort': 8100, - 'eventTimings': True, - 'app': os.getenv('FLUTTER_IOS_APP'), - } - ) - - return desired_caps diff --git a/test/functional/flutter_integration/helper/options.py b/test/functional/flutter_integration/helper/options.py new file mode 100644 index 000000000..a7a6facd9 --- /dev/null +++ b/test/functional/flutter_integration/helper/options.py @@ -0,0 +1,97 @@ +#!/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. + +import os +from typing import Optional + +from appium.options.flutter_integration.base import FlutterOptions +from test.functional.test_helper import get_wda_port, get_worker_info + + +def PATH(p: str) -> str: + """Get the absolute path of a file relative to the folder where this file is located.""" + return os.path.abspath(os.path.join(os.path.dirname(__file__), p)) + + +def is_platform_android() -> bool: + """Check if the current platform is Android.""" + return os.getenv('PLATFORM', 'android').lower() == 'android' + + +def get_flutter_system_port() -> int: + """ + Get a unique Flutter system port for the current worker. + Uses base port 9999 and increments by worker number. + """ + worker_info = get_worker_info() + return 9999 + (worker_info.worker_number or 0) + + +def make_options(app: Optional[str] = None) -> FlutterOptions: + """Get Flutter options configured for testing with parallel execution support.""" + options = FlutterOptions() + + # Set Flutter-specific capabilities + options.flutter_system_port = get_flutter_system_port() + options.flutter_enable_mock_camera = True + options.flutter_element_wait_timeout = 10000 + options.flutter_server_launch_timeout = 120000 + + # Set platform-specific capabilities + if is_platform_android(): + options.platform_name = 'Android' + options.device_name = device_name() + options.new_command_timeout = 120 + options.uiautomator2_server_install_timeout = 120000 + options.adb_exec_timeout = 120000 + options.auto_grant_permissions = True + + if app is not None: + options.app = PATH(os.path.join('..', '..', '..', 'apps', app)) + elif os.getenv('FLUTTER_ANDROID_APP'): + options.app = os.getenv('FLUTTER_ANDROID_APP') + else: # iOS + options.platform_name = 'iOS' + options.device_name = device_name() + options.platform_version = os.getenv('IOS_VERSION') or '17.4' + options.allow_touch_id_enroll = True + options.wda_launch_timeout = 240000 + options.wda_local_port = get_wda_port() + options.event_timings = True + + if app is not None: + options.app = PATH(os.path.join('..', '..', '..', 'apps', app)) + elif os.getenv('FLUTTER_IOS_APP'): + options.app = os.getenv('FLUTTER_IOS_APP') + + return options + + +def device_name() -> str: + """ + Get a unique device name for the current worker. + Uses the base device name and appends the port number for uniqueness. + """ + if is_platform_android(): + prefix = 'Android Emulator' + else: + prefix = os.getenv('IPHONE_MODEL') or 'iPhone 15 Plus' + + worker_info = get_worker_info() + + if worker_info.is_parallel: + port = get_flutter_system_port() if is_platform_android() else get_wda_port() + return f'{prefix} - {port}' + + return prefix diff --git a/test/functional/flutter_integration/helper/test_helper.py b/test/functional/flutter_integration/helper/test_helper.py index 19c669754..85dd66a9c 100644 --- a/test/functional/flutter_integration/helper/test_helper.py +++ b/test/functional/flutter_integration/helper/test_helper.py @@ -12,37 +12,37 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +from typing import TYPE_CHECKING, Generator + +import pytest from appium import webdriver -from appium.options.flutter_integration.base import FlutterOptions from appium.webdriver.client_config import AppiumClientConfig from appium.webdriver.extensions.flutter_integration.flutter_commands import FlutterCommand from test.helpers.constants import SERVER_URL_BASE -from . import desired_capabilities +from .options import make_options + +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver + +@pytest.fixture +def driver() -> Generator['WebDriver', None, None]: + """Create and configure Flutter driver for testing.""" + options = make_options() -class BaseTestCase(object): - def setup_method(self) -> None: - platform_name = os.getenv('PLATFORM', 'android').lower() + client_config = AppiumClientConfig(remote_server_addr=SERVER_URL_BASE) + client_config.timeout = 600 - # set flutter options - flutterOptions = FlutterOptions() - flutterOptions.flutter_system_port = 9999 - flutterOptions.flutter_enable_mock_camera = True - flutterOptions.flutter_element_wait_timeout = 10000 - flutterOptions.flutter_server_launch_timeout = 120000 + driver = webdriver.Remote(options=options, client_config=client_config) - desired_caps = desired_capabilities.get_desired_capabilities(platform_name) + yield driver - client_config = AppiumClientConfig(remote_server_addr=SERVER_URL_BASE) - client_config.timeout = 600 + driver.quit() - self.driver = webdriver.Remote(options=flutterOptions.load_capabilities(desired_caps), client_config=client_config) - self.flutter_command = FlutterCommand(self.driver) - def teardown_method(self) -> None: # type: ignore - if not hasattr(self, 'driver'): - return - self.driver.quit() +@pytest.fixture +def flutter_command(driver: 'WebDriver') -> FlutterCommand: + """Create FlutterCommand instance for the driver.""" + return FlutterCommand(driver) diff --git a/test/functional/ios/helper/options.py b/test/functional/ios/helper/options.py index c3a46e1c5..943e93c00 100644 --- a/test/functional/ios/helper/options.py +++ b/test/functional/ios/helper/options.py @@ -13,10 +13,10 @@ # limitations under the License. import os -from dataclasses import dataclass from typing import Optional from appium.options.ios import XCUITestOptions +from test.functional.test_helper import get_wda_port, get_worker_info def PATH(p: str) -> str: @@ -32,7 +32,7 @@ def make_options(app: Optional[str] = None) -> XCUITestOptions: options.device_name = iphone_device_name() options.platform_version = os.getenv('IOS_VERSION') or '17.4' options.allow_touch_id_enroll = True - options.wda_local_port = wda_port() + options.wda_local_port = get_wda_port() options.simple_is_visible_check = True if app is not None: @@ -46,60 +46,16 @@ def make_options(app: Optional[str] = None) -> XCUITestOptions: return options -@dataclass -class WorkerInfo: - """Information about the current test worker in parallel execution.""" - - worker_number: Optional[int] - total_workers: Optional[int] - - @property - def is_parallel(self) -> bool: - """Check if running in parallel mode.""" - return self.worker_number is not None and self.total_workers is not None - - -def _get_worker_info() -> WorkerInfo: - """ - Get current worker number and total worker count from pytest-xdist environment variables. - - Returns: - WorkerInfo: Worker information or None values if not running in parallel - """ - worker_number = os.getenv('PYTEST_XDIST_WORKER') - worker_count = os.getenv('PYTEST_XDIST_WORKER_COUNT') - - if worker_number and worker_count: - # Extract number from worker string like 'gw0', 'gw1', etc. - try: - worker_num = int(worker_number.replace('gw', '')) - total_workers = int(worker_count) - return WorkerInfo(worker_number=worker_num, total_workers=total_workers) - except (ValueError, AttributeError): - pass - - return WorkerInfo(worker_number=None, total_workers=None) - - -def wda_port() -> int: - """ - Get a unique WDA port for the current worker. - Uses base port 8100 and increments by worker number. - """ - worker_info = _get_worker_info() - return 8100 + (worker_info.worker_number or 0) - - def iphone_device_name() -> str: """ Get a unique device name for the current worker. Uses the base device name and appends the port number for uniqueness. """ prefix = os.getenv('IPHONE_MODEL') or 'iPhone 15 Plus' - worker_info = _get_worker_info() + worker_info = get_worker_info() if worker_info.is_parallel: - port = wda_port() + port = get_wda_port() return f'{prefix} - {port}' return prefix diff --git a/test/functional/test_helper.py b/test/functional/test_helper.py index 817f833ea..90500ea0a 100644 --- a/test/functional/test_helper.py +++ b/test/functional/test_helper.py @@ -1,8 +1,9 @@ import os import socket import time +from dataclasses import dataclass from time import sleep -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any, Callable, Optional from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait @@ -95,3 +96,47 @@ def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout_sec: The found WebElement """ return WebDriverWait(driver, timeout_sec).until(EC.presence_of_element_located((locator, value))) + + +@dataclass +class WorkerInfo: + """Information about the current test worker in parallel execution.""" + + worker_number: Optional[int] + total_workers: Optional[int] + + @property + def is_parallel(self) -> bool: + """Check if running in parallel mode.""" + return self.worker_number is not None and self.total_workers is not None + + +def get_worker_info() -> WorkerInfo: + """ + Get current worker number and total worker count from pytest-xdist environment variables. + + Returns: + WorkerInfo: Worker information or None values if not running in parallel + """ + worker_number = os.getenv('PYTEST_XDIST_WORKER') + worker_count = os.getenv('PYTEST_XDIST_WORKER_COUNT') + + if worker_number and worker_count: + # Extract number from worker string like 'gw0', 'gw1', etc. + try: + worker_num = int(worker_number.replace('gw', '')) + total_workers = int(worker_count) + return WorkerInfo(worker_number=worker_num, total_workers=total_workers) + except (ValueError, AttributeError): + pass + + return WorkerInfo(worker_number=None, total_workers=None) + + +def get_wda_port() -> int: + """ + Get a unique WDA port for the current worker. + Uses base port 8100 and increments by worker number. + """ + worker_info = get_worker_info() + return 8100 + (worker_info.worker_number or 0) From 1d497dea4203803250823e849589b7e68333778a Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Oct 2025 09:26:38 +0200 Subject: [PATCH 2/9] use conftest --- .../flutter_integration/{helper/test_helper.py => conftest.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/functional/flutter_integration/{helper/test_helper.py => conftest.py} (97%) diff --git a/test/functional/flutter_integration/helper/test_helper.py b/test/functional/flutter_integration/conftest.py similarity index 97% rename from test/functional/flutter_integration/helper/test_helper.py rename to test/functional/flutter_integration/conftest.py index 85dd66a9c..128312e83 100644 --- a/test/functional/flutter_integration/helper/test_helper.py +++ b/test/functional/flutter_integration/conftest.py @@ -21,7 +21,7 @@ from appium.webdriver.extensions.flutter_integration.flutter_commands import FlutterCommand from test.helpers.constants import SERVER_URL_BASE -from .options import make_options +from .helper.options import make_options if TYPE_CHECKING: from appium.webdriver.webdriver import WebDriver From 18e33d977ee7a4c943b04e0e44f1d1a9d1f28e73 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Oct 2025 09:58:27 +0200 Subject: [PATCH 3/9] tune caps --- .../flutter_integration/helper/options.py | 56 +++++++++---------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/test/functional/flutter_integration/helper/options.py b/test/functional/flutter_integration/helper/options.py index a7a6facd9..e03eb966c 100644 --- a/test/functional/flutter_integration/helper/options.py +++ b/test/functional/flutter_integration/helper/options.py @@ -13,7 +13,7 @@ # limitations under the License. import os -from typing import Optional +from typing import Any, Dict from appium.options.flutter_integration.base import FlutterOptions from test.functional.test_helper import get_wda_port, get_worker_info @@ -38,7 +38,7 @@ def get_flutter_system_port() -> int: return 9999 + (worker_info.worker_number or 0) -def make_options(app: Optional[str] = None) -> FlutterOptions: +def make_options() -> FlutterOptions: """Get Flutter options configured for testing with parallel execution support.""" options = FlutterOptions() @@ -48,34 +48,30 @@ def make_options(app: Optional[str] = None) -> FlutterOptions: options.flutter_element_wait_timeout = 10000 options.flutter_server_launch_timeout = 120000 - # Set platform-specific capabilities - if is_platform_android(): - options.platform_name = 'Android' - options.device_name = device_name() - options.new_command_timeout = 120 - options.uiautomator2_server_install_timeout = 120000 - options.adb_exec_timeout = 120000 - options.auto_grant_permissions = True - - if app is not None: - options.app = PATH(os.path.join('..', '..', '..', 'apps', app)) - elif os.getenv('FLUTTER_ANDROID_APP'): - options.app = os.getenv('FLUTTER_ANDROID_APP') - else: # iOS - options.platform_name = 'iOS' - options.device_name = device_name() - options.platform_version = os.getenv('IOS_VERSION') or '17.4' - options.allow_touch_id_enroll = True - options.wda_launch_timeout = 240000 - options.wda_local_port = get_wda_port() - options.event_timings = True - - if app is not None: - options.app = PATH(os.path.join('..', '..', '..', 'apps', app)) - elif os.getenv('FLUTTER_IOS_APP'): - options.app = os.getenv('FLUTTER_IOS_APP') - - return options + caps: Dict[str, Any] = ( + { + 'platformName': 'Android', + 'deviceName': device_name(), + 'newCommandTimeout': 120, + 'uiautomator2ServerInstallTimeout': 120000, + 'adbExecTimeout': 120000, + 'app': os.getenv('FLUTTER_ANDROID_APP'), + 'autoGrantPermissions': True, + } + if is_platform_android() + else { + 'deviceName': device_name(), + 'platformName': 'iOS', + 'platformVersion': os.getenv('IOS_VERSION'), + 'allowTouchIdEnroll': True, + 'wdaLaunchTimeout': 240000, + 'wdaLocalPort': 8100, + 'eventTimings': True, + 'app': os.getenv('FLUTTER_IOS_APP'), + } + ) + + return options.load_capabilities(caps) def device_name() -> str: From f100ad1d2e483c6028e05831861737b025eaffcc Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Oct 2025 09:59:55 +0200 Subject: [PATCH 4/9] use dynamic port --- test/functional/flutter_integration/helper/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/flutter_integration/helper/options.py b/test/functional/flutter_integration/helper/options.py index e03eb966c..c704fcf3d 100644 --- a/test/functional/flutter_integration/helper/options.py +++ b/test/functional/flutter_integration/helper/options.py @@ -65,7 +65,7 @@ def make_options() -> FlutterOptions: 'platformVersion': os.getenv('IOS_VERSION'), 'allowTouchIdEnroll': True, 'wdaLaunchTimeout': 240000, - 'wdaLocalPort': 8100, + 'wdaLocalPort': get_wda_port(), 'eventTimings': True, 'app': os.getenv('FLUTTER_IOS_APP'), } From 3383c5d0bb208fa8f171410fee999b58f17202de Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Oct 2025 10:25:46 +0200 Subject: [PATCH 5/9] tune ios tests --- .github/workflows/functional-test.yml | 10 ++++++--- .../flutter_integration/helper/options.py | 21 +++++++++++-------- test/functional/ios/helper/options.py | 3 +-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index abf6e7ad6..ae8e1fa48 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -13,6 +13,7 @@ concurrency: env: APPIUM_TEST_SERVER_PORT: '4723' APPIUM_TEST_SERVER_HOST: '127.0.0.1' + PYTHONUNBUFFERED: 1 jobs: ios_test: @@ -239,6 +240,7 @@ jobs: IPHONE_MODEL: iPhone 16 FLUTTER_ANDROID_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/app-debug.apk" FLUTTER_IOS_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/ios.zip" + PREBUILT_WDA_PATH: ${{ github.workspace }}/wda/WebDriverAgentRunner-Runner.app steps: @@ -300,7 +302,7 @@ jobs: api-level: ${{ env.API_LEVEL }} script: | make install-uv - uv run pytest test/functional/flutter_integration/*_test.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html + uv run pytest -v test/functional/flutter_integration/*_test.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html target: default disable-spellchecker: true disable-animations: true @@ -332,8 +334,8 @@ jobs: run: | npm install --location=global appium appium driver install xcuitest + appium driver run xcuitest download-wda-sim --platform=ios --outdir=$(dirname "$PREBUILT_WDA_PATH") appium driver install appium-flutter-integration-driver --source npm - appium driver run xcuitest build-wda - name: Start Appium server for iOS if: matrix.e2e-tests == 'flutter-ios' @@ -349,9 +351,11 @@ jobs: run: | make install-uv export PLATFORM=ios - uv run pytest test/functional/flutter_integration/*_test.py \ + uv run pytest -v test/functional/flutter_integration/*_test.py \ --doctest-modules \ --junitxml=junit/test-results.xml \ --cov=com \ --cov-report=xml \ --cov-report=html + env: + LOCAL_PREBUILT_WDA: ${{ env.PREBUILT_WDA_PATH }} diff --git a/test/functional/flutter_integration/helper/options.py b/test/functional/flutter_integration/helper/options.py index c704fcf3d..0a44f372b 100644 --- a/test/functional/flutter_integration/helper/options.py +++ b/test/functional/flutter_integration/helper/options.py @@ -19,11 +19,6 @@ from test.functional.test_helper import get_wda_port, get_worker_info -def PATH(p: str) -> str: - """Get the absolute path of a file relative to the folder where this file is located.""" - return os.path.abspath(os.path.join(os.path.dirname(__file__), p)) - - def is_platform_android() -> bool: """Check if the current platform is Android.""" return os.getenv('PLATFORM', 'android').lower() == 'android' @@ -55,22 +50,30 @@ def make_options() -> FlutterOptions: 'newCommandTimeout': 120, 'uiautomator2ServerInstallTimeout': 120000, 'adbExecTimeout': 120000, - 'app': os.getenv('FLUTTER_ANDROID_APP'), + 'app': os.environ['FLUTTER_ANDROID_APP'], 'autoGrantPermissions': True, } if is_platform_android() else { 'deviceName': device_name(), 'platformName': 'iOS', - 'platformVersion': os.getenv('IOS_VERSION'), + 'platformVersion': os.environ['IOS_VERSION'], 'allowTouchIdEnroll': True, 'wdaLaunchTimeout': 240000, 'wdaLocalPort': get_wda_port(), 'eventTimings': True, - 'app': os.getenv('FLUTTER_IOS_APP'), + 'app': os.environ['FLUTTER_IOS_APP'], } ) + if local_prebuilt_wda := os.getenv('LOCAL_PREBUILT_WDA'): + caps.update( + { + 'usePreinstalledWDA': True, + 'prebuiltWDAPath': local_prebuilt_wda, + } + ) + return options.load_capabilities(caps) @@ -82,7 +85,7 @@ def device_name() -> str: if is_platform_android(): prefix = 'Android Emulator' else: - prefix = os.getenv('IPHONE_MODEL') or 'iPhone 15 Plus' + prefix = os.environ['IPHONE_MODEL'] worker_info = get_worker_info() diff --git a/test/functional/ios/helper/options.py b/test/functional/ios/helper/options.py index 943e93c00..e8025ff7a 100644 --- a/test/functional/ios/helper/options.py +++ b/test/functional/ios/helper/options.py @@ -38,8 +38,7 @@ def make_options(app: Optional[str] = None) -> XCUITestOptions: if app is not None: options.app = PATH(os.path.join('..', '..', '..', 'apps', app)) - local_prebuilt_wda = os.getenv('LOCAL_PREBUILT_WDA') - if local_prebuilt_wda: + if local_prebuilt_wda := os.getenv('LOCAL_PREBUILT_WDA'): options.use_preinstalled_wda = True options.prebuilt_wda_path = local_prebuilt_wda From 5155362ea40267ab31691c8ed384677dc92526d3 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Oct 2025 10:56:59 +0200 Subject: [PATCH 6/9] start simulator ui --- .github/workflows/functional-test.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index ae8e1fa48..779f3cf83 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -48,7 +48,8 @@ jobs: xcode-version: ${{ env.XCODE_VERSION }} - run: defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false - + - name: Start iOS Simulator UI + run: open -Fn "$(xcode-select --print-path)/Applications/Simulator.app" - name: Prepare iOS simulator uses: futureware-tech/simulator-action@v4 with: @@ -165,7 +166,7 @@ jobs: ~/.android/avd/* ~/.android/adb* key: avd-${{ env.API_LEVEL }} - - name: create AVD and generate snapshot for caching + - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 with: @@ -192,7 +193,7 @@ jobs: restore-keys: | ${{ runner.os }}-uv-shared- - - name: run tests + - name: Run tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ env.API_LEVEL }} @@ -320,6 +321,10 @@ jobs: with: xcode-version: ${{ env.XCODE_VERSION }} + - name: Start iOS Simulator UI + if: matrix.e2e-tests == 'flutter-ios' + run: open -Fn "$(xcode-select --print-path)/Applications/Simulator.app" + - uses: futureware-tech/simulator-action@v4 if: matrix.e2e-tests == 'flutter-ios' with: From 797c216e5287cfb65e5dc1183f7e16042a5413bd Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Oct 2025 11:18:59 +0200 Subject: [PATCH 7/9] install drivers before simulator stratup --- .github/workflows/functional-test.yml | 52 +++++++++++++-------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index 779f3cf83..17e173152 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -47,21 +47,11 @@ jobs: with: xcode-version: ${{ env.XCODE_VERSION }} - - run: defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false - - name: Start iOS Simulator UI - run: open -Fn "$(xcode-select --print-path)/Applications/Simulator.app" - - name: Prepare iOS simulator - uses: futureware-tech/simulator-action@v4 - with: - model: ${{ env.IPHONE_MODEL }} - os_version: ${{ env.IOS_VERSION }} - wait_for_boot: true - shutdown_after_job: false - - name: Install Appium and drivers run: | npm install -g appium appium driver install xcuitest + appium driver run xcuitest download-wda-sim --platform=ios --outdir=$(dirname "$PREBUILT_WDA_PATH") - name: Start Appium server uses: ./.github/actions/setup-appium-server @@ -70,9 +60,17 @@ jobs: host: ${{ env.APPIUM_TEST_SERVER_HOST }} server_args: '--relaxed-security' - - name: Downloading prebuilt WDA + - name: Start iOS Simulator UI run: | - appium driver run xcuitest download-wda-sim --platform=ios --outdir=$(dirname "$PREBUILT_WDA_PATH") + defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false + open -Fn "$(xcode-select --print-path)/Applications/Simulator.app" + - name: Prepare iOS simulator + uses: futureware-tech/simulator-action@v4 + with: + model: ${{ env.IPHONE_MODEL }} + os_version: ${{ env.IOS_VERSION }} + wait_for_boot: true + shutdown_after_job: false - name: Set up Python uses: actions/setup-python@v5 @@ -321,19 +319,6 @@ jobs: with: xcode-version: ${{ env.XCODE_VERSION }} - - name: Start iOS Simulator UI - if: matrix.e2e-tests == 'flutter-ios' - run: open -Fn "$(xcode-select --print-path)/Applications/Simulator.app" - - - uses: futureware-tech/simulator-action@v4 - if: matrix.e2e-tests == 'flutter-ios' - with: - # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md - model: ${{ env.IPHONE_MODEL }} - os_version: ${{ env.IOS_VERSION }} - wait_for_boot: true - shutdown_after_job: false - - name: Install Appium and drivers if: matrix.e2e-tests == 'flutter-ios' run: | @@ -351,6 +336,21 @@ jobs: server_args: '--relaxed-security' log_file: 'appium_ios.log' + - 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@v4 + if: matrix.e2e-tests == 'flutter-ios' + with: + # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md + model: ${{ env.IPHONE_MODEL }} + os_version: ${{ env.IOS_VERSION }} + wait_for_boot: true + shutdown_after_job: false + - name: Run IOS tests if: matrix.e2e-tests == 'flutter-ios' run: | From 21d35ca9aada24126a996a13d7aa8a1dad0f75b4 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Oct 2025 14:20:20 +0200 Subject: [PATCH 8/9] tune --- .github/workflows/functional-test.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index 17e173152..8954fe642 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -72,6 +72,9 @@ jobs: wait_for_boot: true shutdown_after_job: false + - name: Finalize iOS simulator boot + run: xcrun --sdk iphonesimulator --show-sdk-version + - name: Set up Python uses: actions/setup-python@v5 with: @@ -92,7 +95,7 @@ jobs: - name: Run Tests run: | - uv run pytest ${{ matrix.test_targets.target}} \ + uv run pytest -v ${{ matrix.test_targets.target}} \ --doctest-modules \ --junitxml=junit/test-results.xml \ --cov=com \ @@ -198,7 +201,7 @@ jobs: arch: ${{ env.ARCH }} script: | make install-uv - uv run pytest ${{ matrix.test_targets.target}} --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html + uv run pytest -v ${{ matrix.test_targets.target}} --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html target: google_apis profile: Nexus 5X disable-spellchecker: true @@ -351,6 +354,10 @@ jobs: wait_for_boot: true shutdown_after_job: false + - name: Finalize iOS simulator boot + if: matrix.e2e-tests == 'flutter-ios' + run: xcrun --sdk iphonesimulator --show-sdk-version + - name: Run IOS tests if: matrix.e2e-tests == 'flutter-ios' run: | From 5c4d21be82ea5734f424790ef9cd7762a5ad5d39 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 22 Oct 2025 15:13:07 +0200 Subject: [PATCH 9/9] simplify --- .github/workflows/functional-test.yml | 56 +++++++++++---------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index 8954fe642..2a2b58207 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -271,6 +271,12 @@ jobs: with: node-version: 'lts/*' + - name: Select Xcode + if: matrix.e2e-tests == 'flutter-ios' + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: ${{ env.XCODE_VERSION }} + - name: Install Appium and drivers if: matrix.e2e-tests == 'flutter-android' run: | @@ -278,14 +284,21 @@ jobs: appium driver install uiautomator2 appium driver install appium-flutter-integration-driver --source npm - - name: Start Appium server for Android - if: matrix.e2e-tests == 'flutter-android' + - name: Install Appium and drivers + if: matrix.e2e-tests == 'flutter-ios' + run: | + npm install --location=global appium + appium driver install xcuitest + appium driver run xcuitest download-wda-sim --platform=ios --outdir=$(dirname "$PREBUILT_WDA_PATH") + appium driver install appium-flutter-integration-driver --source npm + + - name: Start Appium server uses: ./.github/actions/setup-appium-server with: port: ${{ env.APPIUM_TEST_SERVER_PORT }} host: ${{ env.APPIUM_TEST_SERVER_HOST }} server_args: '--relaxed-security' - log_file: 'appium_flutter_android.log' + log_file: appium-${{ matrix.e2e-tests }}.log - name: Cache uv modules uses: actions/cache@v4 @@ -309,36 +322,6 @@ jobs: disable-spellchecker: true disable-animations: true - - name: Save server output - if: always() && matrix.e2e-tests == 'flutter-android' - uses: actions/upload-artifact@master - with: - name: appium-flutter-android.log - path: appium_flutter_android.log - - - name: Select Xcode - if: matrix.e2e-tests == 'flutter-ios' - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: ${{ env.XCODE_VERSION }} - - - name: Install Appium and drivers - if: matrix.e2e-tests == 'flutter-ios' - run: | - npm install --location=global appium - appium driver install xcuitest - appium driver run xcuitest download-wda-sim --platform=ios --outdir=$(dirname "$PREBUILT_WDA_PATH") - appium driver install appium-flutter-integration-driver --source npm - - - name: Start Appium server for iOS - if: matrix.e2e-tests == 'flutter-ios' - uses: ./.github/actions/setup-appium-server - with: - port: ${{ env.APPIUM_TEST_SERVER_PORT }} - host: ${{ env.APPIUM_TEST_SERVER_HOST }} - server_args: '--relaxed-security' - log_file: 'appium_ios.log' - - name: Start iOS Simulator UI if: matrix.e2e-tests == 'flutter-ios' run: | @@ -371,3 +354,10 @@ jobs: --cov-report=html env: LOCAL_PREBUILT_WDA: ${{ env.PREBUILT_WDA_PATH }} + + - name: Save server output + if: ${{ always() }} + uses: actions/upload-artifact@master + with: + name: appium-${{ matrix.e2e-tests }}.log + path: appium-${{ matrix.e2e-tests }}.log