From f169f3d64e880472f82e9c2b744bb57329d6122d Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum <nweiz@google.com> Date: Wed, 22 Apr 2015 17:50:43 -0700 Subject: [PATCH] Add IE support. Closes #31 R=kevmoo@google.com Review URL: https://codereview.chromium.org//1088933006 --- .status | 9 +- CHANGELOG.md | 2 +- README.md | 2 + lib/src/backend/test_platform.dart | 17 ++- lib/src/executable.dart | 1 + lib/src/runner/browser/internet_explorer.dart | 98 +++++++++++++++++ lib/src/runner/browser/server.dart | 4 +- .../browser/internet_explorer_test.dart | 104 ++++++++++++++++++ test/runner/runner_test.dart | 3 +- 9 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 lib/src/runner/browser/internet_explorer.dart create mode 100644 test/runner/browser/internet_explorer_test.dart diff --git a/.status b/.status index e4e15514..870ef8a0 100644 --- a/.status +++ b/.status @@ -28,6 +28,12 @@ test/runner/browser/phantom_js: Skip test/runner/browser/loader_test: Pass, Slow +[ $system != windows ] +test/runner/browser/internet_explorer: SkipByDesign + +[ $system != macos ] +test/runner/browser/safari: SkipByDesign + # The test harness for browser tests doesn't play nicely with the new way of # doing browser tests. [ $browser ] @@ -35,6 +41,3 @@ test/*: SkipByDesign test/*/*: SkipByDesign test/*/*/*: SkipByDesign test/*/*/*/*: SkipByDesign - -[ $runtime == safari ] -test/matcher/prints_test: Fail # Issue 4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0089c858..3601d835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ * When running a test suite via `dart path/to/test.dart`, throw an exception if the suite fails so that the exit code is set properly. -* Add support for running on Windows. +* Add support for running on Windows and Internet Explorer. ### 0.12.0-beta.10 diff --git a/README.md b/README.md index d1602fd9..731a0324 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,8 @@ valid identifiers are: * `safari`: Whether the test is running on Apple Safari. +* `ie`: Whether the test is running on Microsoft Internet Explorer. + * `dart-vm`: Whether the test is running on the Dart VM in any context, including Dartium. It's identical to `!js`. diff --git a/lib/src/backend/test_platform.dart b/lib/src/backend/test_platform.dart index d311d692..fef5a624 100644 --- a/lib/src/backend/test_platform.dart +++ b/lib/src/backend/test_platform.dart @@ -40,9 +40,22 @@ class TestPlatform { static const TestPlatform safari = const TestPlatform._("Safari", "safari", isBrowser: true, isJS: true); + /// Microsoft Internet Explorer. + static const TestPlatform internetExplorer = const TestPlatform._( + "Internet Explorer", "ie", + isBrowser: true, isJS: true); + /// A list of all instances of [TestPlatform]. - static const List<TestPlatform> all = - const [vm, dartium, contentShell, chrome, phantomJS, firefox, safari]; + static const List<TestPlatform> all = const [ + vm, + dartium, + contentShell, + chrome, + phantomJS, + firefox, + safari, + internetExplorer + ]; /// Finds a platform by its identifier string. /// diff --git a/lib/src/executable.dart b/lib/src/executable.dart index a9d9ccfc..f2f8c3c1 100644 --- a/lib/src/executable.dart +++ b/lib/src/executable.dart @@ -78,6 +78,7 @@ bool get _usesTransformer { void main(List<String> args) { var allPlatforms = TestPlatform.all.toList(); if (!Platform.isMacOS) allPlatforms.remove(TestPlatform.safari); + if (!Platform.isWindows) allPlatforms.remove(TestPlatform.internetExplorer); _parser.addFlag("help", abbr: "h", negatable: false, help: "Shows this usage information."); diff --git a/lib/src/runner/browser/internet_explorer.dart b/lib/src/runner/browser/internet_explorer.dart new file mode 100644 index 00000000..5453252d --- /dev/null +++ b/lib/src/runner/browser/internet_explorer.dart @@ -0,0 +1,98 @@ +// Copyright (c) 2015, 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. + +library test.runner.browser.internet_explorer; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:stack_trace/stack_trace.dart'; + +import '../../utils.dart'; +import '../application_exception.dart'; +import 'browser.dart'; + +/// A class for running an instance of Internet Explorer. +/// +/// Any errors starting or running the process are reported through [onExit]. +class InternetExplorer implements Browser { + /// The underlying process. + Process _process; + + Future get onExit => _onExitCompleter.future; + final _onExitCompleter = new Completer(); + + /// A future that completes when the browser process has started. + /// + /// This is used to ensure that [close] works regardless of when it's called. + Future get _onProcessStarted => _onProcessStartedCompleter.future; + final _onProcessStartedCompleter = new Completer(); + + /// Starts a new instance of Internet Explorer open to the given [url], which + /// may be a [Uri] or a [String]. + /// + /// If [executable] is passed, it's used as the Internet Explorer executable. + /// Otherwise the default executable name will be used. + InternetExplorer(url, {String executable}) { + if (executable == null) executable = _defaultExecutable(); + + // Don't return a Future here because there's no need for the caller to wait + // for the process to actually start. They should just wait for the HTTP + // request instead. + Process.start(executable, ['-extoff', url.toString()]) + .then((process) { + _process = process; + _onProcessStartedCompleter.complete(); + + // TODO(nweiz): the browser's standard output is almost always useless + // noise, but we should allow the user to opt in to seeing it. + return _process.exitCode; + }).then((exitCode) { + if (exitCode == 0) return null; + + return UTF8.decodeStream(_process.stderr).then((error) { + throw new ApplicationException( + "Internet Explorer failed with exit code $exitCode:\n$error"); + }); + }).then(_onExitCompleter.complete).catchError((error, stackTrace) { + if (stackTrace == null) stackTrace = new Trace.current(); + _onExitCompleter.completeError( + new ApplicationException( + "Failed to start Internet Explorer: ${getErrorMessage(error)}."), + stackTrace); + }); + } + + Future close() { + _onProcessStarted.then((_) => _process.kill()); + + // Swallow exceptions. The user should explicitly use [onExit] for these. + return onExit.catchError((_) {}); + } + + /// Return the default executable for the current operating system. + String _defaultExecutable() { + // Chrome could be installed in several places on Windows. The only way to + // find it is to check. + var prefixes = [ + Platform.environment['PROGRAMW6432'], + Platform.environment['PROGRAMFILES'], + Platform.environment['PROGRAMFILES(X86)'] + ]; + var suffix = r'Internet Explorer\iexplore.exe'; + + for (var prefix in prefixes) { + if (prefix == null) continue; + + var path = p.join(prefix, suffix); + if (new File(p.join(prefix, suffix)).existsSync()) return path; + } + + // Fall back on looking it up on the path. This probably won't work, but at + // least it will fail with a useful error message. + return "iexplore.exe"; + } +} diff --git a/lib/src/runner/browser/server.dart b/lib/src/runner/browser/server.dart index 06c97668..5fbbf8ad 100644 --- a/lib/src/runner/browser/server.dart +++ b/lib/src/runner/browser/server.dart @@ -28,9 +28,10 @@ import 'browser.dart'; import 'browser_manager.dart'; import 'compiler_pool.dart'; import 'chrome.dart'; -import 'dartium.dart'; import 'content_shell.dart'; +import 'dartium.dart'; import 'firefox.dart'; +import 'internet_explorer.dart'; import 'phantom_js.dart'; import 'safari.dart'; @@ -389,6 +390,7 @@ void main() { case TestPlatform.phantomJS: return new PhantomJS(url); case TestPlatform.firefox: return new Firefox(url); case TestPlatform.safari: return new Safari(url); + case TestPlatform.internetExplorer: return new InternetExplorer(url); default: throw new ArgumentError("$browser is not a browser."); } diff --git a/test/runner/browser/internet_explorer_test.dart b/test/runner/browser/internet_explorer_test.dart new file mode 100644 index 00000000..47c7d09f --- /dev/null +++ b/test/runner/browser/internet_explorer_test.dart @@ -0,0 +1,104 @@ +// Copyright (c) 2015, 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. + +@TestOn("vm && windows") + +import 'dart:async'; + +import 'package:test/test.dart'; +import 'package:test/src/runner/browser/internet_explorer.dart'; +import 'package:test/src/util/io.dart'; +import 'package:shelf/shelf.dart' as shelf; +import 'package:shelf/shelf_io.dart' as shelf_io; +import 'package:shelf_web_socket/shelf_web_socket.dart'; + +import '../../io.dart'; +import '../../utils.dart'; + +void main() { + group("running JavaScript", () { + // The JavaScript to serve in the server. We use actual JavaScript here to + // avoid the pain of compiling to JS in a test + var javaScript; + + var servePage = (request) { + var path = request.url.path; + + // We support both shelf 0.5.x and 0.6.x. The former has a leading "/" + // here, the latter does not. + if (path.startsWith("/")) path = path.substring(1); + + if (path.isEmpty) { + return new shelf.Response.ok(""" +<!doctype html> +<html> +<head> + <script src="index.js"></script> +</head> +</html> +""", headers: {'content-type': 'text/html'}); + } else if (path == "index.js") { + return new shelf.Response.ok(javaScript, + headers: {'content-type': 'application/javascript'}); + } else { + return new shelf.Response.notFound(null); + } + }; + + var server; + var webSockets; + setUp(() { + var webSocketsController = new StreamController(); + webSockets = webSocketsController.stream; + + return shelf_io.serve( + new shelf.Cascade() + .add(webSocketHandler(webSocketsController.add)) + .add(servePage).handler, + 'localhost', 0).then((server_) { + server = server_; + }); + }); + + tearDown(() { + if (server != null) server.close(); + + javaScript = null; + server = null; + webSockets = null; + }); + + test("starts IE with the given URL", () { + javaScript = ''' +var webSocket = new WebSocket(window.location.href.replace("http://", "ws://")); +webSocket.addEventListener("open", function() { + webSocket.send("loaded!"); +}); +'''; + var ie = new InternetExplorer( + baseUrlForAddress(server.address, server.port)); + + return webSockets.first.then((webSocket) { + return webSocket.first.then( + (message) => expect(message, equals("loaded!"))); + }).whenComplete(ie.close); + }); + }); + + test("a process can be killed synchronously after it's started", () { + return shelf_io.serve(expectAsync((_) {}, count: 0), 'localhost', 0) + .then((server) { + var ie = new InternetExplorer( + baseUrlForAddress(server.address, server.port)); + return ie.close().whenComplete(server.close); + }); + }); + + test("reports an error in onExit", () { + var ie = new InternetExplorer("http://dart-lang.org", + executable: "_does_not_exist"); + expect(ie.onExit, throwsA(isApplicationException(startsWith( + "Failed to start Internet Explorer: $noSuchFileMessage")))); + }); +} diff --git a/test/runner/runner_test.dart b/test/runner/runner_test.dart index b60af6fe..634d22ce 100644 --- a/test/runner/runner_test.dart +++ b/test/runner/runner_test.dart @@ -40,7 +40,8 @@ final _defaultConcurrency = math.max(1, Platform.numberOfProcessors ~/ 2); final _browsers = "[vm (default), dartium, content-shell, chrome, phantomjs, firefox" + - (Platform.isMacOS ? ", safari" : "") + "]"; + (Platform.isMacOS ? ", safari" : "") + + (Platform.isWindows ? ", ie" : "") + "]"; final _usage = """ Usage: pub run test:test [files or directories...] -- GitLab