flutter_driver_helper 1.1.1

  • Readme
  • Changelog
  • Example
  • Installing
  • 68

Flutter Driver Helper #

pub package

Utility for easy work with flutter_driver in UI / integration tests

Article with description (Russian). #

Advantages #

  • With screens (Page Objects) we separate UI-elements from actions and can reuse these screens in several tests.
  • Also we get rid of await on every line, that was easy to forget to write.
  • Logging actions while testing

Full example is in example dir. #

Example #

Flutter Driver Helper has several useful utilities for writing UI / integration tests.

From Android's point of view, Flutter Driver is like Espresso, while Flutter Driver Helper acts like Kakao.

Usual Flutter Driver test looks like

final list = find.byValueKey("list");
final item42 = find.byValueKey("item_42");
final secondScreen = find.byValueKey("second_screen");

await driver.tap(secondScreen);
await driver.waitForAbsent(item42);
await driver.scrollUntilVisible(list, item42);
await driver.waitFor(item42);
await driver.tap(find.pageBack());

This may be rewritten with Flutter Driver Helper to more readable style:

class MainScreen extends BaseScreen {
  MainScreen(FlutterDriver driver) : super(driver);

  DWidget get secondScreen => dWidget('second_screen');
}

class SecondScreen extends BaseScreen {
  SecondScreen(FlutterDriver driver) : super(driver);

  DWidget get list => dWidget("list");

  DScrollItem item(int index) => dScrollItem('item_$index', list);
}

...

final mainScreen = MainScreen(driver);
final secondScreen = SecondScreen(driver);

await runTestActions([
  mainScreen.secondScreen.tap(),
  secondScreen.item(42).waitForAbsent(),
  secondScreen.item(42).scrollUntilVisible(dyScroll: -300),
  secondScreen.item(42).waitFor(),
  secondScreen.pageBack.tap(),
]);

Logging #

Method runTestActions logs executed actions like this:

Running check result hasText summa = 0
Running setText 12 on field_1
Running check result hasText summa = 12
Running tap on field_2
Running take screenshot field_2_variants
Running tap on variant_4
Running check result hasText summa = 16
Running tap on button_snackbar
Running tap on variant_2
Running check result hasText summa = 5
Running scrollUntilVisible list item_42

Screenshots #

For convenient screenshots making diring UI tests You can use Screenshoter.

[1.1.1] - 27.01.2020. #

  • idle as TestAction.
  • new checks in DWidget: textContains, textStartsWith, textEndsWith.
  • allow disable indices in screenshoter.
  • add DWidget#scroll.
  • in BaseScreen methods dWidget & dScrollItem are protected.

[1.1.0] - 15.01.2020. #

  • TestAction is now class.
  • logging in runTestActions.

[1.0.2] - 12.01.2020. #

  • Fix health issue.

[1.0.1] - 12.01.2020. #

  • Updated example.
  • TestAction returns Future<void>.
  • getCenter method in DWidget.

[1.0.0] - 28.12.2019. #

  • Initial Open Source release.

example/lib/main.dart

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'test_hooks.dart';

void setTargetPlatformForDesktop() {
  if (Platform.isLinux || Platform.isWindows) {
    debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
  }
}

void main() {
  setTargetPlatformForDesktop();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      key: ValueKey("app"),
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
      routes: {
        "second_screen": (_) => SecondScreen(),
      },
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int i1 = 0;
  int i2 = 0;
  TimeOfDay timeOfDay;
  bool ch = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: <Widget>[
              TextField(
                key: ValueKey("field_1"),
                keyboardType: TextInputType.number,
                onChanged: (s) => setState(() => i1 = int.parse(s)),
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: "first number",
                ),
              ),
              SizedBox(height: 12),
              DropdownButtonFormField<int>(
                key: ValueKey("field_2"),
                items: List.generate(20, (i) {
                  return DropdownMenuItem<int>(
                    child: Text(
                      "it is $i",
                      key: ValueKey("variant_$i"),
                    ),
                    value: i,
                  );
                }),
                value: i2,
                onChanged: (i) => setState(() => i2 = i),
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: "second number",
                ),
                isDense: true,
              ),
              SizedBox(height: 12),
              Text(
                'summa = ${i1 + i2}',
                key: ValueKey("result"),
                style: TextStyle(fontSize: 20.0),
              ),
              SizedBox(height: 12),
              SnackbarButton(() => setState(() => i2 = 7)),
              SizedBox(height: 12),
              FlatButton(
                key: ValueKey("second_screen"),
                onPressed: () => Navigator.pushNamed(context, "second_screen"),
                child: Text("Second screen"),
              ),
              SizedBox(height: 12),
              FlatButton(
                key: ValueKey("select_time"),
                onPressed: () async {
                  final time = await selectTime(
                    context: context,
                    initialTime: TimeOfDay(hour: 19, minute: 18),
                  );
                  if (time != null) {
                    setState(() => timeOfDay = time);
                  }
                },
                child: Text("Select time"),
              ),
              if (timeOfDay != null) ...[
                SizedBox(height: 12),
                Text(
                  timeOfDay.toString(),
                  key: ValueKey("time"),
                  style: TextStyle(fontSize: 20.0),
                ),
              ],
              SwitchListTile(
                key: ValueKey("ch_switch"),
                value: ch,
                onChanged: (ch) => setState(() => this.ch = ch),
              ),
              if (ch) ...[
                SizedBox(height: 12),
                Text(
                  "some_text",
                  key: ValueKey("some_text"),
                  style: TextStyle(fontSize: 20.0),
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }
}

class SnackbarButton extends StatelessWidget {
  final Function() onSnackbarAction;

  SnackbarButton(this.onSnackbarAction);

  @override
  Widget build(BuildContext context) {
    return MaterialButton(
      color: Colors.red,
      key: ValueKey("button_snackbar"),
      child: Text("snackbar"),
      onPressed: () {
        Scaffold.of(context).showSnackBar(
          SnackBar(
            content: Text(
              "snackbar_text",
              key: ValueKey("snackbar_text"),
            ),
            action: SnackBarAction(
              key: ValueKey("action_make_7"),
              label: "make 7",
              onPressed: onSnackbarAction,
            ),
          ),
        );
      },
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second screen"),
      ),
      body: Scrollbar(
        child: ListView.builder(
          key: ValueKey("list"),
          itemBuilder: (context, index) => Padding(
            padding: const EdgeInsets.all(14.0),
            child: Text(
              "Item #$index",
              key: ValueKey("item_$index"),
            ),
          ),
          itemCount: 10000,
        ),
      ),
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  flutter_driver_helper: ^1.1.1

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:flutter_driver_helper/flutter_driver_helper.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
35
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
68
Learn more about scoring.

We analyzed this package on Mar 27, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.6
  • Flutter: 1.12.13+hotfix.8

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
flutter_driver 0.0.0
meta ^1.1.8 1.1.8
test ^1.9.4 1.9.4 1.14.2
Transitive dependencies
_fe_analyzer_shared 1.0.3
analyzer 0.39.4
archive 2.0.11 2.0.13
args 1.5.2 1.6.0
async 2.4.0 2.4.1
boolean_selector 1.0.5 2.0.0
charcode 1.1.2 1.1.3
collection 1.14.11 1.14.12
convert 2.1.1
coverage 0.13.9
crypto 2.1.3 2.1.4
csslib 0.16.1
file 5.1.0
flutter_test 0.0.0
fuchsia_remote_debug_protocol 0.0.0
glob 1.2.0
html 0.14.0+3
http 0.12.0+4
http_multi_server 2.2.0
http_parser 3.1.4
image 2.1.4 2.1.12
intl 0.16.0 0.16.1
io 0.3.3
js 0.6.1+1
json_rpc_2 2.1.0
logging 0.11.4
matcher 0.12.6
mime 0.9.6+3
multi_server_socket 1.0.2
node_interop 1.0.3
node_io 1.0.1+2
node_preamble 1.4.8
package_config 1.9.3
package_resolver 1.0.10
path 1.6.4
pedantic 1.8.0+1 1.9.0
petitparser 2.4.0 3.0.1
platform 2.2.1
pool 1.4.0
process 3.0.12
pub_semver 1.4.2 1.4.4
quiver 2.0.5 2.1.3
shelf 0.7.5
shelf_packages_handler 1.0.4 2.0.0
shelf_static 0.2.8
shelf_web_socket 0.2.3
sky_engine 0.0.99
source_map_stack_trace 1.1.5 2.0.0
source_maps 0.10.9
source_span 1.5.5 1.7.0
stack_trace 1.9.3
stream_channel 2.0.0
string_scanner 1.0.5
term_glyph 1.1.0
test_api 0.2.11 0.2.15
test_core 0.2.15 0.3.3
typed_data 1.1.6
vector_math 2.0.8
vm_service 2.3.1 4.0.0
vm_service_client 0.2.6+2 0.2.6+3
watcher 0.9.7+14
web_socket_channel 1.1.0
xml 3.5.0 3.7.0
yaml 2.2.0