From 9e660b56c3a06338b1592f422a113d848c0f2105 Mon Sep 17 00:00:00 2001 From: Shu Chen Date: Thu, 5 Oct 2023 14:51:40 +0100 Subject: [PATCH] test: Add initial integration tests Refer to `docs/integration_tests.md` for notes. --- docs/integration_tests.md | 54 ++++++++++++++++ integration_test/unreadmarker_test.dart | 82 +++++++++++++++++++++++++ pubspec.lock | 65 ++++++++++++++++---- pubspec.yaml | 6 +- test_driver/perf_driver.dart | 27 ++++++++ 5 files changed, 220 insertions(+), 14 deletions(-) create mode 100644 docs/integration_tests.md create mode 100644 integration_test/unreadmarker_test.dart create mode 100644 test_driver/perf_driver.dart diff --git a/docs/integration_tests.md b/docs/integration_tests.md new file mode 100644 index 0000000000..4861191c87 --- /dev/null +++ b/docs/integration_tests.md @@ -0,0 +1,54 @@ +# Integration Tests + +Integration tests in flutter allow you to run test code +on physical devices. These tests also allow performance +metrics to be captured for analysis, and more accurately +reflect production performance as it runs on real devices. + +For more background see +[Flutter docs on integration testing][flutter-docs] + +[flutter-docs]: https://docs.flutter.dev/cookbook/testing/integration/profiling + +## Writing tests + +Writing an integration test is very much similar to +writing widget tests: you have the same access to the +`testWidgets` function as well as a `WidgetTester` to +set up and run interactions. + +A big difference is the usage of an +`IntegrationTestWidgetsFlutterBinding` binding +that has a `traceAction` method that will capture +performance metrics like widget build times as well +as rendering times. + +These integration tests are recommended to be written +in `integration_tests/`. + +The integration tests also interact with test driver +code, which a sample exists as `test_driver/perf_driver.dart`. + +This test driver grabs performance metrics obtained +by a `traceAction` call with a `reportKey: test` and +writes both the entire timeline as well as a summary +to disk inside the `build` directory. + +## Running tests + +The command to run an integration test: + +``` +$ flutter drive \ +--driver=test_driver/perf_driver.dart \ +--target=integration_test/unreadmarker_test.dart \ +-d \ +--profile \ +--no-dds +``` + +Obtain the `device_id` using `flutter devices`. + +Note: the `--no-dds` flag is required for running tests +on mobile devices or simulators. It could be removed +for other contexts. diff --git a/integration_test/unreadmarker_test.dart b/integration_test/unreadmarker_test.dart new file mode 100644 index 0000000000..2ed4b1226d --- /dev/null +++ b/integration_test/unreadmarker_test.dart @@ -0,0 +1,82 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:zulip/api/model/events.dart'; +import 'package:zulip/api/model/model.dart'; +import 'package:zulip/model/narrow.dart'; +import 'package:zulip/model/store.dart'; +import 'package:zulip/widgets/message_list.dart'; +import 'package:zulip/widgets/store.dart'; + +import '../test/api/fake_api.dart'; +import '../test/model/binding.dart'; +import '../test/example_data.dart' as eg; +import '../test/model/message_list_test.dart'; +import '../test/model/test_store.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + TestZulipBinding.ensureInitialized(); + + late PerAccountStore store; + late FakeApiConnection connection; + + Future> setupMessageListPage(WidgetTester tester, { + Narrow narrow = const AllMessagesNarrow(), + bool foundOldest = true, + int? messageCount, + List? messages, + }) async { + addTearDown(testBinding.reset); + await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot()); + store = await testBinding.globalStore.perAccount(eg.selfAccount.id); + connection = store.connection as FakeApiConnection; + + // prepare message list data + store.addUser(eg.selfUser); + assert((messageCount == null) != (messages == null)); + messages ??= List.generate(messageCount!, (index) { + return eg.streamMessage(id: index, sender: eg.selfUser, flags: [MessageFlag.read]); + }); + connection.prepare(json: + newestResult(foundOldest: foundOldest, messages: messages).toJson()); + + await tester.pumpWidget( + MaterialApp( + home: GlobalStoreWidget( + child: PerAccountStoreWidget( + accountId: eg.selfAccount.id, + child: MessageListPage(narrow: narrow))))); + await tester.pumpAndSettle(); + return messages; + } + + testWidgets('Perf test', (tester) async { + final messages = await setupMessageListPage(tester, messageCount: 500); + final messageIds = messages.map((e) => e.id).toList(); + await tester.pump(const Duration(seconds: 2)); + + await binding.traceAction( + () async { + store.handleEvent(UpdateMessageFlagsRemoveEvent( + id: 1, + flag: MessageFlag.read, + messages: messageIds, + messageDetails: {}, + )); + await tester.pumpAndSettle(); + await tester.pumpAndSettle(); + store.handleEvent(UpdateMessageFlagsAddEvent( + id: 2, + flag: MessageFlag.read, + messages: messageIds, + all: true, + )); + await tester.pumpAndSettle(); + await tester.pumpAndSettle(); + }, + reportKey: 'test', + ); + }); +} diff --git a/pubspec.lock b/pubspec.lock index 67fd3f522b..3d8469cf71 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: "direct main" description: name: app_settings - sha256: "67ca58aba6ec311d89597c2716d2e37da54b8c7cef28b7749e6551c57f88c1f4" + sha256: "09bc7fe0313a507087bec1a3baf555f0576e816a760cbb31813a88890a09d9e5" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.1" args: dependency: transitive description: @@ -293,10 +293,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "6.1.4" file_picker: dependency: "direct main" description: @@ -350,6 +350,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_driver: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" flutter_lints: dependency: "direct dev" description: @@ -389,6 +394,11 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" glob: dependency: transitive description: @@ -501,6 +511,11 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: "direct main" description: @@ -577,10 +592,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -681,10 +696,10 @@ packages: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" plugin_platform_interface: dependency: transitive description: @@ -701,6 +716,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" pub_semver: dependency: transitive description: @@ -874,6 +897,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" term_glyph: dependency: transitive description: @@ -1006,10 +1037,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d" + sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 url: "https://pub.dev" source: hosted - version: "11.9.0" + version: "11.10.0" watcher: dependency: transitive description: @@ -1022,10 +1053,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: "14f1f70c51119012600c5f1f60ca68efda5a9b6077748163c6af2893ec5df8fc" url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.2.1-beta" web_socket_channel: dependency: transitive description: @@ -1034,6 +1065,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.0" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + url: "https://pub.dev" + source: hosted + version: "3.0.2" webkit_inspection_protocol: dependency: transitive description: @@ -1075,5 +1114,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-73.0.dev <4.0.0" + dart: ">=3.2.0-157.0.dev <4.0.0" flutter: ">=3.14.0-5.0.pre.23" diff --git a/pubspec.yaml b/pubspec.yaml index efdadb7223..8b27cba8ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,7 +52,7 @@ dependencies: path_provider: ^2.0.13 path: ^1.8.3 sqlite3_flutter_libs: ^0.5.13 - app_settings: ^5.0.0 + app_settings: ^5.1.1 image_picker: ^1.0.0 package_info_plus: ^4.0.1 collection: ^1.17.2 @@ -61,8 +61,12 @@ dependencies: sdk: flutter dev_dependencies: + flutter_driver: + sdk: flutter flutter_test: sdk: flutter + integration_test: + sdk: flutter # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is diff --git a/test_driver/perf_driver.dart b/test_driver/perf_driver.dart new file mode 100644 index 0000000000..39d68b3af4 --- /dev/null +++ b/test_driver/perf_driver.dart @@ -0,0 +1,27 @@ +import 'package:flutter_driver/flutter_driver.dart' as driver; +import 'package:integration_test/integration_test_driver.dart'; + +Future main() { + return integrationDriver( + responseDataCallback: (data) async { + if (data != null) { + final timeline = driver.Timeline.fromJson(data['test']); + + // Convert the Timeline into a TimelineSummary that's easier to + // read and understand. + final summary = driver.TimelineSummary.summarize(timeline); + + // Then, write the entire timeline to disk in a json format. + // This file can be opened in the Chrome browser's tracing tools + // found by navigating to chrome://tracing. + // Optionally, save the summary to disk by setting includeSummary + // to true + await summary.writeTimelineToFile( + 'trace_output', + pretty: true, + includeSummary: true, + ); + } + }, + ); +}