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
7 changes: 7 additions & 0 deletions _test/example/append_body/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>

<head>
<script defer src="main.dart.js"></script>
</head>

</html>
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ void main() {
print('Count is: ${++count}');
});

document.body.appendText('Hello World!!');
document.body.appendText('Hello World!');

registerExtension('ext.flutter.disassemble', (_, __) async {
document.body.appendText('start disassemble ');
Expand Down
89 changes: 89 additions & 0 deletions dwds/test/devtools_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

@Timeout(Duration(minutes: 5))
import 'package:test/test.dart';
import 'package:webdriver/io.dart';

import 'fixtures/context.dart';

final context = TestContext(
path: 'append_body/index.html',
);

void main() {
group('Injected client', () {
setUp(() async {
await context.setUp(serveDevTools: true);
await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
// Wait for DevTools to actually open.
await Future.delayed(const Duration(seconds: 1));
});

tearDown(() async {
await context.tearDown();
});

test('can launch devtools', () async {
var windows = await context.webDriver.windows.toList();
await context.webDriver.driver.switchTo.window(windows.last);
expect(await context.webDriver.title, 'Dart DevTools');
});

test('can not launch devtools for the same app in multiple tabs', () async {
var appUrl = await context.webDriver.currentUrl;
// Open a new tab, select it, and navigate to the app
await context.webDriver.driver
.execute("window.open('$appUrl', '_blank');", []);
await Future.delayed(const Duration(seconds: 1));
var windows = await context.webDriver.windows.toList();
var oldAppWindow = windows[0];
var newAppWindow = windows[1];
var devToolsWindow = windows[2];
await newAppWindow.setAsActive();

// Try to open devtools and check for the alert.
await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
await Future.delayed(const Duration(seconds: 1));
var alert = context.webDriver.driver.switchTo.alert;
expect(alert, isNotNull);
expect(await alert.text,
contains('This app is already being debugged in a different tab'));
await alert.accept();

// Now close the old app and try to re-open devtools.
await oldAppWindow.setAsActive();
await oldAppWindow.close();
await devToolsWindow.setAsActive();
await devToolsWindow.close();
await newAppWindow.setAsActive();
await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
await Future.delayed(const Duration(seconds: 1));
windows = await context.webDriver.windows.toList();
devToolsWindow = windows.firstWhere((window) => window != newAppWindow);
await devToolsWindow.setAsActive();
expect(await context.webDriver.title, 'Dart DevTools');
});
});

group('Injected client without DevTools', () {
setUp(() async {
await context.setUp(serveDevTools: false);
});

tearDown(() async {
await context.tearDown();
});

test('gives a good error if devtools is not served', () async {
// Try to open devtools and check for the alert.
await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
await Future.delayed(const Duration(seconds: 1));
var alert = context.webDriver.driver.switchTo.alert;
expect(alert, isNotNull);
expect(await alert.text, contains('--debug'));
await alert.accept();
});
});
}
27 changes: 25 additions & 2 deletions dwds/test/fixtures/context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class TestContext {
DebugConnection debugConnection;
ChromeProxyService chromeProxyService;
int port;
File _entryFile;
String _entryContents;

/// Top level directory in which we run the test server..
String workingDirectory;
Expand All @@ -47,9 +49,15 @@ class TestContext {
this.pathToServe = 'example'}) {
workingDirectory = p.normalize(
p.absolute(directory ?? p.relative('../_test', from: p.current)));
_entryFile = File(p.absolute(p.join(p.relative('../_test', from: p.current),
'example', 'append_body', 'main.dart')));
_entryContents = _entryFile.readAsStringSync();
}

Future<void> setUp() async {
Future<void> setUp(
{ReloadConfiguration reloadConfiguration, bool serveDevTools}) async {
reloadConfiguration ??= ReloadConfiguration.none;
serveDevTools ??= false;
port = await findUnusedPort();
try {
chromeDriver = await Process.start(
Expand All @@ -70,7 +78,7 @@ class TestContext {
await daemonClient.buildResults
.firstWhere((results) => results.results
.any((result) => result.status == BuildStatus.succeeded))
.timeout(Duration(seconds: 60));
.timeout(const Duration(seconds: 60));

var debugPort = await findUnusedPort();
var capabilities = Capabilities.chrome
Expand All @@ -89,6 +97,8 @@ class TestContext {
pathToServe,
daemonClient.buildResults,
() async => connection,
reloadConfiguration,
serveDevTools,
);

appUrl = 'http://localhost:$port/$path';
Expand All @@ -110,9 +120,22 @@ class TestContext {
}

Future<Null> tearDown() async {
_entryFile.writeAsStringSync(_entryContents);
await daemonClient.close();
await testServer.stop();
await webDriver?.quit();
chromeDriver.kill();
}

Future<void> changeInput() async {
_entryFile.writeAsStringSync(
_entryContents.replaceAll('Hello World!', 'Gary is awesome!'));

// Wait for the build.
await daemonClient.buildResults.firstWhere((results) => results.results
.any((result) => result.status == BuildStatus.succeeded));

// Allow change to propagate to the browser.
await Future.delayed(const Duration(seconds: 2));
}
}
4 changes: 4 additions & 0 deletions dwds/test/fixtures/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class TestServer {
String target,
Stream<BuildResults> buildResults,
Future<ChromeConnection> Function() chromeConnection,
ReloadConfiguration reloadConfiguration,
bool serveDevTools,
) async {
var pipeline = const Pipeline();

Expand All @@ -59,6 +61,8 @@ class TestServer {
buildResults: filteredBuildResults,
chromeConnection: chromeConnection,
logWriter: (level, message) => printOnFailure(message),
reloadConfiguration: reloadConfiguration,
serveDevTools: serveDevTools,
verbose: true,
);

Expand Down
189 changes: 189 additions & 0 deletions dwds/test/reload_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

@Timeout(Duration(minutes: 5))
import 'dart:io';

import 'package:dwds/dwds.dart';
import 'package:test/test.dart';
import 'package:vm_service_lib/vm_service_lib.dart';

import 'fixtures/context.dart';

final context = TestContext(
path: 'append_body/index.html',
);

void main() {
group('Injected client with live reload', () {
setUp(() async {
await context.setUp(reloadConfiguration: ReloadConfiguration.liveReload);
});

tearDown(() async {
await context.tearDown();
});

test('can live reload changes ', () async {
await context.changeInput();

var source = await context.webDriver.pageSource;

// A full reload should clear the state.
expect(source.contains('Hello World!'), isFalse);
expect(source.contains('Gary is awesome!'), isTrue);
});
});

group('Injected client', () {
setUp(() async {
await context.setUp();
});

tearDown(() async {
await context.tearDown();
});

test('destroys and recreates the isolate during a hot restart', () async {
var client = context.debugConnection.vmService;
await client.streamListen('Isolate');
await context.changeInput();

var eventsDone = expectLater(
client.onIsolateEvent,
emitsThrough(emitsInOrder([
_hasKind(EventKind.kIsolateExit),
_hasKind(EventKind.kIsolateStart),
_hasKind(EventKind.kIsolateRunnable),
])));

expect(await client.callServiceExtension('hotRestart'),
const TypeMatcher<Success>());

await eventsDone;
});

test('destroys and recreates the isolate during a page refresh', () async {
var client = context.debugConnection.vmService;
await client.streamListen('Isolate');
await context.changeInput();

var eventsDone = expectLater(
client.onIsolateEvent,
emitsThrough(emitsInOrder([
_hasKind(EventKind.kIsolateExit),
_hasKind(EventKind.kIsolateStart),
_hasKind(EventKind.kIsolateRunnable),
])));

await context.webDriver.driver.refresh();

await eventsDone;
});

test('can hot restart via the service extension', () async {
var client = context.debugConnection.vmService;
await context.changeInput();

expect(await client.callServiceExtension('hotRestart'),
const TypeMatcher<Success>());
await Future.delayed(const Duration(seconds: 2));

var source = await context.webDriver.pageSource;
// Main is re-invoked which shouldn't clear the state.
expect(source, contains('Hello World!'));
expect(source, contains('Gary is awesome!'));
});

test('can refresh the page via the fullReload service extension', () async {
var client = context.debugConnection.vmService;
await context.changeInput();

expect(await client.callServiceExtension('fullReload'), isA<Success>());
await Future.delayed(const Duration(seconds: 2));

var source = await context.webDriver.pageSource;
// Should see only the new text
expect(source, isNot(contains('Hello World!')));
expect(source, contains('Gary is awesome!'));
});

test('can hot restart while paused', () async {
var client = context.debugConnection.vmService;
var vm = await client.getVM();
var isolateId = vm.isolates.first.id;
await client.streamListen('Debug');
var stream = client.onEvent('Debug');
var scriptList = await client.getScripts(isolateId);
var main = scriptList.scripts
.firstWhere((script) => script.uri.contains('main.dart'));
await client.addBreakpoint(isolateId, main.id, 13);
await stream
.firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);

await context.changeInput();
await client.streamListen('Isolate');
stream = client.onEvent('Isolate');
await client.callServiceExtension('hotRestart');
await stream.firstWhere((event) => event.kind == EventKind.kIsolateStart);
var source = await context.webDriver.pageSource;

// Main is re-invoked which shouldn't clear the state.
expect(source.contains('Hello World!'), isTrue);
expect(source.contains('Gary is awesome!'), isTrue);

// Should not be paused.
vm = await client.getVM();
isolateId = vm.isolates.first.id;
var isolate = await client.getIsolate(isolateId) as Isolate;
expect(isolate.pauseEvent.kind, EventKind.kResume);
expect(isolate.breakpoints.isEmpty, isTrue);

// TODO(sdk/issues/37364) - Remove once corresponding SDK issue is fixed.
}, skip: Platform.isWindows);
});

group('Injected client with hot restart', () {
setUp(() async {
await context.setUp(reloadConfiguration: ReloadConfiguration.hotRestart);
});

tearDown(() async {
await context.tearDown();
});

test('can hot restart changes ', () async {
await context.changeInput();

var source = await context.webDriver.pageSource;

// Main is re-invoked which shouldn't clear the state.
expect(source.contains('Hello World!'), isTrue);
expect(source.contains('Gary is awesome!'), isTrue);
// The ext.flutter.disassemble callback is invoked and waited for.
expect(source,
contains('start disassemble end disassemble Gary is awesome'));
});

test('fires isolate create/destroy events during hot restart', () async {
var client = context.debugConnection.vmService;
await client.streamListen('Isolate');

var eventsDone = expectLater(
client.onIsolateEvent,
emitsThrough(emitsInOrder([
_hasKind(EventKind.kIsolateExit),
_hasKind(EventKind.kIsolateStart),
_hasKind(EventKind.kIsolateRunnable),
])));

await context.changeInput();

await eventsDone;
});
});
}

TypeMatcher<Event> _hasKind(String kind) =>
isA<Event>().having((e) => e.kind, 'kind', kind);
File renamed without changes.
Loading