Skip to content

Commit 5812bd1

Browse files
Add support for connecting to the DevTools server from integration tests (#9190)
1 parent e2986ba commit 5812bd1

6 files changed

Lines changed: 140 additions & 65 deletions

File tree

packages/devtools_app/integration_test/test/live_connection/devtools_extensions_test.dart

Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
44

5+
// Do not delete these arguments. They are parsed by test runner.
6+
// test-argument:startDevToolsServer=true
7+
// test-argument:appPath="../devtools_extensions/example/app_that_uses_foo"
8+
59
import 'package:devtools_app/devtools_app.dart';
610
import 'package:devtools_app/src/extensions/embedded/view.dart';
711
import 'package:devtools_app/src/extensions/extension_screen.dart';
@@ -40,32 +44,32 @@ void main() {
4044
testWidgets('end to end extensions flow', (tester) async {
4145
await pumpDevTools(tester);
4246

43-
logStatus(
44-
'verify static extensions are available before connecting to an app',
45-
);
46-
expect(extensionService.availableExtensions.length, 3);
47-
expect(extensionService.visibleExtensions.length, 3);
48-
await _verifyExtensionsSettingsMenu(tester, [
49-
ExtensionEnabledState.none, // bar
50-
ExtensionEnabledState.none, // baz
51-
ExtensionEnabledState.none, // foo
52-
]);
47+
// TODO(https://github.com/flutter/devtools/issues/9196): re-enable this
48+
// test verification once DTD can be started from the integration test
49+
// harness.
50+
// logStatus(
51+
// 'verify static extensions are available before connecting to an app',
52+
// );
53+
// expect(extensionService.availableExtensions.length, 2);
54+
// expect(extensionService.visibleExtensions.length, 2);
55+
// await _verifyExtensionsSettingsMenu(tester, [
56+
// ExtensionEnabledState.none, // dart_foo
57+
// ExtensionEnabledState.none, // standalone_extension
58+
// ]);
5359

5460
await connectToTestApp(tester, testApp);
5561

56-
expect(extensionService.availableExtensions.length, 5);
57-
expect(extensionService.visibleExtensions.length, 5);
62+
expect(extensionService.availableExtensions.length, 3);
63+
expect(extensionService.visibleExtensions.length, 3);
5864
await _verifyExtensionsSettingsMenu(tester, [
59-
ExtensionEnabledState.none, // bar
60-
ExtensionEnabledState.none, // baz
65+
ExtensionEnabledState.none, // dart_foo
6166
ExtensionEnabledState.none, // foo
62-
ExtensionEnabledState.none, // provider
63-
ExtensionEnabledState.none, // some_tool
67+
ExtensionEnabledState.none, // standalone_extension
6468
], closeMenuWhenDone: false);
6569

6670
await _verifyExtensionVisibilitySetting(tester);
6771

68-
// Bar extension.
72+
// dart_foo extension.
6973
// Enable, test context menu actions, then disable from context menu.
7074
await _switchToExtensionScreen(
7175
tester,
@@ -74,72 +78,64 @@ void main() {
7478
);
7579
await _answerEnableExtensionPrompt(tester, enable: true);
7680
await _verifyExtensionsSettingsMenu(tester, [
77-
ExtensionEnabledState.enabled, // bar
78-
ExtensionEnabledState.none, // baz
81+
ExtensionEnabledState.enabled, // dart_foo
7982
ExtensionEnabledState.none, // foo
80-
ExtensionEnabledState.none, // provider
81-
ExtensionEnabledState.none, // some_tool
83+
ExtensionEnabledState.none, // standalone_extension
8284
]);
8385

8486
await _verifyContextMenuActionsAndDisable(tester);
8587

86-
expect(extensionService.availableExtensions.length, 5);
87-
expect(extensionService.visibleExtensions.length, 4);
88+
expect(extensionService.availableExtensions.length, 3);
89+
expect(extensionService.visibleExtensions.length, 2);
8890
await _verifyExtensionTabVisibility(
8991
tester,
9092
extensionIndex: 0,
9193
visible: false,
9294
);
9395
await _verifyExtensionsSettingsMenu(tester, [
94-
ExtensionEnabledState.disabled, // bar
95-
ExtensionEnabledState.none, // baz
96+
ExtensionEnabledState.disabled, // dart_foo
9697
ExtensionEnabledState.none, // foo
97-
ExtensionEnabledState.none, // provider
98-
ExtensionEnabledState.none, // some_tool
98+
ExtensionEnabledState.none, // standalone_extension
9999
]);
100100

101-
// Baz extension. Hide immediately.
101+
// foo extension. Hide immediately.
102102
await _switchToExtensionScreen(
103103
tester,
104104
extensionIndex: 1,
105105
initialLoad: true,
106106
);
107107
await _answerEnableExtensionPrompt(tester, enable: false);
108108

109-
expect(extensionService.availableExtensions.length, 5);
110-
expect(extensionService.visibleExtensions.length, 3);
109+
expect(extensionService.availableExtensions.length, 3);
110+
expect(extensionService.visibleExtensions.length, 1);
111111
await _verifyExtensionTabVisibility(
112112
tester,
113113
extensionIndex: 1,
114114
visible: false,
115115
);
116116
await _verifyExtensionsSettingsMenu(tester, [
117-
ExtensionEnabledState.disabled, // bar
118-
ExtensionEnabledState.disabled, // baz
119-
ExtensionEnabledState.none, // foo
120-
ExtensionEnabledState.none, // provider
121-
ExtensionEnabledState.none, // some_tool
117+
ExtensionEnabledState.disabled, // dart_foo
118+
ExtensionEnabledState.disabled, // foo
119+
ExtensionEnabledState.none, // standalone_extension
122120
]);
123121

124-
// Re-enable Baz extension from the extensions settings menu.
122+
// Re-enable foo extension from the extensions settings menu.
125123
logStatus('verify we can re-enable an extension from the settings menu');
126124
await _changeExtensionSetting(tester, extensionIndex: 1, enable: true);
127125

128-
expect(extensionService.availableExtensions.length, 5);
129-
expect(extensionService.visibleExtensions.length, 4);
126+
expect(extensionService.availableExtensions.length, 3);
127+
expect(extensionService.visibleExtensions.length, 2);
130128
await _switchToExtensionScreen(tester, extensionIndex: 1);
131129
expect(find.byType(EnableExtensionPrompt), findsNothing);
132130
expect(find.byType(EmbeddedExtensionView), findsOneWidget);
133131
expect(find.byType(HtmlElementView), findsOneWidget);
134132
await _verifyExtensionsSettingsMenu(tester, [
135-
ExtensionEnabledState.disabled, // bar
136-
ExtensionEnabledState.enabled, // baz
137-
ExtensionEnabledState.none, // foo
138-
ExtensionEnabledState.none, // provider
139-
ExtensionEnabledState.none, // some_tool
133+
ExtensionEnabledState.disabled, // dart_foo
134+
ExtensionEnabledState.enabled, // foo
135+
ExtensionEnabledState.none, // standalone_extension
140136
]);
141137

142-
// Foo extension. Disable directly from settings menu.
138+
// standalone_extension. Disable directly from settings menu.
143139
logStatus(
144140
'verify we can disable an extension screen directly from the settings menu',
145141
);
@@ -151,19 +147,17 @@ void main() {
151147

152148
logStatus('disable the extension from the settings menu');
153149
await _changeExtensionSetting(tester, extensionIndex: 2, enable: false);
154-
expect(extensionService.availableExtensions.length, 5);
155-
expect(extensionService.visibleExtensions.length, 3);
150+
expect(extensionService.availableExtensions.length, 3);
151+
expect(extensionService.visibleExtensions.length, 1);
156152
await _verifyExtensionTabVisibility(
157153
tester,
158154
extensionIndex: 2,
159155
visible: false,
160156
);
161157
await _verifyExtensionsSettingsMenu(tester, [
162-
ExtensionEnabledState.disabled, // bar
163-
ExtensionEnabledState.enabled, // baz
164-
ExtensionEnabledState.disabled, // foo
165-
ExtensionEnabledState.none, // provider
166-
ExtensionEnabledState.none, // some_tool
158+
ExtensionEnabledState.disabled, // dart_foo
159+
ExtensionEnabledState.enabled, // foo
160+
ExtensionEnabledState.disabled, // standalone_extension
167161
]);
168162
});
169163
}
@@ -332,7 +326,7 @@ Future<void> _verifyExtensionVisibilitySetting(WidgetTester tester) async {
332326
preferences.devToolsExtensions.showOnlyEnabledExtensions.value,
333327
isFalse,
334328
);
335-
expect(extensionService.visibleExtensions.length, 5);
329+
expect(extensionService.visibleExtensions.length, 3);
336330
// No need to open the settings menu as it should already be open.
337331
await _toggleShowOnlyEnabledExtensions(tester);
338332
expect(
@@ -347,7 +341,7 @@ Future<void> _verifyExtensionVisibilitySetting(WidgetTester tester) async {
347341
preferences.devToolsExtensions.showOnlyEnabledExtensions.value,
348342
isFalse,
349343
);
350-
expect(extensionService.visibleExtensions.length, 5);
344+
expect(extensionService.visibleExtensions.length, 3);
351345

352346
await _closeExtensionSettingsMenu(tester);
353347
}

packages/devtools_app/integration_test/test_infra/run/_in_file_args.dart

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,18 @@ class TestFileArgs {
3939
TestFileArgs._parse(
4040
Map<_TestFileArgItems, dynamic> args, {
4141
required this.appPath,
42-
}) : experimentsOn = args[_TestFileArgItems.experimentsOn] ?? false;
42+
}) : experimentsOn = args[_TestFileArgItems.experimentsOn] ?? false,
43+
startDevToolsServer =
44+
args[_TestFileArgItems.startDevToolsServer] ?? false;
45+
46+
/// The path to the application to connect to.
47+
final String appPath;
4348

4449
/// Whether experiments are enabled in the test.
4550
final bool experimentsOn;
4651

47-
/// The path to the application to connect to.
48-
final String appPath;
52+
/// Whether the DevTools server should be started and connected to DevTools.
53+
final bool startDevToolsServer;
4954

5055
/// Parses 'test-argument' comments in [fileContent].
5156
static Map<_TestFileArgItems, dynamic> _parseFileContent(String fileContent) {
@@ -62,4 +67,15 @@ class TestFileArgs {
6267
}
6368

6469
/// The different arguments accepted as "file args."
65-
enum _TestFileArgItems { experimentsOn, appPath }
70+
enum _TestFileArgItems {
71+
/// The path to the Dart or Flutter application to run and connect to DevTools
72+
/// for this test.
73+
appPath,
74+
75+
/// Whether to enable DevTools experimental features for this test.
76+
experimentsOn,
77+
78+
/// Whether to start the DevTools server and connect it to DevTools for this
79+
/// test.
80+
startDevToolsServer,
81+
}

packages/devtools_app/integration_test/test_infra/run/run_test.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:async';
66
import 'dart:convert';
7+
import 'dart:io';
78

89
import 'package:args/args.dart';
910
import 'package:devtools_shared/devtools_test_utils.dart';
@@ -12,6 +13,10 @@ import '_in_file_args.dart';
1213
import '_test_app_driver.dart';
1314
import '_utils.dart';
1415

16+
/// The identifier for the stdout line that contains the DevTools server
17+
/// address when starting from the `dart devtools` command.
18+
const _devToolsServerAddressLine = 'Serving DevTools at ';
19+
1520
/// Runs one test.
1621
///
1722
/// Do not use this method directly, but instead use the run_tests.dart
@@ -24,6 +29,58 @@ Future<void> runFlutterIntegrationTest(
2429
IntegrationTestApp? testApp;
2530
late String testAppUri;
2631

32+
String? devToolsServerAddress;
33+
Process? devToolsServerProcess;
34+
if (testFileArgs.startDevToolsServer) {
35+
// TODO(https://github.com/flutter/devtools/issues/9196): support starting
36+
// DTD and passing the URI to DevTools server. Workspace roots should be set
37+
// on the DTD instance based on the connected test app.
38+
39+
// Start the DevTools server. This will use the DevTools server that is
40+
// shipped with the Dart SDK.
41+
// TODO(https://github.com/flutter/devtools/issues/9197): launch the
42+
// DevTools server from source so that end to end changes (server + app) can
43+
// be tested.
44+
devToolsServerProcess = await Process.start('dart', [
45+
'devtools',
46+
// Do not launch DevTools app in the browser. This DevTools server
47+
// instance will be used to connect to the DevTools app that is run from
48+
// Flutter driver from the integration test runner.
49+
'--no-launch-browser',
50+
// Disable CORS restrictions so that we can connect to the server from
51+
// DevTools app that is served on a different origin.
52+
'--disable-cors',
53+
]);
54+
55+
final addressCompleter = Completer<void>();
56+
final sub = devToolsServerProcess.stdout.transform(utf8.decoder).listen((
57+
line,
58+
) {
59+
if (line.startsWith(_devToolsServerAddressLine)) {
60+
// This will pull the server address from a String like:
61+
// "Serving DevTools at http://127.0.0.1:9104.".
62+
final regexp = RegExp(
63+
r'http:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+',
64+
);
65+
final match = regexp.firstMatch(line);
66+
if (match != null) {
67+
devToolsServerAddress = match.group(0);
68+
addressCompleter.complete();
69+
}
70+
}
71+
});
72+
73+
await addressCompleter.future.timeout(
74+
const Duration(seconds: 10),
75+
onTimeout: () async {
76+
await sub.cancel();
77+
devToolsServerProcess?.kill();
78+
throw Exception('Timed out waiting for DevTools server to start.');
79+
},
80+
);
81+
await sub.cancel();
82+
}
83+
2784
if (!offline) {
2885
if (testRunnerArgs.testAppUri == null) {
2986
// Create the test app and start it.
@@ -72,6 +129,9 @@ Future<void> runFlutterIntegrationTest(
72129
'test_args=${jsonEncode(testArgs)}',
73130
if (testFileArgs.experimentsOn) 'enable_experiments=true',
74131
if (testRunnerArgs.updateGoldens) 'update_goldens=true',
132+
if (devToolsServerAddress != null)
133+
// Add the trailing slash because this is what DevTools app expects.
134+
'debug_devtools_server=$devToolsServerAddress/',
75135
],
76136
debugLogging: debugTestScript,
77137
);
@@ -81,6 +141,11 @@ Future<void> runFlutterIntegrationTest(
81141
await testApp.stop();
82142
}
83143

144+
if (devToolsServerProcess != null) {
145+
debugLog('killing the DevTools server');
146+
devToolsServerProcess.kill();
147+
}
148+
84149
debugLog('cancelling stream subscriptions');
85150
await testRunner.cancelAllStreamSubscriptions();
86151
}

packages/devtools_app/lib/src/extensions/embedded/_view_web.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,6 @@ class _ExtensionIFrameController extends DisposableController
156156
}
157157

158158
void _postMessage(DevToolsExtensionEvent event) async {
159-
// In [integrationTestMode] we are loading a placeholder url
160-
// (https://flutter.dev/) in the extension iFrame, so trying to post a
161-
// message causes a cross-origin security error. Return early when
162-
// [integrationTestMode] is true so that [_postMessage] calls are a no-op.
163-
if (integrationTestMode) return;
164-
165159
await _iFrameReady.future;
166160
final message = event.toJson();
167161
assert(

packages/devtools_app/lib/src/shared/development_helpers.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ bool debugShowAnalyticsConsentMessage = false;
5454
/// This flag should never be checked in with a value of true - this is covered
5555
/// by a test.
5656
final debugDevToolsExtensions =
57-
_debugDevToolsExtensions || integrationTestMode || testMode || stagerMode;
57+
_debugDevToolsExtensions || testMode || stagerMode;
5858
const _debugDevToolsExtensions = false;
5959

6060
List<DevToolsExtensionConfig> debugHandleRefreshAvailableExtensions({

packages/devtools_shared/lib/src/test/io_utils.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,15 @@ mixin IOMixin {
8282
}) async {
8383
final processId = process.pid;
8484
if (debugLogging) {
85-
print('Sending SIGTERM to $processId.');
85+
print(
86+
'Cancelling all stream subscriptions for process $processId before '
87+
'killing.',
88+
);
8689
}
8790
await cancelAllStreamSubscriptions();
91+
if (debugLogging) {
92+
print('Sending SIGTERM to $processId.');
93+
}
8894
Process.killPid(processId);
8995
return process.exitCode.timeout(
9096
killTimeout,

0 commit comments

Comments
 (0)