Skip to content

Commit f0349fd

Browse files
authored
Merge branch 'master' into tests-cleanup
2 parents 991d57e + 2ffa930 commit f0349fd

26 files changed

+1467
-0
lines changed

.github/workflows/functional-test.yml

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,122 @@ jobs:
194194
with:
195195
name: appium-android-${{matrix.test_targets.name}}.log
196196
path: appium.log
197+
198+
flutter_e2e_test:
199+
# These flutter integration driver tests are maintained by: MummanaSubramanya
200+
strategy:
201+
fail-fast: false
202+
matrix:
203+
include:
204+
- platform: macos-14
205+
e2e-tests: flutter-ios
206+
- platform: ubuntu-latest
207+
e2e-tests: flutter-android
208+
209+
runs-on: ${{ matrix.platform }}
210+
211+
env:
212+
API_LEVEL: 28
213+
ARCH: x86
214+
CI: true
215+
XCODE_VERSION: 15.4
216+
IOS_VERSION: 17.5
217+
IPHONE_MODEL: iPhone 15
218+
FLUTTER_ANDROID_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/app-debug.apk"
219+
FLUTTER_IOS_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/ios.zip"
220+
221+
steps:
222+
223+
- uses: actions/checkout@v4
224+
225+
- uses: actions/setup-java@v4
226+
if: matrix.e2e-tests == 'flutter-android'
227+
with:
228+
distribution: 'zulu'
229+
java-version: '17'
230+
231+
- name: Enable KVM group perms
232+
if: matrix.e2e-tests == 'flutter-android'
233+
run: |
234+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
235+
sudo udevadm control --reload-rules
236+
sudo udevadm trigger --name-match=kvm
237+
238+
- name: Set up Python 3.12
239+
uses: actions/setup-python@v3
240+
with:
241+
python-version: 3.12
242+
243+
- name: Install Node.js
244+
uses: actions/setup-node@v4
245+
with:
246+
node-version: 'lts/*'
247+
248+
- name: Install Appium
249+
run: npm install --location=global appium
250+
251+
- name: Install Android drivers and Run Appium
252+
if: matrix.e2e-tests == 'flutter-android'
253+
run: |
254+
appium driver install uiautomator2
255+
appium driver install appium-flutter-integration-driver --source npm
256+
nohup appium --allow-insecure=adb_shell --relaxed-security --log-timestamp --log-no-colors 2>&1 > appium_flutter_android.log &
257+
258+
- name: Run Android tests
259+
if: matrix.e2e-tests == 'flutter-android'
260+
uses: reactivecircus/android-emulator-runner@v2
261+
with:
262+
api-level: ${{ env.API_LEVEL }}
263+
script: |
264+
pip install --upgrade pip
265+
pip install --upgrade pipenv
266+
pipenv lock --clear
267+
pipenv install -d --system
268+
export PLATFORM=android
269+
pytest test/functional/flutter_integration/*_test.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
270+
target: default
271+
disable-spellchecker: true
272+
disable-animations: true
273+
274+
- name: Save server output
275+
if: always() && matrix.e2e-tests == 'flutter-android'
276+
uses: actions/upload-artifact@master
277+
with:
278+
name: appium-flutter-android.log
279+
path: appium_flutter_android.log
280+
281+
- name: Select Xcode
282+
if: matrix.e2e-tests == 'flutter-ios'
283+
uses: maxim-lobanov/setup-xcode@v1
284+
with:
285+
xcode-version: ${{ env.XCODE_VERSION }}
286+
287+
- uses: futureware-tech/simulator-action@v3
288+
if: matrix.e2e-tests == 'flutter-ios'
289+
with:
290+
# https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md
291+
model: ${{ env.IPHONE_MODEL }}
292+
os_version: ${{ env.IOS_VERSION }}
293+
294+
- name: install dependencies
295+
if: matrix.e2e-tests == 'flutter-ios'
296+
run: brew install ffmpeg
297+
298+
- name: Install IOS drivers and Run Appium
299+
if: matrix.e2e-tests == 'flutter-ios'
300+
run: |
301+
appium driver install xcuitest
302+
appium driver install appium-flutter-integration-driver --source npm
303+
appium driver run xcuitest build-wda
304+
nohup appium --allow-insecure=adb_shell --relaxed-security --log-timestamp --log-no-colors 2>&1 > appium_ios.log &
305+
306+
- name: Run IOS tests
307+
if: matrix.e2e-tests == 'flutter-ios'
308+
run: |
309+
# Separate 'run' creates differnet pipenv env. Does them in one run for now.
310+
pip install --upgrade pip
311+
pip install --upgrade pipenv
312+
pipenv lock --clear
313+
pipenv install -d --system
314+
export PLATFORM=ios
315+
pytest test/functional/flutter_integration/*_test.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html

appium/common/helper.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import base64
1516
from typing import Any, Dict
1617

1718
from appium import version as appium_version
@@ -33,3 +34,9 @@ def library_version() -> str:
3334
"""Return a version of this python library"""
3435

3536
return appium_version.version
37+
38+
39+
def encode_file_to_base64(file_path: str) -> str:
40+
"""Return base64 encoded string for given file"""
41+
with open(file_path, 'rb') as file:
42+
return base64.b64encode(file.read()).decode('utf-8')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .base import FlutterOptions
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from typing import Dict
19+
20+
from appium.options.common.automation_name_option import AUTOMATION_NAME
21+
from appium.options.common.base import AppiumOptions
22+
from appium.options.flutter_integration.flutter_element_wait_timeout_option import FlutterElementWaitTimeOutOption
23+
from appium.options.flutter_integration.flutter_enable_mock_camera_option import FlutterEnableMockCameraOption
24+
from appium.options.flutter_integration.flutter_server_launch_timeout_option import FlutterServerLaunchTimeOutOption
25+
from appium.options.flutter_integration.flutter_system_port_option import FlutterSystemPortOption
26+
27+
28+
class FlutterOptions(
29+
AppiumOptions,
30+
FlutterElementWaitTimeOutOption,
31+
FlutterEnableMockCameraOption,
32+
FlutterServerLaunchTimeOutOption,
33+
FlutterSystemPortOption,
34+
):
35+
36+
@property
37+
def default_capabilities(self) -> Dict:
38+
return {
39+
AUTOMATION_NAME: 'FlutterIntegration',
40+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from datetime import timedelta
19+
from typing import Optional, Union
20+
21+
from appium.options.common.supports_capabilities import SupportsCapabilities
22+
23+
FLUTTER_ELEMENT_WAIT_TIMEOUT = 'flutterElementWaitTimeout'
24+
25+
26+
class FlutterElementWaitTimeOutOption(SupportsCapabilities):
27+
28+
@property
29+
def flutter_element_wait_timeout(self) -> Optional[timedelta]:
30+
"""
31+
Maximum timeout to wait for element for Flutter integration test
32+
33+
Returns:
34+
Optional[timedelta]: The timeout value as a `timedelta` object if set, or `None` if the timeout is not defined.
35+
"""
36+
return self.get_capability(FLUTTER_ELEMENT_WAIT_TIMEOUT)
37+
38+
@flutter_element_wait_timeout.setter
39+
def flutter_element_wait_timeout(self, value: Union[timedelta, int]) -> None:
40+
"""
41+
Sets the maximum timeout to wait for a Flutter element in an integration test.
42+
Default timeout is 5000ms
43+
44+
Args:
45+
value (Union[timedelta, int]): The timeout value, either as a `timedelta` object or an integer in milliseconds.
46+
If provided as a `timedelta`, it will be converted to milliseconds.
47+
"""
48+
self.set_capability(
49+
FLUTTER_ELEMENT_WAIT_TIMEOUT,
50+
(int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value),
51+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from typing import Optional
19+
20+
from appium.options.common.supports_capabilities import SupportsCapabilities
21+
22+
FLUTTER_ENABLE_MOCK_CAMERA = 'flutterEnableMockCamera'
23+
24+
25+
class FlutterEnableMockCameraOption(SupportsCapabilities):
26+
27+
@property
28+
def flutter_enable_mock_camera(self) -> bool:
29+
"""
30+
Get state of the mock camera for Flutter integration test
31+
32+
Returns:
33+
bool: A boolean indicating whether the mock camera is enabled (True) or disabled (False).
34+
"""
35+
return self.get_capability(FLUTTER_ENABLE_MOCK_CAMERA)
36+
37+
@flutter_enable_mock_camera.setter
38+
def flutter_enable_mock_camera(self, value: bool) -> None:
39+
"""
40+
Setter method enable or disable the mock camera for Flutter integration test
41+
Default state is `False`
42+
43+
Args:
44+
value (bool): A boolean value indicating whether to enable (True) or disable (False) the mock camera.
45+
"""
46+
self.set_capability(FLUTTER_ENABLE_MOCK_CAMERA, value)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from datetime import timedelta
19+
from typing import Optional, Union
20+
21+
from appium.options.common.supports_capabilities import SupportsCapabilities
22+
23+
FLUTTER_SERVER_LAUNCH_TIMEOUT = 'flutterServerLaunchTimeout'
24+
25+
26+
class FlutterServerLaunchTimeOutOption(SupportsCapabilities):
27+
28+
@property
29+
def flutter_server_launch_timeout(self) -> Optional[timedelta]:
30+
"""
31+
Gets the current timeout for launching the Flutter server in a Flutter application.
32+
33+
Returns:
34+
Optional[timedelta]: The timeout value as a `timedelta` object if set, or `None` if the timeout is not defined.
35+
36+
"""
37+
return self.get_capability(FLUTTER_SERVER_LAUNCH_TIMEOUT)
38+
39+
@flutter_server_launch_timeout.setter
40+
def flutter_server_launch_timeout(self, value: Union[timedelta, int]) -> None:
41+
"""
42+
Sets the timeout for launching the Flutter server in Flutter application.
43+
Default timeout is 5000ms
44+
45+
Args:
46+
value (Union[timedelta, int]): The timeout value, either as a `timedelta` object or an integer in milliseconds.
47+
If provided as a `timedelta`, it will be converted to milliseconds.
48+
"""
49+
self.set_capability(
50+
FLUTTER_SERVER_LAUNCH_TIMEOUT,
51+
(int(value.total_seconds() * 1000) if isinstance(value, timedelta) else value),
52+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from typing import Optional
19+
20+
from appium.options.common.supports_capabilities import SupportsCapabilities
21+
22+
FLUTTER_SYSTEM_PORT = 'flutterSystemPort'
23+
24+
25+
class FlutterSystemPortOption(SupportsCapabilities):
26+
27+
@property
28+
def flutter_system_port(self) -> Optional[int]:
29+
"""
30+
Get flutter system port for Flutter integration tests.
31+
32+
Returns:
33+
int: returns the port number
34+
"""
35+
return self.get_capability(FLUTTER_SYSTEM_PORT)
36+
37+
@flutter_system_port.setter
38+
def flutter_system_port(self, value: int) -> None:
39+
"""
40+
Sets the system port for Flutter integration tests.
41+
By default the first free port from 10000..11000 range is selected
42+
43+
Args:
44+
value (int): The port number to be used for the Flutter server.
45+
"""
46+
self.set_capability(FLUTTER_SYSTEM_PORT, value)

appium/webdriver/common/appiumby.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,10 @@ class AppiumBy(By):
2525
ACCESSIBILITY_ID = 'accessibility id'
2626
IMAGE = '-image'
2727
CUSTOM = '-custom'
28+
29+
# For Flutter integration usage https://github.com/AppiumTestDistribution/appium-flutter-integration-driver/tree/main
30+
FLUTTER_INTEGRATION_SEMANTICS_LABEL = '-flutter semantics label'
31+
FLUTTER_INTEGRATION_TYPE = '-flutter type'
32+
FLUTTER_INTEGRATION_KEY = '-flutter key'
33+
FLUTTER_INTEGRATION_TEXT = '-flutter text'
34+
FLUTTER_INTEGRATION_TEXT_CONTAINING = '-flutter text containing'

0 commit comments

Comments
 (0)