blob: b20da07929044e06b7455a434c05cc5e05378c18 [file] [log] [blame]
// 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.browser_manager;
import 'dart:async';
import 'dart:convert';
import 'package:http_parser/http_parser.dart';
import '../../backend/metadata.dart';
import '../../backend/suite.dart';
import '../../backend/test_platform.dart';
import '../../util/multi_channel.dart';
import '../../util/remote_exception.dart';
import '../../utils.dart';
import '../load_exception.dart';
import 'iframe_test.dart';
/// A class that manages the connection to a single running browser.
///
/// This is in charge of telling the browser which test suites to load and
/// converting its responses into [Suite] objects.
class BrowserManager {
/// The browser that this is managing.
final TestPlatform browser;
/// The channel used to communicate with the browser.
///
/// This is connected to a page running `static/host.dart`.
final MultiChannel _channel;
/// Creates a new BrowserManager that communicates with [browser] over
/// [webSocket].
BrowserManager(this.browser, CompatibleWebSocket webSocket)
: _channel = new MultiChannel(
webSocket.map(JSON.decode),
mapSink(webSocket, JSON.encode));
/// Tells the browser the load a test suite from the URL [url].
///
/// [url] should be an HTML page with a reference to the JS-compiled test
/// suite. [path] is the path of the original test suite file, which is used
/// for reporting. [metadata] is the parsed metadata for the test suite.
Future<Suite> loadSuite(String path, Uri url, Metadata metadata) {
url = url.replace(fragment: Uri.encodeFull(JSON.encode({
"metadata": metadata.serialize(),
"browser": browser.identifier
})));
var suiteChannel = _channel.virtualChannel();
_channel.sink.add({
"command": "loadSuite",
"url": url.toString(),
"channel": suiteChannel.id
});
// Create a nested MultiChannel because the iframe will be using a channel
// wrapped within the host's channel.
suiteChannel = new MultiChannel(suiteChannel.stream, suiteChannel.sink);
// The stream may close before emitting a value if the browser is killed
// prematurely (e.g. via Control-C).
return maybeFirst(suiteChannel.stream)
.timeout(new Duration(seconds: 7), onTimeout: () {
throw new LoadException(
path,
"Timed out waiting for the test suite to connect on "
"${browser.name}.");
}).then((response) {
if (response == null) return null;
if (response["type"] == "loadException") {
return new Future.error(new LoadException(path, response["message"]));
} else if (response["type"] == "error") {
var asyncError = RemoteException.deserialize(response["error"]);
return new Future.error(
new LoadException(path, asyncError.error),
asyncError.stackTrace);
}
return new Suite(response["tests"].map((test) {
var testMetadata = new Metadata.deserialize(test['metadata']);
var testChannel = suiteChannel.virtualChannel(test['channel']);
return new IframeTest(test['name'], testMetadata, testChannel);
}), metadata: metadata, path: path);
});
}
}