Skip to content

Commit

Permalink
Add end to end integration test for DevTools extensions (#6373)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll authored Sep 13, 2023
1 parent 8bd4bd3 commit e7677e5
Show file tree
Hide file tree
Showing 17 changed files with 390 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ void main() {
final screens = (ScreenMetaData.values.toList()
..removeWhere((data) => !availableScreenIds.contains(data.id)));
for (final screen in screens) {
await switchToScreen(tester, screen);
await switchToScreen(
tester,
tabIcon: screen.icon!,
screenId: screen.id,
);
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ void main() {

testWidgets('Debugger panel', (tester) async {
await pumpAndConnectDevTools(tester, testApp);
await switchToScreen(tester, ScreenMetaData.debugger);
await switchToScreen(
tester,
tabIcon: ScreenMetaData.debugger.icon!,
screenId: ScreenMetaData.debugger.id,
);
await tester.pump(safePumpDuration);

logStatus('looking for the main.dart file');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Do not delete these arguments. They are parsed by test runner.
// test-argument:experimentsOn=true

// import 'package:devtools_app/devtools_app.dart';
import 'package:devtools_app/devtools_app.dart';
import 'package:devtools_app/src/extensions/embedded/view.dart';
import 'package:devtools_app/src/extensions/extension_screen.dart';
import 'package:devtools_app/src/extensions/extension_screen_controls.dart';
import 'package:devtools_app/src/extensions/extension_settings.dart';
import 'package:devtools_app_shared/ui.dart';
import 'package:devtools_shared/devtools_extensions.dart';
import 'package:devtools_test/devtools_integration_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

// To run:
// dart run integration_test/run_tests.dart --target=integration_test/test/live_connection/devtools_extensions_test.dart

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

late TestApp testApp;

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

testWidgets('end to end extensions flow', (tester) async {
await pumpAndConnectDevTools(tester, testApp);

expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 3);
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.none,
ExtensionEnabledState.none,
ExtensionEnabledState.none,
],
);

// Bar extension.
// Enable, test context menu actions, then disable from context menu.
await _switchToExtensionScreen(
tester,
extensionIndex: 0,
initialLoad: true,
);
await _answerEnableExtensionPrompt(tester, enable: true);
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.enabled,
ExtensionEnabledState.none,
ExtensionEnabledState.none,
],
);

await _verifyContextMenuActions(tester);

expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 2);
await _verifyExtensionTabVisibility(
tester,
extensionIndex: 0,
visible: false,
);
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.disabled,
ExtensionEnabledState.none,
ExtensionEnabledState.none,
],
);

// Foo extension. Hide immediately, then re-enable from extensions menu.
await _switchToExtensionScreen(
tester,
extensionIndex: 1,
initialLoad: true,
);
await _answerEnableExtensionPrompt(tester, enable: false);

expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 1);
await _verifyExtensionTabVisibility(
tester,
extensionIndex: 1,
visible: false,
);
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.disabled,
ExtensionEnabledState.disabled,
ExtensionEnabledState.none,
],
);

logStatus('verify we can re-enable an extension from the settings menu');
await _changeExtensionSetting(tester, extensionIndex: 1, enable: true);

expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 2);
await _switchToExtensionScreen(tester, extensionIndex: 1);
expect(find.byType(EnableExtensionPrompt), findsNothing);
expect(find.byType(EmbeddedExtensionView), findsOneWidget);
expect(find.byType(HtmlElementView), findsOneWidget);
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.disabled,
ExtensionEnabledState.enabled,
ExtensionEnabledState.none,
],
);

// Provider extension. Disable directly from settings menu.
logStatus(
'verify we can disable an extension screen directly from the settings menu',
);
await _verifyExtensionTabVisibility(
tester,
extensionIndex: 2,
visible: true,
);

logStatus('disable the extension from the settings menu');
await _changeExtensionSetting(tester, extensionIndex: 2, enable: false);
expect(extensionService.availableExtensions.value.length, 3);
expect(extensionService.visibleExtensions.value.length, 1);
await _verifyExtensionTabVisibility(
tester,
extensionIndex: 2,
visible: false,
);
await _verifyExtensionsSettingsMenu(
tester,
[
ExtensionEnabledState.disabled,
ExtensionEnabledState.enabled,
ExtensionEnabledState.disabled,
],
);
});
}

Future<void> _switchToExtensionScreen(
WidgetTester tester, {
required int extensionIndex,
bool initialLoad = false,
}) async {
final extensionConfig =
extensionService.availableExtensions.value[extensionIndex];
await switchToScreen(
tester,
tabIcon: extensionConfig.icon,
screenId: extensionConfig.displayName,
warnIfTapMissed: false,
);
await tester.pump(safePumpDuration);

if (initialLoad) {
logStatus(
'verify the first load state for the ${extensionConfig.name}'
' extension screen',
);
expect(find.byType(EnableExtensionPrompt), findsOneWidget);
expect(find.byType(EmbeddedExtensionView), findsNothing);
}
}

Future<void> _verifyExtensionTabVisibility(
WidgetTester tester, {
required int extensionIndex,
required bool visible,
}) async {
logStatus(
'verify the extension at index $extensionIndex is '
'${!visible ? 'not' : ''} visible',
);
final extensionConfig =
extensionService.availableExtensions.value[extensionIndex];
final tabFinder = await findTabOrOpenOverflowMenu(
tester,
extensionConfig.icon,
);
expect(tabFinder.evaluate(), visible ? isNotEmpty : isEmpty);
}

Future<void> _answerEnableExtensionPrompt(
WidgetTester tester, {
required bool enable,
}) async {
logStatus('verify we can ${enable ? 'enable' : 'hide'} an extension');
final buttonFinder = find.descendant(
of: find.byType(EnableExtensionPrompt),
matching: find.text(enable ? 'Enable' : 'No, hide this screen'),
);
expect(buttonFinder, findsOneWidget);
await tester.tap(buttonFinder);
await tester.pump(longPumpDuration);

expect(find.byType(EnableExtensionPrompt), findsNothing);
expect(
find.byType(EmbeddedExtensionView),
enable ? findsOneWidget : findsNothing,
);
expect(
find.byType(HtmlElementView),
enable ? findsOneWidget : findsNothing,
);
}

Future<void> _verifyContextMenuActions(WidgetTester tester) async {
logStatus('verify we can perform context menu actions');
final contextMenuFinder = find.descendant(
of: find.byType(EmbeddedExtensionHeader),
matching: find.byType(ContextMenuButton),
);
expect(contextMenuFinder, findsOneWidget);
await tester.tap(contextMenuFinder);
await tester.pump(shortPumpDuration);

final disableExtensionFinder = find.text('Disable extension');
final forceReloadExtensionFinder = find.text('Force reload extension');
expect(disableExtensionFinder, findsOneWidget);
expect(forceReloadExtensionFinder, findsOneWidget);

logStatus('verify we can force reload the extension');
await tester.tap(forceReloadExtensionFinder);
await tester.pumpAndSettle(shortPumpDuration);

logStatus('verify we can disable the extension from the context menu');
await tester.tap(contextMenuFinder);
await tester.pump(shortPumpDuration);
await tester.tap(disableExtensionFinder);
await tester.pumpAndSettle(shortPumpDuration);
await tester.tap(find.text('YES, DISABLE'));
await tester.pumpAndSettle(longPumpDuration);
}

Future<void> _verifyExtensionsSettingsMenu(
WidgetTester tester,
List<ExtensionEnabledState> enabledStates,
) async {
await _openExtensionSettingsMenu(tester);

expect(find.byType(ExtensionSetting), findsNWidgets(enabledStates.length));
final toggleButtonGroups = tester
.widgetList(find.byType(DevToolsToggleButtonGroup))
.cast<DevToolsToggleButtonGroup>()
.toList();
for (int i = 0; i < toggleButtonGroups.length; i++) {
final group = toggleButtonGroups[i];
final expectedStates = switch (enabledStates[i]) {
ExtensionEnabledState.enabled => [true, false],
ExtensionEnabledState.disabled => [false, true],
_ => [false, false],
};
expect(group.selectedStates, expectedStates);
}

await _closeExtensionSettingsMenu(tester);
}

Future<void> _openExtensionSettingsMenu(WidgetTester tester) async {
await tester.tap(find.byType(ExtensionSettingsAction));
await tester.pumpAndSettle(shortPumpDuration);
}

Future<void> _closeExtensionSettingsMenu(WidgetTester tester) async {
await tester.tap(
find.descendant(
of: find.byType(ExtensionSettingsDialog),
matching: find.byType(DialogCloseButton),
),
);
await tester.pumpAndSettle(safePumpDuration);
}

Future<void> _changeExtensionSetting(
WidgetTester tester, {
required int extensionIndex,
required bool enable,
}) async {
final settingValue = enable ? 'Enabled' : 'Disabled';
logStatus(
'changing the extension setting at index $extensionIndex to value $settingValue',
);
await _openExtensionSettingsMenu(tester);
final extensionSetting = tester
.widgetList(find.byType(DevToolsToggleButtonGroup))
.cast<DevToolsToggleButtonGroup>()
.toList()[extensionIndex];
await tester.tap(
find.descendant(
of: find.byWidget(extensionSetting),
matching: find.text(enable ? 'Enabled' : 'Disabled'),
),
);
await tester.pumpAndSettle(shortPumpDuration);
await _closeExtensionSettingsMenu(tester);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

// Do not delete these arguments. They are parsed by test runner.
// test-argument:appPath="test/test_infra/fixtures/memory_app"
// test-argument:experimentsOn=true

// ignore_for_file: avoid_print

import 'package:devtools_app/devtools_app.dart';
import 'package:devtools_app/src/screens/memory/panes/control/primary_controls.dart';
Expand All @@ -19,9 +16,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

// TODO(polina-c): enable the test
// https://github.com/flutter/devtools/issues/6271

// To run:
// dart run integration_test/run_tests.dart --target=integration_test/test/live_connection/eval_and_browse_test.dart

Expand Down Expand Up @@ -155,7 +149,11 @@ class _EvalAndBrowseTester {
/// visible on the screen for testing.
Future<void> prepareMemoryUI() async {
// Open memory screen.
await switchToScreen(tester, ScreenMetaData.memory);
await switchToScreen(
tester,
tabIcon: ScreenMetaData.memory.icon!,
screenId: ScreenMetaData.memory.id,
);

// Close warning and chart to get screen space.
await tapAndPump(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ void main() {
'Open the Performance screen and switch to the Timeline Events tab',
);

await switchToScreen(tester, ScreenMetaData.performance);
await switchToScreen(
tester,
tabIcon: ScreenMetaData.performance.icon!,
screenId: ScreenMetaData.performance.id,
);
await tester.pump(safePumpDuration);

await tester.tap(find.widgetWithText(InkWell, 'Timeline Events'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ void main() {

// TODO(kenz): re-work this integration test so that we do not have to be
// on the inspector screen for this to pass.
await switchToScreen(tester, ScreenMetaData.inspector);
await switchToScreen(
tester,
tabIcon: ScreenMetaData.inspector.icon!,
screenId: ScreenMetaData.inspector.id,
);
await tester.pump(longDuration);

// Ensure all futures are completed before running checks.
Expand Down
Loading

0 comments on commit e7677e5

Please sign in to comment.