How to do widget properties testing in Flutter while you run E2E tests

Lets take a very generic flutter test of the default flutter application from the basic tutorial (https://techwithsach.com/code-snippets/default-flutter-application-main-dart/):

import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {
group('Counter App', () {
final counterTextFinder = find.byValueKey('counter');
final buttonFinder = find.byValueKey('increment');

FlutterDriver driver;

setUpAll(() async {
driver = await FlutterDriver.connect();
});

tearDownAll(() async {
driver?.close();
});

test('starts at 0', () async {
expect(await driver.getText(counterTextFinder), "0");
});

test('increments the counter', () async {
await driver.tap(buttonFinder);
expect(await driver.getText(counterTextFinder), "1");
});
});
}

In order to run this test with our application we need to enable the flutter driver extension in it and that is how you probably do it already in your E2E tests:

import 'package:flutter_driver/driver_extension.dart';
import 'package:my_app/main.dart' as app;

void main() {
enableFlutterDriverExtension();
app.main();
}

But now lets imagine that you need to test button background color or form background or text font. How would you do it within your flutter driver? Some may say it is impossible but if you know how to extend your flutter driver extension functionality you can easily do the widgets’ properties testing within your E2E test.

So lets see how can you extend the flutter driver functionality. If you use some data handler within your enableFlutterDriverExtension function you can extend it with custom commands that can be sent from tests to app in order to trigger some functionality within your app. Example:

enableFlutterDriverExtension(handler: (payload) async {
if(payload == "myOwnCommand") {
// TODO: I can do anything here
}
});

If we define a data handler separately for convenience it can return something back into the test:

Future<String> dataHandler(String msg) async {
switch (msg) {
case "myOwnCommand": {
// TODO: I can do anything here
return "Result";
}
default:
throw AssertionError("No such command");
}
}
enableFlutterDriverExtension(handler: dataHandler);

Now within your test you may call the requestData method of the FlutterDriver in order to send that command to the application and get something out of it:

test('Check button color', () async {
String result = await driver.requestData("myOwnCommand");
// TODO: Place to verify button color
});

Now lets get back to the application. We need to be able to find our button widget within it. Reverse-engineering the internals of flutter driver extension I found a function called collectAllElementsFrom. It is used by the flutter driver within its internal widget finders. We can reuse it and write a simple function that can return list of any requested type of widgets that we need.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test/src/all_elements.dart';
List<T> findWidgets<T extends Widget>() =>
collectAllElementsFrom(
WidgetsBinding.instance.renderViewElement,
skipOffstage: false)
.map((e) => e.widget)
.whereType<T>()
.toList();

This function uses flutter_test package functionality collectAllElementsFrom and loops through the rendered elements in the elements tree returning the list of all related widgets that has specified type. Sounds hard but works like a charm. This coding style should be more familiar to developers with Java and C++ experience.

Pay attention to how this function looks like. It is bread and butter of this approach. You can use this function like List<Text> wdg = findWidgets(); and function will return list of all Text widgets. You can use it then like List<RichText> wdg = findWidgets(); and it will return list of RichText widgets without any changes in the function itself. Almost magic!

Now in our dataHandler function in order to search for any widget we may do following:

case "myOwnCommand": {
List<FloatingActionButton> btns = findWidgets();
FloatingActionButton btn = btns.first;
return btn.backgroundColor.value.toString;
}

Then in our test:

test('Check button color', () async {
String result = await driver.requestData("myOwnCommand");
expect(EXPECTED_COLOR, int.parse(result));
});

And as you can see here, flutter driver extension gives you possibility to verify ANY property of ANY widget and even interact with those.

You need to get the font of some text widget? No problem:

case "myOwnCommand2": {
List<Text> txts = findWidgets();
Text txt = txts.first;
return txt.style.fontFamily;
}

Now you just verify that value within your test and that is all. Does not seems so hard and impossible now, right?

Well awesome tool, in general )