From cc8ff769712163301ded4143be6ce755583f0343 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 7 Feb 2021 12:31:29 +0900 Subject: [PATCH 1/3] Added docstring for macOS screenrecord option --- appium/webdriver/extensions/screen_record.py | 23 ++++++++++- .../android/appium_service_tests.py | 3 +- test/functional/android/common_tests.py | 3 +- test/functional/android/helper/test_helper.py | 29 -------------- test/functional/android/multi_action_tests.py | 3 +- .../find_by_accessibility_id_tests.py | 3 +- .../search_context/find_by_image_tests.py | 2 +- test/functional/android/touch_action_tests.py | 3 +- test/functional/android/webelement_tests.py | 3 +- test/functional/mac/__init__.py | 0 test/functional/mac/execute_script_test.py | 39 +++++++++++++++++++ test/functional/mac/helper/__init__.py | 0 .../mac/helper/desired_capabilities.py | 21 ++++++++++ test/functional/mac/helper/test_helper.py | 26 +++++++++++++ test/functional/mac/webelement_test.py | 26 +++++++++++++ test/functional/test_helper.py | 28 ++++++++++++- 16 files changed, 174 insertions(+), 38 deletions(-) create mode 100644 test/functional/mac/__init__.py create mode 100644 test/functional/mac/execute_script_test.py create mode 100644 test/functional/mac/helper/__init__.py create mode 100644 test/functional/mac/helper/desired_capabilities.py create mode 100644 test/functional/mac/helper/test_helper.py create mode 100644 test/functional/mac/webelement_test.py diff --git a/appium/webdriver/extensions/screen_record.py b/appium/webdriver/extensions/screen_record.py index 931369f6..8fa27e56 100644 --- a/appium/webdriver/extensions/screen_record.py +++ b/appium/webdriver/extensions/screen_record.py @@ -47,8 +47,10 @@ def start_recording_screen(self: T, **options: Any) -> Union[bytes, str]: Only has an effect if `remotePath` is set. timeLimit (int): The actual time limit of the recorded video in seconds. The default value for both iOS and Android is 180 seconds (3 minutes). + The default value for macOS is 600 seconds (10 minutes). The maximum value for Android is 3 minutes. The maximum value for iOS is 10 minutes. + The maximum value for macOS is 10000 seconds (166 minutes). forcedRestart (bool): Whether to ignore the result of previous capture and start a new recording immediately (`True` value). By default (`False`) the endpoint will try to catch and return the result of the previous capture if it's still available. @@ -68,7 +70,7 @@ def start_recording_screen(self: T, **options: Any) -> Union[bytes, str]: 'mjpeg' by default. (Since Appium 1.10.0) videoFps (int): [iOS only] The Frames Per Second rate of the recorded video. Change this value if the resulting video is too slow or too fast. Defaults to 10. This can decrease the resulting file size. - videoFilters (str): [iOS only] The FFMPEG video filters to apply. These filters allow to scale, + videoFilters (str): [iOS, macOS only] The FFMPEG video filters to apply. These filters allow to scale, flip, rotate and do many other useful transformations on the source video stream. The format of the property must comply with https://ffmpeg.org/ffmpeg-filters.html. (Since Appium 1.15) videoScale (str): [iOS only] The scaling value to apply. Read https://trac.ffmpeg.org/wiki/Scaling for @@ -80,10 +82,29 @@ def start_recording_screen(self: T, **options: Any) -> Union[bytes, str]: The default value is the device's native display resolution (if supported), 1280x720 if not. For best results, use a size supported by your device's Advanced Video Coding (AVC) encoder. + bitRate (int): [Android only] The video bit rate for the video, in megabits per second. The default value is 4. You can increase the bit rate to improve video quality, but doing so results in larger movie files. + fps (int): [macOS only] The count of frames per second in the resulting video. + Increasing fps value also increases the size of the resulting video file and the CPU usage. + captureCursor (bool): [macOS only] Whether to capture the mouse cursor while recording the screen. + Disabled by default. + captureClick (bool): [macOS only] Whether to capture the click gestures while recording the screen. + Disabled by default. + deviceId (int): [macOS only] Screen device index to use for the recording. + The list of available devices could be retrieved using + `ffmpeg -f avfoundation -list_devices true -i` command. + This option is mandatory and must be always provided. + preset (str): [macOS only] A preset is a collection of options that will provide a certain encoding + speed to compression ratio. A slower preset will provide better compression + (compression is quality per filesize). This means that, for example, if you target a certain file size + or constant bit rate, you will achieve better quality with a slower preset. + Read https://trac.ffmpeg.org/wiki/Encode/H.264 for more details. + One of the supported encoding presets. Possible values are 'ultrafast', 'superfast', + 'veryfast'(default), 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow' + Returns: bytes: Base-64 encoded content of the recorded media if `stop_recording_screen` isn't called after previous `start_recording_screen`. diff --git a/test/functional/android/appium_service_tests.py b/test/functional/android/appium_service_tests.py index 505172f5..cd22584c 100644 --- a/test/functional/android/appium_service_tests.py +++ b/test/functional/android/appium_service_tests.py @@ -15,7 +15,8 @@ from appium.webdriver.appium_service import AppiumService from appium.webdriver.common.mobileby import MobileBy -from test.functional.android.helper.test_helper import BaseTestCase, wait_for_element +from test.functional.android.helper.test_helper import BaseTestCase +from test.functional.test_helper import wait_for_element DEFAULT_PORT = 4723 diff --git a/test/functional/android/common_tests.py b/test/functional/android/common_tests.py index ea052723..e435050d 100644 --- a/test/functional/android/common_tests.py +++ b/test/functional/android/common_tests.py @@ -19,9 +19,10 @@ from selenium.common.exceptions import NoSuchElementException from appium.webdriver.common.mobileby import MobileBy +from test.functional.test_helper import wait_for_element from ..test_helper import is_ci -from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase, wait_for_element +from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase class TestCommon(BaseTestCase): diff --git a/test/functional/android/helper/test_helper.py b/test/functional/android/helper/test_helper.py index 76d489e1..25f14ce2 100644 --- a/test/functional/android/helper/test_helper.py +++ b/test/functional/android/helper/test_helper.py @@ -17,44 +17,15 @@ import os from typing import TYPE_CHECKING -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait - from appium import webdriver from test.functional.test_helper import is_ci from . import desired_capabilities -if TYPE_CHECKING: - from appium.webdriver.webdriver import WebDriver - from appium.webdriver.webelement import WebElement - -# the emulator is sometimes slow and needs time to think -SLEEPY_TIME = 10 - # The package name of ApiDemos-debug.apk.zip APIDEMO_PKG_NAME = 'io.appium.android.apis' -def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout: int = SLEEPY_TIME) -> 'WebElement': - """Wait until the element located - - Args: - driver: WebDriver instance - locator: Locator like WebDriver, Mobile JSON Wire Protocol - (e.g. `appium.webdriver.common.mobileby.MobileBy.ACCESSIBILITY_ID`) - value: Query value to locator - timeout: Maximum time to wait the element. If time is over, `TimeoutException` is thrown - - Raises: - `selenium.common.exceptions.TimeoutException` - - Returns: - The found WebElement - """ - return WebDriverWait(driver, timeout).until(EC.presence_of_element_located((locator, value))) - - class BaseTestCase: def setup_method(self, method) -> None: # type: ignore desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip') diff --git a/test/functional/android/multi_action_tests.py b/test/functional/android/multi_action_tests.py index 91747723..897f531f 100644 --- a/test/functional/android/multi_action_tests.py +++ b/test/functional/android/multi_action_tests.py @@ -19,8 +19,9 @@ from appium.webdriver.common.mobileby import MobileBy from appium.webdriver.common.multi_action import MultiAction from appium.webdriver.common.touch_action import TouchAction +from test.functional.test_helper import wait_for_element -from .helper.test_helper import BaseTestCase, is_ci, wait_for_element +from .helper.test_helper import BaseTestCase, is_ci class TestMultiAction(BaseTestCase): diff --git a/test/functional/android/search_context/find_by_accessibility_id_tests.py b/test/functional/android/search_context/find_by_accessibility_id_tests.py index dd3a8392..03825a81 100644 --- a/test/functional/android/search_context/find_by_accessibility_id_tests.py +++ b/test/functional/android/search_context/find_by_accessibility_id_tests.py @@ -15,7 +15,8 @@ import pytest from appium.webdriver.common.mobileby import MobileBy -from test.functional.android.helper.test_helper import BaseTestCase, is_ci, wait_for_element +from test.functional.android.helper.test_helper import BaseTestCase, is_ci +from test.functional.test_helper import wait_for_element class TestFindByAccessibilityID(BaseTestCase): diff --git a/test/functional/android/search_context/find_by_image_tests.py b/test/functional/android/search_context/find_by_image_tests.py index bb8c6e7e..2ff55467 100644 --- a/test/functional/android/search_context/find_by_image_tests.py +++ b/test/functional/android/search_context/find_by_image_tests.py @@ -20,7 +20,7 @@ from appium import webdriver from appium.webdriver.common.mobileby import MobileBy from test.functional.android.helper import desired_capabilities -from test.functional.android.helper.test_helper import wait_for_element +from test.functional.test_helper import wait_for_element class TestFindByImage(object): diff --git a/test/functional/android/touch_action_tests.py b/test/functional/android/touch_action_tests.py index 2e92341c..4737dea8 100644 --- a/test/functional/android/touch_action_tests.py +++ b/test/functional/android/touch_action_tests.py @@ -17,8 +17,9 @@ from appium.webdriver.common.mobileby import MobileBy from appium.webdriver.common.touch_action import TouchAction +from test.functional.test_helper import wait_for_element -from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase, is_ci, wait_for_element +from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase, is_ci class TestTouchAction(BaseTestCase): diff --git a/test/functional/android/webelement_tests.py b/test/functional/android/webelement_tests.py index 3918ac5c..825c071b 100644 --- a/test/functional/android/webelement_tests.py +++ b/test/functional/android/webelement_tests.py @@ -14,8 +14,9 @@ # limitations under the License. from appium.webdriver.common.mobileby import MobileBy +from test.functional.test_helper import wait_for_element -from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase, wait_for_element +from .helper.test_helper import APIDEMO_PKG_NAME, BaseTestCase class TestWebelement(BaseTestCase): diff --git a/test/functional/mac/__init__.py b/test/functional/mac/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/functional/mac/execute_script_test.py b/test/functional/mac/execute_script_test.py new file mode 100644 index 00000000..03dadc8e --- /dev/null +++ b/test/functional/mac/execute_script_test.py @@ -0,0 +1,39 @@ +#!/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. + +from appium.webdriver.common.mobileby import MobileBy +from test.functional.mac.helper.test_helper import BaseTestCase +from test.functional.test_helper import wait_for_element + + +class TestExecuteScript(BaseTestCase): + def test_sending_custom_keys(self) -> None: + edit_field = wait_for_element(self.driver, MobileBy.CLASS_NAME, 'XCUIElementTypeTextView') + flagsShift = 1 << 1 + self.driver.execute_script( + 'macos: keys', + { + 'keys': [ + { + 'key': 'h', + 'modifierFlags': flagsShift, + }, + { + 'key': 'i', + 'modifierFlags': flagsShift, + }, + ] + }, + ) + assert edit_field.text == 'HI' diff --git a/test/functional/mac/helper/__init__.py b/test/functional/mac/helper/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/functional/mac/helper/desired_capabilities.py b/test/functional/mac/helper/desired_capabilities.py new file mode 100644 index 00000000..648420ef --- /dev/null +++ b/test/functional/mac/helper/desired_capabilities.py @@ -0,0 +1,21 @@ +#!/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. + +from typing import Any, Dict + + +def get_desired_capabilities() -> Dict[str, Any]: + desired_caps: Dict[str, Any] = {'platformName': 'mac', 'automationName': 'Mac2', 'bundleId': 'com.apple.TextEdit'} + + return desired_caps diff --git a/test/functional/mac/helper/test_helper.py b/test/functional/mac/helper/test_helper.py new file mode 100644 index 00000000..f52bc9aa --- /dev/null +++ b/test/functional/mac/helper/test_helper.py @@ -0,0 +1,26 @@ +#!/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. + + +from appium import webdriver + +from .desired_capabilities import get_desired_capabilities + + +class BaseTestCase(object): + def setup_method(self) -> None: + self.driver = webdriver.Remote('http://localhost:4723/wd/hub', get_desired_capabilities()) + + def teardown_method(self, method) -> None: # type: ignore + self.driver.quit() diff --git a/test/functional/mac/webelement_test.py b/test/functional/mac/webelement_test.py new file mode 100644 index 00000000..d30dea4b --- /dev/null +++ b/test/functional/mac/webelement_test.py @@ -0,0 +1,26 @@ +#!/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. + +from appium.webdriver.common.mobileby import MobileBy +from test.functional.mac.helper.test_helper import BaseTestCase +from test.functional.test_helper import wait_for_element + + +class TestWebElement(BaseTestCase): + def test_clear_text_field(self) -> None: + edit_field = wait_for_element(self.driver, MobileBy.CLASS_NAME, 'XCUIElementTypeTextView') + edit_field.send_keys('helloworld') + assert edit_field.text == 'helloworld' + edit_field.clear() + assert edit_field.text == '' diff --git a/test/functional/test_helper.py b/test/functional/test_helper.py index aa3f4dbf..67ab11d0 100644 --- a/test/functional/test_helper.py +++ b/test/functional/test_helper.py @@ -2,7 +2,14 @@ import socket import time from time import sleep -from typing import Any, Callable +from typing import TYPE_CHECKING, Any, Callable + +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + +if TYPE_CHECKING: + from appium.webdriver.webdriver import WebDriver + from appium.webdriver.webelement import WebElement class NoAvailablePortError(Exception): @@ -69,3 +76,22 @@ def wait_for_condition(method: Callable, timeout_sec: float = 5, interval_sec: f break sleep(interval_sec) return result + + +def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout: int = 10) -> 'WebElement': + """Wait until the element located + + Args: + driver: WebDriver instance + locator: Locator like WebDriver, Mobile JSON Wire Protocol + (e.g. `appium.webdriver.common.mobileby.MobileBy.ACCESSIBILITY_ID`) + value: Query value to locator + timeout: Maximum time to wait the element. If time is over, `TimeoutException` is thrown + + Raises: + `selenium.common.exceptions.TimeoutException` + + Returns: + The found WebElement + """ + return WebDriverWait(driver, timeout).until(EC.presence_of_element_located((locator, value))) From 6ae4ca5edc4933f67cb27eeaac6c666262a7fbe4 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 7 Feb 2021 12:37:37 +0900 Subject: [PATCH 2/3] tweak --- appium/webdriver/extensions/screen_record.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appium/webdriver/extensions/screen_record.py b/appium/webdriver/extensions/screen_record.py index 8fa27e56..d2f30837 100644 --- a/appium/webdriver/extensions/screen_record.py +++ b/appium/webdriver/extensions/screen_record.py @@ -102,8 +102,8 @@ def start_recording_screen(self: T, **options: Any) -> Union[bytes, str]: (compression is quality per filesize). This means that, for example, if you target a certain file size or constant bit rate, you will achieve better quality with a slower preset. Read https://trac.ffmpeg.org/wiki/Encode/H.264 for more details. - One of the supported encoding presets. Possible values are 'ultrafast', 'superfast', - 'veryfast'(default), 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow' + Possible values are 'ultrafast', 'superfast', 'veryfast'(default), 'faster', 'fast', 'medium', 'slow', + 'slower', 'veryslow' Returns: bytes: Base-64 encoded content of the recorded media From babae2610f662a415226126cf0d15d6fc4008008 Mon Sep 17 00:00:00 2001 From: Atsushi Mori Date: Sun, 7 Feb 2021 21:42:49 +0900 Subject: [PATCH 3/3] review comment --- .../android/search_context/find_by_image_tests.py | 2 +- test/functional/test_helper.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/functional/android/search_context/find_by_image_tests.py b/test/functional/android/search_context/find_by_image_tests.py index 2ff55467..6ac10bd5 100644 --- a/test/functional/android/search_context/find_by_image_tests.py +++ b/test/functional/android/search_context/find_by_image_tests.py @@ -70,7 +70,7 @@ def test_find_throws_no_such_element(self) -> None: b64_data = base64.b64encode(png_file.read()).decode('UTF-8') with pytest.raises(TimeoutException): - wait_for_element(self.driver, MobileBy.IMAGE, b64_data, timeout=3) + wait_for_element(self.driver, MobileBy.IMAGE, b64_data, timeout_sec=3) with pytest.raises(NoSuchElementException): self.driver.find_element_by_image(image_path) diff --git a/test/functional/test_helper.py b/test/functional/test_helper.py index 67ab11d0..fa6bef3a 100644 --- a/test/functional/test_helper.py +++ b/test/functional/test_helper.py @@ -78,7 +78,7 @@ def wait_for_condition(method: Callable, timeout_sec: float = 5, interval_sec: f return result -def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout: int = 10) -> 'WebElement': +def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout_sec: float = 10) -> 'WebElement': """Wait until the element located Args: @@ -86,7 +86,7 @@ def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout: int locator: Locator like WebDriver, Mobile JSON Wire Protocol (e.g. `appium.webdriver.common.mobileby.MobileBy.ACCESSIBILITY_ID`) value: Query value to locator - timeout: Maximum time to wait the element. If time is over, `TimeoutException` is thrown + timeout_sec: Maximum time to wait the element. If time is over, `TimeoutException` is thrown Raises: `selenium.common.exceptions.TimeoutException` @@ -94,4 +94,4 @@ def wait_for_element(driver: 'WebDriver', locator: str, value: str, timeout: int Returns: The found WebElement """ - return WebDriverWait(driver, timeout).until(EC.presence_of_element_located((locator, value))) + return WebDriverWait(driver, timeout_sec).until(EC.presence_of_element_located((locator, value)))