How to Use Flutster

An overall summary for the API case is available here.

Prerequisites

You can use the Flutster plugin with the following prerequisites:

In addition, you can use the Flutster API with an API key from Flutster API.

Integrate the Flutster plugin to your app

In order to record your user interface tests in test records, you will integrate the Flutster plugin as follows.

Add the Flutster plugin dependency to your pubspec.yaml file:

    dependencies:
        flutster:

Update dependencies with the following terminal command:

flutter pub get

Import the package from your Dart code:

import 'package:flutster/flutster.dart';

When using the Flutster API, consider the following initialization Dart code in your app:

FlutsterTestRecord.defaultRecord.apiUrl="https://flutster.com";

//Create a user at https://flutster.com
FlutsterTestRecord.defaultRecord.apiUser="Your Flutster User";

//Get your API key on the profile page at https://flutster.com
FlutsterTestRecord.defaultRecord.apiKey="Your Flutster API Key";
//The API key is made of 60 characters 0-f

Flutster is not meant to be used in a production situation. Such a Dart line takes care of this:

//Avoid using Flutster in production
FlutsterTestRecord.defaultRecord.active=!prod;

Optionally, adapt the size of the Flutster button to your screen with this Dart line:

//Set the Flutster button size
FlutsterTestRecord.defaultRecord.buttonSize=30.0;

Replace or place one Scaffold per page you want to test with a FlutsterScaffold. For example:

    return FlutsterScaffold(
      name: "myUniqueWidgetName",
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ...
    );

Where name is used to identify the Scaffold in case there is more than one on the screen.

If you don’t have a Scaffold on a screen you want to test, you can use the following instead:

FlutsterTestRecorder(
  name: "myUniqueWidgetName",
  child: myWidget,
)

Make your text field cursor invisible

We recommend making the text field cursor invisible to increase the chances for the screenshots to match. This can be done by setting the app theme cursorColor to the same color as the background as follows:

      MaterialApp(
          theme: ThemeData(
        textSelectionTheme: TextSelectionThemeData(
          cursorColor: Colors.white,
        ),
...
      ));

Create your test record

Once the Flutster plugin is integrated to your Android application, run the application either on an Android emulator or on a physical device with the help of scrcpy:

App screen with the Flutster floating button in the upper left corner of the screen

Click the blue square Flutster floating button with an equal sign in the upper left corner of the screen:

This opens the Flutster test recorder menu:

Flutster test recorder menu

See all the details of the Flutster test recorder menu here.

Click on the blue circled square button to start recording a Flutster test record:

Flutster test recorder menu in recording state

When in recording state, the Flutster test recorder menu displays a red disk instead of the start recording button:

Close the Flutster test recorder menu:

Interact with your app to record the test with the following limitations:

  • Only the physical keyboard is recorded, not the software onscreen keyboard. This is why scrcpy is necessary for physical devices.
  • Typed texts in text fields will replace the existing content of the text field when the test record will be run.
  • Mouse interactions are recorded only with start – end points and durations. This means that scrolling and drag and dropping may not be replicated accurately.
Flutster floating button while recording

While recording, the Flutster floating button is a red round disk with an equal sign inside.

Once done with your test, double click the recording Flutster floating button to take a screenshot:

The screenshot is the way to validate the test result.

Click on the recording Flutster floating button:

Then, click on the red disk button to stop the test recording:

Optionally, give a name to your test record:

Flutster test recorder menu giving a name to your test record

If you recorded keyboard interactions, it is recommended to combine the key events with the blue sheets button displayed on such event:

Save your test record

There are 2 ways to save your test record:

  • If you set the Flutster API parameters with a user name, API key and server, you can save to the Flutster API with the up arrow in a blue cloud button . Take note of API test record id for later use:

or

  • Copy the test record json code to the clipboard or share it through other means depending on your Android installation with the blue share button .

Run your test record

Create a folder called integration_test in your application folder and place test file inside:

Android Studio: Integration test folder

If you are using the API, here is an example of content for the test file:

import 'package:buddhaplate/main.dart' as theApp;
import 'package:flutster/flutster.dart';
import 'package:flutter/foundation.dart';
import 'package:integration_test/integration_test.dart';
import 'package:flutter_test/flutter_test.dart';

void main() async {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  TestWidgetsFlutterBinding.ensureInitialized();
  testWidgets('Integration testing based on Flutster API',
          (WidgetTester tester) async {

    // Start the app
    theApp.main(testing: true);
    await tester.pumpAndSettle();

    // Simply using the default recorder.
    FlutsterTestRecord record = FlutsterTestRecord.defaultRecord;
    record.apiUrl = "https://flutster.com";
    record.apiUser = "Your Flutster User";//Create a user at https://flutster.com
    record.apiKey =
    "Your Flutster API Key";//The API key is made of 60 characters 0-f

    // Loading and running the first record.
    String loadResult = await record.fromApi(50, tester: tester);
    if (loadResult != "Test record loaded from API") {
      debugPrint(loadResult);
      expect(false, true, reason: loadResult);
    }
    bool result = await record.playToApi(tester);
    expect(result, true, reason: "API test 50 over with this result");

    // Loading and running the second record.
    loadResult = await record.fromApi(52, tester: tester);
    if (loadResult != "Test record loaded from API") {
      debugPrint(loadResult);
      expect(false, true, reason: loadResult);
    }
    result = await record.playToApi(tester);
    expect(result, true, reason: "API test 52 over with this result");
  });
}

If, instead of the API, you are using the json content recorded earlier, please consider the following example test file:

import 'package:buddhaplate/main.dart' as theApp;
import 'package:flutster/flutster.dart';
import 'package:flutter/foundation.dart';
import 'package:integration_test/integration_test.dart';
import 'package:flutter_test/flutter_test.dart';

void main() async {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  TestWidgetsFlutterBinding.ensureInitialized();
  testWidgets('Integration testing based on Flutster json',
          (WidgetTester tester) async {
        // Start the app
        theApp.main(testing: true);
        await tester.pumpAndSettle();

        // Simply using the default recorder.
        FlutsterTestRecord record = FlutsterTestRecord.defaultRecord;
        record.apiUrl = "https://me:moitiemoitie@dev.flutster.com";
        record.apiUser = "louis";
        record.apiKey =
        "e23456789012345678901234567890123456789012345678901234567890";

        record.fromJson(
          '{"testName":"buddhaplate reminders","firstRecordingStart":1655313129211,"events":[{"type":"tap","waitDuration":1487,"time":1655313154032,"widgetName":null,"tapStart.dx":89.06666666666666,"tapStart.dy":403.73333333333335,"tapDuration":103,"tapStop.dx":89.06666666666666,"tapStop.dy":403.73333333333335},{"type":"key","waitDuration":3291,"time":1655313157328,"widgetName":null,"logicalKey.keyId":50,"physicalKey.usbHidUsage":73014444032,"keyEvent.duration":233675750,"typedText":"12","keyEventDown":false,"character":"","logicalKey.keyLabel":"2"},{"type":"tap","waitDuration":1337,"time":1655313158665,"widgetName":null,"tapStart.dx":238.93333333333334,"tapStart.dy":283.73333333333335,"tapDuration":94,"tapStop.dx":238.93333333333334,"tapStop.dy":283.73333333333335},{"type":"screenShot","waitDuration":1961,"time":1655313165640,"widgetName":"reminderScreen","screenShot":"iVBORw...K5CYII=","screenShotComparisonFunctionName":"Pixel - Matching","screenShotAcceptancePctThreshold":99.999,"pixelMatchingTolerance":0.02,"IMEDBlurRatio":0.005,"IMEDSigma":1.0}]}',
          tester: tester,
        );
        bool result = await record.play(tester);
        expect(result, true, reason: "json test over with this result");
      });
}

Note that the json in the above test example has been simplified. In particular, the screenshot isn’t complete.

Edit the run configuration in Android Studio:

Android Studio: Edit configuration

Add a Flutter test configuration:

Android Studio: Add a Flutter test configuration

Set your test file to the run configuration:

Android Studio: Run configuration

Validate and select the newly created run configuration:

Android Studio: Select test run configuration

Run or debug the Flutster test run configuration:

Android Studio: Run or debug Flutster run configuration

Follow the run on the device screen and check the results in Android Studio:

Android Studio: Run result

When using the Flutster API, the test run is visible on flutster.com next to the test record and allows the comparison of screenshots:

Flutster: compare test run and test record

You can replace the test record with the test run in case they differ using the Flutster API interface: