Skip to content

Commit 23433b7

Browse files
committed
Add test scripts for DevTools integration tests
1 parent 8d0db80 commit 23433b7

File tree

4 files changed

+249
-0
lines changed

4 files changed

+249
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
## Instructions for running a DevTools integration test
2+
3+
### Set up ChromeDriver (one time only)
4+
5+
1. Follow the instructions [here](https://docs.flutter.dev/cookbook/testing/integration/introduction#5b-web) to download ChromeDriver.
6+
7+
2. Add `chromedriver` to your PATH by modifying your `.bash_profile` or `.zshrc`:
8+
9+
```
10+
export PATH=${PATH}:/Users/me/folder_containing_chromedriver/
11+
```
12+
13+
3. Verify you can start `chromedriver`:
14+
15+
```
16+
chromedriver --port=4444
17+
```
18+
19+
If you get the error "'chromedriver' cannot be opened because it is from an unidentified developer.", run the following command with your path to the `chromedriver` executable:
20+
21+
```
22+
xattr -d com.apple.quarantine ~/path/to/chromedriver
23+
```
24+
25+
### Running a test
26+
27+
- To run all integration tets: `dart run integration_test/all.dart`
28+
- To run a single integration test: `dart run integration_test/single.dart --target=integration_test/test/my_test.dart`
29+
30+
Special flags:
31+
32+
- `--test-app-uri`: to speed up local development, you can pass in a vm service uri from a Dart or Flutter
33+
app running on your local machine. This saves the cost of spinning up a new test app for each test run. To
34+
do this, pass the vm service uri using the `--test-app-uri=some-uri` run flag.
35+
- `--enable_experiments`: enables experiments for DevTools within the integration test environment
36+
- `--headless`: this will run the integration test on the 'web-server' device instead of the 'chrome' device, meaning you will not be able to see the integration test run in Chrome when running locally.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2022 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'test_infra/_run_test.dart';
8+
9+
// To run this test, run the following from `devtools_app/`:
10+
// `dart run integration_test/all.dart`
11+
//
12+
// Arguments that may be passed to this command:
13+
// --test-app-uri=<some vm service uri> - this will connect DevTools to the app
14+
// you specify instead of spinning up a test app inside
15+
// [runFlutterIntegrationTest].
16+
// --enable-experiments - this will run the DevTools integration tests with
17+
// DevTools experiments enabled (see feature_flags.dart)
18+
// --headless - this will run the integration test on the 'web-server' device
19+
// instead of the 'chrome' device, meaning you will not be able to see the
20+
// integration test run in Chrome when running locally.
21+
22+
void main(List<String> args) async {
23+
const testSuffix = '_test.dart';
24+
25+
// TODO(kenz): if we end up having several subdirectories under
26+
// `integration_test/test`, we could allow the directory to be modified with
27+
// an argument (e.g. --directory=integration_test/test/performance).
28+
final testDirectory = Directory('integration_test/test');
29+
final testFiles = testDirectory
30+
.listSync()
31+
.where((testFile) => testFile.path.endsWith(testSuffix));
32+
for (final testFile in testFiles) {
33+
final testTarget = testFile.path;
34+
await runFlutterIntegrationTest([
35+
...args,
36+
'${TestArgs.testTargetArg}$testTarget',
37+
]);
38+
}
39+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2022 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'test_infra/_run_test.dart';
6+
7+
// To run this test, run the following from `devtools_app/`:
8+
// `dart run integration_test/single.dart --target=path/to/test.dart`
9+
//
10+
// Additional arguments that may be passed to this command:
11+
// --test-app-uri=<some vm service uri> - this will connect DevTools to the app
12+
// you specify instead of spinning up a test app inside
13+
// [runFlutterIntegrationTest].
14+
// --enable-experiments - this will run the DevTools integration tests with
15+
// DevTools experiments enabled (see feature_flags.dart)
16+
// --headless - this will run the integration test on the 'web-server' device
17+
// instead of the 'chrome' device, meaning you will not be able to see the
18+
// integration test run in Chrome when running locally.
19+
20+
Future<void> main(List<String> args) async {
21+
// The call to [runFlutterIntegrationTest] will fail if a test target has not
22+
// been provided (e.g. --target=path/to/test.dart).
23+
await runFlutterIntegrationTest(args);
24+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright 2022 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
import 'dart:io';
7+
8+
import 'package:collection/collection.dart';
9+
10+
import 'io_utils.dart';
11+
import 'test_app_driver.dart';
12+
13+
bool _debugTestScript = false;
14+
15+
Future<void> runFlutterIntegrationTest(
16+
List<String> args, {
17+
String testAppPath = 'test/test_infra/fixtures/flutter_app',
18+
}) async {
19+
final testRunnerArgs = TestArgs(args);
20+
21+
TestFlutterApp? testApp;
22+
late String testAppUri;
23+
24+
final bool shouldCreateTestApp = testRunnerArgs.testAppUri == null;
25+
if (shouldCreateTestApp) {
26+
// Create the test app and start it.
27+
// TODO(kenz): support running Dart CLI test apps from here too.
28+
testApp = TestFlutterApp(appPath: testAppPath);
29+
await testApp.start();
30+
testAppUri = testApp.vmServiceUri.toString();
31+
} else {
32+
testAppUri = testRunnerArgs.testAppUri!;
33+
}
34+
35+
// TODO(kenz): do we need to start chromedriver in headless mode?
36+
// Start chrome driver before running the flutter integration test.
37+
final chromedriver = ChromeDriver();
38+
await chromedriver.start();
39+
40+
// Run the flutter integration test.
41+
final testRunner = TestRunner();
42+
await testRunner.run(
43+
testRunnerArgs.testTarget,
44+
enableExperiments: testRunnerArgs.enableExperiments,
45+
headless: testRunnerArgs.headless,
46+
testAppArguments: {
47+
'service_uri': testAppUri,
48+
},
49+
);
50+
51+
if (shouldCreateTestApp) {
52+
_debugLog('killing the test app');
53+
await testApp?.killGracefully();
54+
}
55+
56+
_debugLog('cancelling stream subscriptions');
57+
await testRunner.cancelAllStreamSubscriptions();
58+
await chromedriver.cancelAllStreamSubscriptions();
59+
60+
_debugLog('killing the chromedriver process');
61+
chromedriver.kill();
62+
}
63+
64+
class ChromeDriver with IOMixin {
65+
late final Process _process;
66+
67+
// TODO(kenz): add error messaging if the chromedriver executable is not
68+
// found. We can also consider using web installers directly in this script:
69+
// https://github.com/flutter/flutter/wiki/Running-Flutter-Driver-tests-with-Web#web-installers-repo.
70+
Future<void> start() async {
71+
_debugLog('starting the chromedriver process');
72+
_process = await Process.start(
73+
'chromedriver',
74+
[
75+
'--port=4444',
76+
],
77+
);
78+
listenToProcessOutput(_process);
79+
}
80+
81+
void kill() {
82+
_process.kill();
83+
}
84+
}
85+
86+
class TestRunner with IOMixin {
87+
Future<void> run(
88+
String testTarget, {
89+
bool headless = false,
90+
bool enableExperiments = false,
91+
Map<String, Object> testAppArguments = const <String, Object>{},
92+
}) async {
93+
_debugLog('starting the flutter drive process');
94+
final process = await Process.start(
95+
'flutter',
96+
[
97+
'drive',
98+
'--profile',
99+
'--driver=test_driver/integration_test.dart',
100+
'--target=$testTarget',
101+
'-d',
102+
headless ? 'web-server' : 'chrome',
103+
if (testAppArguments.isNotEmpty)
104+
'--dart-define=test_args=${jsonEncode(testAppArguments)}',
105+
if (enableExperiments) '--dart-define=enable_experiments=true',
106+
],
107+
);
108+
listenToProcessOutput(process);
109+
110+
await process.exitCode;
111+
process.kill();
112+
_debugLog('flutter drive process has exited');
113+
}
114+
}
115+
116+
void _debugLog(String log) {
117+
if (_debugTestScript) {
118+
print(log);
119+
}
120+
}
121+
122+
class TestArgs {
123+
TestArgs(List<String> args) {
124+
final argWithTestTarget =
125+
args.firstWhereOrNull((arg) => arg.startsWith(testTargetArg));
126+
final target = argWithTestTarget?.substring(testTargetArg.length);
127+
assert(
128+
target != null,
129+
'Please specify a test target (e.g. --target=path/to/test.dart',
130+
);
131+
testTarget = target!;
132+
133+
final argWithTestAppUri =
134+
args.firstWhereOrNull((arg) => arg.startsWith(testAppArg));
135+
testAppUri = argWithTestAppUri?.substring(testAppArg.length);
136+
137+
enableExperiments = args.contains(enableExperimentsArg);
138+
headless = args.contains(headlessArg);
139+
}
140+
141+
static const testTargetArg = '--target=';
142+
static const testAppArg = '--test-app-uri=';
143+
static const enableExperimentsArg = '--enable-experiments';
144+
static const headlessArg = '--headless';
145+
146+
late final String testTarget;
147+
late final String? testAppUri;
148+
late final bool enableExperiments;
149+
late final bool headless;
150+
}

0 commit comments

Comments
 (0)