Skip to content

Commit e660635

Browse files
committed
test: Add initial integration tests
Refer to `docs/integration_tests.md` for notes.
1 parent 8061a9c commit e660635

File tree

5 files changed

+220
-14
lines changed

5 files changed

+220
-14
lines changed

docs/integration_tests.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Integration Tests
2+
3+
Integration tests in flutter allow you to run test code
4+
on physical devices. These tests also allow performance
5+
metrics to be captured for analysis, and more accurately
6+
reflect production performance as it runs on real devices.
7+
8+
For more background see
9+
[Flutter docs on integration testing][flutter-docs]
10+
11+
[flutter-docs]: https://docs.flutter.dev/cookbook/testing/integration/profiling
12+
13+
## Writing tests
14+
15+
Writing an integration test is very much similar to
16+
writing widget tests: you have the same access to the
17+
`testWidgets` function as well as a `WidgetTester` to
18+
set up and run interactions.
19+
20+
A big difference is the usage of an
21+
`IntegrationTestWidgetsFlutterBinding` binding
22+
that has a `traceAction` method that will capture
23+
performance metrics like widget build times as well
24+
as rendering times.
25+
26+
These integration tests are recommended to be written
27+
in `integration_tests/`.
28+
29+
The integration tests also interact with test driver
30+
code, which a sample exists as `test_driver/perf_driver.dart`.
31+
32+
This test driver grabs performance metrics obtained
33+
by a `traceAction` call with a `reportKey: test` and
34+
writes both the entire timeline as well as a summary
35+
to disk inside the `build` directory.
36+
37+
## Running tests
38+
39+
The command to run an integration test:
40+
41+
```
42+
$ flutter drive \
43+
--driver=test_driver/perf_driver.dart \
44+
--target=integration_test/unreadmarker_test.dart \
45+
-d <device_id> \
46+
--profile \
47+
--no-dds
48+
```
49+
50+
Obtain the `device_id` using `flutter devices`.
51+
52+
Note: the `--no-dds` flag is required for running tests
53+
on mobile devices or simulators. It could be removed
54+
for other contexts.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:integration_test/integration_test.dart';
5+
import 'package:zulip/api/model/events.dart';
6+
import 'package:zulip/api/model/model.dart';
7+
import 'package:zulip/model/narrow.dart';
8+
import 'package:zulip/model/store.dart';
9+
import 'package:zulip/widgets/message_list.dart';
10+
import 'package:zulip/widgets/store.dart';
11+
12+
import '../test/api/fake_api.dart';
13+
import '../test/model/binding.dart';
14+
import '../test/example_data.dart' as eg;
15+
import '../test/model/message_list_test.dart';
16+
import '../test/model/test_store.dart';
17+
18+
void main() {
19+
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
20+
TestZulipBinding.ensureInitialized();
21+
22+
late PerAccountStore store;
23+
late FakeApiConnection connection;
24+
25+
Future<List<Message>> setupMessageListPage(WidgetTester tester, {
26+
Narrow narrow = const AllMessagesNarrow(),
27+
bool foundOldest = true,
28+
int? messageCount,
29+
List<Message>? messages,
30+
}) async {
31+
addTearDown(testBinding.reset);
32+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
33+
store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
34+
connection = store.connection as FakeApiConnection;
35+
36+
// prepare message list data
37+
store.addUser(eg.selfUser);
38+
assert((messageCount == null) != (messages == null));
39+
messages ??= List.generate(messageCount!, (index) {
40+
return eg.streamMessage(id: index, sender: eg.selfUser, flags: [MessageFlag.read]);
41+
});
42+
connection.prepare(json:
43+
newestResult(foundOldest: foundOldest, messages: messages).toJson());
44+
45+
await tester.pumpWidget(
46+
MaterialApp(
47+
home: GlobalStoreWidget(
48+
child: PerAccountStoreWidget(
49+
accountId: eg.selfAccount.id,
50+
child: MessageListPage(narrow: narrow)))));
51+
await tester.pumpAndSettle();
52+
return messages;
53+
}
54+
55+
testWidgets('Perf test', (tester) async {
56+
final messages = await setupMessageListPage(tester, messageCount: 500);
57+
final messageIds = messages.map((e) => e.id).toList();
58+
await tester.pump(const Duration(seconds: 2));
59+
60+
await binding.traceAction(
61+
() async {
62+
store.handleEvent(UpdateMessageFlagsRemoveEvent(
63+
id: 1,
64+
flag: MessageFlag.read,
65+
messages: messageIds,
66+
messageDetails: {},
67+
));
68+
await tester.pumpAndSettle();
69+
await tester.pumpAndSettle();
70+
store.handleEvent(UpdateMessageFlagsAddEvent(
71+
id: 2,
72+
flag: MessageFlag.read,
73+
messages: messageIds,
74+
all: true,
75+
));
76+
await tester.pumpAndSettle();
77+
await tester.pumpAndSettle();
78+
},
79+
reportKey: 'test',
80+
);
81+
});
82+
}

pubspec.lock

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ packages:
2929
dependency: "direct main"
3030
description:
3131
name: app_settings
32-
sha256: "67ca58aba6ec311d89597c2716d2e37da54b8c7cef28b7749e6551c57f88c1f4"
32+
sha256: "09bc7fe0313a507087bec1a3baf555f0576e816a760cbb31813a88890a09d9e5"
3333
url: "https://pub.dev"
3434
source: hosted
35-
version: "5.0.0"
35+
version: "5.1.1"
3636
args:
3737
dependency: transitive
3838
description:
@@ -293,10 +293,10 @@ packages:
293293
dependency: transitive
294294
description:
295295
name: file
296-
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
296+
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
297297
url: "https://pub.dev"
298298
source: hosted
299-
version: "7.0.0"
299+
version: "6.1.4"
300300
file_picker:
301301
dependency: "direct main"
302302
description:
@@ -350,6 +350,11 @@ packages:
350350
description: flutter
351351
source: sdk
352352
version: "0.0.0"
353+
flutter_driver:
354+
dependency: "direct dev"
355+
description: flutter
356+
source: sdk
357+
version: "0.0.0"
353358
flutter_lints:
354359
dependency: "direct dev"
355360
description:
@@ -389,6 +394,11 @@ packages:
389394
url: "https://pub.dev"
390395
source: hosted
391396
version: "3.2.0"
397+
fuchsia_remote_debug_protocol:
398+
dependency: transitive
399+
description: flutter
400+
source: sdk
401+
version: "0.0.0"
392402
glob:
393403
dependency: transitive
394404
description:
@@ -501,6 +511,11 @@ packages:
501511
url: "https://pub.dev"
502512
source: hosted
503513
version: "0.2.1"
514+
integration_test:
515+
dependency: "direct dev"
516+
description: flutter
517+
source: sdk
518+
version: "0.0.0"
504519
intl:
505520
dependency: "direct main"
506521
description:
@@ -577,10 +592,10 @@ packages:
577592
dependency: transitive
578593
description:
579594
name: meta
580-
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
595+
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
581596
url: "https://pub.dev"
582597
source: hosted
583-
version: "1.9.1"
598+
version: "1.10.0"
584599
mime:
585600
dependency: transitive
586601
description:
@@ -681,10 +696,10 @@ packages:
681696
dependency: transitive
682697
description:
683698
name: platform
684-
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
699+
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
685700
url: "https://pub.dev"
686701
source: hosted
687-
version: "3.1.0"
702+
version: "3.1.2"
688703
plugin_platform_interface:
689704
dependency: transitive
690705
description:
@@ -701,6 +716,14 @@ packages:
701716
url: "https://pub.dev"
702717
source: hosted
703718
version: "1.5.1"
719+
process:
720+
dependency: transitive
721+
description:
722+
name: process
723+
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
724+
url: "https://pub.dev"
725+
source: hosted
726+
version: "4.2.4"
704727
pub_semver:
705728
dependency: transitive
706729
description:
@@ -874,6 +897,14 @@ packages:
874897
url: "https://pub.dev"
875898
source: hosted
876899
version: "1.2.0"
900+
sync_http:
901+
dependency: transitive
902+
description:
903+
name: sync_http
904+
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
905+
url: "https://pub.dev"
906+
source: hosted
907+
version: "0.3.1"
877908
term_glyph:
878909
dependency: transitive
879910
description:
@@ -1006,10 +1037,10 @@ packages:
10061037
dependency: transitive
10071038
description:
10081039
name: vm_service
1009-
sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d"
1040+
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
10101041
url: "https://pub.dev"
10111042
source: hosted
1012-
version: "11.9.0"
1043+
version: "11.10.0"
10131044
watcher:
10141045
dependency: transitive
10151046
description:
@@ -1022,10 +1053,10 @@ packages:
10221053
dependency: transitive
10231054
description:
10241055
name: web
1025-
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
1056+
sha256: "14f1f70c51119012600c5f1f60ca68efda5a9b6077748163c6af2893ec5df8fc"
10261057
url: "https://pub.dev"
10271058
source: hosted
1028-
version: "0.1.4-beta"
1059+
version: "0.2.1-beta"
10291060
web_socket_channel:
10301061
dependency: transitive
10311062
description:
@@ -1034,6 +1065,14 @@ packages:
10341065
url: "https://pub.dev"
10351066
source: hosted
10361067
version: "2.4.0"
1068+
webdriver:
1069+
dependency: transitive
1070+
description:
1071+
name: webdriver
1072+
sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49"
1073+
url: "https://pub.dev"
1074+
source: hosted
1075+
version: "3.0.2"
10371076
webkit_inspection_protocol:
10381077
dependency: transitive
10391078
description:
@@ -1075,5 +1114,5 @@ packages:
10751114
source: hosted
10761115
version: "3.1.2"
10771116
sdks:
1078-
dart: ">=3.2.0-73.0.dev <4.0.0"
1117+
dart: ">=3.2.0-157.0.dev <4.0.0"
10791118
flutter: ">=3.14.0-5.0.pre.23"

pubspec.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ dependencies:
5252
path_provider: ^2.0.13
5353
path: ^1.8.3
5454
sqlite3_flutter_libs: ^0.5.13
55-
app_settings: ^5.0.0
55+
app_settings: ^5.1.1
5656
image_picker: ^1.0.0
5757
package_info_plus: ^4.0.1
5858
collection: ^1.17.2
@@ -61,8 +61,12 @@ dependencies:
6161
sdk: flutter
6262

6363
dev_dependencies:
64+
flutter_driver:
65+
sdk: flutter
6466
flutter_test:
6567
sdk: flutter
68+
integration_test:
69+
sdk: flutter
6670

6771
# The "flutter_lints" package below contains a set of recommended lints to
6872
# encourage good coding practices. The lint set provided by the package is

test_driver/perf_driver.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'package:flutter_driver/flutter_driver.dart' as driver;
2+
import 'package:integration_test/integration_test_driver.dart';
3+
4+
Future<void> main() {
5+
return integrationDriver(
6+
responseDataCallback: (data) async {
7+
if (data != null) {
8+
final timeline = driver.Timeline.fromJson(data['test']);
9+
10+
// Convert the Timeline into a TimelineSummary that's easier to
11+
// read and understand.
12+
final summary = driver.TimelineSummary.summarize(timeline);
13+
14+
// Then, write the entire timeline to disk in a json format.
15+
// This file can be opened in the Chrome browser's tracing tools
16+
// found by navigating to chrome://tracing.
17+
// Optionally, save the summary to disk by setting includeSummary
18+
// to true
19+
await summary.writeTimelineToFile(
20+
'trace_output',
21+
pretty: true,
22+
includeSummary: true,
23+
);
24+
}
25+
},
26+
);
27+
}

0 commit comments

Comments
 (0)