blob: 08eb39fd87e0a26b9e98b0b01118d00f54290e0b [file] [log] [blame] [view]
# Mod integration testing with Topaz
[TOC]
## Introduction
This step-by-step guide is for running integration tests using
[Flutter Driver](https://docs.flutter.io/flutter/flutter_driver/flutter_driver-library.html)
in Topaz. If you are not looking to run integration testing on your mods, or if
your mod is not written using Flutter, then you dont need this guide.
This is different from unit testing with widgets because the expectation is that
you will be testing simulated user interaction with your mod (tapping buttons,
scrolling, etc, for example), which requires Scenic and cannot be run in QEMU.
The examples in this doc will be focused around a testing mod under
[`//topaz/examples/test/driver_example_mod`](https://fuchsia.googlesource.com/topaz/+/HEAD/examples/test/driver_example_mod).
The name is derived from how it is an example mod relating to the use of
[Flutter Driver](https://docs.flutter.io/flutter/flutter_driver/flutter_driver-library.html).
In addition, you'll see how to set up a hermetic test with Fuchsia
[component testing](/docs/development/tests/test_component.md).
The ultimate goal of this document is to make it possible for you to add
integration tests into
[`//topaz/tests`](https://fuchsia.googlesource.com/topaz/+/HEAD/tests) so that
your mod can be tested in CQ and CI.
## Setup
To start, this doc assumes you’ve already got a mod that you can run on Topaz
(see
[here](/docs/getting_started.md)
if you haven't). For simplicity, well assume it is a standalone mod that
doesnt depend on other mods (in the future this will have been tested and
verified).
Per the introduction section, well be focusing on `driver_example_mod` as the
mod under test.
### Enabling Flutter Driver extensions
If you want to simulate user interaction with your mod, or capture a screenshot
of a certain state you are interested in, you will need to enable the flutter
driver extensions before you can start using the flutter driver. This is done by
adding `flutter_driver_extendable = true` to your `flutter_app` target in the
[`BUILD.gn`](https://fuchsia.googlesource.com/topaz/+/HEAD/examples/test/driver_example_mod/BUILD.gn)
for your mod:
```gn
flutter_app("driver_example_mod") {
// ...
flutter_driver_extendable = true
// ...
}
```
In a debug JIT setting, this will generate a wrapper for your
[`main`](https://fuchsia.googlesource.com/topaz/+/HEAD/examples/test/driver_example_mod/lib/main.dart)
that calls
[`enableFlutterDriverExtension()`](https://docs.flutter.io/flutter/flutter_driver_extension/enableFlutterDriverExtension.html)
from
[`package:flutter_driver/driver_extension.dart`](https://docs.flutter.io/flutter/flutter_driver_extension/flutter_driver_extension-library.html).
(See also:
[`gen_debug_wrapper_main.py`](https://fuchsia.googlesource.com/topaz/+/HEAD/runtime/flutter_runner/build/gen_debug_wrapper_main.py))
## Writing your tests
Next youll get to the exciting part: writing the tests for your mod! These will
require use of the aforementioned
[Flutter Driver](https://docs.flutter.io/flutter/flutter_driver/flutter_driver-library.html)
library.
Tests live in a `test` subfolder of your mod and end in `_test.dart`. These
requirements are stipulated by
[`dart_fuchsia_test`](https://fuchsia.googlesource.com/topaz/+/master/runtime/dart/dart_fuchsia_test.gni),
described [later](#build_gn-target). The tests for `driver_example_mod` are in
[`driver_example_mod_test.dart`](https://fuchsia.googlesource.com/topaz/+/HEAD/examples/test/driver_example_mod/test/driver_example_mod_test.dart).
### Boilerplate
Youll need some boilerplate to set up and tear down your code. The following
helper function starts a basemgr with dev shells, allowing you to inject your
mod as the root view. This helper will eventually be factored into a Modular
Framework service call.
```dart
import 'package:fidl/fidl.dart';
import 'package:fidl_fuchsia_sys/fidl_async.dart';
import 'package:fuchsia_services/services.dart';
const _basemgrUrl = 'fuchsia-pkg://fuchsia.com/basemgr#meta/basemgr.cmx';
// Starts basemgr with dev shells. This should be called from within a
// try/finally or similar construct that closes the component controller.
Future<void> _startBasemgr(
InterfaceRequest<ComponentController> controllerRequest,
String rootModUrl) async {
final context = StartupContext.fromStartupInfo();
final launchInfo = LaunchInfo(url: _basemgrUrl, arguments: [
'--base_shell=fuchsia-pkg://fuchsia.com/dev_base_shell#meta/dev_base_shell.cmx',
'--session_shell=fuchsia-pkg://fuchsia.com/dev_session_shell#meta/dev_session_shell.cmx',
'--session_shell_args=--root_module=$rootModUrl',
'--story_shell=fuchsia-pkg://fuchsia.com/dev_story_shell#meta/dev_story_shell.cmx',
'--test',
'--enable_presenter',
'--run_base_shell_with_test_runner=false'
]);
await context.launcher.createComponent(launchInfo, controllerRequest);
}
```
The first four options start basemgr with dev shells, which simply start and
display the given `root_module` using the Modular framework.
The `--test` option prevents basemgr from overriding your command-line options
with the on-device base shell configuration, but then requires the next two
options to turn off test-only behavior we don't want. `--enable_presenter`
allows basemgr to be a root presenter (display) as it is in production, and
`--run_base_shell_with_test_runner=false` prevents it from needing to connect to
the `TestRunner` service (used for Modular multiprocess testing).
In your test setup, you'll need to start basemgr with your app under test and
connect to Flutter Driver.
```dart
// ...
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
const Pattern _isolatePattern = 'driver_example_mod';
const _testAppUrl =
'fuchsia-pkg://fuchsia.com/driver_example_mod#meta/driver_example_mod.cmx';
// ... _startBasemgr ...
void main() {
group('driver example tests', () {
final controller = ComponentControllerProxy();
FlutterDriver driver;
setUpAll(() async {
await _startBasemgr(controller.ctrl.request(), _testAppUrl);
driver = await FlutterDriver.connect(
fuchsiaModuleTarget: _isolatePattern,
printCommunication: true,
logCommunicationToFile: false);
});
tearDownAll(() async {
await driver?.close();
controller.ctrl.close();
});
// ...
});
}
```
When a Dart app starts in debug mode, it exposes the Dart Observatory (VM
Service) on an HTTP port.
[`FlutterDriver.connect`](https://docs.flutter.io/flutter/flutter_driver/FlutterDriver/connect.html)
connects to the Dart Observatory and finds the isolate for the mod named in
`fuchsiaModuleTarget`. Behind the scenes, Flutter Driver uses
[`FuchsiaCompat`](https://github.com/flutter/flutter/blob/master/packages/flutter_driver/lib/src/common/fuchsia_compat.dart)
and
[`FuchsiaRemoteConnection`](https://github.com/flutter/flutter/blob/master/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart)
to search over all the Dart VMs running on the device to find the isolate that
matches your `fuchsiaModuleTarget`. Once this is found, Flutter Driver opens a
websocket over which to send RPCs which were registered by your mod under test
in
[`enableFlutterDriverExtension()`](https://docs.flutter.io/flutter/flutter_driver_extension/enableFlutterDriverExtension.html).
If youd like to see an example test that pushes a few buttons, you can check
[here](https://fuchsia.googlesource.com/topaz/+/master/examples/test/driver_example_mod/test/driver_example_mod_test.dart).
### Component manifest
A
[component manifest](/docs/the-book/package_metadata.md#Component-manifest)
allows the test to run as a hermetic
[test component](/docs/development/tests/test_component.md)
under its own dedicated environment that will sandbox its services and tear
everything down on completion or failure. This is particularly important for
Flutter Driver tests and other graphical tests as only one Scenic instance may
own the display controller at a time, so any such test that does not properly
clean up can cause subsequent tests to fail.
The component manifest for our tests is
[driver_example_mod_tests.cmx](https://fuchsia.googlesource.com/topaz/+/master/examples/test/driver_example_mod/meta/driver_example_mod_tests.cmx).
```json
{
"facets": {
"fuchsia.test": {
"injected-services": {
"fuchsia.fonts.Provider": "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx",
"fuchsia.sysmem.Allocator": "fuchsia-pkg://fuchsia.com/sysmem_connector#meta/sysmem_connector.cmx",
"fuchsia.tracelink.Registry": "fuchsia-pkg://fuchsia.com/trace_manager#meta/trace_manager.cmx",
"fuchsia.ui.input.ImeService": "fuchsia-pkg://fuchsia.com/ime_service#meta/ime_service.cmx",
"fuchsia.ui.policy.Presenter": "fuchsia-pkg://fuchsia.com/root_presenter#meta/root_presenter.cmx",
"fuchsia.ui.scenic.Scenic": "fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx",
"fuchsia.vulkan.loader.Loader": "fuchsia-pkg://fuchsia.com/vulkan_loader#meta/vulkan_loader.cmx"
},
"system-services": [
"fuchsia.net.SocketProvider"
]
}
},
"program": {
"data": "data/driver_example_mod_tests"
},
"sandbox": {
"features": [
"shell"
],
"services": [
"fuchsia.net.SocketProvider",
"fuchsia.sys.Environment"
]
}
}
```
The
[injected-services](/docs/development/tests/test_component.md#run-external-services)
entry starts the hermetic services our mod will need, mostly related to
graphics. In addition, the `fuchsia.net.SocketProvider` system service and
`shell` feature are needed to allow Flutter Driver to interact with the Dart
Observatory.
### BUILD.gn target
The test itself also needs a target in the
[`BUILD.gn`](https://fuchsia.googlesource.com/topaz/+/HEAD/examples/test/driver_example_mod/BUILD.gn).
```gn
dart_fuchsia_test("driver_example_mod_tests") {
deps = [
"//sdk/fidl/fuchsia.sys",
"//third_party/dart-pkg/git/flutter/packages/flutter_driver",
"//third_party/dart-pkg/pub/test",
"//topaz/public/dart/fuchsia_services",
]
meta = [
{
path = rebase_path("meta/driver_example_mod_tests.cmx")
dest = "driver_example_mod_tests.cmx"
},
]
environments = []
# Flutter driver is only available in debug builds.
if (is_debug) {
environments += [
nuc_env,
vim2_env,
]
}
}
```
[`dart_fuchsia_test`](https://fuchsia.googlesource.com/topaz/+/master/runtime/dart/dart_fuchsia_test.gni)
defines a Dart test that runs on a Fuchsia device. It uses each file in the
`test` subfolder that ends in `_test.dart` as an entrypoint for tests. In
addition, it links the component manifest for the tests and specifies the
[environments](/docs/development/tests/environments.md)
in which to run the test in automated testing (CI/CQ). (See also the predefined
environments in
[//build/testing/environments.gni](/build/testing/environments.gni).)
### Topaz Package
Once you have this target available, you can add it to the build tree. In the
case of `driver_example_mod`, this can be done in
[`//topaz/packages/examples:tests`](https://fuchsia.googlesource.com/topaz/+/HEAD/packages/examples/BUILD.gn)
to be available in `//topaz/bundles:buildbot` and other configurations, like so:
```gn
group("tests") {
testonly = true
public_deps = [
# ...
"//topaz/examples/test/driver_example_mod",
"//topaz/examples/test/driver_example_mod:driver_example_mod_tests",
# ...
]
}
```
With that youre ready to run your test on your device.
## Running Your Tests
To run your tests, you first need to make sure you're building a configuration
that includes your test packages. One typical way is to use the `core` product
and `//topaz/bundles:buildbot` bundle, as that is what the CI/CQ bots use.
```bash
$ fx set core.arm64 --with //topaz/bundles:buildbot
```
(If you are on an Acer or Nuc, use `x64` rather than `arm64` as the board.)
Then, build and pave or OTA as necessary.
```bash
$ fx build
$ fx serve / ota / reboot
```
The tests can then be run using
```bash
$ fx run-test driver_example_mod_tests
```