npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cloud-mobile/appium-flutter-driver-iosv14plus

v2.6.0

Published

Appium Flutter Driver iOS 14+

Downloads

17

Readme

Appium Flutter Driver

NPM version Downloads

Appium Flutter Driver is a test automation tool for Flutter apps on multiple platforms/OSes. Appium Flutter Driver is part of the Appium mobile test automation tool maintained by the community. Feel free to create PRs to fix issues/improve this driver.

Flutter Driver vs Appium Flutter Driver

Even though Flutter comes with superb integration test support, Flutter Driver, it does not fit some specific use cases, such as

  • Writing tests in other languages than Dart
  • Running integration test for Flutter app with embedded webview or native view, or existing native app with embedded Flutter view
  • Running tests on multiple devices simultaneously
  • Running integration tests on device farms that offer Appium support (Please contact the availability for each vendor)

Under the hood, Appium Flutter Driver uses the Dart VM Service Protocol with extension ext.flutter.driver, similar to Flutter Driver, to control the Flutter app-under-test (AUT).

Appium Flutter Driver or Appium UiAutomator2/XCUITest driver

  • Appium Flutter driver manages the application under test and the device under test via Appium UiAutomator2/XCUITest drivers
    • FLUTTER context sends commands to the Dart VM directly over the observatory URL
      • Newer Flutter versions expose its accessibility labels to the system's accessibility features. It means you can find some Flutter elements and can interact with them over accessibility_id etc in the vanilla Appium UiAutomator2/XCUITest drivers, although some elements require over the Dart VM
    • NATIVE_APP context is the same as the regular Appium UiAutomator2/XCUITest driver
      • Please refer to each client's documentation about available commands.
      • Each driver's documentation also may help.
        • https://github.com/appium/appium-uiautomator2-driver
        • https://appium.github.io/appium-xcuitest-driver/latest
    • WEBVIEW context manages the WebView contents over Appium UiAutomator2/XCUITest driver
  • (Recommended if possible) Appium UiAutomator2/XCUITest drivers must be sufficient to achieve automation if the application under test had semanticLabel properly. Then, the accessibility mechanism in each OS can expose elements for Appium through OS's accessibility features. Then, this driver is not necessary.
    • For example, Key does not work in the Appium UiAutomator2/XCUITest drivers, but can work in the Appium Flutter Driver
    • Flutter 3.19 may have identifier for SemanticsProperties (introduced by https://github.com/flutter/flutter/pull/138331). It sets resource-id and accessibilityIdentifier for Android and iOS, then UiAutomator2/XCUITest drivers also can handle Key without this driver
      • "appium:disableIdLocatorAutocompletion": true would be necessary to make resource-id idea work without any package name prefix like Android compose.
      • e.g. https://github.com/flutter/flutter/issues/17988#issuecomment-1867097631

Installation

Appium Flutter Driver version 1.0 and higher require Appium 2.0.

appium driver install --source=npm appium-flutter-driver

As a local:

appium driver install --source local /path/to/appium-flutter-driver/driver

Usage and requirement

If you are unfamiliar with running Appium tests, start with Quickstart Intro first.

Your Flutter application must be compiled in debug or profile mode. The dependency must have flutter_driver package like the below pubspec.yaml example with enableFlutterDriverExtension configuration in the main.dart.

# pubspec.yaml
dev_dependencies:
  flutter_driver:
    sdk: flutter

This snippet, taken from example directory, is a script written as an appium client with webdriverio, and assumes you have appium server (with appium-flutter-driver installed) running on the same host and default port (4723). For more info, see example's README.md

Note

This means this driver depends on flutter_driver.

Each client needs each finder module to handle Finders. Appium Flutter Driver communicates with the Dart VM directory in the FLUTTER context.

Note Expand deprecation policy to package:flutter_driver potentially means this driver will no longer work by the future Flutter updates. They do not cover all cases that can cover the flutter_driver, such as permission dialog handling, thus we're not sure when the time comes though.

Doctor

Since driver version 2.4.0 you can automate the validation for the most of the above requirements as well as various optional ones needed by driver extensions by running the appium driver doctor flutter server command. The check runs for Android for UIAutomator2 driver and iOS for XCUITest driver.

SKIP_ANDROID or SKIP_IOS environment variable helps to skip these checks.

# skip Android check
SKIP_ANDROID=1 appium driver doctor flutter
# skip iOS check
SKIP_IOS=1 appium driver doctor flutter

Note

  • Flutter context does not support page source
    • Please use getRenderTree command instead
  • You can send appium-xcuitest-driver/appium-uiautomator2-driver commands in NATIVE_APP context
  • scrollUntilVisible command : An expectation for checking that an element, known to be present on the widget tree, is visible. Using waitFor to wait element
  • scrollUntilTapable command : An expectation for checking an element is visible and enabled such that you can click it. Using waitTapable to wait element
  • driver.activateApp(appId) starts the given app and attaches to the observatory URL in the FLUTTER context. The method may raise an exception if no observaotry URL was found. The typical case is the appId is already running. Then, the driver will fail to find the observatory URL.
  • getClipboard and setClipboard depend on each NATIVE_APP context behavior
  • Launch via flutter:launchApp or 3rd party tool (via instrument service) and attach to the Dart VM for an iOS real device (profile build)
    1. Do not set app nor bundleId to start a session without launching apps
    2. Start the app process via 3rd party tools such as go-ios to start the app process with debug mode in the middle of the new session process in 1) the above.
      • Then, the appium flutter session establish the WebSocket and proceed the session
  • keyboard interaction may not work in Android because of https://github.com/flutter/flutter/issues/15415 that is caused by flutter_driver

Capabilities

For the Appium Flutter Driver only

| Capability | Description | Example Values | | - | - | -| | appium:retryBackoffTime | The time wait for socket connection retry to get flutter session (default 3000ms)|500| | appium:maxRetryCount | The count for socket connection retry for get flutter session (default 10) | 20| | appium:observatoryWsUri | The URL to attach to the Dart VM. The Appium Flutter Driver finds the WebSocket URL from the device log by default. You can skip the finding the URL process by specifying this capability. Then, this driver attempt to establish a WebSocket connection against the given WebSocket URL. Note that this capability expects the URL is ready for access by outside an appium session. This flutter driver does not do port-forwarding with this capability. You may need to coordinate the port-forwarding as well. | 'ws://127.0.0.1:60992/aaaaaaaaaaa=/ws' | | appium:isolateId | The isolate id to attach to as the initial attempt. A session can change the isolate with flutter:setIsolateId command. The default behavior finds main isolate id and attaches it. | isolates/2978358234363215, 2978358234363215 | | appium:skipPortForward | Whether skip port forwarding from the flutter driver local to the device under test with observatoryWsUri capability. It helps you to manage the application under test, the observatory URL and the port forwarding configuration. The default is true. | true, false | | appium:remoteAdbHost | The IP/hostname of the remote host ADB is running on. This capability only makes sense for Android platform. Providing it will implicitly override the host for the Observatory URL if the latter is determined from device logs. localhost be default | 192.168.1.20 | appium:adbPort | The port number ADB server is running on. This capability only makes sense for Android platform. 5037 by default | 9999 | appium:forwardingPort | The port number that will be used to forward the traffic from the device under test to locahost. Only applicable if skipPortForward is falsy. Not applicable if the test is executed on iOS Simulator. By default, it is the same as in the provided or autodetected Observatory URL. | 9999

UIA2/XCUITest driver

Please check each driver's documentation

  • https://github.com/appium/appium-uiautomator2-driver
  • https://appium.github.io/appium-xcuitest-driver/latest/capabilities/

Context Management

Appium Flutter Driver allows you to send flutter_driver commands to the Dart VM in the FLUTTER context, but it does not support native Android/iOS since the Dart VM can handle in the Dart VM contents. NATIVE_APP context provides you to use the UIA2 driver for Android and the XCUITest driver for iOS automation. WEBVIEW_XXXX context helps WebView testing over the UIA2/XCUITest driver that is not available via the flutter_driver.

Thus, you need to switch proper contexts, FLUTTER, NATIVE_APP or WEBVIEW_XXXX, to automate a proper application target.

Example

# webdriverio
const wdio = require('webdriverio');
const assert = require('assert');
const { byValueKey } = require('appium-flutter-finder');

const osSpecificOps = process.env.APPIUM_OS === 'android' ? {
  'platformName': 'Android',
  'appium:deviceName': 'Pixel 2',
  'appium:app': __dirname +  '/../apps/app-free-debug.apk',
}: process.env.APPIUM_OS === 'ios' ? {
  'platformName': 'iOS',
  'appium:platformVersion': '12.2',
  'appium:deviceName': 'iPhone X',
  'appium:noReset': true,
  'appium:app': __dirname +  '/../apps/Runner.zip',
} : {};

const opts = {
  port: 4723,
  capabilities: {
    ...osSpecificOps,
    'appium:automationName': 'Flutter',
    'appium:retryBackoffTime': 500
  }
};

(async () => {
  const counterTextFinder = byValueKey('counter');
  const buttonFinder = byValueKey('increment');

  const driver = await wdio.remote(opts);

  if (process.env.APPIUM_OS === 'android') {
    await driver.switchContext('NATIVE_APP');
    await (await driver.$('~fab')).click();
    await driver.switchContext('FLUTTER');
  } else {
    console.log('Switching context to `NATIVE_APP` is currently only applicable to Android demo app.')
  }

  assert.strictEqual(await driver.getElementText(counterTextFinder), '0');

  await driver.elementClick(buttonFinder);
  await driver.touchAction({
    action: 'tap',
    element: { elementId: buttonFinder }
  });

  assert.strictEqual(await driver.getElementText(counterTextFinder), '2');

  driver.deleteSession();
})();

Please check example in this repository for more languages.

Several ways to start an application

You have a couple of methods to start the application under test by establishing the Dart VM connection as below:

  1. Start with app in the capabilities
    1. The most standard method. You may need to start a new session with app capability. Then, appium-flutter-driver will start the app, and establish a connection with the Dart VM immediately.
  2. Start with activate_app: for users who want to start the application under test in the middle of a session
    1. Start a session without app capability
    2. Install the application under test via driver.install_app or mobile:installApp command
    3. Activate the app via driver.activate_app or mobile:activateApp command
      • Then, appium-flutter-driver establish a connection with the Dart VM
  3. Launch the app outside the driver: for users who want to manage the application under test by yourselves
    1. Start a session without app capability
    2. Install the application under test via driver.install_app or mobile:installApp command etc
    3. Calls flutter:connectObservatoryWsUrl command to keep finding an observatory URL to the Dart VM
      • appium:retryBackoffTime and appium:maxRetryCount will control the duration to keep finding an observatory URL to the Dart VM
    4. (at the same time) Launch the application under test via outside the appium-flutter-driver
    5. Once flutter:connectObservatoryWsUrl identify the observatory URL, the command will establish a connection to the Dart VM
  4. Launch the app with flutter:launchApp for iOS and attach to the Dart VM: for users whom application under test do not print the observatory url via regular launch/activate app method
    1. Start a session without app capability
    2. Install the application under test via driver.install_app or mobile:installApp command etc
    3. Calls flutter:launchApp command to start an iOS app via instrument service
      • driver.execute_script 'flutter:launchApp', 'com.example.bundleId', {arguments: ['arg1'], environment: {ENV1: 'env'}} is example usage
      • This launching method is the same as the above 3rd party method, but does the same thing only via the appium flutter driver.

Please make sure the target app process stops before starting the target app with the above.

Changelog

Commands for NATIVE_APP/WEBVIEW context

Please check each driver's documentation

  • https://github.com/appium/appium-uiautomator2-driver
  • https://appium.github.io/appium-xcuitest-driver/latest

Commands for FLUTTER context

Legend:

| Icon | Description | | - | - | | :white_check_mark: | integrated to CI | | :ok: | manual tested without CI | | :warning: | available without manual tested | | :x: | unavailable |

Finders

| Flutter Driver API | Status | WebDriver example | | - | - | - | | ancestor | :ok: | | | bySemanticsLabel | :ok: | | | byTooltip | :ok: | byTooltip('Increment') | | byType | :ok: | byType('TextField') | | byValueKey | :ok: | byValueKey('counter') | | descendant | :ok: | | | pageBack | :ok: | pageBack() | | text | :ok: | byText('foo') |

Commands

The below WebDriver example is by webdriverio. flutter: prefix commands are mobile: command in appium for Android and iOS. Please replace them properly with your client.

| Flutter API | Status | WebDriver example (JavaScript, webdriverio) | Scope | | - | - | - | - | | FlutterDriver.connectedTo | :ok: | wdio.remote(opts) | Session | | checkHealth | :ok: | driver.execute('flutter:checkHealth') | Session | | clearTextbox | :ok: | driver.elementClear(find.byType('TextField')) | Session | | clearTimeline | :ok: | driver.execute('flutter:clearTimeline') | Session | | enterText | :ok: | driver.elementSendKeys(find.byType('TextField'), 'I can enter text') (no focus required) driver.elementClick(find.byType('TextField')); driver.execute('flutter:enterText', 'I can enter text') (focus required by tap/click first) | Session | | forceGC | :ok: | driver.execute('flutter:forceGC') | Session | | getBottomLeft | :ok: | driver.execute('flutter:getBottomLeft', buttonFinder) | Widget | | getBottomRight | :ok: | driver.execute('flutter:getBottomRight', buttonFinder) | Widget | | getCenter | :ok: | driver.execute('flutter:getCenter', buttonFinder) | Widget | | getRenderObjectDiagnostics | :ok: | driver.execute('flutter:getRenderObjectDiagnostics', counterTextFinder) | Widget | | getRenderTree | :ok: | driver.execute('flutter: getRenderTree') | Session | | getSemanticsId | :ok: | driver.execute('flutter:getSemanticsId', counterTextFinder) | Widget | | getText | :ok: | driver.getElementText(counterTextFinder) | Widget | | getTopLeft | :ok: | driver.execute('flutter:getTopLeft', buttonFinder) | Widget | | getTopRight | :ok: | driver.execute('flutter:getTopRight', buttonFinder) | Widget | | getVmFlags | :x: | | Session | | getWidgetDiagnostics | :x: | | Widget | | requestData | :ok: | driver.execute('flutter:requestData', json.dumps({"deepLink": "myapp://item/id1"})) | Session | | runUnsynchronized | :x: | | Session | | setFrameSync |:ok:| driver.execute('flutter:setFrameSync', bool , durationMilliseconds)| Session | | screenshot | :ok: | driver.takeScreenshot() | Session | | screenshot | :ok: | driver.saveScreenshot('a.png') | Session | | scroll | :ok: | driver.execute('flutter:scroll', find.byType('ListView'), {dx: 50, dy: -100, durationMilliseconds: 200, frequency: 30}) | Widget | | scrollIntoView | :ok: | driver.execute('flutter:scrollIntoView', find.byType('TextField'), {alignment: 0.1}) driver.execute('flutter:scrollIntoView', find.byType('TextField'), {alignment: 0.1, timeout: 30000}) | Widget | | scrollUntilVisible | :ok: | driver.execute('flutter:scrollUntilVisible', find.byType('ListView'), {item:find.byType('TextField'), dxScroll: 90, dyScroll: -400});, driver.execute('flutter:scrollUntilVisible', find.byType('ListView'), {item:find.byType('TextField'), dxScroll: 90, dyScroll: -400, waitTimeoutMilliseconds: 20000}); | Widget | | scrollUntilTapable | :ok: | driver.execute('flutter:scrollUntilTapable', find.byType('ListView'), {item:find.byType('TextField'), dxScroll: 90, dyScroll: -400});, driver.execute('flutter:scrollUntilTapable', find.byType('ListView'), {item:find.byType('TextField'), dxScroll: 90, dyScroll: -400, waitTimeoutMilliseconds: 20000}); | Widget | | setSemantics | :x: | | Session | | setTextEntryEmulation | :ok: | driver.execute('flutter:setTextEntryEmulation', false) | Session | | startTracing | :x: | | Session | | stopTracingAndDownloadTimeline | :x: | | Session | | tap | :ok: | driver.elementClick(buttonFinder) | Widget | | tap | :ok: | driver.touchAction({action: 'tap', element: {elementId: buttonFinder}}) | Widget | | tap | :ok: | driver.execute('flutter:clickElement', buttonFinder, {timeout:5000}) | Widget | | traceAction | :x: | | Session | | waitFor | :ok: | driver.execute('flutter:waitFor', buttonFinder, 100) | Widget | | waitForAbsent | :ok: | driver.execute('flutter:waitForAbsent', buttonFinder) | Widget | | waitForTappable | :ok: | driver.execute('flutter:waitForTappable', buttonFinder) | Widget | | waitUntilNoTransientCallbacks | :x: | | Widget | | - | :ok: | driver.execute('flutter:getVMInfo') | System | | - | :ok: | driver.execute('flutter:setIsolateId', 'isolates/2978358234363215') | System | | - | :ok: | driver.execute('flutter:getIsolate', 'isolates/2978358234363215') or driver.execute('flutter:getIsolate') | System | | :question: | :ok: | driver.execute('flutter:longTap', find.byValueKey('increment'), {durationMilliseconds: 10000, frequency: 30}) | Widget | | :question: | :ok: | driver.execute('flutter:waitForFirstFrame') | Widget | | - | :ok: | (Ruby) driver.execute_script 'flutter:connectObservatoryWsUrl' | Flutter Driver | | - | :ok: | (Ruby) driver.execute_script 'flutter:launchApp', 'bundleId', {arguments: ['arg1'], environment: {ENV1: 'env'}} | Flutter Driver |

NOTE flutter:launchApp launches an app via instrument service. mobile:activateApp and driver.activate_app are via XCTest API. They are a bit different.

isolate handling

Change the flutter engine attache to

  1. Get available isolate ids
    • id key in the value of isolates by flutter:getVMInfo
  2. Set the id via setIsolateId
# ruby
info = driver.execute_script 'flutter:getVMInfo'
# Change the target engine to "info['isolates'][0]['id']"
driver.execute_script 'flutter:setIsolateId', info['isolates'][0]['id']

Check current isolate, or a particular isolate

  1. Get available isolates
    • driver.execute('flutter:getVMInfo').isolates (JS)
  2. Get a particular isolate or current isolate
    • Current isolate: driver.execute('flutter:getIsolate') (JS)
    • Particular isolate: driver.execute('flutter:getIsolate', 'isolates/2978358234363215') (JS)

Commands across contexts

These Appium commands can work across context

  • deleteSession
  • setContext
  • getCurrentContext
  • getContexts
  • activateApp('appId')/mobile:activateApp
    • mobile:activateApp has skipAttachObservatoryUrl key to not try to attach to an observatory url. e.g. driver.execute_script 'mobile:activateApp', {skipAttachObservatoryUrl: true, appId: 'com.android.chrome'}
  • terminateApp('appId')/mobile:terminateApp
  • installApp(appPath, options)
  • getClipboard
  • setClipboard

Troubleshooting

  • input texts https://github.com/appium/appium-flutter-driver/issues/417
  • Looks hanging in click https://github.com/appium/appium-flutter-driver/issues/181#issuecomment-1323684510
    • flutter:setFrameSync may help

TODO?

  • [ ] switching context between Flutter and AndroidView
  • [ ] switching context between Flutter and UiKitView
  • [ ] Web: FLUTTER_WEB context?
  • [ ] macOS: with https://github.com/appium/appium-mac2-driver
  • [ ] Windws?
  • [ ] Linux?

Release appium-flutter-driver

$ cd driver
$ sh release.sh
$ npm version <major|minor|patch>
# update changelog
$ git commit -am 'chore: bump version'
$ git tag <version number> # e.g. git tag v0.0.32
$ git push origin v0.0.32
$ git push origin main
$ npm publish

Java implementation

https://github.com/ashwithpoojary98/javaflutterfinder
https://github.com/5v1988/appium-flutter-client