Simplifying Flutter App Automation
Tags: flutter, javascript, tutorial, mobile
Published: 2022-11-24 17:17:03

Have you been struggling to automate Flutter Apps? Are you already weary of the numerous materials on the basic Flutter Counter App? Let's change that narrative and try a public app. What will this cost you? Nothing, just your attention (because this is quite a read) and a follow on some GitHub links for now(and a youtube channel).

My aim is to make this as simple as possible and also fast. Every tester's biggest nightmare is automating mobile apps. Not because it is that hard but because setting up the automation environment for either platform isn't the simplest of tasks.

Then there is the headache of writing separate scripts for iOS and Android. This was a headache shared by both the developers and the testers. The advent of cross-platform technology became a breath of fresh air but it had its bottlenecks. Thanks to appium and the awesome contributors of this open-source tool, these bottlenecks are gradually dissipating. There are two notable cross-platform tools out there and they are react native and flutter. I shall be walking us through how to automate the latter in simple steps.

It is important to mention that Flutter as a tool, comes with its own automated testing feature called the Flutter Driver but this isn't embraced so much in the testing community because of the following;

- Learning Dart Language (the language behind flutter) is essential. This is a huge learning curve.
- It is more or less a white-box testing which most testers shy away from or hardly do.

That said, appium has swept in to save the day by creating a tool that uses appium but still employs the Dart VM required to test flutter apps. Over time, based on what I have seen, appium will extend the languages that can be used to automate flutter which means people can stick to the language they are most convenient with. (This is already out). The most convenient so far is using appium with WedDriverIO.
It is safe to assume that you have node installed and running on your system, if not, find a link to get node [here](https://nodejs.org/en/download/)


### _Let's get Started_
For this demo, you should have set up appium on your system and all other flutter dependencies. To setup appium, follow the links to set it up on a [Windows](https://www.linkedin.com/pulse/setting-up-appium-windows-ivan-tay/) or [MacBook](https://www.linkedin.com/pulse/setting-up-appium-mac-os-x-ivan-tay) devices. Install appium-doctor before you begin setup, run appium-doctor after completing the setup to know if there are dependencies missing, the optional dependencies aren't show stoppers so you can ignore them for this demo. A successful setup should look like the image below.


![A successful appium check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/baqgx4vsh97xop72cfj4.jpg)
You get green ticks when all is setup successfully.

After this has been setup, download VsCode, install and launch it. Open Terminal inside the VsCode and let us begin. Run the `**npm init -y**` command to create a package.json file then install appium, @wdio/cli, appium-flutter-driver, and appium-flutter-finder. You can install all of them as dev-dependencies. `**npm i -D appium appium-flutter-driver appium-flutter-finder @wdio/cli.**`

Appium is needed as the server to run the mobile test, @wdio/cli is needed to be able to use webdriverIO commands from cli, appium flutter-driver would allow us to have a remote driver to work on the app, appium-flutter-finder will help locate elements and allow the driver to interact with it. The beauty of this setup is that the Desired Capabilities required to run the mobile tests can be created inside a wdio.conf.js but let us pause and not create a wdio.conf.js yet. Let's get a flutter app and build an apk and  iOS app. As a sign of gratitude kindly follow [Mtconcept](https://github.com/Mtconcept) and clone this shopping [repo](https://github.com/Mtconcept/store_ui)

Download Flutter and set tool to Path from [here](https://docs.flutter.dev/get-started/install/macos)

Macbook will be used in this illustration as it would be possible to setup the iOS app and Android apk on the intended Flutter Project. (Only a MacBook runs iOS configs).

First navigate into the cloned project and make a tweak to allow Appium to access the app through the flutter-driver APIs. In the pubspec.yaml file, make this change while in the main.dart file make this change. When this is done, run `**flutter pub get**` to update these changes.


![modified pubspec.yaml](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/siqdoquyjydrsp31qw9l.jpg)

pubscpec.yaml modification


![modified dart file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clludg484z953swln7nl.jpg)

main.dart file modification

To get the apk needed, open your Android Studio, and Launch the emulator, then run `**flutter run**`. You can close the emulator and android studio for now.

To get the iOS runner app, run `**open ios/Runner.xcworkspace**` to launch the X-Code app, click the runner workspace to reveal the runner project and runner target. Check that you're signed in on X-Code with your apple ID, if you're not, sign in. Click the Runner under target, the signing & capabilities tab, select your ID under teams and ensure that the Bundle Identifier is `**com.example.fashionShopUi**`


![Xcode config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/164rjga4s8czq6xctzwm.jpg)

Xcode Config

Have a simulator launched from the workspace then run `**flutter run**` command then close the simulator. 
This would allow the app to create an observatory uri, which is important to the accessibility of the app to be used by appium.

These actions will create a debug build for the iOS and Android platforms. The Android apk should be
`**../build/app/outputs/flutter-apk/app-debug.apk**` while the iOS would be `**../build/ios/iphonesimulator/Runner.app**`. These locations would be added to the appium:app capabilities during configuration of the `**wdio.confs.js**` files. You can copy both files into the root directory of your project if you wish. The location of these apps in the pipeline when you deploy will be entirely up to you anyways but it is best you put  it where it would be easy to configure in the pipeline. This instance, I will use an absolute path to the build file  copied into the project.


![created apps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kp5jzdmkwspes7nu6ush.jpg)

apk and runner app created

Run `**npx wdio config**` to begin the wdio.conf.js file setup.  Look at the terminal closely for selection instructions. Select;

1. On my local machine as the automation backend.
2. Mocha as the framework to use (or anyone you're familiar with)
3. We are using Javascript so no compiler is required so hit enter
4. Allow the config to create a test destination so hit enter again
5. For this example, we will opt out of getting autogenerated test files so hit enter(I use it, because I am lazy. Come on, don't judge me)
6. You can select your preferred reporter, there are plenty, I leave it at the default spec reporter
7. Opt out of using plugin
8. Deselect chromedriver as a service, scroll down and select appium
9. Leave base url as local host.
Type **Y** to allow `**npm install**`

After this, install **assert** as a dev-dependency to validate and verify some actions. `**npm i -D assert**`

Create a directory named test, inside it, create another directory and name it specs, then create a JavaScript file like <filename>.e2e.js (Naming convention best practise)

Make a duplicate of the wdio.conf.js file, rename both as **wdio.ios.conf.js** and **wdio.android.conf.js** respectively.


![modified config files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r1gdoxrjhjnr3w0nuv4l.jpg)

Modify the contents as follows.

```
exports.config = {
    // ====================
    // Runner Configuration
    // ====================
    runner: 'local',
    path: '/wd/hub',
    port: 4723,
    
    specs: [
        './test/specs/**/*.js'
    ],
    
    exclude: [
        // 'path/to/excluded/files'
    ],
    // ============
    // Capabilities
    // ============
    // Define your capabilities here. WebdriverIO can run multiple capabilities at the same time.
    maxInstances: 10,
    capabilities: [{
        platformName: 'Android',
        'appium:deviceName': 'emulator-5554',
        'appium:automationName': 'Flutter',
        'appium:app': '../build/app/outputs/flutter-apk/app-debug.apk',
        maxInstances: 5
    }],
    logLevel: 'info',
    bail: 0,
    baseUrl: 'http://localhost',
    waitforTimeout: 10000,
    connectionRetryTimeout: 120000,
    connectionRetryCount: 1,
    services: ['appium'],
    framework: 'mocha',
    reporters: ['spec'],
    mochaOpts: {
        ui: 'bdd',
        timeout: 60000
    },
}
```
**wdio.android.conf.js**

```
exports.config = {
    // ====================
    // Runner Configuration
    // ====================
    runner: 'local',
    path: '/wd/hub',
    port: 4723,
    specs: [
        './test/specs/**/*.js'
    ],
    exclude: [
        // 'path/to/excluded/files'
    ],
    // ============
    // Capabilities
    // ============
    // Define your capabilities here. WebdriverIO can run multiple capabilities at the same time.
    maxInstances: 10,
    capabilities: [{
        platformName: 'iOS',
        'appium:platformVersion': '16.1',
        'appium:deviceName': 'iPhone 14',
        'appium:noReset': false,
        'appium:automationName': 'Flutter',
        'appium:app': '../build/ios/iphonesimulator/Runner.app',
        maxInstances: 5
    }],
    logLevel: 'info',
    bail: 0,
    baseUrl: 'http://localhost',
    waitforTimeout: 10000,
    connectionRetryTimeout: 120000,
    connectionRetryCount: 1,
    services: ['appium'],
    framework: 'mocha',
    reporters: ['spec'],
    mochaOpts: {
        ui: 'bdd',
        timeout: 60000
    },
}
```
**wdio.ios.conf.js**

### _Identifying Elements in Flutter app._

Unlike the the traditional iOS and Android apps whose elements are identified by resource ID, Accessibility ID, Name, Xpath , iOSPredicate , and iOSClassChain, flutter elements are identified differently and are the same elements on both platforms. Elements are identified on Flutter apps by Tooltip, Text, Type, ValueKey and a few more. 
To get this identification, you can launch the app under test on an emulator, in the cloned project where the apk was made, you can run the app through debug by Starting the app in debug mode.


![start debugging](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zsw0ou7z21cfnlhzjia.jpg)

After which you select Dart & Flutter as the config


![select config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0uvyxalhldm79aiijnf.jpg)

Then select the emulator to be debugged


![select emulator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v3d923w8jkm08weew3th.jpg)

Once this is done, you can start the debug by clicking the flutter icon


![flutter inspector starter icon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzwc3v2e6wvjconq1s0o.jpg)

This will open the Flutter Inspector Layout


![Flutter Inspector layout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3po8dxv4j0prhq97c7vm.jpg)

Click the select Widget mode to be able to select and display elements on the screen displayed on the emulator


![Widget mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2kv7858zn1g8tcifd1gc.jpg)

When an element is selected, the file where the code is displayed along with the element layout on the inspector and one gets to pick a means of Identification.


![Layout Format](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q0fe3vyl5als2kyvqu6o.jpg)

In this example, two identification types were used. In your e2e.js file, you should have a code that looks like this;

```
const find = require('appium-flutter-finder')
const assert = require('assert')
describe('First Test on Flutter', ()=>{
    it('Launch a page and Press the Button',  async ()=> {
        const button = find.byText('Get Started')
        assert.strictEqual(await driver.getElementText(find.byText('Get Started')),'Get Started')
        await driver.elementClick(button)
        await driver.execute('flutter:waitFor', find.byText('ExYu Market'))
        await driver.elementSendKeys(find.byType('TextFormField'), 'Jeans')
    })
})
```

The Code basically finds the Get started button, verifies the Text on the button, clicks it, then waits for the next screen to type "Jeans" in the text field.
When this is done, it is time to test that this works.

#### _Android_
Launch Android Studio, and launch the emulator then run npx wdio run wdio.android.conf.js the emulator will launch the app and when it has passed, you see the image below


![Android Run Execution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vqe07fo1estj470mrjd.jpg)

**Successful Android Run**

#### _iOS_

Launch the Runner on Xcode from the parent project with open ios/Runner.xcworkspace then launch a simulator and run npx wdio run wdio.ios.conf.js the simulator will launch the app and when it passes, you will see the image below.

![iOS Run](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/amx86pju0oi9odt2ozqt.jpg)

**Successful iOS Run**

Follow me on [GitHub](https://github.com/bhadmus) to see more and subscribe to my [YouTube channel](https://www.youtube.com/@bhadmusautomates) to see a playlist of a more details steps to create an automated test for flutter apps using appium.