Skip to content

Commit 5d2ca84

Browse files
feat: add flutter integration driver commands and tests
1 parent f47fe5b commit 5d2ca84

18 files changed

+834
-0
lines changed

.github/workflows/functional-test.yml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,138 @@ jobs:
196196
with:
197197
name: appium-android-${{matrix.test_targets.name}}.log
198198
path: appium.log
199+
200+
flutter_e2e_test:
201+
strategy:
202+
fail-fast: false
203+
matrix:
204+
include:
205+
- platform: macos-14
206+
e2e-tests: flutter-ios
207+
- platform: ubuntu-latest
208+
e2e-tests: flutter-android
209+
210+
runs-on: ${{ matrix.platform }}
211+
212+
env:
213+
API_LEVEL: 29
214+
ARCH: x86
215+
CI: true
216+
XCODE_VERSION: 15.4
217+
IOS_VERSION: 17.5
218+
IPHONE_MODEL: iPhone 15
219+
FLUTTER_ANDROID_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/app-debug.apk"
220+
FLUTTER_IOS_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/ios.zip"
221+
222+
steps:
223+
224+
- uses: actions/checkout@v4
225+
226+
- uses: actions/setup-java@v4
227+
if: matrix.e2e-tests == 'flutter-android'
228+
with:
229+
distribution: 'zulu'
230+
java-version: '17'
231+
232+
- name: Enable KVM group perms
233+
if: matrix.e2e-tests == 'flutter-android'
234+
run: |
235+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
236+
sudo udevadm control --reload-rules
237+
sudo udevadm trigger --name-match=kvm
238+
239+
- name: AVD cache
240+
if: matrix.e2e-tests == 'flutter-android'
241+
uses: actions/cache@v3
242+
id: avd-cache
243+
with:
244+
path: |
245+
~/.android/avd/*
246+
~/.android/adb*
247+
key: avd-${{ env.API_LEVEL }}
248+
249+
- name: Create AVD and generate snapshot for caching
250+
if: matrix.e2e-tests == 'flutter-android' && steps.avd-cache.outputs.cache-hit != 'true'
251+
uses: reactivecircus/android-emulator-runner@v2
252+
with:
253+
api-level: ${{ env.API_LEVEL }}
254+
arch: ${{ env.ARCH }}
255+
target: google_apis
256+
force-avd-creation: false
257+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
258+
disable-animations: false
259+
script: echo "Generated AVD snapshot for caching."
260+
261+
- name: Set up Python 3.12
262+
uses: actions/setup-python@v3
263+
with:
264+
python-version: 3.12
265+
266+
- name: Install Node.js
267+
uses: actions/setup-node@v4
268+
with:
269+
node-version: 'lts/*'
270+
271+
- name: Install Appium
272+
run: npm install --location=global appium
273+
274+
- name: Install Android drivers and Run Appium
275+
if: matrix.e2e-tests == 'flutter-android'
276+
run: |
277+
appium driver install uiautomator2
278+
appium driver install appium-flutter-integration-driver --source npm
279+
nohup appium --allow-insecure=adb_shell --relaxed-security --log-timestamp --log-no-colors 2>&1 > appium_android.log &
280+
281+
- name: Run Android tests
282+
if: matrix.e2e-tests == 'flutter-android'
283+
uses: reactivecircus/android-emulator-runner@v2
284+
with:
285+
api-level: ${{ env.API_LEVEL }}
286+
arch: ${{ env.ARCH }}
287+
script: |
288+
pip install --upgrade pip
289+
pip install --upgrade pipenv
290+
pipenv lock --clear
291+
pipenv install -d --system
292+
export PLATFORM=android
293+
pytest test/functional/flutter/*_test.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
294+
target: google_apis
295+
profile: Nexus 5X
296+
disable-spellchecker: true
297+
disable-animations: true
298+
299+
- name: Select Xcode
300+
if: matrix.e2e-tests == 'flutter-ios'
301+
uses: maxim-lobanov/setup-xcode@v1
302+
with:
303+
xcode-version: ${{ env.XCODE_VERSION }}
304+
- run: defaults write com.apple.iphonesimulator PasteboardAutomaticSync -bool false
305+
306+
- uses: futureware-tech/simulator-action@v3
307+
with:
308+
# https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md
309+
model: ${{ env.IPHONE_MODEL }}
310+
os_version: ${{ env.IOS_VERSION }}
311+
312+
- name: install dependencies
313+
if: matrix.e2e-tests == 'flutter-ios'
314+
run: brew install ffmpeg
315+
316+
- name: Install IOS drivers and Run Appium
317+
if: matrix.e2e-tests == 'flutter-ios'
318+
run: |
319+
appium driver install xcuitest
320+
appium driver install appium-flutter-integration-driver --source npm
321+
appium driver run xcuitest build-wda
322+
nohup appium --allow-insecure=adb_shell --relaxed-security --log-timestamp --log-no-colors 2>&1 > appium_ios.log &
323+
324+
- name: Run IOS tests
325+
if: matrix.e2e-tests == 'flutter-ios'
326+
run: |
327+
# Separate 'run' creates differnet pipenv env. Does them in one run for now.
328+
pip install --upgrade pip
329+
pip install --upgrade pipenv
330+
pipenv lock --clear
331+
pipenv install -d --system
332+
export PLATFORM=ios
333+
pytest test/functional/flutter/*_test.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html

appium/options/flutter/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .base import FlutterOptions

appium/options/flutter/base.py

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+
from appium.options.common.automation_name_option import AUTOMATION_NAME
20+
from appium.options.common.base import AppiumOptions
21+
from appium.options.flutter.flutter_element_wait_timeout_option import FlutterElementWaitTimeOutOption
22+
from appium.options.flutter.flutter_enable_mock_camera_option import FlutterEnableMockCameraOption
23+
from appium.options.flutter.flutter_server_launch_timeout_option import FlutterServerLaunchTimeOutOption
24+
from appium.options.flutter.flutter_system_port_option import FlutterSystemPortOption
25+
26+
27+
class FlutterOptions(
28+
AppiumOptions,
29+
FlutterElementWaitTimeOutOption,
30+
FlutterEnableMockCameraOption,
31+
FlutterServerLaunchTimeOutOption,
32+
FlutterSystemPortOption
33+
):
34+
35+
@property
36+
def default_capabilities(self) -> Dict:
37+
return {
38+
AUTOMATION_NAME: 'FlutterIntegration',
39+
}
40+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
from appium.options.common.supports_capabilities import SupportsCapabilities
20+
21+
22+
FLUTTER_ELEMENT_WAIT_TIMEOUT= 'flutterElementWaitTimeout'
23+
24+
25+
class FlutterElementWaitTimeOutOption(SupportsCapabilities):
26+
27+
@property
28+
def flutter_element_wait_timeout(self) -> Optional[int]:
29+
return self.get_capability(FLUTTER_ELEMENT_WAIT_TIMEOUT)
30+
31+
@flutter_element_wait_timeout.setter
32+
def flutter_element_wait_timeout(self, time_in_millis: int) -> None:
33+
self.set_capability(FLUTTER_ELEMENT_WAIT_TIMEOUT, time_in_millis)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
from appium.options.common.supports_capabilities import SupportsCapabilities
20+
21+
22+
FLUTTER_ENABLE_MOCK_CAMERA = 'flutterEnableMockCamera'
23+
24+
25+
class FlutterEnableMockCameraOption(SupportsCapabilities):
26+
27+
@property
28+
def flutter_enable_mock_camera(self) -> Optional[int]:
29+
return self.get_capability(FLUTTER_ENABLE_MOCK_CAMERA)
30+
31+
@flutter_enable_mock_camera.setter
32+
def flutter_enable_mock_camera(self, value: bool) -> None:
33+
self.set_capability(FLUTTER_ENABLE_MOCK_CAMERA, value)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
from appium.options.common.supports_capabilities import SupportsCapabilities
20+
21+
22+
FLUTTER_SERVER_LAUNCH_TIMEOUT= 'flutterServerLaunchTimeout'
23+
24+
25+
class FlutterServerLaunchTimeOutOption(SupportsCapabilities):
26+
27+
@property
28+
def flutter_server_launch_timeout(self) -> Optional[int]:
29+
return self.get_capability(FLUTTER_SERVER_LAUNCH_TIMEOUT)
30+
31+
@flutter_server_launch_timeout.setter
32+
def flutter_server_launch_timeout(self, time_in_millis: int) -> None:
33+
self.set_capability(FLUTTER_SERVER_LAUNCH_TIMEOUT, time_in_millis)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
from appium.options.common.supports_capabilities import SupportsCapabilities
20+
21+
22+
FLUTTER_SYSTEM_PORT = 'flutterSystemPort'
23+
24+
25+
class FlutterSystemPortOption(SupportsCapabilities):
26+
27+
@property
28+
def flutter_system_port(self) -> Optional[int]:
29+
return self.get_capability(FLUTTER_SYSTEM_PORT)
30+
31+
@flutter_system_port.setter
32+
def flutter_system_port(self, value: int) -> None:
33+
self.set_capability(FLUTTER_SYSTEM_PORT, value)

appium/webdriver/common/flutterby.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 selenium.webdriver.common.by import By
19+
20+
class FlutterBy(By):
21+
FLUTTER_SEMANTICS_LABEL = '-flutter semantics label'
22+
FLUTTER_TYPE = '-flutter type'
23+
FLUTTER_KEY = '-flutter key'
24+
FLUTTER_TEXT = '-flutter text'
25+
FLUTTER_TEXT_CONTAINING = '-flutter text containing'

0 commit comments

Comments
 (0)