@@ -7,6 +7,7 @@ library test.runner.browser.browser_manager;
7
7
import 'dart:async' ;
8
8
import 'dart:convert' ;
9
9
10
+ import 'package:async/async.dart' ;
10
11
import 'package:http_parser/http_parser.dart' ;
11
12
import 'package:pool/pool.dart' ;
12
13
@@ -17,18 +18,32 @@ import '../../util/multi_channel.dart';
17
18
import '../../util/remote_exception.dart' ;
18
19
import '../../util/stack_trace_mapper.dart' ;
19
20
import '../../utils.dart' ;
21
+ import '../application_exception.dart' ;
20
22
import '../environment.dart' ;
21
23
import '../load_exception.dart' ;
22
24
import '../runner_suite.dart' ;
25
+ import 'browser.dart' ;
26
+ import 'chrome.dart' ;
27
+ import 'content_shell.dart' ;
28
+ import 'dartium.dart' ;
29
+ import 'firefox.dart' ;
23
30
import 'iframe_test.dart' ;
31
+ import 'internet_explorer.dart' ;
32
+ import 'phantom_js.dart' ;
33
+ import 'safari.dart' ;
24
34
25
35
/// A class that manages the connection to a single running browser.
26
36
///
27
37
/// This is in charge of telling the browser which test suites to load and
28
38
/// converting its responses into [Suite] objects.
29
39
class BrowserManager {
30
- /// The browser that this is managing.
31
- final TestPlatform browser;
40
+ /// The browser instance that this is connected to via [_channel] .
41
+ final Browser _browser;
42
+
43
+ // TODO(nweiz): Consider removing the duplication between this and
44
+ // [_browser.name].
45
+ /// The [TestPlatform] for [_browser] .
46
+ final TestPlatform _platform;
32
47
33
48
/// The channel used to communicate with the browser.
34
49
///
@@ -60,16 +75,78 @@ class BrowserManager {
60
75
CancelableCompleter _pauseCompleter;
61
76
62
77
/// The environment to attach to each suite.
63
- _BrowserEnvironment _environment;
78
+ Future <_BrowserEnvironment > _environment;
79
+
80
+ /// Starts the browser identified by [platform] and has it connect to [url] .
81
+ ///
82
+ /// [url] should serve a page that establishes a WebSocket connection with
83
+ /// this process. That connection, once established, should be emitted via
84
+ /// [future] .
85
+ ///
86
+ /// Returns the browser manager, or throws an [ApplicationException] if a
87
+ /// connection fails to be established.
88
+ static Future <BrowserManager > start (TestPlatform platform, Uri url,
89
+ Future <CompatibleWebSocket > future) {
90
+ var browser = _newBrowser (url, platform);
91
+
92
+ var completer = new Completer ();
93
+
94
+ // TODO(nweiz): Gracefully handle the browser being killed before the
95
+ // tests complete.
96
+ browser.onExit.then ((_) {
97
+ throw new ApplicationException (
98
+ "${platform .name } exited before connecting." );
99
+ }).catchError ((error, stackTrace) {
100
+ if (completer.isCompleted) return ;
101
+ completer.completeError (error, stackTrace);
102
+ });
103
+
104
+ future.then ((webSocket) {
105
+ if (completer.isCompleted) return ;
106
+ completer.complete (new BrowserManager ._(browser, platform, webSocket));
107
+ }).catchError ((error, stackTrace) {
108
+ browser.close ();
109
+ if (completer.isCompleted) return ;
110
+ completer.completeError (error, stackTrace);
111
+ });
112
+
113
+ return completer.future.timeout (new Duration (seconds: 30 ), onTimeout: () {
114
+ browser.close ();
115
+ throw new ApplicationException (
116
+ "Timed out waiting for ${platform .name } to connect." );
117
+ });
118
+ }
119
+
120
+ /// Starts the browser identified by [browser] and has it load [url] .
121
+ static Browser _newBrowser (Uri url, TestPlatform browser) {
122
+ switch (browser) {
123
+ case TestPlatform .dartium: return new Dartium (url);
124
+ case TestPlatform .contentShell: return new ContentShell (url);
125
+ case TestPlatform .chrome: return new Chrome (url);
126
+ case TestPlatform .phantomJS: return new PhantomJS (url);
127
+ case TestPlatform .firefox: return new Firefox (url);
128
+ case TestPlatform .safari: return new Safari (url);
129
+ case TestPlatform .internetExplorer: return new InternetExplorer (url);
130
+ default :
131
+ throw new ArgumentError ("$browser is not a browser." );
132
+ }
133
+ }
64
134
65
135
/// Creates a new BrowserManager that communicates with [browser] over
66
136
/// [webSocket] .
67
- BrowserManager (this .browser , CompatibleWebSocket webSocket)
137
+ BrowserManager ._ (this ._browser, this ._platform , CompatibleWebSocket webSocket)
68
138
: _channel = new MultiChannel (
69
139
webSocket.map (JSON .decode),
70
140
mapSink (webSocket, JSON .encode)) {
71
- _environment = new _BrowserEnvironment (this );
72
- _channel.stream.listen (_onMessage, onDone: _onDone);
141
+ _environment = _loadBrowserEnvironment ();
142
+ _channel.stream.listen (_onMessage, onDone: close);
143
+ }
144
+
145
+ /// Loads [_BrowserEnvironment] .
146
+ Future <_BrowserEnvironment > _loadBrowserEnvironment () async {
147
+ var observatoryUrl;
148
+ if (_platform.isDartVM) observatoryUrl = await _browser.observatoryUrl;
149
+ return new _BrowserEnvironment (this , observatoryUrl);
73
150
}
74
151
75
152
/// Tells the browser the load a test suite from the URL [url] .
@@ -84,7 +161,7 @@ class BrowserManager {
84
161
{StackTraceMapper mapper}) async {
85
162
url = url.replace (fragment: Uri .encodeFull (JSON .encode ({
86
163
"metadata" : metadata.serialize (),
87
- "browser" : browser .identifier
164
+ "browser" : _platform .identifier
88
165
})));
89
166
90
167
// The stream may close before emitting a value if the browser is killed
@@ -130,13 +207,14 @@ class BrowserManager {
130
207
throw new LoadException (
131
208
path,
132
209
"Timed out waiting for the test suite to connect on "
133
- "${browser .name }." );
210
+ "${_platform .name }." );
134
211
});
135
212
});
136
213
137
214
if (response == null ) {
138
215
closeIframe ();
139
- return null ;
216
+ throw new LoadException (
217
+ path, "Connection closed before test suite loaded." );
140
218
}
141
219
142
220
if (response["type" ] == "loadException" ) {
@@ -152,12 +230,12 @@ class BrowserManager {
152
230
asyncError.stackTrace);
153
231
}
154
232
155
- return new RunnerSuite (_environment, response["tests" ].map ((test) {
233
+ return new RunnerSuite (await _environment, response["tests" ].map ((test) {
156
234
var testMetadata = new Metadata .deserialize (test['metadata' ]);
157
235
var testChannel = suiteChannel.virtualChannel (test['channel' ]);
158
236
return new IframeTest (test['name' ], testMetadata, testChannel,
159
237
mapper: mapper);
160
- }), platform: browser , metadata: metadata, path: path,
238
+ }), platform: _platform , metadata: metadata, path: path,
161
239
onClose: () => closeIframe ());
162
240
}
163
241
@@ -183,12 +261,15 @@ class BrowserManager {
183
261
_pauseCompleter.complete ();
184
262
}
185
263
186
- /// The callback called when the WebSocket is closed.
187
- void _onDone () {
264
+ /// Closes the manager and releases any resources it owns, including closing
265
+ /// the browser.
266
+ Future close () => _closeMemoizer.runOnce (() {
188
267
_closed = true ;
189
268
if (_pauseCompleter != null ) _pauseCompleter.complete ();
190
269
_pauseCompleter = null ;
191
- }
270
+ return _browser.close ();
271
+ });
272
+ final _closeMemoizer = new AsyncMemoizer ();
192
273
}
193
274
194
275
/// An implementation of [Environment] for the browser.
@@ -197,7 +278,9 @@ class BrowserManager {
197
278
class _BrowserEnvironment implements Environment {
198
279
final BrowserManager _manager;
199
280
200
- _BrowserEnvironment (this ._manager);
281
+ final Uri observatoryUrl;
282
+
283
+ _BrowserEnvironment (this ._manager, this .observatoryUrl);
201
284
202
285
CancelableFuture displayPause () => _manager._displayPause ();
203
286
}
0 commit comments