Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2025 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.

// Do not delete these arguments. They are parsed by test runner.
// test-argument:appPath="test/test_infra/fixtures/networking_app/bin/main.dart"

import 'package:devtools_app/devtools_app.dart';
import 'package:devtools_app/src/shared/table/table.dart' show DevToolsTable;
import 'package:devtools_test/helpers.dart';
import 'package:devtools_test/integration_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:integration_test/integration_test.dart';

// To run:
// dart run integration_test/run_tests.dart --target=integration_test/test/live_connection/network_screen_test.dart --test-app-device=cli

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

late TestApp testApp;

setUpAll(() {
testApp = TestApp.fromEnvironment();
expect(testApp.vmServiceUri, isNotNull);
});

tearDown(() async {
await resetHistory();
await http.get(Uri.parse('http://localhost:${testApp.controlPort}/exit/'));
});

testWidgets('nnn', (tester) async {
await pumpAndConnectDevTools(tester, testApp);
await _prepareNetworkScreen(tester);

final helper = _NetworkScreenHelper(tester, testApp.controlPort!);

// Instruct the app to make a GET request via the dart:io HttpClient.
await helper.triggerRequest('get/');
_expectInRequestTable('GET');
await helper.clear();

// Instruct the app to make a POST request via the dart:io HttpClient.
await helper.triggerRequest('post/');
_expectInRequestTable('POST');
await helper.clear();

// Instruct the app to make a PUT request via the dart:io HttpClient.
await helper.triggerRequest('put/');
_expectInRequestTable('PUT');
await helper.clear();

// Instruct the app to make a DELETE request via the dart:io HttpClient.
await helper.triggerRequest('delete/');
_expectInRequestTable('DELETE');
await helper.clear();

// Instruct the app to make a GET request via Dio.
await helper.triggerRequest('dio/get/');
_expectInRequestTable('GET');
await helper.clear();

// Instruct the app to make a POST request via Dio.
await helper.triggerRequest('dio/post/');
_expectInRequestTable('POST');
});
}

final class _NetworkScreenHelper {
_NetworkScreenHelper(this._tester, this._controlPort);

final WidgetTester _tester;

final int _controlPort;

Future<void> clear() async {
// Press the 'Clear' button between tests.
await _tester.tap(find.text('Clear'));
await _tester.pump(safePumpDuration);
}

Future<void> triggerRequest(String path) async {
await http.get(Uri.parse('http://localhost:$_controlPort/$path'));
await Future.delayed(const Duration(milliseconds: 200));
await _tester.pump(safePumpDuration);
}
}

void _expectInRequestTable(String text) {
expect(
find.descendant(
of: find.byType(DevToolsTable<NetworkRequest>),
matching: find.text(text),
),
findsOneWidget,
);
}

/// Prepares the UI of the network screen for an integration test.
Future<void> _prepareNetworkScreen(WidgetTester tester) async {
await switchToScreen(
tester,
tabIcon: ScreenMetaData.network.icon,
tabIconAsset: ScreenMetaData.network.iconAsset,
screenId: ScreenMetaData.network.id,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,23 @@ class TestFlutterApp extends IntegrationTestApp {
Future<void> waitForAppStart() async {
// Set this up now, but we don't await it yet. We want to make sure we don't
// miss it while waiting for debugPort below.
final started = waitFor(
final started = _waitFor(
event: FlutterDaemonConstants.appStartedKey,
timeout: IntegrationTestApp._appStartTimeout,
);

final debugPort = await waitFor(
final debugPort = await _waitFor(
event: FlutterDaemonConstants.appDebugPortKey,
timeout: IntegrationTestApp._appStartTimeout,
);
final wsUriString =
(debugPort[FlutterDaemonConstants.paramsKey]!
as Map<String, Object?>)[FlutterDaemonConstants.wsUriKey]
as String;
_vmServiceWsUri = Uri.parse(wsUriString);
final vmServiceWsUri = Uri.parse(wsUriString);

// Map to WS URI.
_vmServiceWsUri = convertToWebSocketUrl(
serviceProtocolUrl: _vmServiceWsUri,
);
_vmServiceWsUri = convertToWebSocketUrl(serviceProtocolUrl: vmServiceWsUri);

// Now await the started event; if it had already happened the future will
// have already completed.
Expand Down Expand Up @@ -101,7 +99,7 @@ class TestFlutterApp extends IntegrationTestApp {
// Set up the response future before we send the request to avoid any
// races. If the method we're calling is app.stop then we tell waitFor not
// to throw if it sees an app.stop event before the response to this request.
final responseFuture = waitFor(
final responseFuture = _waitFor(
id: requestId,
ignoreAppStopEvent: method == 'app.stop',
);
Expand All @@ -113,7 +111,7 @@ class TestFlutterApp extends IntegrationTestApp {
}
}

Future<Map<String, Object?>> waitFor({
Future<Map<String, Object?>> _waitFor({
String? event,
int? id,
Duration? timeout,
Expand Down Expand Up @@ -199,6 +197,10 @@ class TestDartCliApp extends IntegrationTestApp {
: super(appPath, TestAppDevice.cli);

static const vmServicePrefix = 'The Dart VM service is listening on ';
static const controlPortKey = 'controlPort';

int? get controlPort => _controlPort;
late final int? _controlPort;

@override
Future<void> startProcess() async {
Expand All @@ -215,20 +217,32 @@ class TestDartCliApp extends IntegrationTestApp {

@override
Future<void> waitForAppStart() async {
final vmServiceUri = await waitFor(
final vmServiceUriString = await _waitFor(
message: vmServicePrefix,
timeout: IntegrationTestApp._appStartTimeout,
);
final parsedVmServiceUri = Uri.parse(vmServiceUri);
final vmServiceUri = Uri.parse(vmServiceUriString);
_controlPort = await _waitFor(
message: controlPortKey,
timeout: const Duration(seconds: 1),
optional: true,
);

// Map to WS URI.
_vmServiceWsUri = convertToWebSocketUrl(
serviceProtocolUrl: parsedVmServiceUri,
);
_vmServiceWsUri = convertToWebSocketUrl(serviceProtocolUrl: vmServiceUri);
}

Future<String> waitFor({required String message, Duration? timeout}) {
final response = Completer<String>();
/// Waits for [message] to appear on stdout.
///
/// After [timeout], if no such message has appeared, then either `null` is
/// returned, if [optional] is `true`, or an exception is thrown, if
/// [optional] is `false`.
Future<T> _waitFor<T>({
required String message,
Duration? timeout,
bool optional = false,
}) {
final response = Completer<T>();
late StreamSubscription<String> sub;
sub = stdoutController.stream.listen(
(String line) => _handleStdout(
Expand All @@ -239,25 +253,38 @@ class TestDartCliApp extends IntegrationTestApp {
),
);

return _timeoutWithMessages<String>(
if (optional) {
return response.future
.timeout(
timeout ?? IntegrationTestApp._defaultTimeout,
onTimeout: () => null as T,
)
.whenComplete(() => sub.cancel());
}

return _timeoutWithMessages<T>(
() => response.future,
timeout: timeout,
message: 'Did not receive expected message: $message.',
).whenComplete(() => sub.cancel());
}

void _handleStdout(
void _handleStdout<T>(
String line, {
required StreamSubscription<String> subscription,
required Completer<String> response,
required Completer<T> response,
required String message,
}) async {
if (message == vmServicePrefix && line.startsWith(vmServicePrefix)) {
final vmServiceUri = line.substring(
line.indexOf(vmServicePrefix) + vmServicePrefix.length,
);
await subscription.cancel();
response.complete(vmServiceUri);
response.complete(vmServiceUri as T);
} else if (message == controlPortKey && line.contains(controlPortKey)) {
final asJson = jsonDecode(line) as Map;
await subscription.cancel();
response.complete(asJson[controlPortKey] as T);
}
}
}
Expand Down Expand Up @@ -286,7 +313,7 @@ abstract class IntegrationTestApp with IOMixin {
final _allMessages = StreamController<String>.broadcast();

Uri get vmServiceUri => _vmServiceWsUri;
late Uri _vmServiceWsUri;
late final Uri _vmServiceWsUri;

Future<void> startProcess();

Expand Down Expand Up @@ -416,9 +443,10 @@ enum TestAppDevice {

/// A mapping of test app device to the unsupported tests for that device.
static final _unsupportedTestsForDevice = <TestAppDevice, List<String>>{
TestAppDevice.flutterTester: [],
TestAppDevice.flutterTester: ['network_screen_test.dart'],
TestAppDevice.flutterChrome: [
'eval_and_browse_test.dart',
'network_screen_test.dart',
'perfetto_test.dart',
'performance_screen_event_recording_test.dart',
'service_connection_test.dart',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ Future<void> runFlutterIntegrationTest(
// Run the flutter integration test.
final testRunner = IntegrationTestRunner();
try {
final testArgs = <String, Object>{if (!offline) 'service_uri': testAppUri};
final testArgs = <String, Object?>{
if (!offline) 'service_uri': testAppUri,
if (testApp is TestDartCliApp) 'control_port': testApp.controlPort,
};
final testTarget = testRunnerArgs.testTarget!;
debugLog('starting test run for $testTarget');
await testRunner.run(
Expand Down
Loading